diff --git a/engine-tests/src/test_utils/mod.rs b/engine-tests/src/test_utils/mod.rs index dac0405ea..b4492c815 100644 --- a/engine-tests/src/test_utils/mod.rs +++ b/engine-tests/src/test_utils/mod.rs @@ -246,7 +246,7 @@ impl AuroraRunner { &[crate::prelude::storage::EthConnectorStorageId::FungibleToken as u8], ); let ft_value = { - let mut current_ft: FungibleToken = trie + let mut current_ft: FungibleToken = trie .get(&ft_key) .map(|bytes| FungibleToken::try_from_slice(&bytes).unwrap()) .unwrap_or_default(); diff --git a/engine/src/connector.rs b/engine/src/connector.rs index b79efdc31..380f70e37 100644 --- a/engine/src/connector.rs +++ b/engine/src/connector.rs @@ -1,7 +1,7 @@ use crate::admin_controlled::{AdminControlled, PausedMask}; use crate::deposit_event::DepositedEvent; use crate::engine::Engine; -use crate::fungible_token::{FungibleToken, FungibleTokenMetadata}; +use crate::fungible_token::{FungibleToken, FungibleTokenMetadata, FungibleTokenOps}; use crate::json::parse_json; use crate::parameters::{ BalanceOfCallArgs, BalanceOfEthCallArgs, FinishDepositCallArgs, InitCallArgs, @@ -29,10 +29,9 @@ pub const UNPAUSE_ALL: PausedMask = 0; pub const PAUSE_DEPOSIT: PausedMask = 1 << 0; pub const PAUSE_WITHDRAW: PausedMask = 1 << 1; -#[derive(BorshSerialize, BorshDeserialize)] -pub struct EthConnectorContract { +pub struct EthConnectorContract { contract: EthConnector, - ft: FungibleToken, + ft: FungibleTokenOps, paused_mask: PausedMask, io: I, } @@ -62,11 +61,15 @@ pub struct OnTransferMessageData { pub fee: U256, } -impl EthConnectorContract { +impl EthConnectorContract { pub fn get_instance(io: I) -> Self { Self { contract: Self::get_contract_data(&io, &EthConnectorStorageId::Contract), - ft: Self::get_contract_data(&io, &EthConnectorStorageId::FungibleToken), + ft: Self::get_contract_data::( + &io, + &EthConnectorStorageId::FungibleToken, + ) + .ops(io), paused_mask: Self::get_contract_data(&io, &EthConnectorStorageId::PausedMask), io, } @@ -103,7 +106,7 @@ impl EthConnectorContract { let current_account_id = sdk::current_account_id(); let owner_id = AccountId::try_from(current_account_id).unwrap(); - let mut ft = FungibleToken::new(); + let mut ft = FungibleTokenOps::new(io); // Register FT account for current contract ft.internal_register_account(&owner_id); @@ -599,7 +602,7 @@ impl EthConnectorContract { fn save_ft_contract(&mut self) { self.io.write_borsh( &Self::get_contract_key(&EthConnectorStorageId::FungibleToken), - &self.ft, + &self.ft.data(), ); } @@ -644,7 +647,7 @@ impl EthConnectorContract { } } -impl AdminControlled for EthConnectorContract { +impl AdminControlled for EthConnectorContract { fn get_paused(&self) -> PausedMask { self.paused_mask } diff --git a/engine/src/engine.rs b/engine/src/engine.rs index ddf14300a..0a2fd3e0a 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -9,17 +9,16 @@ use evm::{Config, CreateScheme, ExitError, ExitFatal, ExitReason}; use crate::connector::EthConnectorContract; #[cfg(feature = "contract")] use crate::contract::current_address; -use crate::map::{BijectionMap, LookupMap}; +use crate::map::BijectionMap; use aurora_engine_sdk::io::{StorageIntermediate, IO}; -use aurora_engine_sdk::near_runtime::Runtime; use crate::parameters::{NewCallArgs, TransactionStatus}; use crate::prelude::precompiles::native::{ExitToEthereum, ExitToNear}; use crate::prelude::precompiles::Precompiles; use crate::prelude::{ address_to_key, bytes_to_key, sdk, storage_to_key, u256_to_arr, AccountId, Address, - BorshDeserialize, BorshSerialize, KeyPrefix, KeyPrefixU8, PromiseArgs, PromiseCreateArgs, - TryFrom, TryInto, Vec, Wei, ERC20_MINT_SELECTOR, H256, U256, + BorshDeserialize, BorshSerialize, KeyPrefix, PromiseArgs, PromiseCreateArgs, TryFrom, TryInto, + Vec, Wei, ERC20_MINT_SELECTOR, H256, U256, }; use crate::transaction::NormalizedEthTransaction; @@ -201,6 +200,44 @@ impl From for GasPaymentError { } } +pub struct ERC20Address(Address); +impl AsRef<[u8]> for ERC20Address { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() + } +} +impl TryFrom> for ERC20Address { + type Error = AddressParseError; + + fn try_from(bytes: Vec) -> Result { + if bytes.len() == 20 { + Ok(Self(Address::from_slice(&bytes))) + } else { + Err(AddressParseError) + } + } +} +pub struct AddressParseError; +impl AsRef<[u8]> for AddressParseError { + fn as_ref(&self) -> &[u8] { + b"ERR_PARSE_ADDRESS" + } +} + +pub struct NEP141Account(AccountId); +impl AsRef<[u8]> for NEP141Account { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() + } +} +impl TryFrom> for NEP141Account { + type Error = aurora_engine_types::account_id::ParseAccountError; + + fn try_from(bytes: Vec) -> Result { + AccountId::try_from(bytes).map(Self) + } +} + pub const ERR_INVALID_NEP141_ACCOUNT_ID: &str = "ERR_INVALID_NEP141_ACCOUNT_ID"; #[derive(Debug)] @@ -274,7 +311,7 @@ impl StackExecutorParams { } } - fn make_executor<'a, I: IO + Default + Copy>( + fn make_executor<'a, I: IO + Copy>( &'a self, engine: &'a Engine, ) -> executor::StackExecutor<'static, 'a, executor::MemoryStackState>, Precompiles> @@ -306,9 +343,6 @@ pub struct EngineState { pub bridge_prover_id: AccountId, /// How many blocks after staging upgrade can deploy it. pub upgrade_delay_blocks: u64, - /// Mapping between relayer account id and relayer evm address - pub relayers_evm_addresses: - LookupMap, } impl From for EngineState { @@ -318,7 +352,6 @@ impl From for EngineState { owner_id: args.owner_id, bridge_prover_id: args.bridge_prover_id, upgrade_delay_blocks: args.upgrade_delay_blocks, - relayers_evm_addresses: LookupMap::default(), } } } @@ -336,7 +369,7 @@ pub(crate) const CONFIG: &Config = &Config::london(); /// Key for storing the state of the engine. const STATE_KEY: &[u8; 5] = b"STATE"; -impl Engine { +impl Engine { pub fn new(origin: Address, io: I) -> Result { Engine::get_state(&io).map(|state| Self::new_with_state(state, origin, io)) } @@ -688,26 +721,32 @@ impl Engine { status.into_result(result) } + fn relayer_key(account_id: &[u8]) -> Vec { + bytes_to_key(KeyPrefix::RelayerEvmAddressMap, account_id) + } + pub fn register_relayer(&mut self, account_id: &[u8], evm_address: Address) { - self.state - .relayers_evm_addresses - .insert_raw(account_id, evm_address.as_bytes()); + let key = Self::relayer_key(account_id); + self.io.write_storage(&key, evm_address.as_bytes()); } - #[allow(dead_code)] pub fn get_relayer(&self, account_id: &[u8]) -> Option
{ - self.state - .relayers_evm_addresses - .get_raw(account_id) - .map(|result| Address(result.as_slice().try_into().unwrap())) + let key = Self::relayer_key(account_id); + self.io + .read_storage(&key) + .map(|v| Address::from_slice(&v.to_vec())) + } + + pub fn nep141_erc20_map(io: I) -> BijectionMap { + BijectionMap::new(KeyPrefix::Nep141Erc20Map, KeyPrefix::Erc20Nep141Map, io) } pub fn register_token( &mut self, - erc20_token: &[u8], - nep141_token: &[u8], + erc20_token: Address, + nep141_token: AccountId, ) -> Result<(), RegisterTokenError> { - match Self::get_erc20_from_nep141(self.io, nep141_token) { + match Self::get_erc20_from_nep141(&self.io, &nep141_token) { Err(GetErc20FromNep141Error::Nep141NotFound) => (), Err(GetErc20FromNep141Error::InvalidNep141AccountId) => { return Err(RegisterTokenError::InvalidNep141AccountId); @@ -715,19 +754,19 @@ impl Engine { Ok(_) => return Err(RegisterTokenError::TokenAlreadyRegistered), } - Self::nep141_erc20_map(self.io).insert(nep141_token, erc20_token); + let erc20_token = ERC20Address(erc20_token); + let nep141_token = NEP141Account(nep141_token); + Self::nep141_erc20_map(self.io).insert(&nep141_token, &erc20_token); Ok(()) } pub fn get_erc20_from_nep141( - io: I, - nep141_account_id: &[u8], + io: &I, + nep141_account_id: &AccountId, ) -> Result, GetErc20FromNep141Error> { - AccountId::try_from(nep141_account_id) - .map_err(|_| GetErc20FromNep141Error::InvalidNep141AccountId)?; - - Self::nep141_erc20_map(io) - .lookup_left(nep141_account_id) + let key = bytes_to_key(KeyPrefix::Nep141Erc20Map, nep141_account_id.as_bytes()); + io.read_storage(&key) + .map(|v| v.to_vec()) .ok_or(GetErc20FromNep141Error::Nep141NotFound) } @@ -787,10 +826,10 @@ impl Engine { (recipient, fee) }; - let token = sdk::predecessor_account_id(); + let token = AccountId::try_from(sdk::predecessor_account_id()).unwrap(); let erc20_token = Address(unwrap_res_or_finish!( unwrap_res_or_finish!( - Self::get_erc20_from_nep141(self.io, &token), + Self::get_erc20_from_nep141(&self.io, &token), output_on_fail, self.io ) @@ -878,16 +917,6 @@ impl Engine { self.io.return_output(b"\"0\""); } - pub fn nep141_erc20_map( - io: I, - ) -> BijectionMap< - I, - { KeyPrefix::Nep141Erc20Map as KeyPrefixU8 }, - { KeyPrefix::Erc20Nep141Map as KeyPrefixU8 }, - > { - BijectionMap { io } - } - fn filter_promises_from_logs>(logs: T) -> Vec { logs.into_iter() .filter_map(|log| { @@ -984,7 +1013,7 @@ pub fn compute_block_hash(chain_id: [u8; 32], block_height: u64, account_id: &[u sdk::sha256(&data) } -impl evm::backend::Backend for Engine { +impl evm::backend::Backend for Engine { /// Returns the "effective" gas price (as defined by EIP-1559) fn gas_price(&self) -> U256 { self.gas_price @@ -1115,7 +1144,7 @@ impl evm::backend::Backend for Engine { } } -impl ApplyBackend for Engine { +impl ApplyBackend for Engine { fn apply(&mut self, values: A, _logs: L, delete_empty: bool) where A: IntoIterator>, diff --git a/engine/src/fungible_token.rs b/engine/src/fungible_token.rs index 88f26dbf6..39e76afe3 100644 --- a/engine/src/fungible_token.rs +++ b/engine/src/fungible_token.rs @@ -14,7 +14,29 @@ const GAS_FOR_RESOLVE_TRANSFER: Gas = 5_000_000_000_000; const GAS_FOR_FT_ON_TRANSFER: Gas = 10_000_000_000_000; #[derive(Debug, Default, BorshDeserialize, BorshSerialize)] -pub struct FungibleToken { +pub struct FungibleToken { + /// Total ETH supply on Near (nETH as NEP-141 token) + pub total_eth_supply_on_near: Balance, + + /// Total ETH supply on Aurora (ETH in Aurora EVM) + pub total_eth_supply_on_aurora: Balance, + + /// The storage size in bytes for one account. + pub account_storage_usage: StorageUsage, +} + +impl FungibleToken { + pub fn ops(self, io: I) -> FungibleTokenOps { + FungibleTokenOps { + total_eth_supply_on_near: self.total_eth_supply_on_near, + total_eth_supply_on_aurora: self.total_eth_supply_on_aurora, + account_storage_usage: self.account_storage_usage, + io, + } + } +} + +pub struct FungibleTokenOps { /// Total ETH supply on Near (nETH as NEP-141 token) pub total_eth_supply_on_near: Balance, @@ -24,7 +46,6 @@ pub struct FungibleToken { /// The storage size in bytes for one account. pub account_storage_usage: StorageUsage, - #[borsh_skip] io: I, } @@ -89,9 +110,17 @@ impl From for JsonValue { } } -impl FungibleToken { - pub fn new() -> Self { - Self::default() +impl FungibleTokenOps { + pub fn new(io: I) -> Self { + FungibleToken::default().ops(io) + } + + pub fn data(&self) -> FungibleToken { + FungibleToken { + total_eth_supply_on_near: self.total_eth_supply_on_near, + total_eth_supply_on_aurora: self.total_eth_supply_on_aurora, + account_storage_usage: self.account_storage_usage, + } } /// Balance of nETH (ETH on NEAR token) diff --git a/engine/src/lib.rs b/engine/src/lib.rs index ef87dcba1..558f70b3e 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -417,9 +417,7 @@ mod contract { }; sdk::log!(crate::prelude::format!("Deployed ERC-20 in Aurora at: {:#?}", address).as_str()); - engine - .register_token(address.as_bytes(), args.nep141.as_bytes()) - .sdk_unwrap(); + engine.register_token(address, args.nep141).sdk_unwrap(); io.return_output(&address.as_bytes().try_to_vec().sdk_expect("ERR_SERIALIZE")); // TODO: charge for storage @@ -742,7 +740,7 @@ mod contract { let args: GetErc20FromNep141CallArgs = io.read_input_borsh().sdk_unwrap(); io.return_output( - Engine::get_erc20_from_nep141(io, args.nep141.as_bytes()) + Engine::get_erc20_from_nep141(&io, &args.nep141) .sdk_unwrap() .as_slice(), ); @@ -751,11 +749,13 @@ mod contract { #[no_mangle] pub extern "C" fn get_nep141_from_erc20() { let mut io = Runtime; + let erc20_address: crate::engine::ERC20Address = + io.read_input().to_vec().try_into().sdk_unwrap(); io.return_output( Engine::nep141_erc20_map(io) - .lookup_right(io.read_input().to_vec().as_slice()) + .lookup_right(&erc20_address) .sdk_expect("ERC20_NOT_FOUND") - .as_slice(), + .as_ref(), ); } diff --git a/engine/src/map.rs b/engine/src/map.rs index 28de64c14..faf69b69e 100644 --- a/engine/src/map.rs +++ b/engine/src/map.rs @@ -1,101 +1,57 @@ -pub use crate::prelude::{bytes_to_key, BorshDeserialize, BorshSerialize, KeyPrefixU8, Vec}; +pub use crate::prelude::{bytes_to_key, PhantomData, TryFrom, TryInto, Vec}; use aurora_engine_sdk::io::{StorageIntermediate, IO}; -use aurora_engine_sdk::near_runtime::Runtime; - -/// An non-iterable implementation of a map that stores its content directly on the trie. -/// Use `key_prefix` as a unique prefix for keys. -#[derive(BorshSerialize, BorshDeserialize)] -pub struct LookupMap { - #[borsh_skip] - pub io: I, -} - -impl Default for LookupMap { - fn default() -> Self { - Self { io: Runtime } - } +use aurora_engine_types::storage::KeyPrefix; + +/// A map storing a 1:1 relation between elements of types L and R. +/// The map is backed by storage of type I. +pub struct BijectionMap { + left_prefix: KeyPrefix, + right_prefix: KeyPrefix, + io: I, + left_phantom: PhantomData, + right_phantom: PhantomData, } -impl LookupMap { - /// Create a new map. - pub fn new(io: I) -> Self { - Self { io } - } - - /// Build key for this map scope - fn raw_key_to_storage_key(&self, key_raw: &[u8]) -> Vec { - bytes_to_key(K.into(), key_raw) - } - - /// Returns `true` if the serialized key is present in the map. - #[allow(dead_code)] - pub fn contains_key_raw(&self, key_raw: &[u8]) -> bool { - let storage_key = self.raw_key_to_storage_key(key_raw); - self.io.storage_has_key(&storage_key) - } - - /// Returns the serialized value corresponding to the serialized key. - #[allow(dead_code)] - pub fn get_raw(&self, key_raw: &[u8]) -> Option> { - let storage_key = self.raw_key_to_storage_key(key_raw); - self.io.read_storage(&storage_key).map(|s| s.to_vec()) - } - - /// Inserts a serialized key-value pair into the map. - pub fn insert_raw(&mut self, key_raw: &[u8], value_raw: &[u8]) { - let storage_key = self.raw_key_to_storage_key(key_raw); - self.io.write_storage(&storage_key, value_raw); - } - - /// Removes a serialized key from the map, returning the serialized value at the key if the key - /// was previously in the map. - #[allow(dead_code)] - pub fn remove_raw(&mut self, key_raw: &[u8]) -> Option> { - let storage_key = self.raw_key_to_storage_key(key_raw); - self.io.remove_storage(&storage_key).map(|s| s.to_vec()) - } -} - -#[derive(BorshSerialize, BorshDeserialize, Default)] -pub struct BijectionMap { - #[borsh_skip] - pub io: I, -} - -impl BijectionMap { - fn left_to_right(&self) -> LookupMap { - LookupMap { io: self.io } +impl + TryFrom>, R: AsRef<[u8]> + TryFrom>, I: IO> + BijectionMap +{ + pub fn new(left_prefix: KeyPrefix, right_prefix: KeyPrefix, io: I) -> Self { + Self { + left_prefix, + right_prefix, + io, + left_phantom: PhantomData, + right_phantom: PhantomData, + } } - fn right_to_left(&self) -> LookupMap { - LookupMap { io: self.io } - } + pub fn insert(&mut self, left: &L, right: &R) { + let key = self.left_key(left); + self.io.write_storage(&key, right.as_ref()); - pub fn insert(&self, value_left: &[u8], value_right: &[u8]) { - self.left_to_right().insert_raw(value_left, value_right); - self.right_to_left().insert_raw(value_right, value_left); + let key = self.right_key(right); + self.io.write_storage(&key, left.as_ref()); } - pub fn lookup_left(&self, value_left: &[u8]) -> Option> { - self.left_to_right().get_raw(value_left) + pub fn lookup_left(&self, left: &L) -> Option { + let key = self.left_key(left); + self.io + .read_storage(&key) + .and_then(|v| v.to_vec().try_into().ok()) } - #[allow(dead_code)] - pub fn lookup_right(&self, value_right: &[u8]) -> Option> { - self.right_to_left().get_raw(value_right) + pub fn lookup_right(&self, right: &R) -> Option { + let key = self.right_key(right); + self.io + .read_storage(&key) + .and_then(|v| v.to_vec().try_into().ok()) } - #[allow(dead_code)] - pub fn remove_left(&self, value_left: &[u8]) { - if let Some(value_right) = self.left_to_right().remove_raw(value_left) { - self.right_to_left().remove_raw(value_right.as_slice()); - } + fn left_key(&self, left: &L) -> Vec { + bytes_to_key(self.left_prefix, left.as_ref()) } - #[allow(dead_code)] - pub fn remove_right(&self, value_right: &[u8]) { - if let Some(value_left) = self.right_to_left().remove_raw(value_right) { - self.left_to_right().remove_raw(value_left.as_slice()); - } + fn right_key(&self, right: &R) -> Vec { + bytes_to_key(self.right_prefix, right.as_ref()) } }