From cc15e61e082638a184dc3fcb1347636d2766b751 Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Mon, 23 Jan 2023 13:11:46 +0400 Subject: [PATCH 01/12] feat: add erc-20 as a gas token --- engine/src/engine.rs | 53 +++++++++++++++++++++++++++++++++++++------- engine/src/lib.rs | 1 + 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 7901888c4..d1ac3aa47 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -414,10 +414,20 @@ impl From for EngineState { } } +/// Used to select which gas token to pay in. +pub enum GasToken { + /// Gas is paid in Ether. + ETH, + /// Gas is paid in a ERC-20 compatible token. + ERC20(Address), +} + + pub struct Engine<'env, I: IO, E: Env> { state: EngineState, origin: Address, gas_price: U256, + gas_token: GasToken, current_account_id: AccountId, io: I, env: &'env E, @@ -453,6 +463,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { state, origin, gas_price: U256::zero(), + gas_token: GasToken::ETH, current_account_id, io, env, @@ -463,10 +474,13 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { } } + // TODO: If we don't actually charge_gas here after changes, this should + // be renamed. pub fn charge_gas( &mut self, sender: &Address, transaction: &NormalizedEthTransaction, + gas_token: GasToken, ) -> Result { if transaction.max_fee_per_gas.is_zero() { return Ok(GasPaymentResult::default()); @@ -482,11 +496,22 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { .map(Wei::new) .ok_or(GasPaymentError::EthAmountOverflow)?; - let new_balance = get_balance(&self.io, sender) - .checked_sub(prepaid_amount) - .ok_or(GasPaymentError::OutOfFund)?; - - set_balance(&mut self.io, sender, &new_balance); + match gas_token { + GasToken::ETH => { + // TODO: Remove the set_balance as we need to just check if they can spend + // Use new `can_transfer` function instead. + // Also needs to be verified how it is done on geth. + let new_balance = get_balance(&self.io, sender) + .checked_sub(prepaid_amount) + .ok_or(GasPaymentError::OutOfFund)?; + + // This part is questionable. + set_balance(&mut self.io, sender, &new_balance); + } + GasToken::ERC20(addr) => { + // TODO: Needs SputnikVM balance check + } + } self.gas_price = effective_gas_price; @@ -947,7 +972,6 @@ pub fn submit( let prepaid_amount = match engine.charge_gas(&sender, &transaction) { Ok(gas_result) => gas_result, Err(GasPaymentError::OutOfFund) => { - increment_nonce(&mut io, &sender); let result = SubmitResult::new(TransactionStatus::OutOfFund, 0, vec![]); return Ok(result); } @@ -1115,6 +1139,7 @@ pub fn get_authorizer() -> EngineAuthorizer { EngineAuthorizer::from_accounts(once(account)) } +// TODO: Gas must be charged from here. pub fn refund_unused_gas( io: &mut I, sender: &Address, @@ -1141,8 +1166,12 @@ pub fn refund_unused_gas( .checked_sub(spent_amount) .ok_or(GasPaymentError::EthAmountOverflow)?; - add_balance(io, sender, refund)?; - add_balance(io, relayer, reward_amount)?; + if refund > Wei::zero() { + add_balance(io, sender, refund)?; + } + if reward_amount > Wei::zero() { + add_balance(io, relayer, reward_amount)?; + } Ok(()) } @@ -1325,6 +1354,14 @@ pub fn get_balance(io: &I, address: &Address) -> Wei { Wei::new(raw) } +/// Checks whether there are enough funds for the given address and amount. +/// +/// This does not take gas into account. +pub fn can_transfer(io: &I, address: &Address, amount: Wei) -> bool { + let current_balance = get_balance(io, address); + amount >= current_balance +} + pub fn remove_storage(io: &mut I, address: &Address, key: &H256, generation: u32) { io.remove_storage(storage_to_key(address, key, generation).as_ref()); } diff --git a/engine/src/lib.rs b/engine/src/lib.rs index ff133643a..67e52b23d 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -30,6 +30,7 @@ pub mod log_entry; pub mod pausables; mod prelude; pub mod xcc; +mod erc20; #[cfg(target_arch = "wasm32")] #[global_allocator] From c69fb529584d29e07659fb16eaab9119bb5821eb Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Mon, 23 Jan 2023 19:05:36 +0400 Subject: [PATCH 02/12] feat: add setGasToken precompile --- engine-precompiles/src/lib.rs | 17 +-- engine-precompiles/src/set_gas_token.rs | 157 ++++++++++++++++++++++++ engine-types/src/types/address.rs | 45 +++++-- engine-types/src/types/gas.rs | 2 + engine/src/engine.rs | 100 ++++++++++----- engine/src/lib.rs | 1 - 6 files changed, 267 insertions(+), 55 deletions(-) create mode 100644 engine-precompiles/src/set_gas_token.rs diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs index fc6d9cc4e..772cd3a72 100644 --- a/engine-precompiles/src/lib.rs +++ b/engine-precompiles/src/lib.rs @@ -15,6 +15,7 @@ pub mod prepaid_gas; pub mod promise_result; pub mod random; pub mod secp256k1; +pub mod set_gas_token; mod utils; pub mod xcc; @@ -111,8 +112,8 @@ impl HardFork for Istanbul {} impl HardFork for Berlin {} pub struct Precompiles<'a, I, E, H> { - pub all_precompiles: prelude::BTreeMap>, - pub paused_precompiles: prelude::BTreeSet
, + pub all_precompiles: BTreeMap>, + pub paused_precompiles: BTreeSet
, } impl<'a, I, E, H> Precompiles<'a, I, E, H> { @@ -149,7 +150,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::Preco Some(result.and_then(|output| post_process(output, handle))) } - fn is_precompile(&self, address: prelude::H160) -> bool { + fn is_precompile(&self, address: H160) -> bool { self.all_precompiles.contains_key(&Address::new(address)) } } @@ -270,7 +271,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E, RandomSeed::ADDRESS, CurrentAccount::ADDRESS, ]; - let fun: prelude::Vec> = vec![ + let fun: Vec> = vec![ Box::new(ECRecover), Box::new(SHA256), Box::new(RIPEMD160), @@ -386,10 +387,10 @@ pub enum AllPrecompiles<'a, I, E, H> { /// fn for making an address by concatenating the bytes from two given numbers, /// Note that 32 + 128 = 160 = 20 bytes (the length of an address). This function is used /// as a convenience for specifying the addresses of the various precompiles. -pub const fn make_address(x: u32, y: u128) -> prelude::types::Address { +pub const fn make_address(x: u32, y: u128) -> Address { let x_bytes = x.to_be_bytes(); let y_bytes = y.to_be_bytes(); - prelude::types::Address::new(H160([ + Address::new(H160([ x_bytes[0], x_bytes[1], x_bytes[2], @@ -413,10 +414,10 @@ pub const fn make_address(x: u32, y: u128) -> prelude::types::Address { ])) } -const fn make_h256(x: u128, y: u128) -> prelude::H256 { +const fn make_h256(x: u128, y: u128) -> H256 { let x_bytes = x.to_be_bytes(); let y_bytes = y.to_be_bytes(); - prelude::H256([ + H256([ x_bytes[0], x_bytes[1], x_bytes[2], diff --git a/engine-precompiles/src/set_gas_token.rs b/engine-precompiles/src/set_gas_token.rs new file mode 100644 index 000000000..3dbcd0ae3 --- /dev/null +++ b/engine-precompiles/src/set_gas_token.rs @@ -0,0 +1,157 @@ +use crate::set_gas_token::events::SetGasTokenLog; +use crate::{EvmPrecompileResult, Precompile, PrecompileOutput}; +use aurora_engine_types::types::{Address, EthGas}; +use evm::backend::Log; +use evm::{Context, ExitError}; +use std::borrow::Cow; + +pub use consts::SET_GAS_TOKEN_ADDRESS; + +mod costs { + use crate::prelude::types::EthGas; + + // TODO: gas costs, could be calculated returning logs of NEAR gas used prior and after. + // Should check if the gas check adds gas itself as well..? + pub(super) const SET_GAS_TOKEN_GAS: EthGas = EthGas::new(0); +} + +pub mod consts { + use aurora_engine_types::types::Address; + + /// Change gas token precompile address. + /// + /// Address: `0x076dae45c8e16a92258252fe04dedd97f1ea93d6` + /// + /// This address is computed as: `keccak("setGasToken")[12..]` + pub const SET_GAS_TOKEN_ADDRESS: Address = + crate::make_address(0x076dae45, 0xc8e16a92258252fe04dedd97f1ea93d6); +} + +pub mod events { + use crate::set_gas_token::consts; + use aurora_engine_types::types::Address; + use aurora_engine_types::H256; + use evm::backend::Log; + + // TODO + pub(crate) const SET_GAS_TOKEN_SIGNATURE: H256 = crate::make_h256( + 0x29d0b6eaa171d0d1607729f506329510, + 0x7bc9766ba17d250f129cb5bd06503d13, + ); + + pub(crate) struct SetGasTokenLog { + pub sender: Address, + pub gas_token: Address, + } + + impl SetGasTokenLog { + pub(crate) fn encode(self) -> Log { + let data = ethabi::encode(&[ethabi::Token::Address(self.gas_token.raw())]); + let sender_address = { + let mut buf = [0u8; 32]; + buf[12..].copy_from_slice(self.sender.as_bytes()); + H256(buf) + }; + let topics = vec![SET_GAS_TOKEN_SIGNATURE, sender_address]; + + let raw_log = ethabi::RawLog { topics, data }; + + Log { + address: consts::SET_GAS_TOKEN_ADDRESS.raw(), + topics: raw_log.topics, + data: raw_log.data, + } + } + } + + #[cfg(test)] + pub fn set_gas_token_schema() -> ethabi::Event { + ethabi::Event { + name: "SetGasToken".into(), + inputs: vec![ + ethabi::EventParam { + name: "sender".into(), + kind: ethabi::ParamType::Address, + indexed: true, + }, + ethabi::EventParam { + name: "gas_token".into(), + kind: ethabi::ParamType::Address, + indexed: true, + }, + ], + anonymous: false, + } + } +} + +/// A precompile contract used to set the gas token. +/// +/// Takes an input which must be an approved ERC-20 contract, or ETH itself at +/// the address `0x0`. +pub struct SetGasToken; + +impl SetGasToken { + pub const ADDRESS: Address = SET_GAS_TOKEN_ADDRESS; +} + +impl Precompile for SetGasToken { + fn required_gas(_input: &[u8]) -> Result { + Ok(costs::SET_GAS_TOKEN_GAS) + } + + fn run( + &self, + input: &[u8], + target_gas: Option, + context: &Context, + is_static: bool, + ) -> EvmPrecompileResult { + let required_gas = Self::required_gas(input)?; + if let Some(target_gas) = target_gas { + if required_gas > target_gas { + return Err(ExitError::OutOfGas); + } + } + + // It's not allowed to call exit precompiles in static mode + if is_static { + return Err(ExitError::Other(Cow::from("ERR_INVALID_IN_STATIC"))); + } else if context.address != Self::ADDRESS.raw() { + return Err(ExitError::Other(Cow::from("ERR_INVALID_IN_DELEGATE"))); + } + + let set_gas_token_log: Log = { + let sender = Address::new(context.caller); + let gas_token = Address::try_from_slice(input) + .map_err(|_e| ExitError::Other(Cow::from("ERR_INVALID_ETH_ADDRESS")))?; + SetGasTokenLog { sender, gas_token }.encode() + }; + + Ok(PrecompileOutput { + cost: required_gas, + logs: vec![set_gas_token_log], + ..Default::default() + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use aurora_engine_sdk::types::near_account_to_evm_address; + + #[test] + fn test_precompile_id() { + assert_eq!( + SET_GAS_TOKEN_ADDRESS, + near_account_to_evm_address("setGasToken".as_bytes()) + ); + } + + #[test] + fn test_signature() { + let schema = events::set_gas_token_schema(); + assert_eq!(schema.signature(), events::SET_GAS_TOKEN_SIGNATURE); + } +} diff --git a/engine-types/src/types/address.rs b/engine-types/src/types/address.rs index 13be5223d..6d3b6135d 100755 --- a/engine-types/src/types/address.rs +++ b/engine-types/src/types/address.rs @@ -4,6 +4,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +const ADDRESS_HEX_LENGTH: usize = 40; +const ADDRESS_BYTE_LENGTH: usize = 20; + /// Base Eth Address type #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -26,10 +29,10 @@ impl Address { } pub fn decode(address: &str) -> Result { - if address.len() != 40 { + if address.len() != ADDRESS_HEX_LENGTH { return Err(error::AddressError::IncorrectLength); } - let mut result = [0u8; 20]; + let mut result = [0u8; ADDRESS_BYTE_LENGTH]; hex::decode_to_slice(address, &mut result) .map_err(|_| error::AddressError::FailedDecodeHex)?; Ok(Address::new(H160(result))) @@ -40,18 +43,25 @@ impl Address { } pub fn try_from_slice(raw_addr: &[u8]) -> Result { - if raw_addr.len() != 20 { - return Err(error::AddressError::IncorrectLength); + use core::cmp::Ordering; + match raw_addr.len().cmp(&ADDRESS_BYTE_LENGTH) { + Ordering::Greater => Err(error::AddressError::IncorrectLength), + Ordering::Less => { + let mut buf = [0u8; ADDRESS_BYTE_LENGTH]; + let pos = ADDRESS_BYTE_LENGTH - raw_addr.len(); + buf[pos..].copy_from_slice(raw_addr); + Ok(Self::new(H160::from_slice(&buf))) + } + Ordering::Equal => Ok(Self::new(H160::from_slice(raw_addr))), } - Ok(Self::new(H160::from_slice(raw_addr))) } - pub const fn from_array(array: [u8; 20]) -> Self { + pub const fn from_array(array: [u8; ADDRESS_BYTE_LENGTH]) -> Self { Self(H160(array)) } pub const fn zero() -> Self { - Address::new(H160([0u8; 20])) + Address::new(H160([0u8; ADDRESS_BYTE_LENGTH])) } } @@ -71,15 +81,15 @@ impl BorshSerialize for Address { impl BorshDeserialize for Address { fn deserialize(buf: &mut &[u8]) -> io::Result { - if buf.len() < 20 { + if buf.len() < ADDRESS_BYTE_LENGTH { return Err(io::Error::new( io::ErrorKind::Other, format!("{}", error::AddressError::IncorrectLength), )); } // Guaranty no panics. The length checked early - let address = Self(H160::from_slice(&buf[..20])); - *buf = &buf[20..]; + let address = Self(H160::from_slice(&buf[..ADDRESS_BYTE_LENGTH])); + *buf = &buf[ADDRESS_BYTE_LENGTH..]; Ok(address) } } @@ -139,8 +149,19 @@ mod tests { } #[test] - fn test_wrong_address_19() { - let serialized_addr = [0u8; 19]; + fn test_address_less_than_20_byte_length() { + let serialized_addr = [0x1u8; 1]; + let addr = Address::try_from_slice(&serialized_addr).unwrap(); + let expected = Address::try_from_slice( + &hex::decode("0000000000000000000000000000000000000001").unwrap(), + ) + .unwrap(); + assert_eq!(addr, expected); + } + + #[test] + fn test_address_greater_than_20_byte_length() { + let serialized_addr = [0x11u8; 21]; let addr = Address::try_from_slice(&serialized_addr); let err = addr.unwrap_err(); matches!(err, error::AddressError::IncorrectLength); diff --git a/engine-types/src/types/gas.rs b/engine-types/src/types/gas.rs index 0232b776d..b715faf70 100644 --- a/engine-types/src/types/gas.rs +++ b/engine-types/src/types/gas.rs @@ -56,6 +56,8 @@ impl Display for EthGas { } impl EthGas { + pub const MAX: EthGas = EthGas(u64::MAX); + /// Constructs a new `EthGas` with a given u64 value. pub const fn new(gas: u64) -> EthGas { Self(gas) diff --git a/engine/src/engine.rs b/engine/src/engine.rs index d1ac3aa47..7a363afbc 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -1,7 +1,7 @@ use crate::parameters::{CallArgs, NEP141FtOnTransferArgs, ResultLog, SubmitResult, ViewCallArgs}; use core::mem; use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log}; -use evm::executor; +use evm::{executor, Context}; use evm::{Config, CreateScheme, ExitError, ExitFatal, ExitReason}; use crate::connector::EthConnectorContract; @@ -20,14 +20,16 @@ use crate::pausables::{ use crate::prelude::parameters::RefundCallArgs; use crate::prelude::precompiles::native::{exit_to_ethereum, exit_to_near}; use crate::prelude::precompiles::xcc::cross_contract_call; -use crate::prelude::precompiles::Precompiles; +use crate::prelude::precompiles::{self, Precompiles}; use crate::prelude::transactions::{EthTransactionKind, NormalizedEthTransaction}; use crate::prelude::{ address_to_key, bytes_to_key, sdk, storage_to_key, u256_to_arr, vec, AccountId, Address, BTreeMap, BorshDeserialize, BorshSerialize, KeyPrefix, PromiseArgs, PromiseCreateArgs, ToString, Vec, Wei, Yocto, ERC20_MINT_SELECTOR, H160, H256, U256, }; -use aurora_engine_precompiles::PrecompileConstructorContext; +use aurora_engine_precompiles::set_gas_token::SetGasToken; +use aurora_engine_precompiles::{Precompile, PrecompileConstructorContext}; +use aurora_engine_types::types::EthGas; use core::cell::RefCell; use core::iter::once; @@ -422,7 +424,6 @@ pub enum GasToken { ERC20(Address), } - pub struct Engine<'env, I: IO, E: Env> { state: EngineState, origin: Address, @@ -510,6 +511,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { } GasToken::ERC20(addr) => { // TODO: Needs SputnikVM balance check + todo!() } } @@ -529,7 +531,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { ) -> EngineResult { let origin = Address::new(self.origin()); let value = Wei::zero(); - self.deploy_code(origin, value, input, u64::MAX, Vec::new(), handler) + self.deploy_code(origin, value, input, EthGas::MAX, Vec::new(), handler) } pub fn deploy_code( @@ -537,20 +539,25 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { origin: Address, value: Wei, input: Vec, - gas_limit: u64, + gas_limit: EthGas, access_list: Vec<(H160, Vec)>, // See EIP-2930 handler: &mut P, ) -> EngineResult { let pause_flags = EnginePrecompilesPauser::from_io(self.io).paused(); let precompiles = self.create_precompiles(pause_flags, handler); - let executor_params = StackExecutorParams::new(gas_limit, precompiles); + let executor_params = StackExecutorParams::new(gas_limit.as_u64(), precompiles); let mut executor = executor_params.make_executor(self); let address = executor.create_address(CreateScheme::Legacy { caller: origin.raw(), }); - let (exit_reason, return_value) = - executor.transact_create(origin.raw(), value.raw(), input, gas_limit, access_list); + let (exit_reason, return_value) = executor.transact_create( + origin.raw(), + value.raw(), + input, + gas_limit.as_u64(), + access_list, + ); let result = if exit_reason.is_succeed() { address.0.to_vec() } else { @@ -591,7 +598,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { &contract, value, input, - u64::MAX, + EthGas::MAX, Vec::new(), handler, ) @@ -605,7 +612,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { &contract, value, input, - u64::MAX, + EthGas::MAX, Vec::new(), handler, ) @@ -620,21 +627,21 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { contract: &Address, value: Wei, input: Vec, - gas_limit: u64, + gas_limit: EthGas, access_list: Vec<(H160, Vec)>, // See EIP-2930 handler: &mut P, ) -> EngineResult { let pause_flags = EnginePrecompilesPauser::from_io(self.io).paused(); let precompiles = self.create_precompiles(pause_flags, handler); - let executor_params = StackExecutorParams::new(gas_limit, precompiles); + let executor_params = StackExecutorParams::new(gas_limit.as_u64(), precompiles); let mut executor = executor_params.make_executor(self); let (exit_reason, result) = executor.transact_call( origin.raw(), contract.raw(), value.raw(), input, - gas_limit, + gas_limit.as_u64(), access_list, ); @@ -740,7 +747,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { sender: Address, receiver: Address, value: Wei, - gas_limit: u64, + gas_limit: EthGas, handler: &mut P, ) -> EngineResult { self.call( @@ -873,8 +880,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { promise_handler: ro_promise_handler, }); // Cross contract calls are not enabled on mainnet yet. - tmp.all_precompiles - .remove(&aurora_engine_precompiles::xcc::cross_contract_call::ADDRESS); + tmp.all_precompiles.remove(&cross_contract_call::ADDRESS); tmp } else { Precompiles::new_london(PrecompileConstructorContext { @@ -953,7 +959,7 @@ pub fn submit( check_nonce(&io, &sender, &transaction.nonce)?; // Check intrinsic gas is covered by transaction gas limit - match transaction.intrinsic_gas(crate::engine::CONFIG) { + match transaction.intrinsic_gas(CONFIG) { Err(_e) => { return Err(EngineErrorKind::GasOverflow.into()); } @@ -969,7 +975,8 @@ pub fn submit( } let mut engine = Engine::new_with_state(state, sender, current_account_id, io, env); - let prepaid_amount = match engine.charge_gas(&sender, &transaction) { + // TODO: Have GasToken derived from storage. + let prepaid_amount = match engine.charge_gas(&sender, &transaction, GasToken::ETH) { Ok(gas_result) => gas_result, Err(GasPaymentError::OutOfFund) => { let result = SubmitResult::new(TransactionStatus::OutOfFund, 0, vec![]); @@ -979,7 +986,7 @@ pub fn submit( return Err(EngineErrorKind::GasPayment(err).into()); } }; - let gas_limit: u64 = transaction + let gas_limit: EthGas = transaction .gas_limit .try_into() .map_err(|_| EngineErrorKind::GasOverflow)?; @@ -989,16 +996,41 @@ pub fn submit( .map(|a| (a.address, a.storage_keys)) .collect(); let result = if let Some(receiver) = transaction.to { - engine.call( - &sender, - &receiver, - transaction.value, - transaction.data, - gas_limit, - access_list, - handler, - ) - // TODO: charge for storage + // We are making this precompile here special for now as we want to + // ensure that it can only be directly called. This may change in the + // future after careful research and consideration. For example, we are + // not sure if there are consequences in changing the gas during the + // execution of the transaction. + if receiver == precompiles::set_gas_token::SET_GAS_TOKEN_ADDRESS { + let set_gas_token = SetGasToken; + let context = Context { + address: receiver.raw(), + caller: transaction.address.raw(), + apparent_value: Default::default(), + }; + match set_gas_token.run(&transaction.data, Some(gas_limit), &context, false) { + Ok(v) => { + let tx_status = TransactionStatus::Succeed(vec![]); + let mut result_logs = Vec::new(); + for log in v.logs { + result_logs.push(log.into()); + } + Ok(SubmitResult::new(tx_status, v.cost.as_u64(), result_logs)) + } + Err(e) => Err(EngineError::from(EngineErrorKind::EvmError(e))), + } + } else { + engine.call( + &sender, + &receiver, + transaction.value, + transaction.data, + gas_limit, + access_list, + handler, + ) + // TODO: charge for storage + } } else { // Execute a contract deployment: engine.deploy_code( @@ -1062,14 +1094,14 @@ pub fn refund_on_error( &erc20_address, Wei::zero(), input, - u64::MAX, + EthGas::MAX, Vec::new(), handler, ) } // ETH exit; transfer ETH back from precompile address None => { - let exit_address = aurora_engine_precompiles::native::exit_to_near::ADDRESS; + let exit_address = exit_to_near::ADDRESS; let mut engine = Engine::new_with_state(state, exit_address, current_account_id, io, env); let refund_address = args.recipient_address; @@ -1079,7 +1111,7 @@ pub fn refund_on_error( &refund_address, amount, Vec::new(), - u64::MAX, + EthGas::MAX, vec![ (exit_address.raw(), Vec::new()), (refund_address.raw(), Vec::new()), @@ -1957,7 +1989,7 @@ mod tests { let mut engine = Engine::new_with_state(EngineState::default(), origin, current_account_id, io, &env); - let gas_limit = u64::MAX; + let gas_limit = EthGas::MAX; let mut handler = Noop; let receiver = make_address(1, 1); let value = Wei::new_u64(1000); diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 67e52b23d..ff133643a 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -30,7 +30,6 @@ pub mod log_entry; pub mod pausables; mod prelude; pub mod xcc; -mod erc20; #[cfg(target_arch = "wasm32")] #[global_allocator] From e704caf4fcd8d4b78723bf2bc18287da084cf60a Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Tue, 24 Jan 2023 11:31:30 +0400 Subject: [PATCH 03/12] fix: clippy --- engine-precompiles/src/set_gas_token.rs | 2 +- engine-tests/src/tests/standalone/sanity.rs | 4 ++-- engine/src/engine.rs | 25 ++++++++++++--------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/engine-precompiles/src/set_gas_token.rs b/engine-precompiles/src/set_gas_token.rs index 3dbcd0ae3..9ff1d9d8b 100644 --- a/engine-precompiles/src/set_gas_token.rs +++ b/engine-precompiles/src/set_gas_token.rs @@ -1,9 +1,9 @@ +use crate::prelude::{vec, Cow}; use crate::set_gas_token::events::SetGasTokenLog; use crate::{EvmPrecompileResult, Precompile, PrecompileOutput}; use aurora_engine_types::types::{Address, EthGas}; use evm::backend::Log; use evm::{Context, ExitError}; -use std::borrow::Cow; pub use consts::SET_GAS_TOKEN_ADDRESS; diff --git a/engine-tests/src/tests/standalone/sanity.rs b/engine-tests/src/tests/standalone/sanity.rs index 26197c6a3..db3d72a9a 100644 --- a/engine-tests/src/tests/standalone/sanity.rs +++ b/engine-tests/src/tests/standalone/sanity.rs @@ -2,7 +2,7 @@ use aurora_engine::engine; use aurora_engine_sdk::env::DEFAULT_PREPAID_GAS; use aurora_engine_test_doubles::io::{Storage, StoragePointer}; use aurora_engine_test_doubles::promise::PromiseTracker; -use aurora_engine_types::types::{Address, Wei}; +use aurora_engine_types::types::{Address, EthGas, Wei}; use aurora_engine_types::{account_id::AccountId, H160, H256, U256}; use std::sync::RwLock; @@ -41,7 +41,7 @@ fn test_deploy_code() { origin, Wei::zero(), evm_deploy(&code_to_deploy), - u64::MAX, + EthGas::MAX, Vec::new(), &mut handler, ); diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 7a363afbc..a435286a4 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -428,7 +428,6 @@ pub struct Engine<'env, I: IO, E: Env> { state: EngineState, origin: Address, gas_price: U256, - gas_token: GasToken, current_account_id: AccountId, io: I, env: &'env E, @@ -464,7 +463,6 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { state, origin, gas_price: U256::zero(), - gas_token: GasToken::ETH, current_account_id, io, env, @@ -509,7 +507,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { // This part is questionable. set_balance(&mut self.io, sender, &new_balance); } - GasToken::ERC20(addr) => { + GasToken::ERC20(_addr) => { // TODO: Needs SputnikVM balance check todo!() } @@ -814,7 +812,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { &erc20_token, Wei::zero(), setup_receive_erc20_tokens_input(args, &recipient), - u64::MAX, + EthGas::MAX, Vec::new(), // TODO: are there values we should put here? handler, ) @@ -986,10 +984,13 @@ pub fn submit( return Err(EngineErrorKind::GasPayment(err).into()); } }; - let gas_limit: EthGas = transaction - .gas_limit - .try_into() - .map_err(|_| EngineErrorKind::GasOverflow)?; + let gas_limit: EthGas = { + let gas_limit: u64 = transaction + .gas_limit + .try_into() + .map_err(|_| EngineErrorKind::GasOverflow)?; + EthGas::new(gas_limit) + }; let access_list = transaction .access_list .into_iter() @@ -1564,7 +1565,7 @@ unsafe fn schedule_promise_callback( handler.promise_attach_callback(base_id, promise) } -impl<'env, I: IO + Copy, E: Env> evm::backend::Backend for Engine<'env, I, E> { +impl<'env, I: IO + Copy, E: Env> Backend for Engine<'env, I, E> { /// Returns the "effective" gas price (as defined by EIP-1559) fn gas_price(&self) -> U256 { self.gas_price @@ -1851,6 +1852,7 @@ impl<'env, J: IO + Copy, E: Env> ApplyBackend for Engine<'env, J, E> { } #[cfg(test)] +#[cfg(feature = "std")] mod tests { use super::*; use crate::parameters::{FunctionCallArgsV1, FunctionCallArgsV2}; @@ -2139,7 +2141,10 @@ mod tests { data: vec![], access_list: vec![], }; - let actual_result = engine.charge_gas(&origin, &transaction).unwrap(); + // TODO: Add other tests than just ETH as a gas token. + let actual_result = engine + .charge_gas(&origin, &transaction, GasToken::ETH) + .unwrap(); let expected_result = GasPaymentResult { prepaid_amount: Wei::zero(), From c927cb632838b30bdc0a86a1e9f269c6105df670 Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Tue, 24 Jan 2023 15:36:49 +0400 Subject: [PATCH 04/12] feat: set gas token from precompile log --- engine-precompiles/src/lib.rs | 6 +- engine-precompiles/src/set_gas_token.rs | 29 ++++++-- engine-sdk/src/io.rs | 2 +- engine-types/src/storage.rs | 2 + engine/src/connector.rs | 12 ++-- engine/src/engine.rs | 93 ++++++++++++++----------- engine/src/fungible_token.rs | 2 +- engine/src/gas_token.rs | 52 ++++++++++++++ engine/src/lib.rs | 1 + 9 files changed, 140 insertions(+), 59 deletions(-) create mode 100644 engine/src/gas_token.rs diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs index 772cd3a72..622c123b1 100644 --- a/engine-precompiles/src/lib.rs +++ b/engine-precompiles/src/lib.rs @@ -207,7 +207,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E, RandomSeed::ADDRESS, CurrentAccount::ADDRESS, ]; - let fun: prelude::Vec> = vec![ + let fun: Vec> = vec![ Box::new(ECRecover), Box::new(SHA256), Box::new(RIPEMD160), @@ -236,7 +236,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E, RandomSeed::ADDRESS, CurrentAccount::ADDRESS, ]; - let fun: prelude::Vec> = vec![ + let fun: Vec> = vec![ Box::new(ECRecover), Box::new(SHA256), Box::new(RIPEMD160), @@ -307,7 +307,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E, RandomSeed::ADDRESS, CurrentAccount::ADDRESS, ]; - let fun: prelude::Vec> = vec![ + let fun: Vec> = vec![ Box::new(ECRecover), Box::new(SHA256), Box::new(RIPEMD160), diff --git a/engine-precompiles/src/set_gas_token.rs b/engine-precompiles/src/set_gas_token.rs index 9ff1d9d8b..f2fff531b 100644 --- a/engine-precompiles/src/set_gas_token.rs +++ b/engine-precompiles/src/set_gas_token.rs @@ -33,8 +33,7 @@ pub mod events { use aurora_engine_types::H256; use evm::backend::Log; - // TODO - pub(crate) const SET_GAS_TOKEN_SIGNATURE: H256 = crate::make_h256( + pub const SET_GAS_TOKEN_SIGNATURE: H256 = crate::make_h256( 0x29d0b6eaa171d0d1607729f506329510, 0x7bc9766ba17d250f129cb5bd06503d13, ); @@ -46,13 +45,13 @@ pub mod events { impl SetGasTokenLog { pub(crate) fn encode(self) -> Log { - let data = ethabi::encode(&[ethabi::Token::Address(self.gas_token.raw())]); - let sender_address = { + let gas_token_address = { let mut buf = [0u8; 32]; - buf[12..].copy_from_slice(self.sender.as_bytes()); + buf[12..].copy_from_slice(self.gas_token.as_bytes()); H256(buf) }; - let topics = vec![SET_GAS_TOKEN_SIGNATURE, sender_address]; + let data = ethabi::encode(&[ethabi::Token::Address(self.sender.raw())]); + let topics = vec![SET_GAS_TOKEN_SIGNATURE, gas_token_address]; let raw_log = ethabi::RawLog { topics, data }; @@ -139,6 +138,7 @@ impl Precompile for SetGasToken { #[cfg(test)] mod tests { use super::*; + use crate::prelude::H160; use aurora_engine_sdk::types::near_account_to_evm_address; #[test] @@ -154,4 +154,21 @@ mod tests { let schema = events::set_gas_token_schema(); assert_eq!(schema.signature(), events::SET_GAS_TOKEN_SIGNATURE); } + + #[test] + fn test_run() { + let set_gas_token = SetGasToken; + let user = H160([0x11u8; 20]); + let target_gas = EthGas::new(10_000); + let context = Context { + address: SET_GAS_TOKEN_ADDRESS.raw(), + caller: user, + apparent_value: Default::default(), + }; + let input = hex::decode("8BEc47865aDe3B172A928df8f990Bc7f2A3b9f79").unwrap(); // Aurora mainnet contract address + let result = set_gas_token + .run(&input, Some(target_gas), &context, false) + .unwrap(); + println!("{result:?}"); + } } diff --git a/engine-sdk/src/io.rs b/engine-sdk/src/io.rs index 7aeb7aaa8..6742f9b7e 100644 --- a/engine-sdk/src/io.rs +++ b/engine-sdk/src/io.rs @@ -156,7 +156,7 @@ pub trait IO { Ok(U256::from_big_endian(&result)) } - fn write_borsh( + fn write_storage_borsh( &mut self, key: &[u8], value: &T, diff --git a/engine-types/src/storage.rs b/engine-types/src/storage.rs index e1b1be281..c37ca498b 100644 --- a/engine-types/src/storage.rs +++ b/engine-types/src/storage.rs @@ -30,6 +30,7 @@ pub enum KeyPrefix { Nep141Erc20Map = 0x8, Erc20Nep141Map = 0x9, CrossContractCall = 0xa, + GasToken = 0xb, } impl From for u8 { @@ -47,6 +48,7 @@ impl From for u8 { Nep141Erc20Map => 0x8, Erc20Nep141Map => 0x9, CrossContractCall => 0xa, + GasToken => 0xb, } } } diff --git a/engine/src/connector.rs b/engine/src/connector.rs index 60bcc141d..63c3af329 100644 --- a/engine/src/connector.rs +++ b/engine/src/connector.rs @@ -104,7 +104,7 @@ impl EthConnectorContract { ft.internal_register_account(&owner_id); let paused_mask = UNPAUSE_ALL; - io.write_borsh( + io.write_storage_borsh( &construct_contract_key(&EthConnectorStorageId::PausedMask), &paused_mask, ); @@ -618,7 +618,7 @@ impl EthConnectorContract { /// Save eth-connector fungible token contract data fn save_ft_contract(&mut self) { - self.io.write_borsh( + self.io.write_storage_borsh( &construct_contract_key(&EthConnectorStorageId::FungibleToken), &self.ft.data(), ); @@ -633,7 +633,7 @@ impl EthConnectorContract { /// Save already used event proof as hash key fn save_used_event(&mut self, key: &str) { - self.io.write_borsh(&self.used_event_key(key), &0u8); + self.io.write_storage_borsh(&self.used_event_key(key), &0u8); } /// Check is event of proof already used @@ -666,7 +666,7 @@ impl AdminControlled for EthConnectorContract { /// Set admin paused status fn set_paused(&mut self, paused_mask: PausedMask) { self.paused_mask = paused_mask; - self.io.write_borsh( + self.io.write_storage_borsh( &construct_contract_key(&EthConnectorStorageId::PausedMask), &self.paused_mask, ); @@ -700,12 +700,12 @@ pub fn set_contract_data( eth_custodian_address: Address::decode(&args.eth_custodian_address)?, }; // Save eth-connector specific data - io.write_borsh( + io.write_storage_borsh( &construct_contract_key(&EthConnectorStorageId::Contract), &contract_data, ); - io.write_borsh( + io.write_storage_borsh( &construct_contract_key(&EthConnectorStorageId::FungibleTokenMetadata), &args.metadata, ); diff --git a/engine/src/engine.rs b/engine/src/engine.rs index a435286a4..56c86eb36 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -1,25 +1,13 @@ -use crate::parameters::{CallArgs, NEP141FtOnTransferArgs, ResultLog, SubmitResult, ViewCallArgs}; -use core::mem; -use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log}; -use evm::{executor, Context}; -use evm::{Config, CreateScheme, ExitError, ExitFatal, ExitReason}; - +use crate::accounting; use crate::connector::EthConnectorContract; -use crate::errors; +use crate::gas_token::GasToken; use crate::map::BijectionMap; -use aurora_engine_sdk::caching::FullCache; -use aurora_engine_sdk::env::Env; -use aurora_engine_sdk::io::{StorageIntermediate, IO}; -use aurora_engine_sdk::promise::{PromiseHandler, PromiseId, ReadOnlyPromiseHandler}; - -use crate::accounting; +use crate::parameters::{CallArgs, NEP141FtOnTransferArgs, ResultLog, SubmitResult, ViewCallArgs}; use crate::parameters::{DeployErc20TokenArgs, NewCallArgs, TransactionStatus}; use crate::pausables::{ EngineAuthorizer, EnginePrecompilesPauser, PausedPrecompilesChecker, PrecompileFlags, }; use crate::prelude::parameters::RefundCallArgs; -use crate::prelude::precompiles::native::{exit_to_ethereum, exit_to_near}; -use crate::prelude::precompiles::xcc::cross_contract_call; use crate::prelude::precompiles::{self, Precompiles}; use crate::prelude::transactions::{EthTransactionKind, NormalizedEthTransaction}; use crate::prelude::{ @@ -27,11 +15,19 @@ use crate::prelude::{ BTreeMap, BorshDeserialize, BorshSerialize, KeyPrefix, PromiseArgs, PromiseCreateArgs, ToString, Vec, Wei, Yocto, ERC20_MINT_SELECTOR, H160, H256, U256, }; -use aurora_engine_precompiles::set_gas_token::SetGasToken; +use crate::{errors, gas_token}; use aurora_engine_precompiles::{Precompile, PrecompileConstructorContext}; +use aurora_engine_sdk::caching::FullCache; +use aurora_engine_sdk::env::Env; +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_sdk::promise::{PromiseHandler, PromiseId, ReadOnlyPromiseHandler}; use aurora_engine_types::types::EthGas; use core::cell::RefCell; use core::iter::once; +use core::mem; +use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log}; +use evm::{executor, Context}; +use evm::{Config, CreateScheme, ExitError, ExitFatal, ExitReason}; /// Used as the first byte in the concatenation of data used to compute the blockhash. /// Could be useful in the future as a version byte, or to distinguish different types of blocks. @@ -416,14 +412,6 @@ impl From for EngineState { } } -/// Used to select which gas token to pay in. -pub enum GasToken { - /// Gas is paid in Ether. - ETH, - /// Gas is paid in a ERC-20 compatible token. - ERC20(Address), -} - pub struct Engine<'env, I: IO, E: Env> { state: EngineState, origin: Address, @@ -496,7 +484,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { .ok_or(GasPaymentError::EthAmountOverflow)?; match gas_token { - GasToken::ETH => { + GasToken::Base => { // TODO: Remove the set_balance as we need to just check if they can spend // Use new `can_transfer` function instead. // Also needs to be verified how it is done on geth. @@ -507,7 +495,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { // This part is questionable. set_balance(&mut self.io, sender, &new_balance); } - GasToken::ERC20(_addr) => { + GasToken::Erc20(_addr) => { // TODO: Needs SputnikVM balance check todo!() } @@ -572,7 +560,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { }; let (values, logs) = executor.into_state().deconstruct(); - let logs = filter_promises_from_logs(&self.io, handler, logs, &self.current_account_id); + let logs = apply_actions_from_logs(&mut self.io, handler, logs, &self.current_account_id); self.apply(values, Vec::::new(), true); @@ -653,7 +641,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { }; let (values, logs) = executor.into_state().deconstruct(); - let logs = filter_promises_from_logs(&self.io, handler, logs, &self.current_account_id); + let logs = apply_actions_from_logs(&mut self.io, handler, logs, &self.current_account_id); // There is no way to return the logs to the NEAR log method as it only // allows a return of UTF-8 strings. @@ -878,7 +866,8 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { promise_handler: ro_promise_handler, }); // Cross contract calls are not enabled on mainnet yet. - tmp.all_precompiles.remove(&cross_contract_call::ADDRESS); + tmp.all_precompiles + .remove(&precompiles::xcc::cross_contract_call::ADDRESS); tmp } else { Precompiles::new_london(PrecompileConstructorContext { @@ -974,7 +963,7 @@ pub fn submit( let mut engine = Engine::new_with_state(state, sender, current_account_id, io, env); // TODO: Have GasToken derived from storage. - let prepaid_amount = match engine.charge_gas(&sender, &transaction, GasToken::ETH) { + let prepaid_amount = match engine.charge_gas(&sender, &transaction, GasToken::Base) { Ok(gas_result) => gas_result, Err(GasPaymentError::OutOfFund) => { let result = SubmitResult::new(TransactionStatus::OutOfFund, 0, vec![]); @@ -1003,7 +992,7 @@ pub fn submit( // not sure if there are consequences in changing the gas during the // execution of the transaction. if receiver == precompiles::set_gas_token::SET_GAS_TOKEN_ADDRESS { - let set_gas_token = SetGasToken; + let set_gas_token = precompiles::set_gas_token::SetGasToken; let context = Context { address: receiver.raw(), caller: transaction.address.raw(), @@ -1102,7 +1091,7 @@ pub fn refund_on_error( } // ETH exit; transfer ETH back from precompile address None => { - let exit_address = exit_to_near::ADDRESS; + let exit_address = precompiles::native::exit_to_near::ADDRESS; let mut engine = Engine::new_with_state(state, exit_address, current_account_id, io, env); let refund_address = args.recipient_address; @@ -1468,8 +1457,8 @@ fn remove_account(io: &mut I, address: &Address, generation: u32) remove_all_storage(io, address, generation); } -fn filter_promises_from_logs( - io: &I, +fn apply_actions_from_logs( + io: &mut I, handler: &mut P, logs: T, current_account_id: &AccountId, @@ -1481,8 +1470,8 @@ where { logs.into_iter() .filter_map(|log| { - if log.address == exit_to_near::ADDRESS.raw() - || log.address == exit_to_ethereum::ADDRESS.raw() + if log.address == precompiles::native::exit_to_near::ADDRESS.raw() + || log.address == precompiles::native::exit_to_ethereum::ADDRESS.raw() { if log.topics.is_empty() { if let Ok(promise) = PromiseArgs::try_from_slice(&log.data) { @@ -1514,8 +1503,8 @@ where // `topics` field. Some(log.into()) } - } else if log.address == cross_contract_call::ADDRESS.raw() { - if log.topics[0] == cross_contract_call::AMOUNT_TOPIC { + } else if log.address == precompiles::xcc::cross_contract_call::ADDRESS.raw() { + if log.topics[0] == precompiles::xcc::cross_contract_call::AMOUNT_TOPIC { // NEAR balances are 128-bit, so the leading 16 bytes of the 256-bit topic // value should always be zero. assert_eq!(&log.topics[1].as_bytes()[0..16], &[0; 16]); @@ -1533,6 +1522,21 @@ where } // do not pass on these "internal logs" to caller None + } else if log.address == precompiles::set_gas_token::SET_GAS_TOKEN_ADDRESS.raw() { + if log.topics[0] == precompiles::set_gas_token::events::SET_GAS_TOKEN_SIGNATURE { + let sender_address = { + let mut buf = [0u8; 20]; + buf.copy_from_slice(&log.data[12..]); + Address::from_array(buf) + }; + let gas_token: GasToken = { + let mut buf = [0u8; 20]; + buf.copy_from_slice(&log.topics[1].as_bytes()[12..]); // No need to `get` here given we know the length + GasToken::from_address(Address::from_array(buf)) + }; + gas_token::set_gas_token(io, sender_address, gas_token); + } + None } else { Some(log.into()) } @@ -2143,7 +2147,7 @@ mod tests { }; // TODO: Add other tests than just ETH as a gas token. let actual_result = engine - .charge_gas(&origin, &transaction, GasToken::ETH) + .charge_gas(&origin, &transaction, GasToken::Base) .unwrap(); let expected_result = GasPaymentResult { @@ -2257,7 +2261,12 @@ mod tests { let mut io = StoragePointer(&storage); let expected_state = EngineState::default(); let refund_amount = Wei::new_u64(1000); - add_balance(&mut io, &exit_to_near::ADDRESS, refund_amount).unwrap(); + add_balance( + &mut io, + &precompiles::native::exit_to_near::ADDRESS, + refund_amount, + ) + .unwrap(); set_state(&mut io, expected_state.clone()); let args = RefundCallArgs { recipient_address, @@ -2407,7 +2416,7 @@ mod tests { fn test_filtering_promises_from_logs_with_none_keeps_all() { let storage = Storage::default(); let storage = RwLock::new(storage); - let io = StoragePointer(&storage); + let mut io = StoragePointer(&storage); let current_account_id = AccountId::default(); let mut handler = Noop; let logs = vec![Log { @@ -2416,7 +2425,7 @@ mod tests { data: vec![], }]; - let actual_logs = filter_promises_from_logs(&io, &mut handler, logs, ¤t_account_id); + let actual_logs = apply_actions_from_logs(&mut io, &mut handler, logs, ¤t_account_id); let expected_logs = vec![ResultLog { address: Default::default(), topics: vec![], diff --git a/engine/src/fungible_token.rs b/engine/src/fungible_token.rs index 045e68aec..5eb3cc41d 100644 --- a/engine/src/fungible_token.rs +++ b/engine/src/fungible_token.rs @@ -537,7 +537,7 @@ impl FungibleTokenOps { self.io.write_storage(&key, &accounts_counter.to_le_bytes()); } self.io - .write_borsh(&Self::account_to_key(account_id), &amount); + .write_storage_borsh(&Self::account_to_key(account_id), &amount); } /// Get accounts counter for statistics diff --git a/engine/src/gas_token.rs b/engine/src/gas_token.rs new file mode 100644 index 000000000..43c0af8ee --- /dev/null +++ b/engine/src/gas_token.rs @@ -0,0 +1,52 @@ +use crate::prelude::{BorshDeserialize, BorshSerialize}; +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_types::storage; +use aurora_engine_types::storage::KeyPrefix; +use aurora_engine_types::types::Address; + +mod errors { + pub const ERR_DESERIALIZE_GAS_TOKEN: &str = "ERR_DESERIALIZE_GAS_TOKEN"; +} + +#[derive(BorshSerialize, BorshDeserialize)] +/// Used to select which gas token to pay in. +pub enum GasToken { + /// Gas is paid in Ether. + Base, + /// Gas is paid in a ERC-20 compatible token. + Erc20(Address), +} + +impl GasToken { + // TODO: wait for use of this + // fn into_address(self) -> Address { + // use GasToken::*; + // match self { + // Base => Address::from_array([0u8; 20]), + // Erc20(addr) => addr, + // } + // } + + pub(crate) fn from_address(address: Address) -> GasToken { + use GasToken::*; + if address == Address::zero() { + Base + } else { + Erc20(address) + } + } +} + +/// Sets the gas token for a given address and returns the old value, if any. +pub fn set_gas_token(io: &mut I, address: Address, gas_token: GasToken) -> Option { + let key = storage::bytes_to_key(KeyPrefix::GasToken, address.as_bytes()); + io.write_storage_borsh(&key, &gas_token) + .map(|v| v.to_value().expect(errors::ERR_DESERIALIZE_GAS_TOKEN)) +} + +/// Gets the gas token set for a given address, if any. +pub fn get_gas_token(io: &I, address: &Address) -> Option { + let key = storage::bytes_to_key(KeyPrefix::GasToken, address.as_bytes()); + io.read_storage(&key) + .map(|v| v.to_value().expect(errors::ERR_DESERIALIZE_GAS_TOKEN)) +} diff --git a/engine/src/lib.rs b/engine/src/lib.rs index ff133643a..f5a6559d6 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -25,6 +25,7 @@ pub mod deposit_event; pub mod engine; pub mod errors; pub mod fungible_token; +pub mod gas_token; pub mod json; pub mod log_entry; pub mod pausables; From 9dfd4f3027e46e0e7aa0146ce04cf1d4e43b173c Mon Sep 17 00:00:00 2001 From: Oleksandr Anyshchenko Date: Tue, 24 Jan 2023 12:58:45 +0100 Subject: [PATCH 05/12] Update set_gas_token.rs --- engine-precompiles/src/set_gas_token.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/engine-precompiles/src/set_gas_token.rs b/engine-precompiles/src/set_gas_token.rs index f2fff531b..13614d7cf 100644 --- a/engine-precompiles/src/set_gas_token.rs +++ b/engine-precompiles/src/set_gas_token.rs @@ -28,6 +28,7 @@ pub mod consts { } pub mod events { + use crate::prelude::vec; use crate::set_gas_token::consts; use aurora_engine_types::types::Address; use aurora_engine_types::H256; From d36b2a0a3763f49a11069c10a6012e62e28a0a9f Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Tue, 24 Jan 2023 17:49:11 +0400 Subject: [PATCH 06/12] feat: add state migration and refactor state --- engine/src/engine.rs | 113 ++++---------------------- engine/src/errors.rs | 2 - engine/src/gas_token.rs | 7 +- engine/src/lib.rs | 46 ++++++----- engine/src/parameters.rs | 5 +- engine/src/state.rs | 171 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 219 insertions(+), 125 deletions(-) create mode 100644 engine/src/state.rs diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 56c86eb36..03f0e44d8 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -1,9 +1,8 @@ -use crate::accounting; use crate::connector::EthConnectorContract; use crate::gas_token::GasToken; use crate::map::BijectionMap; use crate::parameters::{CallArgs, NEP141FtOnTransferArgs, ResultLog, SubmitResult, ViewCallArgs}; -use crate::parameters::{DeployErc20TokenArgs, NewCallArgs, TransactionStatus}; +use crate::parameters::{DeployErc20TokenArgs, TransactionStatus}; use crate::pausables::{ EngineAuthorizer, EnginePrecompilesPauser, PausedPrecompilesChecker, PrecompileFlags, }; @@ -12,9 +11,12 @@ use crate::prelude::precompiles::{self, Precompiles}; use crate::prelude::transactions::{EthTransactionKind, NormalizedEthTransaction}; use crate::prelude::{ address_to_key, bytes_to_key, sdk, storage_to_key, u256_to_arr, vec, AccountId, Address, - BTreeMap, BorshDeserialize, BorshSerialize, KeyPrefix, PromiseArgs, PromiseCreateArgs, - ToString, Vec, Wei, Yocto, ERC20_MINT_SELECTOR, H160, H256, U256, + BTreeMap, BorshDeserialize, KeyPrefix, PromiseArgs, PromiseCreateArgs, ToString, Vec, Wei, + Yocto, ERC20_MINT_SELECTOR, H160, H256, U256, }; +use crate::state::error::EngineStateError; +use crate::state::EngineState; +use crate::{accounting, state}; use crate::{errors, gas_token}; use aurora_engine_precompiles::{Precompile, PrecompileConstructorContext}; use aurora_engine_sdk::caching::FullCache; @@ -335,21 +337,6 @@ impl AsRef<[u8]> for RegisterTokenError { } } -#[derive(Debug)] -pub enum EngineStateError { - NotFound, - DeserializationFailed, -} - -impl AsRef<[u8]> for EngineStateError { - fn as_ref(&self) -> &[u8] { - match self { - Self::NotFound => errors::ERR_STATE_NOT_FOUND, - Self::DeserializationFailed => errors::ERR_STATE_CORRUPTED, - } - } -} - pub struct StackExecutorParams<'a, I, E, H> { precompiles: Precompiles<'a, I, E, H>, gas_limit: u64, @@ -385,33 +372,6 @@ pub struct GasPaymentResult { pub priority_fee_per_gas: U256, } -/// Engine internal state, mostly configuration. -/// Should not contain anything large or enumerable. -#[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)] -pub struct EngineState { - /// Chain id, according to the EIP-155 / ethereum-lists spec. - pub chain_id: [u8; 32], - /// Account which can upgrade this contract. - /// Use empty to disable updatability. - pub owner_id: AccountId, - /// Account of the bridge prover. - /// Use empty to not use base token as bridged asset. - pub bridge_prover_id: AccountId, - /// How many blocks after staging upgrade can deploy it. - pub upgrade_delay_blocks: u64, -} - -impl From for EngineState { - fn from(args: NewCallArgs) -> Self { - EngineState { - chain_id: args.chain_id, - owner_id: args.owner_id, - bridge_prover_id: args.bridge_prover_id, - upgrade_delay_blocks: args.upgrade_delay_blocks, - } - } -} - pub struct Engine<'env, I: IO, E: Env> { state: EngineState, origin: Address, @@ -427,9 +387,6 @@ pub struct Engine<'env, I: IO, E: Env> { pub(crate) const CONFIG: &Config = &Config::london(); -/// Key for storing the state of the engine. -const STATE_KEY: &[u8; 5] = b"STATE"; - impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { pub fn new( origin: Address, @@ -437,7 +394,8 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> { io: I, env: &'env E, ) -> Result { - get_state(&io).map(|state| Self::new_with_state(state, origin, current_account_id, io, env)) + state::get_state(&io) + .map(|state| Self::new_with_state(state, origin, current_account_id, io, env)) } pub fn new_with_state( @@ -1138,22 +1096,6 @@ pub fn compute_block_hash(chain_id: [u8; 32], block_height: u64, account_id: &[u sdk::sha256(&data) } -pub fn get_state(io: &I) -> Result { - match io.read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) { - None => Err(EngineStateError::NotFound), - Some(bytes) => EngineState::try_from_slice(&bytes.to_vec()) - .map_err(|_| EngineStateError::DeserializationFailed), - } -} - -/// Saves state into the storage. -pub fn set_state(io: &mut I, state: EngineState) { - io.write_storage( - &bytes_to_key(KeyPrefix::Config, STATE_KEY), - &state.try_to_vec().expect("ERR_SER"), - ); -} - pub fn get_authorizer() -> EngineAuthorizer { // TODO: a temporary account until the engine adapts std with near-plugins let account = AccountId::new("aurora").expect("Failed to parse account from string"); @@ -2107,7 +2049,7 @@ mod tests { let storage = RwLock::new(storage); let mut io = StoragePointer(&storage); add_balance(&mut io, &origin, Wei::new_u64(22000)).unwrap(); - set_state(&mut io, EngineState::default()); + state::set_state(&mut io, EngineState::default()).unwrap(); let nep141_token = AccountId::new("testcoin").unwrap(); let mut handler = Noop; @@ -2245,7 +2187,7 @@ mod tests { let storage = RwLock::new(storage); let mut io = StoragePointer(&storage); let expected_state = EngineState::default(); - set_state(&mut io, expected_state.clone()); + state::set_state(&mut io, expected_state.clone()).unwrap(); let engine = Engine::new(origin, current_account_id, io, &env).unwrap(); let actual_state = engine.state; @@ -2267,7 +2209,7 @@ mod tests { refund_amount, ) .unwrap(); - set_state(&mut io, expected_state.clone()); + state::set_state(&mut io, expected_state.clone()).unwrap(); let args = RefundCallArgs { recipient_address, erc20_address: None, @@ -2289,7 +2231,7 @@ mod tests { let storage = RwLock::new(storage); let mut io = StoragePointer(&storage); let expected_state = EngineState::default(); - set_state(&mut io, expected_state.clone()); + state::set_state(&mut io, expected_state.clone()).unwrap(); let value = Wei::new_u64(1000); let args = RefundCallArgs { recipient_address: Default::default(), @@ -2311,7 +2253,7 @@ mod tests { let storage = RwLock::new(storage); let mut io = StoragePointer(&storage); let expected_state = EngineState::default(); - set_state(&mut io, expected_state); + state::set_state(&mut io, expected_state).unwrap(); let relayer = make_address(1, 1); let gas_result = GasPaymentResult { prepaid_amount: Default::default(), @@ -2329,7 +2271,7 @@ mod tests { let storage = RwLock::new(storage); let mut io = StoragePointer(&storage); let expected_state = EngineState::default(); - set_state(&mut io, expected_state); + state::set_state(&mut io, expected_state).unwrap(); let relayer = make_address(1, 1); let gas_result = GasPaymentResult { prepaid_amount: Wei::new_u64(8000), @@ -2385,33 +2327,6 @@ mod tests { ); } - #[test] - fn test_missing_engine_state_is_not_found() { - let storage = Storage::default(); - let storage = RwLock::new(storage); - let io = StoragePointer(&storage); - - let actual_error = get_state(&io).unwrap_err(); - let actual_error = std::str::from_utf8(actual_error.as_ref()).unwrap(); - let expected_error = std::str::from_utf8(errors::ERR_STATE_NOT_FOUND).unwrap(); - - assert_eq!(expected_error, actual_error); - } - - #[test] - fn test_empty_engine_state_is_corrupted() { - let storage = Storage::default(); - let storage = RwLock::new(storage); - let mut io = StoragePointer(&storage); - - io.write_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY), &[]); - let actual_error = get_state(&io).unwrap_err(); - let actual_error = std::str::from_utf8(actual_error.as_ref()).unwrap(); - let expected_error = std::str::from_utf8(errors::ERR_STATE_CORRUPTED).unwrap(); - - assert_eq!(expected_error, actual_error); - } - #[test] fn test_filtering_promises_from_logs_with_none_keeps_all() { let storage = Storage::default(); diff --git a/engine/src/errors.rs b/engine/src/errors.rs index 57993de31..afaeb46d6 100644 --- a/engine/src/errors.rs +++ b/engine/src/errors.rs @@ -49,8 +49,6 @@ pub const ERR_GAS_OVERFLOW: &[u8; 16] = b"ERR_GAS_OVERFLOW"; pub const ERR_BALANCE_OVERFLOW: &[u8; 20] = b"ERR_BALANCE_OVERFLOW"; pub const ERR_GAS_ETH_AMOUNT_OVERFLOW: &[u8; 27] = b"ERR_GAS_ETH_AMOUNT_OVERFLOW"; pub const ERR_PARSE_ADDRESS: &[u8; 17] = b"ERR_PARSE_ADDRESS"; -pub const ERR_STATE_NOT_FOUND: &[u8; 19] = b"ERR_STATE_NOT_FOUND"; -pub const ERR_STATE_CORRUPTED: &[u8; 19] = b"ERR_STATE_CORRUPTED"; pub const ERR_CONNECTOR_STORAGE_KEY_NOT_FOUND: &[u8; 35] = b"ERR_CONNECTOR_STORAGE_KEY_NOT_FOUND"; pub const ERR_FAILED_DESERIALIZE_CONNECTOR_DATA: &[u8; 37] = diff --git a/engine/src/gas_token.rs b/engine/src/gas_token.rs index 43c0af8ee..6b8f70192 100644 --- a/engine/src/gas_token.rs +++ b/engine/src/gas_token.rs @@ -8,10 +8,11 @@ mod errors { pub const ERR_DESERIALIZE_GAS_TOKEN: &str = "ERR_DESERIALIZE_GAS_TOKEN"; } -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Default, Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] /// Used to select which gas token to pay in. pub enum GasToken { /// Gas is paid in Ether. + #[default] Base, /// Gas is paid in a ERC-20 compatible token. Erc20(Address), @@ -35,6 +36,10 @@ impl GasToken { Erc20(address) } } + + pub(crate) fn from_array(bytes: [u8; 20]) -> GasToken { + Self::from_address(Address::from_array(bytes)) + } } /// Sets the gas token for a given address and returns the old value, if any. diff --git a/engine/src/lib.rs b/engine/src/lib.rs index f5a6559d6..b4a97bad9 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -30,6 +30,7 @@ pub mod json; pub mod log_entry; pub mod pausables; mod prelude; +pub mod state; pub mod xcc; #[cfg(target_arch = "wasm32")] @@ -72,7 +73,7 @@ mod contract { use borsh::{BorshDeserialize, BorshSerialize}; use crate::connector::{self, EthConnectorContract}; - use crate::engine::{self, Engine, EngineState}; + use crate::engine::{self, Engine}; use crate::fungible_token::FungibleTokenMetadata; use crate::json::parse_json; use crate::parameters::{ @@ -97,7 +98,7 @@ mod contract { use crate::prelude::{ sdk, u256_to_arr, Address, PromiseResult, ToString, Yocto, ERR_FAILED_PARSE, H256, }; - use crate::{errors, pausables}; + use crate::{errors, pausables, state}; use aurora_engine_sdk::env::Env; use aurora_engine_sdk::io::{StorageIntermediate, IO}; use aurora_engine_sdk::near_runtime::{Runtime, ViewEnv}; @@ -105,6 +106,7 @@ mod contract { #[cfg(feature = "integration-test")] use crate::prelude::NearGas; + use crate::state::EngineState; const CODE_KEY: &[u8; 4] = b"CODE"; const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; @@ -118,12 +120,12 @@ mod contract { #[no_mangle] pub extern "C" fn new() { let mut io = Runtime; - if let Ok(state) = engine::get_state(&io) { + if let Ok(state) = state::get_state(&io) { require_owner_only(&state, &io.predecessor_account_id()); } let args: NewCallArgs = io.read_input_borsh().sdk_unwrap(); - engine::set_state(&mut io, args.into()); + state::set_state(&mut io, args.into()).sdk_unwrap(); } /// Get version of the contract. @@ -141,7 +143,7 @@ mod contract { #[no_mangle] pub extern "C" fn get_owner() { let mut io = Runtime; - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); io.return_output(state.owner_id.as_bytes()); } @@ -157,13 +159,13 @@ mod contract { #[no_mangle] pub extern "C" fn get_chain_id() { let mut io = Runtime; - io.return_output(&engine::get_state(&io).sdk_unwrap().chain_id) + io.return_output(&state::get_state(&io).sdk_unwrap().chain_id) } #[no_mangle] pub extern "C" fn get_upgrade_index() { let mut io = Runtime; - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); let index = internal_get_upgrade_index(); io.return_output(&(index + state.upgrade_delay_blocks).to_le_bytes()) } @@ -172,7 +174,7 @@ mod contract { #[no_mangle] pub extern "C" fn stage_upgrade() { let mut io = Runtime; - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); let block_height = io.block_height(); require_owner_only(&state, &io.predecessor_account_id()); io.read_input_and_store(&bytes_to_key(KeyPrefix::Config, CODE_KEY)); @@ -186,7 +188,7 @@ mod contract { #[no_mangle] pub extern "C" fn deploy_upgrade() { let io = Runtime; - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); let index = internal_get_upgrade_index(); if io.block_height() <= index + state.upgrade_delay_blocks { @@ -200,7 +202,11 @@ mod contract { /// code. #[no_mangle] pub extern "C" fn state_migration() { - // TODO: currently we don't have migrations + let mut io = Runtime; + io.assert_private_call().sdk_unwrap(); + + // TODO: Must be removed after migration. + state::legacy::migrate_state(&mut io).sdk_unwrap(); } /// Resumes previously [`paused`] precompiles. @@ -209,7 +215,7 @@ mod contract { #[no_mangle] pub extern "C" fn resume_precompiles() { let io = Runtime; - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); let predecessor_account_id = io.predecessor_account_id(); require_owner_only(&state, &predecessor_account_id); @@ -296,7 +302,7 @@ mod contract { let io = Runtime; let input = io.read_input().to_vec(); let current_account_id = io.current_account_id(); - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); let relayer_address = predecessor_address(&io.predecessor_account_id()); let result = engine::submit( io, @@ -339,7 +345,7 @@ mod contract { #[no_mangle] pub extern "C" fn factory_update() { let mut io = Runtime; - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); let bytes = io.read_input().to_vec(); let router_bytecode = crate::xcc::RouterCode::new(bytes); @@ -367,7 +373,7 @@ mod contract { #[no_mangle] pub extern "C" fn factory_set_wnear_address() { let mut io = Runtime; - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); let address = io.read_input_arr20().sdk_unwrap(); crate::xcc::set_wnear_address(&mut io, &Address::from_array(address)); @@ -448,7 +454,7 @@ mod contract { } else { // Exit call failed; need to refund tokens let args: RefundCallArgs = io.read_input_borsh().sdk_unwrap(); - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); let refund_result = engine::refund_on_error(io, &io, state, args, &mut Runtime).sdk_unwrap(); @@ -477,7 +483,7 @@ mod contract { let mut io = Runtime; let block_height = io.read_input_borsh().sdk_unwrap(); let account_id = io.current_account_id(); - let chain_id = engine::get_state(&io) + let chain_id = state::get_state(&io) .map(|state| state.chain_id) .sdk_unwrap(); let block_hash = @@ -527,11 +533,11 @@ mod contract { pub extern "C" fn begin_chain() { use crate::prelude::U256; let mut io = Runtime; - let mut state = engine::get_state(&io).sdk_unwrap(); + let mut state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); let args: BeginChainArgs = io.read_input_borsh().sdk_unwrap(); state.chain_id = args.chain_id; - engine::set_state(&mut io, state); + state::set_state(&mut io, state); // set genesis block balances for account_balance in args.genesis_alloc { engine::set_balance( @@ -541,14 +547,14 @@ mod contract { ) } // return new chain ID - io.return_output(&engine::get_state(&io).sdk_unwrap().chain_id) + io.return_output(&state::get_state(&io).sdk_unwrap().chain_id) } #[cfg(feature = "evm_bully")] #[no_mangle] pub extern "C" fn begin_block() { let io = Runtime; - let state = engine::get_state(&io).sdk_unwrap(); + let state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); let _args: BeginBlockArgs = io.read_input_borsh().sdk_unwrap(); // TODO: https://github.com/aurora-is-near/aurora-engine/issues/2 diff --git a/engine/src/parameters.rs b/engine/src/parameters.rs index 4a6ecfa73..b29dfc39f 100644 --- a/engine/src/parameters.rs +++ b/engine/src/parameters.rs @@ -21,11 +21,10 @@ pub struct NewCallArgs { /// Account which can upgrade this contract. /// Use empty to disable updatability. pub owner_id: AccountId, - /// Account of the bridge prover. - /// Use empty to not use base token as bridged asset. - pub bridge_prover_id: AccountId, /// How many blocks after staging upgrade can deploy it. pub upgrade_delay_blocks: u64, + /// The default gas token. + pub default_gas_token: [u8; 20], } /// Borsh-encoded log for use in a `SubmitResult`. diff --git a/engine/src/state.rs b/engine/src/state.rs new file mode 100644 index 000000000..571c7a31a --- /dev/null +++ b/engine/src/state.rs @@ -0,0 +1,171 @@ +use crate::gas_token::GasToken; +use crate::parameters::NewCallArgs; +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_types::account_id::AccountId; +use aurora_engine_types::storage::{bytes_to_key, KeyPrefix}; +use borsh::{BorshDeserialize, BorshSerialize}; + +/// Key for storing the state of the engine. +const STATE_KEY: &[u8; 5] = b"STATE"; + +/// Engine internal state V2, mostly configuration. +/// Should not contain anything large or enumerable. +#[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)] +pub struct EngineState { + /// Chain id, according to the EIP-155 / ethereum-lists spec. + pub chain_id: [u8; 32], + /// Account which can upgrade this contract. + /// Use empty to disable updatability. + pub owner_id: AccountId, + /// How many blocks after staging upgrade can deploy it. + pub upgrade_delay_blocks: u64, + /// The default gas token. + pub default_gas_token: GasToken, +} + +impl From for EngineState { + fn from(args: NewCallArgs) -> Self { + EngineState { + chain_id: args.chain_id, + owner_id: args.owner_id, + upgrade_delay_blocks: args.upgrade_delay_blocks, + default_gas_token: GasToken::from_array(args.default_gas_token), + } + } +} + +/// Gets the state from storage, if it exists otherwise it will error. +pub fn get_state(io: &I) -> Result { + match io.read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) { + None => Err(error::EngineStateError::NotFound), + Some(bytes) => EngineState::try_from_slice(&bytes.to_vec()) + .map_err(|_| error::EngineStateError::DeserializationFailed), + } +} + +/// Saves state into the storage. Does not return the previous state. +pub fn set_state(io: &mut I, state: EngineState) -> Result<(), error::EngineStateError> { + io.write_storage( + &bytes_to_key(KeyPrefix::Config, STATE_KEY), + &state + .try_to_vec() + .map_err(|_| error::EngineStateError::SerializationFailed)?, + ); + + Ok(()) +} + +/// Engine state error module. +pub mod error { + pub const ERR_STATE_NOT_FOUND: &[u8; 19] = b"ERR_STATE_NOT_FOUND"; + pub const ERR_STATE_SERIALIZATION_FAILED: &[u8; 26] = b"ERR_STATE_SERIALIZE_FAILED"; + pub const ERR_STATE_CORRUPTED: &[u8; 19] = b"ERR_STATE_CORRUPTED"; + + #[derive(Debug)] + /// Engine state error kinds. + pub enum EngineStateError { + /// The engine state is missing from storage, need to initialize with contract `new` method. + NotFound, + /// The engine state serialized had failed. + SerializationFailed, + /// The state of the engine is corrupted, possibly due to failed state migration. + DeserializationFailed, + } + + impl AsRef<[u8]> for EngineStateError { + fn as_ref(&self) -> &[u8] { + match self { + Self::NotFound => ERR_STATE_NOT_FOUND, + Self::SerializationFailed => ERR_STATE_SERIALIZATION_FAILED, + Self::DeserializationFailed => ERR_STATE_CORRUPTED, + } + } + } +} + +pub mod legacy { + use super::*; + + /// Migrates the state from the old format to the latest format. + /// + /// While this function works for a single v1 -> v2 migration, it would need to be + /// altered to handle all earlier migrations if needed. + pub fn migrate_state(io: &mut I) -> Result<(), error::EngineStateError> { + // Get the engine legacy state. + let engine_state_legacy = match io.read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) + { + None => Err(error::EngineStateError::NotFound), + Some(bytes) => EngineStateLegacy::try_from_slice(&bytes.to_vec()) + .map_err(|_| error::EngineStateError::DeserializationFailed), + }?; + + // Migrate to new engine state. + let engine_state_new = EngineState { + chain_id: engine_state_legacy.chain_id, + owner_id: engine_state_legacy.owner_id, + upgrade_delay_blocks: 0, + default_gas_token: Default::default(), + }; + + // Set the new engine state. + io.write_storage( + &bytes_to_key(KeyPrefix::Config, STATE_KEY), + &engine_state_new + .try_to_vec() + .map_err(|_| error::EngineStateError::SerializationFailed)?, + ); + + Ok(()) + } + + /// Engine internal state V1, mostly configuration. + /// Should not contain anything large or enumerable. + #[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)] + pub struct EngineStateLegacy { + /// Chain id, according to the EIP-155 / ethereum-lists spec. + pub chain_id: [u8; 32], + /// Account which can upgrade this contract. + /// Use empty to disable updatability. + pub owner_id: AccountId, + /// Account of the bridge prover. + /// Use empty to not use base token as bridged asset. + pub bridge_prover_id: AccountId, + /// How many blocks after staging upgrade can deploy it. + pub upgrade_delay_blocks: u64, + } +} + +#[cfg(test)] +#[cfg(feature = "std")] +mod tests { + use super::*; + use aurora_engine_test_doubles::io::{Storage, StoragePointer}; + use std::sync::RwLock; + + #[test] + fn test_missing_engine_state_is_not_found() { + let storage = Storage::default(); + let storage = RwLock::new(storage); + let io = StoragePointer(&storage); + + let actual_error = get_state(&io).unwrap_err(); + let actual_error = std::str::from_utf8(actual_error.as_ref()).unwrap(); + let expected_error = std::str::from_utf8(error::ERR_STATE_NOT_FOUND).unwrap(); + + assert_eq!(expected_error, actual_error); + } + + #[test] + fn test_empty_engine_state_is_corrupted() { + let storage = Storage::default(); + let storage = RwLock::new(storage); + let mut io = StoragePointer(&storage); + + io.write_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY), &[]); + let actual_error = get_state(&io).unwrap_err(); + let actual_error = std::str::from_utf8(actual_error.as_ref()).unwrap(); + let expected_error = std::str::from_utf8(error::ERR_STATE_CORRUPTED).unwrap(); + + assert_eq!(expected_error, actual_error); + } +} From fed6a15ca51bbf9dcdcfbd9832c23ddce6204e54 Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Tue, 24 Jan 2023 18:21:44 +0400 Subject: [PATCH 07/12] fix: all check and clippy issues --- .../src/relayer_db/mod.rs | 22 +++++++++---------- engine-standalone-storage/src/sync/mod.rs | 22 +++++++++---------- engine-tests/src/test_utils/mod.rs | 2 +- .../src/test_utils/standalone/mocks/mod.rs | 8 +++---- engine-tests/src/tests/eth_connector.rs | 3 +-- engine-tests/src/tests/sanity.rs | 4 ++-- engine-tests/src/tests/standalone/sanity.rs | 6 ++--- engine-tests/src/tests/state_migration.rs | 2 +- 8 files changed, 34 insertions(+), 35 deletions(-) diff --git a/engine-standalone-storage/src/relayer_db/mod.rs b/engine-standalone-storage/src/relayer_db/mod.rs index 80fefca95..f258dc0f8 100644 --- a/engine-standalone-storage/src/relayer_db/mod.rs +++ b/engine-standalone-storage/src/relayer_db/mod.rs @@ -1,4 +1,4 @@ -use aurora_engine::engine; +use aurora_engine::{engine, state}; use aurora_engine_sdk::env::{self, Env, DEFAULT_PREPAID_GAS}; use aurora_engine_transactions::EthTransactionKind; use aurora_engine_types::account_id::AccountId; @@ -63,7 +63,7 @@ where pub fn initialize_transactions( storage: &mut Storage, mut rows: I, - engine_state: engine::EngineState, + engine_state: state::EngineState, ) -> Result<(), error::Error> where I: FallibleIterator, @@ -159,13 +159,13 @@ where } pub mod error { - use aurora_engine::engine; + use aurora_engine::{engine, state}; #[derive(Debug)] pub enum Error { Storage(crate::Error), Postgres(postgres::Error), - EngineState(engine::EngineStateError), + EngineState(state::error::EngineStateError), Engine(engine::EngineError), } @@ -181,8 +181,8 @@ pub mod error { } } - impl From for Error { - fn from(e: engine::EngineStateError) -> Self { + impl From for Error { + fn from(e: state::error::EngineStateError) -> Self { Self::EngineState(e) } } @@ -198,7 +198,7 @@ pub mod error { mod test { use super::FallibleIterator; use crate::sync::types::{TransactionKind, TransactionMessage}; - use aurora_engine::{connector, engine, parameters}; + use aurora_engine::{connector, parameters, state}; use aurora_engine_types::H256; /// Requires a running postgres server to work. A snapshot of the DB can be @@ -210,11 +210,11 @@ mod test { fn test_fill_db() { let mut storage = crate::Storage::open("rocks_tmp/").unwrap(); let mut connection = super::connect_without_tls(&Default::default()).unwrap(); - let engine_state = engine::EngineState { + let engine_state = state::EngineState { chain_id: aurora_engine_types::types::u256_to_arr(&1313161555.into()), owner_id: "aurora".parse().unwrap(), - bridge_prover_id: "prover.bridge.near".parse().unwrap(), upgrade_delay_blocks: 0, + default_gas_token: Default::default(), }; // Initialize engine and connector states in storage. @@ -231,12 +231,12 @@ mod test { .unwrap(); let result = storage.with_engine_access(block_height, 0, &[], |io| { let mut local_io = io; - engine::set_state(&mut local_io, engine_state.clone()); + state::set_state(&mut local_io, engine_state.clone()).unwrap(); connector::EthConnectorContract::create_contract( io, engine_state.owner_id.clone(), parameters::InitCallArgs { - prover_account: engine_state.bridge_prover_id.clone(), + prover_account: "prover.bridge.near".parse().unwrap(), eth_custodian_address: "6bfad42cfc4efc96f529d786d643ff4a8b89fa52" .to_string(), metadata: Default::default(), diff --git a/engine-standalone-storage/src/sync/mod.rs b/engine-standalone-storage/src/sync/mod.rs index 9142e6b86..6ba30c5d8 100644 --- a/engine-standalone-storage/src/sync/mod.rs +++ b/engine-standalone-storage/src/sync/mod.rs @@ -1,7 +1,7 @@ use aurora_engine::pausables::{ EnginePrecompilesPauser, PausedPrecompilesManager, PrecompileFlags, }; -use aurora_engine::{connector, engine, parameters::SubmitResult, xcc}; +use aurora_engine::{connector, engine, parameters::SubmitResult, state, xcc}; use aurora_engine_sdk::env::{self, Env, DEFAULT_PREPAID_GAS}; use aurora_engine_types::{ account_id::AccountId, @@ -137,7 +137,7 @@ fn execute_transaction<'db>( let transaction_bytes: Vec = tx.into(); let tx_hash = aurora_engine_sdk::keccak(&transaction_bytes); - let result = engine::get_state(&io) + let result = state::get_state(&io) .map(|engine_state| { let submit_result = engine::submit( io, @@ -335,12 +335,12 @@ fn non_submit_execute<'db>( } TransactionKind::RefundOnError(maybe_args) => { - let result: Result, engine::EngineStateError> = + let result: Result, state::error::EngineStateError> = maybe_args .clone() .map(|args| { let mut handler = crate::promise::NoScheduler { promise_data }; - let engine_state = engine::get_state(&io)?; + let engine_state = state::get_state(&io)?; let result = engine::refund_on_error(io, &env, engine_state, args, &mut handler); Ok(TransactionExecutionResult::Submit(result)) @@ -367,7 +367,7 @@ fn non_submit_execute<'db>( None } TransactionKind::NewEngine(args) => { - engine::set_state(&mut io, args.clone().into()); + state::set_state(&mut io, args.clone().into())?; None } @@ -420,9 +420,9 @@ pub enum ConsumeMessageOutcome { #[derive(Debug)] pub struct TransactionIncludedOutcome { - pub hash: aurora_engine_types::H256, + pub hash: H256, pub info: TransactionMessage, - pub diff: crate::Diff, + pub diff: Diff, pub maybe_result: Result, error::Error>, } @@ -434,11 +434,11 @@ pub enum TransactionExecutionResult { } pub mod error { - use aurora_engine::{connector, engine, fungible_token}; + use aurora_engine::{connector, engine, fungible_token, state}; #[derive(Debug)] pub enum Error { - EngineState(engine::EngineStateError), + EngineState(state::error::EngineStateError), Engine(engine::EngineError), DeployErc20(engine::DeployErc20Error), FtOnTransfer(connector::error::FtTransferCallError), @@ -452,8 +452,8 @@ pub mod error { ConnectorStorage(connector::error::StorageReadError), } - impl From for Error { - fn from(e: engine::EngineStateError) -> Self { + impl From for Error { + fn from(e: state::error::EngineStateError) -> Self { Self::EngineState(e) } } diff --git a/engine-tests/src/test_utils/mod.rs b/engine-tests/src/test_utils/mod.rs index 881124cf3..fd9e5f10d 100644 --- a/engine-tests/src/test_utils/mod.rs +++ b/engine-tests/src/test_utils/mod.rs @@ -619,8 +619,8 @@ pub(crate) fn deploy_evm() -> AuroraRunner { let args = NewCallArgs { chain_id: crate::prelude::u256_to_arr(&U256::from(runner.chain_id)), owner_id: str_to_account_id(runner.aurora_account_id.as_str()), - bridge_prover_id: str_to_account_id("bridge_prover.near"), upgrade_delay_blocks: 1, + default_gas_token: [0u8; 20], }; let account_id = runner.aurora_account_id.clone(); diff --git a/engine-tests/src/test_utils/standalone/mocks/mod.rs b/engine-tests/src/test_utils/standalone/mocks/mod.rs index 990ae733f..0f761c6eb 100644 --- a/engine-tests/src/test_utils/standalone/mocks/mod.rs +++ b/engine-tests/src/test_utils/standalone/mocks/mod.rs @@ -1,9 +1,9 @@ use crate::test_utils; -use aurora_engine::engine; use aurora_engine::fungible_token::FungibleTokenMetadata; use aurora_engine::parameters::{ FinishDepositCallArgs, InitCallArgs, NEP141FtOnTransferArgs, NewCallArgs, }; +use aurora_engine::{engine, state}; use aurora_engine_sdk::env::{Env, DEFAULT_PREPAID_GAS}; use aurora_engine_sdk::io::IO; use aurora_engine_types::types::{Address, Balance, NEP141Wei, NearGas, Wei}; @@ -17,7 +17,7 @@ pub const ETH_CUSTODIAN_ADDRESS: Address = aurora_engine_precompiles::make_address(0xd045f7e1, 0x9b2488924b97f9c145b5e51d0d895a65); pub fn compute_block_hash(block_height: u64) -> H256 { - aurora_engine::engine::compute_block_hash([0u8; 32], block_height, b"aurora") + engine::compute_block_hash([0u8; 32], block_height, b"aurora") } pub fn insert_block(storage: &mut Storage, block_height: u64) { @@ -52,11 +52,11 @@ pub fn init_evm(mut io: I, env: &E, chain_id: u64) { let new_args = NewCallArgs { chain_id: aurora_engine_types::types::u256_to_arr(&U256::from(chain_id)), owner_id: env.current_account_id(), - bridge_prover_id: test_utils::str_to_account_id("bridge_prover.near"), upgrade_delay_blocks: 1, + default_gas_token: [0u8; 20], // Base coin }; - engine::set_state(&mut io, new_args.into()); + state::set_state(&mut io, new_args.into()).unwrap(); let connector_args = InitCallArgs { prover_account: test_utils::str_to_account_id("prover.near"), diff --git a/engine-tests/src/tests/eth_connector.rs b/engine-tests/src/tests/eth_connector.rs index 03f7d51bc..257960829 100644 --- a/engine-tests/src/tests/eth_connector.rs +++ b/engine-tests/src/tests/eth_connector.rs @@ -13,7 +13,6 @@ use aurora_engine_types::types::{Fee, NEP141Wei}; use borsh::{BorshDeserialize, BorshSerialize}; use byte_slice_cast::AsByteSlice; use ethabi::ethereum_types::U256; -use near_sdk::test_utils::accounts; use near_sdk_sim::transaction::ExecutionStatus; use near_sdk_sim::{to_yocto, ExecutionResult, UserAccount, DEFAULT_GAS, STORAGE_AMOUNT}; use serde_json::json; @@ -71,8 +70,8 @@ fn init_contract( &NewCallArgs { chain_id: [0u8; 32], owner_id: str_to_account_id(master_account.account_id.clone().as_str()), - bridge_prover_id: str_to_account_id(accounts(0).as_str()), upgrade_delay_blocks: 1, + default_gas_token: [0u8; 20], } .try_to_vec() .unwrap(), diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs index fb51786bd..2b19141d8 100644 --- a/engine-tests/src/tests/sanity.rs +++ b/engine-tests/src/tests/sanity.rs @@ -136,10 +136,10 @@ fn test_state_format() { let args = aurora_engine::parameters::NewCallArgs { chain_id: aurora_engine_types::types::u256_to_arr(&666.into()), owner_id: "boss".parse().unwrap(), - bridge_prover_id: "prover_mcprovy_face".parse().unwrap(), upgrade_delay_blocks: 3, + default_gas_token: [0u8; 20], }; - let state: aurora_engine::engine::EngineState = args.into(); + let state: aurora_engine::state::EngineState = args.into(); let expected_hex: String = [ "000000000000000000000000000000000000000000000000000000000000029a", "04000000626f7373", diff --git a/engine-tests/src/tests/standalone/sanity.rs b/engine-tests/src/tests/standalone/sanity.rs index db3d72a9a..bc54cd731 100644 --- a/engine-tests/src/tests/standalone/sanity.rs +++ b/engine-tests/src/tests/standalone/sanity.rs @@ -1,4 +1,4 @@ -use aurora_engine::engine; +use aurora_engine::{engine, state}; use aurora_engine_sdk::env::DEFAULT_PREPAID_GAS; use aurora_engine_test_doubles::io::{Storage, StoragePointer}; use aurora_engine_test_doubles::promise::PromiseTracker; @@ -15,11 +15,11 @@ fn test_deploy_code() { buf }; let owner_id: AccountId = "aurora".parse().unwrap(); - let state = engine::EngineState { + let state = state::EngineState { chain_id, owner_id: owner_id.clone(), - bridge_prover_id: "mr_the_prover".parse().unwrap(), upgrade_delay_blocks: 0, + default_gas_token: Default::default(), }; let origin = Address::new(H160([0u8; 20])); let storage = RwLock::new(Storage::default()); diff --git a/engine-tests/src/tests/state_migration.rs b/engine-tests/src/tests/state_migration.rs index e175e0fca..4fe8bc90d 100644 --- a/engine-tests/src/tests/state_migration.rs +++ b/engine-tests/src/tests/state_migration.rs @@ -41,8 +41,8 @@ pub fn deploy_evm() -> AuroraAccount { let new_args = NewCallArgs { chain_id: crate::prelude::u256_to_arr(&U256::from(aurora_runner.chain_id)), owner_id: str_to_account_id(main_account.account_id.as_str()), - bridge_prover_id: prover_account.clone(), upgrade_delay_blocks: 1, + default_gas_token: [0u8; 20], }; main_account .call( From 87c83c0d9f22b0f2b565d105bb4498241138d415 Mon Sep 17 00:00:00 2001 From: Oleksandr Anyshchenko Date: Tue, 24 Jan 2023 18:23:32 +0100 Subject: [PATCH 08/12] Fix migration test --- etc/tests/state-migration-test/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/tests/state-migration-test/src/lib.rs b/etc/tests/state-migration-test/src/lib.rs index 1e7ae6ccf..969dc84bf 100644 --- a/etc/tests/state-migration-test/src/lib.rs +++ b/etc/tests/state-migration-test/src/lib.rs @@ -3,7 +3,7 @@ extern crate alloc; use alloc::vec::Vec; -use aurora_engine::engine::{self, EngineState}; +use aurora_engine::state::{self, EngineState}; use aurora_engine_sdk::near_runtime::Runtime; use aurora_engine_sdk::io::{IO, StorageIntermediate}; use aurora_engine_types::storage; @@ -18,7 +18,7 @@ struct NewFancyState { #[no_mangle] pub extern "C" fn state_migration() { let mut io = Runtime; - let old_state = match engine::get_state(&io) { + let old_state = match state::get_state(&io) { Ok(state) => state, Err(e) => aurora_engine_sdk::panic_utf8(e.as_ref()), }; From cf8ead222ef25c92e634ba8f7e1ed58179f67c3c Mon Sep 17 00:00:00 2001 From: "Joshua J. Bouw" Date: Thu, 26 Jan 2023 15:31:47 +0400 Subject: [PATCH 09/12] fix: post merge --- engine/src/engine.rs | 12 ++++++++---- engine/src/lib.rs | 1 - engine/src/state.rs | 2 ++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/engine/src/engine.rs b/engine/src/engine.rs index d03e27b7f..8104c9be6 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -1,13 +1,14 @@ use crate::connector::EthConnectorContract; use crate::map::BijectionMap; -use crate::{errors, state}; +use crate::{errors, gas_token, state}; use aurora_engine_sdk::caching::FullCache; use aurora_engine_sdk::env::Env; use aurora_engine_sdk::io::{StorageIntermediate, IO}; use aurora_engine_sdk::promise::{PromiseHandler, PromiseId, ReadOnlyPromiseHandler}; use crate::accounting; -use crate::parameters::{DeployErc20TokenArgs, TransactionStatus}; +use crate::gas_token::GasToken; +use crate::parameters::{DeployErc20TokenArgs, NEP141FtOnTransferArgs, TransactionStatus}; use crate::pausables::{ EngineAuthorizer, EnginePrecompilesPauser, PausedPrecompilesChecker, PrecompileFlags, }; @@ -20,7 +21,9 @@ use crate::prelude::{ Yocto, ERC20_MINT_SELECTOR, H160, H256, U256, }; use crate::state::EngineState; -use aurora_engine_precompiles::PrecompileConstructorContext; +use aurora_engine_precompiles::{Precompile, PrecompileConstructorContext}; +use aurora_engine_types::parameters::engine::{CallArgs, ResultLog, SubmitResult, ViewCallArgs}; +use aurora_engine_types::types::EthGas; use core::cell::RefCell; use core::iter::once; use core::mem; @@ -958,7 +961,7 @@ pub fn submit( let tx_status = TransactionStatus::Succeed(vec![]); let mut result_logs = Vec::new(); for log in v.logs { - result_logs.push(log.into()); + result_logs.push(evm_log_to_result_log(log)); } Ok(SubmitResult::new(tx_status, v.cost.as_u64(), result_logs)) } @@ -1813,6 +1816,7 @@ mod tests { use super::*; use crate::parameters::{FunctionCallArgsV1, FunctionCallArgsV2}; use aurora_engine_precompiles::make_address; + use aurora_engine_precompiles::native::exit_to_near; use aurora_engine_sdk::env::Fixed; use aurora_engine_sdk::promise::Noop; use aurora_engine_test_doubles::io::{Storage, StoragePointer}; diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 66794958d..0c95abcec 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -106,7 +106,6 @@ mod contract { #[cfg(feature = "integration-test")] use crate::prelude::NearGas; - use crate::state::EngineState; const CODE_KEY: &[u8; 4] = b"CODE"; const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; diff --git a/engine/src/state.rs b/engine/src/state.rs index 571c7a31a..afadeb25c 100644 --- a/engine/src/state.rs +++ b/engine/src/state.rs @@ -5,6 +5,8 @@ use aurora_engine_types::account_id::AccountId; use aurora_engine_types::storage::{bytes_to_key, KeyPrefix}; use borsh::{BorshDeserialize, BorshSerialize}; +pub use error::EngineStateError; + /// Key for storing the state of the engine. const STATE_KEY: &[u8; 5] = b"STATE"; From df11f1486ffe2456749858aa817fedcd83205b85 Mon Sep 17 00:00:00 2001 From: Oleksandr Anyshchenko Date: Thu, 26 Jan 2023 13:14:53 +0100 Subject: [PATCH 10/12] Update engine.rs --- engine/src/engine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/engine.rs b/engine/src/engine.rs index f5ccc6a49..e58b27617 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -2319,7 +2319,7 @@ mod tests { #[test] fn test_filtering_promises_from_logs_with_none_keeps_all() { let storage = RefCell::new(Storage::default()); - let io = StoragePointer(&storage); + let mut io = StoragePointer(&storage); let current_account_id = AccountId::default(); let mut handler = Noop; let logs = vec![Log { From 43b5019acbf84c3b213867c0bc182288e5fc0696 Mon Sep 17 00:00:00 2001 From: Oleksandr Anyshchenko Date: Mon, 6 Feb 2023 11:19:55 +0100 Subject: [PATCH 11/12] chore: fix test_state_format test --- engine-tests/src/test_utils/mod.rs | 3 +-- engine-tests/src/tests/sanity.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/engine-tests/src/test_utils/mod.rs b/engine-tests/src/test_utils/mod.rs index fd9e5f10d..762e3c9e4 100644 --- a/engine-tests/src/test_utils/mod.rs +++ b/engine-tests/src/test_utils/mod.rs @@ -825,8 +825,7 @@ pub(crate) fn as_account_id(account_id: &str) -> near_primitives_core::types::Ac } pub(crate) fn str_to_account_id(account_id: &str) -> AccountId { - use aurora_engine_types::str::FromStr; - AccountId::from_str(account_id).unwrap() + AccountId::new(account_id).unwrap() } pub fn unwrap_success(result: SubmitResult) -> Vec { diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs index 2b19141d8..c1cb599e9 100644 --- a/engine-tests/src/tests/sanity.rs +++ b/engine-tests/src/tests/sanity.rs @@ -137,14 +137,14 @@ fn test_state_format() { chain_id: aurora_engine_types::types::u256_to_arr(&666.into()), owner_id: "boss".parse().unwrap(), upgrade_delay_blocks: 3, - default_gas_token: [0u8; 20], + default_gas_token: [255u8; 20], }; let state: aurora_engine::state::EngineState = args.into(); let expected_hex: String = [ "000000000000000000000000000000000000000000000000000000000000029a", "04000000626f7373", - "1300000070726f7665725f6d6370726f76795f66616365", "0300000000000000", + "01ffffffffffffffffffffffffffffffffffffffff", ] .concat(); assert_eq!(hex::encode(state.try_to_vec().unwrap()), expected_hex); From e8c34ef7886e18808abd9e16068e82690c98eb21 Mon Sep 17 00:00:00 2001 From: Oleksandr Anyshchenko Date: Fri, 10 Feb 2023 18:00:20 +0100 Subject: [PATCH 12/12] chore: versioning state --- .../src/relayer_db/mod.rs | 7 +- engine-tests/src/tests/sanity.rs | 1 + engine-tests/src/tests/standalone/sanity.rs | 5 +- engine-types/src/types/mod.rs | 4 - engine/src/deposit_event.rs | 8 +- engine/src/engine.rs | 6 +- engine/src/lib.rs | 30 ++-- engine/src/state.rs | 145 ++++++++++-------- 8 files changed, 103 insertions(+), 103 deletions(-) diff --git a/engine-standalone-storage/src/relayer_db/mod.rs b/engine-standalone-storage/src/relayer_db/mod.rs index e2a1d4a6e..9cfb4466a 100644 --- a/engine-standalone-storage/src/relayer_db/mod.rs +++ b/engine-standalone-storage/src/relayer_db/mod.rs @@ -198,6 +198,7 @@ pub mod error { mod test { use super::FallibleIterator; use crate::sync::types::{TransactionKind, TransactionMessage}; + use aurora_engine::state::EngineStateV2; use aurora_engine::{connector, parameters, state}; use aurora_engine_types::H256; @@ -210,12 +211,12 @@ mod test { fn test_fill_db() { let mut storage = crate::Storage::open("rocks_tmp/").unwrap(); let mut connection = super::connect_without_tls(&Default::default()).unwrap(); - let engine_state = state::EngineState { + let engine_state = state::EngineState::V2(EngineStateV2 { chain_id: aurora_engine_types::types::u256_to_arr(&1313161555.into()), owner_id: "aurora".parse().unwrap(), upgrade_delay_blocks: 0, default_gas_token: Default::default(), - }; + }); // Initialize engine and connector states in storage. // Use explicit scope so borrows against `storage` are dropped before processing DB rows. @@ -234,7 +235,7 @@ mod test { state::set_state(&mut local_io, engine_state.clone()).unwrap(); connector::EthConnectorContract::create_contract( io, - engine_state.owner_id.clone(), + engine_state.owner_id(), parameters::InitCallArgs { prover_account: "prover.bridge.near".parse().unwrap(), eth_custodian_address: "6bfad42cfc4efc96f529d786d643ff4a8b89fa52" diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs index b94e14f86..cb7766765 100644 --- a/engine-tests/src/tests/sanity.rs +++ b/engine-tests/src/tests/sanity.rs @@ -141,6 +141,7 @@ fn test_state_format() { }; let state: aurora_engine::state::EngineState = args.into(); let expected_hex: String = [ + "00", // V2 - "00", V1 - "01", "000000000000000000000000000000000000000000000000000000000000029a", "04000000626f7373", "0300000000000000", diff --git a/engine-tests/src/tests/standalone/sanity.rs b/engine-tests/src/tests/standalone/sanity.rs index cf78d0d77..187d32372 100644 --- a/engine-tests/src/tests/standalone/sanity.rs +++ b/engine-tests/src/tests/standalone/sanity.rs @@ -1,3 +1,4 @@ +use aurora_engine::state::EngineStateV2; use aurora_engine::{engine, state}; use aurora_engine_sdk::env::DEFAULT_PREPAID_GAS; use aurora_engine_test_doubles::io::{Storage, StoragePointer}; @@ -15,12 +16,12 @@ fn test_deploy_code() { buf }; let owner_id: AccountId = "aurora".parse().unwrap(); - let state = state::EngineState { + let state = state::EngineState::V2(EngineStateV2 { chain_id, owner_id: owner_id.clone(), upgrade_delay_blocks: 0, default_gas_token: Default::default(), - }; + }); let origin = Address::new(H160([0u8; 20])); let storage = RefCell::new(Storage::default()); let io = StoragePointer(&storage); diff --git a/engine-types/src/types/mod.rs b/engine-types/src/types/mod.rs index 82e666199..2efe0b2b4 100644 --- a/engine-types/src/types/mod.rs +++ b/engine-types/src/types/mod.rs @@ -133,10 +133,6 @@ impl Stack { } } -pub fn str_from_slice(inp: &[u8]) -> &str { - str::from_utf8(inp).unwrap() -} - #[cfg(test)] mod tests { use super::*; diff --git a/engine/src/deposit_event.rs b/engine/src/deposit_event.rs index 496d24a2e..dc34a4fb0 100644 --- a/engine/src/deposit_event.rs +++ b/engine/src/deposit_event.rs @@ -527,10 +527,8 @@ mod tests { let parse_error = TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee) .unwrap_err(); - let actual_parse_error = std::str::from_utf8(parse_error.as_ref()).unwrap(); - let expected_parse_error = AddressError::IncorrectLength.to_string(); - assert_eq!(expected_parse_error, actual_parse_error); + assert_eq!(parse_error.as_ref(), AddressError::IncorrectLength.as_ref()); } #[test] @@ -541,10 +539,8 @@ mod tests { let parse_error = TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee) .unwrap_err(); - let actual_parse_error = std::str::from_utf8(parse_error.as_ref()).unwrap(); - let expected_parse_error = AddressError::FailedDecodeHex.to_string(); - assert_eq!(expected_parse_error, actual_parse_error); + assert_eq!(parse_error.as_ref(), AddressError::FailedDecodeHex.as_ref()); } #[test] diff --git a/engine/src/engine.rs b/engine/src/engine.rs index b00b8f7f4..c46cfa208 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -872,7 +872,7 @@ pub fn submit( // Validate the chain ID, if provided inside the signature: if let Some(chain_id) = transaction.chain_id { - if U256::from(chain_id) != U256::from(state.chain_id) { + if U256::from(chain_id) != U256::from(state.chain_id()) { return Err(EngineErrorKind::InvalidChainId.into()); } } @@ -1535,7 +1535,7 @@ impl<'env, I: IO + Copy, E: Env> Backend for Engine<'env, I, E> { if idx.saturating_sub(U256::from(256)) <= number && number < idx { // since `idx` comes from `u64` it is always safe to downcast `number` from `U256` compute_block_hash( - self.state.chain_id, + self.state.chain_id(), number.low_u64(), self.current_account_id.as_bytes(), ) @@ -1595,7 +1595,7 @@ impl<'env, I: IO + Copy, E: Env> Backend for Engine<'env, I, E> { /// Returns the states chain ID. fn chain_id(&self) -> U256 { - U256::from(self.state.chain_id) + U256::from(self.state.chain_id()) } /// Checks if an address exists. diff --git a/engine/src/lib.rs b/engine/src/lib.rs index a33e765fd..08fae60b6 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -94,7 +94,9 @@ mod contract { near_account_to_evm_address, SdkExpect, SdkProcess, SdkUnwrap, }; use crate::prelude::storage::{bytes_to_key, KeyPrefix}; - use crate::prelude::{sdk, u256_to_arr, Address, PromiseResult, Yocto, ERR_FAILED_PARSE, H256}; + use crate::prelude::{ + sdk, u256_to_arr, Address, PromiseResult, ToString, Yocto, ERR_FAILED_PARSE, H256, + }; use crate::{errors, pausables, state}; use aurora_engine_sdk::env::Env; use aurora_engine_sdk::io::{StorageIntermediate, IO}; @@ -140,7 +142,7 @@ mod contract { pub extern "C" fn get_owner() { let mut io = Runtime; let state = state::get_state(&io).sdk_unwrap(); - io.return_output(state.owner_id.as_bytes()); + io.return_output(state.owner_id().as_bytes()); } /// Get bridge prover id for this contract. @@ -155,7 +157,7 @@ mod contract { #[no_mangle] pub extern "C" fn get_chain_id() { let mut io = Runtime; - io.return_output(&state::get_state(&io).sdk_unwrap().chain_id) + io.return_output(&state::get_state(&io).sdk_unwrap().chain_id()) } #[no_mangle] @@ -163,7 +165,7 @@ mod contract { let mut io = Runtime; let state = state::get_state(&io).sdk_unwrap(); let index = internal_get_upgrade_index(); - io.return_output(&(index + state.upgrade_delay_blocks).to_le_bytes()) + io.return_output(&(index + state.upgrade_delay_blocks()).to_le_bytes()) } /// Stage new code for deployment. @@ -187,7 +189,7 @@ mod contract { let state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); let index = internal_get_upgrade_index(); - if io.block_height() <= index + state.upgrade_delay_blocks { + if io.block_height() <= index + state.upgrade_delay_blocks() { sdk::panic_utf8(errors::ERR_NOT_ALLOWED_TOO_EARLY); } Runtime::self_deploy(&bytes_to_key(KeyPrefix::Config, CODE_KEY)); @@ -197,13 +199,7 @@ mod contract { /// to make any necessary changes to the state such that it aligns with the newly deployed /// code. #[no_mangle] - pub extern "C" fn state_migration() { - let mut io = Runtime; - io.assert_private_call().sdk_unwrap(); - - // TODO: Must be removed after migration. - state::legacy::migrate_state(&mut io).sdk_unwrap(); - } + pub extern "C" fn state_migration() {} /// Resumes previously [`paused`] precompiles. /// @@ -479,7 +475,7 @@ mod contract { let block_height = io.read_input_borsh().sdk_unwrap(); let account_id = io.current_account_id(); let chain_id = state::get_state(&io) - .map(|state| state.chain_id) + .map(|state| state.chain_id()) .sdk_unwrap(); let block_hash = crate::engine::compute_block_hash(chain_id, block_height, account_id.as_bytes()); @@ -531,7 +527,7 @@ mod contract { let mut state = state::get_state(&io).sdk_unwrap(); require_owner_only(&state, &io.predecessor_account_id()); let args: BeginChainArgs = io.read_input_borsh().sdk_unwrap(); - state.chain_id = args.chain_id; + state.set_chain_id(args.chain_id); state::set_state(&mut io, state).sdk_unwrap(); // set genesis block balances for account_balance in args.genesis_alloc { @@ -542,7 +538,7 @@ mod contract { ) } // return new chain ID - io.return_output(&state::get_state(&io).sdk_unwrap().chain_id) + io.return_output(&state::get_state(&io).sdk_unwrap().chain_id()) } #[cfg(feature = "evm_bully")] @@ -911,7 +907,7 @@ mod contract { #[no_mangle] pub extern "C" fn mint_account() { use crate::connector::ZERO_ATTACHED_BALANCE; - use crate::prelude::{NEP141Wei, ToString, U256}; + use crate::prelude::{NEP141Wei, U256}; use evm::backend::ApplyBackend; const GAS_FOR_VERIFY: NearGas = NearGas::new(20_000_000_000_000); const GAS_FOR_FINISH: NearGas = NearGas::new(50_000_000_000_000); @@ -987,7 +983,7 @@ mod contract { } fn require_owner_only(state: &state::EngineState, predecessor_account_id: &AccountId) { - if &state.owner_id != predecessor_account_id { + if &state.owner_id() != predecessor_account_id { sdk::panic_utf8(errors::ERR_NOT_ALLOWED); } } diff --git a/engine/src/state.rs b/engine/src/state.rs index e17ffe6fa..50948d3a1 100644 --- a/engine/src/state.rs +++ b/engine/src/state.rs @@ -10,10 +10,59 @@ pub use error::EngineStateError; /// Key for storing the state of the engine. const STATE_KEY: &[u8; 5] = b"STATE"; +/// Engine internal state, mostly configuration. +#[derive(BorshSerialize, BorshDeserialize, Clone, PartialEq, Eq, Debug)] +pub enum EngineState { + V2(EngineStateV2), + V1(EngineStateV1), +} + +impl EngineState { + pub fn chain_id(&self) -> [u8; 32] { + match self { + EngineState::V2(state) => state.chain_id, + EngineState::V1(state) => state.chain_id, + } + } + + pub fn set_chain_id(&mut self, chain_id: [u8; 32]) { + match self { + EngineState::V2(state) => state.chain_id = chain_id, + EngineState::V1(state) => state.chain_id = chain_id, + } + } + + pub fn owner_id(&self) -> AccountId { + match self { + EngineState::V2(state) => state.owner_id.clone(), + EngineState::V1(state) => state.owner_id.clone(), + } + } + + pub fn upgrade_delay_blocks(&self) -> u64 { + match self { + EngineState::V2(state) => state.upgrade_delay_blocks, + EngineState::V1(state) => state.upgrade_delay_blocks, + } + } + + pub fn deserialize(bytes: &[u8]) -> Option { + Self::try_from_slice(bytes) + .or_else(|_| EngineStateV1::try_from_slice(bytes).map(Self::V1)) + .ok() + } +} + +impl Default for EngineState { + fn default() -> Self { + Self::V2(EngineStateV2::default()) + } +} + /// Engine internal state V2, mostly configuration. /// Should not contain anything large or enumerable. #[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)] -pub struct EngineState { +pub struct EngineStateV2 { /// Chain id, according to the EIP-155 / ethereum-lists spec. pub chain_id: [u8; 32], /// Account which can upgrade this contract. @@ -25,33 +74,50 @@ pub struct EngineState { pub default_gas_token: GasToken, } +/// Engine internal state V1, mostly configuration. +/// Should not contain anything large or enumerable. +#[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)] +pub struct EngineStateV1 { + /// Chain id, according to the EIP-155 / ethereum-lists spec. + pub chain_id: [u8; 32], + /// Account which can upgrade this contract. + /// Use empty to disable updatability. + pub owner_id: AccountId, + /// Account of the bridge prover. + /// Use empty to not use base token as bridged asset. + pub bridge_prover_id: AccountId, + /// How many blocks after staging upgrade can deploy it. + pub upgrade_delay_blocks: u64, +} + impl From for EngineState { fn from(args: NewCallArgs) -> Self { - EngineState { + EngineState::V2(EngineStateV2 { chain_id: args.chain_id, owner_id: args.owner_id, upgrade_delay_blocks: args.upgrade_delay_blocks, default_gas_token: GasToken::from_array(args.default_gas_token), - } + }) } } /// Gets the state from storage, if it exists otherwise it will error. -pub fn get_state(io: &I) -> Result { +pub fn get_state(io: &I) -> Result { match io.read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) { - None => Err(error::EngineStateError::NotFound), - Some(bytes) => EngineState::try_from_slice(&bytes.to_vec()) - .map_err(|_| error::EngineStateError::DeserializationFailed), + None => Err(EngineStateError::NotFound), + Some(bytes) => { + EngineState::deserialize(&bytes.to_vec()).ok_or(EngineStateError::DeserializationFailed) + } } } /// Saves state into the storage. Does not return the previous state. -pub fn set_state(io: &mut I, state: EngineState) -> Result<(), error::EngineStateError> { +pub fn set_state(io: &mut I, state: EngineState) -> Result<(), EngineStateError> { io.write_storage( &bytes_to_key(KeyPrefix::Config, STATE_KEY), &state .try_to_vec() - .map_err(|_| error::EngineStateError::SerializationFailed)?, + .map_err(|_| EngineStateError::SerializationFailed)?, ); Ok(()) @@ -85,58 +151,6 @@ pub mod error { } } -pub mod legacy { - use super::*; - - /// Migrates the state from the old format to the latest format. - /// - /// While this function works for a single v1 -> v2 migration, it would need to be - /// altered to handle all earlier migrations if needed. - pub fn migrate_state(io: &mut I) -> Result<(), error::EngineStateError> { - // Get the engine legacy state. - let engine_state_legacy = match io.read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) - { - None => Err(error::EngineStateError::NotFound), - Some(bytes) => EngineStateLegacy::try_from_slice(&bytes.to_vec()) - .map_err(|_| error::EngineStateError::DeserializationFailed), - }?; - - // Migrate to new engine state. - let engine_state_new = EngineState { - chain_id: engine_state_legacy.chain_id, - owner_id: engine_state_legacy.owner_id, - upgrade_delay_blocks: 0, - default_gas_token: Default::default(), - }; - - // Set the new engine state. - io.write_storage( - &bytes_to_key(KeyPrefix::Config, STATE_KEY), - &engine_state_new - .try_to_vec() - .map_err(|_| error::EngineStateError::SerializationFailed)?, - ); - - Ok(()) - } - - /// Engine internal state V1, mostly configuration. - /// Should not contain anything large or enumerable. - #[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)] - pub struct EngineStateLegacy { - /// Chain id, according to the EIP-155 / ethereum-lists spec. - pub chain_id: [u8; 32], - /// Account which can upgrade this contract. - /// Use empty to disable updatability. - pub owner_id: AccountId, - /// Account of the bridge prover. - /// Use empty to not use base token as bridged asset. - pub bridge_prover_id: AccountId, - /// How many blocks after staging upgrade can deploy it. - pub upgrade_delay_blocks: u64, - } -} - #[cfg(test)] #[cfg(feature = "std")] mod tests { @@ -148,12 +162,9 @@ mod tests { fn test_missing_engine_state_is_not_found() { let storage = RefCell::new(Storage::default()); let io = StoragePointer(&storage); - let actual_error = get_state(&io).unwrap_err(); - let actual_error = std::str::from_utf8(actual_error.as_ref()).unwrap(); - let expected_error = std::str::from_utf8(error::ERR_STATE_NOT_FOUND).unwrap(); - assert_eq!(expected_error, actual_error); + assert_eq!(actual_error.as_ref(), error::ERR_STATE_NOT_FOUND); } #[test] @@ -163,9 +174,7 @@ mod tests { io.write_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY), &[]); let actual_error = get_state(&io).unwrap_err(); - let actual_error = std::str::from_utf8(actual_error.as_ref()).unwrap(); - let expected_error = std::str::from_utf8(error::ERR_STATE_CORRUPTED).unwrap(); - assert_eq!(expected_error, actual_error); + assert_eq!(actual_error.as_ref(), error::ERR_STATE_CORRUPTED); } }