From ccc1b4872ef8c77094cd1c2f2ad81653fd848adb Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Wed, 31 Aug 2022 14:36:52 +0200 Subject: [PATCH 1/5] Precompile implementation of ERC20 contract --- Cargo.lock | 8 +- engine-precompiles/Cargo.toml | 2 +- engine-precompiles/src/erc20.rs | 428 +++++++++++++++++++++++++++ engine-precompiles/src/lib.rs | 27 +- engine-precompiles/src/xcc.rs | 4 +- engine-standalone-storage/Cargo.toml | 2 +- engine-standalone-tracing/Cargo.toml | 8 +- engine-tests/Cargo.toml | 6 +- engine-transactions/Cargo.toml | 2 +- engine/Cargo.toml | 2 +- 10 files changed, 460 insertions(+), 29 deletions(-) create mode 100644 engine-precompiles/src/erc20.rs diff --git a/Cargo.lock b/Cargo.lock index c71b97c60..93a5d4429 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "evm" version = "0.35.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.36.0-aurora#7dfbeb535e7105a7531a4e6c559f0f5d45f20014" +source = "git+https://github.com/aurora-is-near/sputnikvm.git?rev=cb9286b87eb868e921a25f9af74dc9a9f720b39e#cb9286b87eb868e921a25f9af74dc9a9f720b39e" dependencies = [ "auto_impl", "environmental", @@ -1224,7 +1224,7 @@ dependencies = [ [[package]] name = "evm-core" version = "0.35.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.36.0-aurora#7dfbeb535e7105a7531a4e6c559f0f5d45f20014" +source = "git+https://github.com/aurora-is-near/sputnikvm.git?rev=cb9286b87eb868e921a25f9af74dc9a9f720b39e#cb9286b87eb868e921a25f9af74dc9a9f720b39e" dependencies = [ "parity-scale-codec 3.1.5", "primitive-types 0.11.1", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "evm-gasometer" version = "0.35.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.36.0-aurora#7dfbeb535e7105a7531a4e6c559f0f5d45f20014" +source = "git+https://github.com/aurora-is-near/sputnikvm.git?rev=cb9286b87eb868e921a25f9af74dc9a9f720b39e#cb9286b87eb868e921a25f9af74dc9a9f720b39e" dependencies = [ "environmental", "evm-core", @@ -1246,7 +1246,7 @@ dependencies = [ [[package]] name = "evm-runtime" version = "0.35.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.36.0-aurora#7dfbeb535e7105a7531a4e6c559f0f5d45f20014" +source = "git+https://github.com/aurora-is-near/sputnikvm.git?rev=cb9286b87eb868e921a25f9af74dc9a9f720b39e#cb9286b87eb868e921a25f9af74dc9a9f720b39e" dependencies = [ "auto_impl", "environmental", diff --git a/engine-precompiles/Cargo.toml b/engine-precompiles/Cargo.toml index ccdc9232c..1c6b78a0f 100644 --- a/engine-precompiles/Cargo.toml +++ b/engine-precompiles/Cargo.toml @@ -17,7 +17,7 @@ aurora-engine-types = { path = "../engine-types", default-features = false } aurora-engine-sdk = { path = "../engine-sdk", default-features = false } borsh = { version = "0.9.3", default-features = false } bn = { version = "0.5.11", package = "zeropool-bn", default-features = false } -evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false } +evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false } libsecp256k1 = { version = "0.7.0", default-features = false, features = ["static-context", "hmac"] } num = { version = "0.4.0", default-features = false, features = ["alloc"] } ripemd = { version = "0.1.1", default-features = false } diff --git a/engine-precompiles/src/erc20.rs b/engine-precompiles/src/erc20.rs new file mode 100644 index 000000000..84fe5e376 --- /dev/null +++ b/engine-precompiles/src/erc20.rs @@ -0,0 +1,428 @@ +//! This is not a single precompile, but rather a "precompile template". +//! In particular, this is meant to replace the implementation of any ERC-20 contract +//! with an equivalent implementation in pure Rust. This will then be compiled to +//! wasm, and executed in the NEAR runtime directly, as opposed to the Solidity code +//! compiled to EVM which then runs in the EVM interpreter on top of wasm. +//! Therefore, this should be significantly more efficient than the EVM version. + +use crate::{HandleBasedPrecompile, PrecompileOutput}; +use aurora_engine_types::{types::EthGas, String, Vec, H160, H256, U256}; +use evm::backend::Backend; +use evm::executor::stack::{PrecompileFailure, PrecompileHandle, StackState}; +use evm::ExitRevert; + +mod consts { + pub(super) const TRANSFER_FROM_ARGS: &[ethabi::ParamType] = &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256), + ]; + pub(super) const BALANCE_OF_ARGS: &[ethabi::ParamType] = &[ethabi::ParamType::Address]; + pub(super) const TRANSFER_ARGS: &[ethabi::ParamType] = + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256)]; + pub(super) const ALLOWANCE_ARGS: &[ethabi::ParamType] = + &[ethabi::ParamType::Address, ethabi::ParamType::Address]; + pub(super) const APPROVE_ARGS: &[ethabi::ParamType] = + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256)]; + pub(super) const APPROVE_SELECTOR: &[u8] = &[0x09, 0x5e, 0xa7, 0xb3]; + pub(super) const BALANCE_OF_SELECTOR: &[u8] = &[0x70, 0xa0, 0x82, 0x31]; + pub(super) const TOTAL_SUPPLY_SELECTOR: &[u8] = &[0x18, 0x16, 0x0d, 0xdd]; + pub(super) const TRANSFER_SELECTOR: &[u8] = &[0xa9, 0x05, 0x9c, 0xbb]; + pub(super) const ALLOWANCE_SELECTOR: &[u8] = &[0xdd, 0x62, 0xed, 0x3e]; + pub(super) const TRANSFER_FROM_SELECTOR: &[u8] = &[0x23, 0xb8, 0x72, 0xdd]; + pub(super) const NAME_SELECTOR: &[u8] = &[0x06, 0xfd, 0xde, 0x03]; + pub(super) const SYMBOL_SELECTOR: &[u8] = &[0x95, 0xd8, 0x9b, 0x41]; + pub(super) const DECIMALS_SELECTOR: &[u8] = &[0x31, 0x3c, 0xe5, 0x67]; +} + +pub struct Erc20; + +impl<'config> Erc20 { + fn name( + &self, + handle: &mut impl PrecompileHandle<'config>, + ) -> Result { + let name_key = slot(5); + let address = handle.context().address; + let name = read_as_string(handle.state_mut(), address, name_key); + + // TODO: cost? + let cost = EthGas::new(0); + Ok(PrecompileOutput { + cost, + output: ethabi::encode(&[ethabi::Token::String(name)]), + logs: Vec::new(), + }) + } + + fn symbol( + &self, + handle: &mut impl PrecompileHandle<'config>, + ) -> Result { + let symbol_key = slot(6); + let address = handle.context().address; + let symbol = read_as_string(handle.state_mut(), address, symbol_key); + + // TODO: cost? + let cost = EthGas::new(0); + Ok(PrecompileOutput { + cost, + output: ethabi::encode(&[ethabi::Token::String(symbol)]), + logs: Vec::new(), + }) + } + + fn total_supply( + &self, + handle: &mut impl PrecompileHandle<'config>, + ) -> Result { + let total_supply_key = slot(4); + let address = handle.context().address; + let total_supply = read_as_u256(handle.state_mut(), address, total_supply_key); + + // TODO: cost? + let cost = EthGas::new(0); + Ok(PrecompileOutput { + cost, + output: ethabi::encode(&[ethabi::Token::Uint(total_supply)]), + logs: Vec::new(), + }) + } + + fn balance_of( + &self, + handle: &mut impl PrecompileHandle<'config>, + owner: &H160, + ) -> Result { + let address = handle.context().address; + let state = handle.state_mut(); + let balance_key = Self::create_balance_storage_key(owner); + let balance = read_as_u256(state, address, balance_key); + + // TODO: cost? + let cost = EthGas::new(0); + Ok(PrecompileOutput { + cost, + output: ethabi::encode(&[ethabi::Token::Uint(balance)]), + logs: Vec::new(), + }) + } + + fn transfer( + &self, + handle: &mut impl PrecompileHandle<'config>, + to: &H160, + amount: U256, + ) -> Result { + let (erc20_address, from) = { + let ctx = handle.context(); + (ctx.address, ctx.caller) + }; + let state = handle.state_mut(); + + let balance_key = Self::create_balance_storage_key(&from); + let current_balance = read_as_u256(state, erc20_address, balance_key); + if current_balance < amount { + // TODO: proper error message + return Err(PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + }); + } + + write_u256(state, erc20_address, balance_key, current_balance - amount); + let balance_key = Self::create_balance_storage_key(to); + // TODO: is this saturating add or checked add? + write_u256( + state, + erc20_address, + balance_key, + read_as_u256(state, erc20_address, balance_key).saturating_add(amount), + ); + + // TODO: cost? + let cost = EthGas::new(0); + Ok(PrecompileOutput { + cost, + output: ethabi::encode(&[ethabi::Token::Bool(true)]), + // TODO: proper event output + logs: Vec::new(), + }) + } + + fn allowance( + &self, + handle: &mut impl PrecompileHandle<'config>, + owner: &H160, + spender: &H160, + ) -> Result { + let address = handle.context().address; + let state = handle.state_mut(); + let allowance_key = Self::create_allowance_storage_key(owner, spender); + let allowance = read_as_u256(state, address, allowance_key); + + // TODO: cost? + let cost = EthGas::new(0); + Ok(PrecompileOutput { + cost, + output: ethabi::encode(&[ethabi::Token::Uint(allowance)]), + logs: Vec::new(), + }) + } + + fn approve( + &self, + handle: &mut impl PrecompileHandle<'config>, + spender: &H160, + amount: U256, + ) -> Result { + let (erc20_address, owner) = { + let ctx = handle.context(); + (ctx.address, ctx.caller) + }; + let state = handle.state_mut(); + + let allowance_key = Self::create_allowance_storage_key(&owner, spender); + // TODO: is this saturating add or checked add? + write_u256( + state, + erc20_address, + allowance_key, + read_as_u256(state, erc20_address, allowance_key).saturating_add(amount), + ); + + // TODO: cost? + let cost = EthGas::new(0); + Ok(PrecompileOutput { + cost, + output: ethabi::encode(&[ethabi::Token::Bool(true)]), + // TODO: proper event output + logs: Vec::new(), + }) + } + + fn transfer_from( + &self, + handle: &mut impl PrecompileHandle<'config>, + from: &H160, + to: &H160, + amount: U256, + ) -> Result { + let (erc20_address, spender) = { + let ctx = handle.context(); + (ctx.address, ctx.caller) + }; + let state = handle.state_mut(); + + let allowance_key = Self::create_allowance_storage_key(from, &spender); + let current_allowance = read_as_u256(state, erc20_address, allowance_key); + if current_allowance < amount { + // TODO: proper error message + return Err(PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + }); + } + + let balance_key = Self::create_balance_storage_key(from); + let current_balance = read_as_u256(state, erc20_address, balance_key); + if current_balance < amount { + // TODO: proper error message + return Err(PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + }); + } + + if current_allowance != U256::MAX { + write_u256( + state, + erc20_address, + allowance_key, + current_allowance - amount, + ); + } + write_u256(state, erc20_address, balance_key, current_balance - amount); + let balance_key = Self::create_balance_storage_key(to); + // TODO: is this saturating add or checked add? + write_u256( + state, + erc20_address, + balance_key, + read_as_u256(state, erc20_address, balance_key).saturating_add(amount), + ); + + // TODO: cost? + let cost = EthGas::new(0); + Ok(PrecompileOutput { + cost, + output: ethabi::encode(&[ethabi::Token::Bool(true)]), + // TODO: proper event output + logs: Vec::new(), + }) + } + + fn create_balance_storage_key(owner: &H160) -> H256 { + let mut bytes = Vec::with_capacity(64); + bytes.extend_from_slice(&[0u8; 12]); + bytes.extend_from_slice(owner.as_bytes()); + bytes.extend_from_slice(&[0u8; 31]); + bytes.push(2); // balance mapping is in "slot 2" + + aurora_engine_sdk::keccak(&bytes) + } + + fn create_allowance_storage_key(owner: &H160, spender: &H160) -> H256 { + let mut bytes = Vec::with_capacity(64); + bytes.extend_from_slice(&[0u8; 12]); + bytes.extend_from_slice(owner.as_bytes()); + bytes.extend_from_slice(&[0u8; 31]); + bytes.push(3); // allowance mapping is in "slot 3" + + let hash1 = aurora_engine_sdk::keccak(&bytes); + + bytes.clear(); + bytes.extend_from_slice(&[0u8; 12]); + bytes.extend_from_slice(spender.as_bytes()); + bytes.extend_from_slice(hash1.as_bytes()); + + aurora_engine_sdk::keccak(&bytes) + } +} + +const fn slot(n: u8) -> H256 { + let mut tmp = [0u8; 32]; + tmp[31] = n; + H256(tmp) +} + +fn read_as_string(state: &S, address: H160, key: H256) -> String { + let value = state.storage(address, key); + + if value.0[31] % 2 == 1 { + panic!("Long format strings not implemented"); + } + + let length = usize::from(value.0[31] / 2); + let bytes = &value.as_bytes()[0..length]; + // TODO: is lossy conversion fine here? + String::from_utf8_lossy(bytes).into() +} + +fn read_as_u256(state: &S, address: H160, key: H256) -> U256 { + U256::from_big_endian(state.storage(address, key).as_bytes()) +} + +fn write_u256<'a, S: StackState<'a>>(state: &mut S, address: H160, key: H256, value: U256) { + let mut bytes = [0u8; 32]; + value.to_big_endian(&mut bytes); + state.set_storage(address, key, H256(bytes)) +} + +impl<'config> HandleBasedPrecompile<'config> for Erc20 { + fn run_with_handle( + &self, + handle: &mut impl PrecompileHandle<'config>, + ) -> Result { + let input = handle.input(); + + if input.len() < 4 { + return Err(PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + }); + } + + let selector = &input[0..4]; + match selector { + consts::NAME_SELECTOR => self.name(handle), + consts::SYMBOL_SELECTOR => self.symbol(handle), + consts::DECIMALS_SELECTOR => { + // TODO: cost + Ok(PrecompileOutput::without_logs( + EthGas::new(0), + ethabi::encode(&[ethabi::Token::Uint(18.into())]), + )) + } + consts::TOTAL_SUPPLY_SELECTOR => self.total_supply(handle), + consts::BALANCE_OF_SELECTOR => { + let parsed_args = + ethabi::decode(consts::BALANCE_OF_ARGS, &input[4..]).map_err(|_| { + PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + } + })?; + let owner = as_address(&parsed_args[0]).unwrap(); + self.balance_of(handle, owner) + } + consts::TRANSFER_SELECTOR => { + let parsed_args = + ethabi::decode(consts::TRANSFER_ARGS, &input[4..]).map_err(|_| { + PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + } + })?; + let to = as_address(&parsed_args[0]).unwrap(); // unwrap is because of the types passed to `decode` above + let amount = *as_uint(&parsed_args[1]).unwrap(); + self.transfer(handle, to, amount) + } + consts::ALLOWANCE_SELECTOR => { + let parsed_args = + ethabi::decode(consts::ALLOWANCE_ARGS, &input[4..]).map_err(|_| { + PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + } + })?; + let owner = as_address(&parsed_args[0]).unwrap(); + let spender = as_address(&parsed_args[1]).unwrap(); + self.allowance(handle, owner, spender) + } + consts::APPROVE_SELECTOR => { + let parsed_args = + ethabi::decode(consts::APPROVE_ARGS, &input[4..]).map_err(|_| { + PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + } + })?; + let spender = as_address(&parsed_args[0]).unwrap(); // unwrap is because of the types passed to `decode` above + let amount = *as_uint(&parsed_args[1]).unwrap(); + self.approve(handle, spender, amount) + } + consts::TRANSFER_FROM_SELECTOR => { + let parsed_args = + ethabi::decode(consts::TRANSFER_FROM_ARGS, &input[4..]).map_err(|_| { + PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + } + })?; + let from = as_address(&parsed_args[0]).unwrap(); // unwrap is because of the types passed to `decode` above + let to = as_address(&parsed_args[1]).unwrap(); + let amount = *as_uint(&parsed_args[2]).unwrap(); + self.transfer_from(handle, from, to, amount) + } + _ => Err(PrecompileFailure::Revert { + exit_status: ExitRevert::Reverted, + output: Vec::new(), + }), + } + } +} + +fn as_address(token: ðabi::Token) -> Option<&H160> { + match token { + ethabi::Token::Address(a) => Some(a), + _ => None, + } +} + +fn as_uint(token: ðabi::Token) -> Option<&U256> { + match token { + ethabi::Token::Uint(x) => Some(x), + _ => None, + } +} diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs index 78a956e05..db71588f1 100644 --- a/engine-precompiles/src/lib.rs +++ b/engine-precompiles/src/lib.rs @@ -6,6 +6,7 @@ pub mod account_ids; pub mod alt_bn256; pub mod blake2; +pub mod erc20; pub mod hash; pub mod identity; pub mod modexp; @@ -80,10 +81,10 @@ pub trait Precompile { ) -> EvmPrecompileResult; } -pub trait HandleBasedPrecompile { +pub trait HandleBasedPrecompile<'config> { fn run_with_handle( &self, - handle: &mut impl PrecompileHandle, + handle: &mut impl PrecompileHandle<'config>, ) -> Result; } @@ -114,12 +115,12 @@ pub struct Precompiles<'a, I, E, H> { pub all_precompiles: prelude::BTreeMap>, } -impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::PrecompileSet - for Precompiles<'a, I, E, H> +impl<'a, 'config, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> + executor::stack::PrecompileSet<'config> for Precompiles<'a, I, E, H> { fn execute( &self, - handle: &mut impl PrecompileHandle, + handle: &mut impl PrecompileHandle<'config>, ) -> Option> { let address = handle.code_address(); @@ -130,6 +131,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::Preco AllPrecompiles::PrepaidGas(p) => process_precompile(p, handle), AllPrecompiles::PromiseResult(p) => process_precompile(p, handle), AllPrecompiles::CrossContractCall(p) => process_handle_based_precompile(p, handle), + AllPrecompiles::Erc20(p) => process_handle_based_precompile(p, handle), AllPrecompiles::Generic(p) => process_precompile(p.as_ref(), handle), }; Some(result.and_then(|output| post_process(output, handle))) @@ -140,9 +142,9 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::Preco } } -fn process_precompile( +fn process_precompile<'config>( p: &dyn Precompile, - handle: &mut impl PrecompileHandle, + handle: &mut impl PrecompileHandle<'config>, ) -> Result { let input = handle.input(); let gas_limit = handle.gas_limit(); @@ -153,16 +155,16 @@ fn process_precompile( .map_err(|exit_status| PrecompileFailure::Error { exit_status }) } -fn process_handle_based_precompile( - p: &impl HandleBasedPrecompile, - handle: &mut impl PrecompileHandle, +fn process_handle_based_precompile<'config>( + p: &impl HandleBasedPrecompile<'config>, + handle: &mut impl PrecompileHandle<'config>, ) -> Result { p.run_with_handle(handle) } -fn post_process( +fn post_process<'config>( output: PrecompileOutput, - handle: &mut impl PrecompileHandle, + handle: &mut impl PrecompileHandle<'config>, ) -> Result { handle.record_cost(output.cost.as_u64())?; for log in output.logs { @@ -366,6 +368,7 @@ pub enum AllPrecompiles<'a, I, E, H> { PrepaidGas(PrepaidGas<'a, E>), PromiseResult(PromiseResult), Generic(Box), + Erc20(erc20::Erc20), } /// fn for making an address by concatenating the bytes from two given numbers, diff --git a/engine-precompiles/src/xcc.rs b/engine-precompiles/src/xcc.rs index e8e009ad2..b42d1d64b 100644 --- a/engine-precompiles/src/xcc.rs +++ b/engine-precompiles/src/xcc.rs @@ -88,10 +88,10 @@ pub mod cross_contract_call { crate::make_h256(0x72657175697265645f6e656172, 0x72657175697265645f6e656172); } -impl HandleBasedPrecompile for CrossContractCall { +impl<'config, I: IO> HandleBasedPrecompile<'config> for CrossContractCall { fn run_with_handle( &self, - handle: &mut impl PrecompileHandle, + handle: &mut impl PrecompileHandle<'config>, ) -> Result { let input = handle.input(); let target_gas = handle.gas_limit().map(EthGas::new); diff --git a/engine-standalone-storage/Cargo.toml b/engine-standalone-storage/Cargo.toml index 8768f40c7..e2aea5435 100644 --- a/engine-standalone-storage/Cargo.toml +++ b/engine-standalone-storage/Cargo.toml @@ -20,7 +20,7 @@ aurora-engine-sdk = { path = "../engine-sdk", default-features = false, features aurora-engine-transactions = { path = "../engine-transactions", default-features = false, features = ["std"] } aurora-engine-precompiles = { path = "../engine-precompiles", default-features = false, features = ["std"] } borsh = { version = "0.9.3" } -evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false } +evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false } hex = "0.4.3" rocksdb = { version = "0.18.0", default-features = false } postgres = "0.19.2" diff --git a/engine-standalone-tracing/Cargo.toml b/engine-standalone-tracing/Cargo.toml index 99aaa2416..78af25c9c 100644 --- a/engine-standalone-tracing/Cargo.toml +++ b/engine-standalone-tracing/Cargo.toml @@ -15,10 +15,10 @@ crate-type = ["lib"] [dependencies] aurora-engine-types = { path = "../engine-types", default-features = false, features = ["std"] } -evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false, features = ["std"] } -evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false, features = ["std", "tracing"] } -evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false, features = ["std", "tracing"] } -evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false, features = ["std", "tracing"] } +evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false, features = ["std"] } +evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false, features = ["std", "tracing"] } +evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false, features = ["std", "tracing"] } +evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false, features = ["std", "tracing"] } hex = { version = "0.4", default-features = false, features = ["std"] } serde = { version = "1", features = ["derive"], optional = true } diff --git a/engine-tests/Cargo.toml b/engine-tests/Cargo.toml index b74330172..2ffa8dbf6 100644 --- a/engine-tests/Cargo.toml +++ b/engine-tests/Cargo.toml @@ -24,9 +24,9 @@ engine-standalone-storage = { path = "../engine-standalone-storage" } engine-standalone-tracing = { path = "../engine-standalone-tracing" } borsh = { version = "0.9.3", default-features = false } sha3 = { version = "0.10.2", default-features = false } -evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false, features = ["std", "tracing"] } -evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false, features = ["std", "tracing"] } -evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false, features = ["std", "tracing"] } +evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false, features = ["std", "tracing"] } +evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false, features = ["std", "tracing"] } +evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false, features = ["std", "tracing"] } rlp = { version = "0.5.0", default-features = false } base64 = "0.13.0" bstr = "0.2" diff --git a/engine-transactions/Cargo.toml b/engine-transactions/Cargo.toml index 8b7351786..f220badf4 100644 --- a/engine-transactions/Cargo.toml +++ b/engine-transactions/Cargo.toml @@ -16,7 +16,7 @@ autobenches = false aurora-engine-types = { path = "../engine-types", default-features = false } aurora-engine-sdk = { path = "../engine-sdk", default-features = false } aurora-engine-precompiles = { path = "../engine-precompiles", default-features = false } -evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false } +evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false } rlp = { version = "0.5.0", default-features = false } serde = { version = "1", features = ["derive"], optional = true } diff --git a/engine/Cargo.toml b/engine/Cargo.toml index f9fa20cd3..a43f80c53 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -24,7 +24,7 @@ base64 = { version = "0.13.0", default-features = false, features = ["alloc"] } borsh = { version = "0.9.3", default-features = false } byte-slice-cast = { version = "1.0", default-features = false } ethabi = { version = "17.1", default-features = false } -evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.36.0-aurora", default-features = false } +evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", rev = "cb9286b87eb868e921a25f9af74dc9a9f720b39e", default-features = false } hex = { version = "0.4", default-features = false, features = ["alloc"] } rjson = { git = "https://github.com/aurora-is-near/rjson", rev = "cc3da949", default-features = false, features = ["integer"] } rlp = { version = "0.5.0", default-features = false } From 7392eb43e5f502c2fbb94dbfa774ae56fb66544f Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Wed, 31 Aug 2022 14:57:53 +0200 Subject: [PATCH 2/5] Include mechanism for registering native contracts and including them dynamically in the precompiles --- engine/src/engine.rs | 15 ++++++++++++++- engine/src/lib.rs | 13 +++++++++++++ engine/src/native_contracts.rs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 engine/src/native_contracts.rs diff --git a/engine/src/engine.rs b/engine/src/engine.rs index f879cf4b6..0ccdad16b 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -362,7 +362,7 @@ impl<'env, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> StackExecutorParams< env: &'env E, ro_promise_handler: H, ) -> Self { - let precompiles = if cfg!(all(feature = "mainnet", not(feature = "integration-test"))) { + let mut precompiles = if cfg!(all(feature = "mainnet", not(feature = "integration-test"))) { let mut tmp = Precompiles::new_london(PrecompileConstructorContext { current_account_id, random_seed, @@ -383,6 +383,19 @@ impl<'env, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> StackExecutorParams< promise_handler: ro_promise_handler, }) }; + // Insert the native contracts + for (contract, kind) in crate::native_contracts::read_native_contracts(&io) { + match kind { + crate::native_contracts::ContractType::Erc20 => { + precompiles.all_precompiles.insert( + contract, + aurora_engine_precompiles::AllPrecompiles::Erc20( + aurora_engine_precompiles::erc20::Erc20, + ), + ); + } + } + } Self { precompiles, diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 96be012ff..7f16dec87 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -27,6 +27,7 @@ pub mod errors; pub mod fungible_token; pub mod json; pub mod log_entry; +pub mod native_contracts; mod prelude; pub mod xcc; @@ -200,6 +201,18 @@ mod contract { /// MUTATIVE METHODS /// + #[no_mangle] + pub extern "C" fn register_native_contract() { + let mut io = Runtime; + let state = engine::get_state(&io).sdk_unwrap(); + require_owner_only(&state, &io.predecessor_account_id()); + + let input: (Address, crate::native_contracts::ContractType) = + io.read_input_borsh().sdk_unwrap(); + aurora_engine_sdk::log!(&crate::prelude::format!("Register native {:?}", input)); + crate::native_contracts::insert_native_contract(&mut io, input.0, input.1); + } + /// Deploy code into the EVM. #[no_mangle] pub extern "C" fn deploy_code() { diff --git a/engine/src/native_contracts.rs b/engine/src/native_contracts.rs new file mode 100644 index 000000000..c07001c00 --- /dev/null +++ b/engine/src/native_contracts.rs @@ -0,0 +1,31 @@ +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_types::{ + storage::{self, KeyPrefix}, + types::Address, + Vec, +}; +use borsh::{BorshDeserialize, BorshSerialize}; + +const KEY_BYTES: &[u8] = b"native_contracts"; + +#[derive(Debug, BorshDeserialize, BorshSerialize)] +pub enum ContractType { + Erc20, +} + +pub fn read_native_contracts(io: &I) -> Vec<(Address, ContractType)> { + let key = storage::bytes_to_key(KeyPrefix::Config, KEY_BYTES); + io.read_storage(&key) + .map(|v| v.to_value().unwrap()) + .unwrap_or_default() +} + +pub fn insert_native_contract(io: &mut I, address: Address, kind: ContractType) { + let key = storage::bytes_to_key(KeyPrefix::Config, KEY_BYTES); + let mut current_value: Vec<(Address, ContractType)> = io + .read_storage(&key) + .map(|v| v.to_value().unwrap()) + .unwrap_or_default(); + current_value.push((address, kind)); + io.write_borsh(&key, ¤t_value); +} From 9afcfb88f954e15d936892b7922b585d1c44063e Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Wed, 31 Aug 2022 15:23:45 +0200 Subject: [PATCH 3/5] Test gas reduction of using native ERC-20 implementation --- engine-tests/src/tests/uniswap.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/engine-tests/src/tests/uniswap.rs b/engine-tests/src/tests/uniswap.rs index 8582de8a9..803062e85 100644 --- a/engine-tests/src/tests/uniswap.rs +++ b/engine-tests/src/tests/uniswap.rs @@ -10,6 +10,7 @@ use crate::test_utils::{ }; use aurora_engine_types::types::Wei; use aurora_engine_types::H160; +use borsh::BorshSerialize; use libsecp256k1::SecretKey; use rand::SeedableRng; @@ -28,7 +29,7 @@ fn test_uniswap_input_multihop() { let mut context = UniswapTestContext::new("uniswap"); // evm_gas = 970k - // near total gas = 122 Tgas + // near total gas = 106 Tgas let tokens = context.create_tokens(10, MINT_AMOUNT.into()); for (token_a, token_b) in tokens.iter().zip(tokens.iter().skip(1)) { @@ -38,7 +39,7 @@ fn test_uniswap_input_multihop() { let (_amount_out, _evm_gas, profile) = context.exact_input(&tokens, INPUT_AMOUNT.into()); - assert_eq!(122, profile.all_gas() / 1_000_000_000_000); + assert_eq!(106, profile.all_gas() / 1_000_000_000_000); } #[test] @@ -391,6 +392,20 @@ impl UniswapTestContext { .unwrap(); assert!(result.status.is_ok(), "Minting ERC-20 tokens failed"); + let (maybe_outcome, maybe_error) = runner.call( + "register_native_contract", + "aurora", + ( + contract.0.address, + aurora_engine::native_contracts::ContractType::Erc20, + ) + .try_to_vec() + .unwrap(), + ); + assert!(maybe_error.is_none()); + let outcome = maybe_outcome.unwrap(); + println!("{:?}", outcome.logs); + contract } } From 1437b5a455c98bfd1851bb3f374f3b86d405e71d Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Wed, 31 Aug 2022 16:26:13 +0200 Subject: [PATCH 4/5] Fix some gas costs --- engine-tests/src/tests/standard_precompiles.rs | 2 +- engine-tests/src/tests/uniswap.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/engine-tests/src/tests/standard_precompiles.rs b/engine-tests/src/tests/standard_precompiles.rs index 94454ba63..9d16dfc3c 100644 --- a/engine-tests/src/tests/standard_precompiles.rs +++ b/engine-tests/src/tests/standard_precompiles.rs @@ -54,7 +54,7 @@ fn profile_identity() { #[test] fn profile_modexp() { let profile = precompile_execution_profile("test_modexp"); - test_utils::assert_gas_bound(profile.all_gas(), 7); + test_utils::assert_gas_bound(profile.all_gas(), 8); } #[test] diff --git a/engine-tests/src/tests/uniswap.rs b/engine-tests/src/tests/uniswap.rs index 803062e85..87242040a 100644 --- a/engine-tests/src/tests/uniswap.rs +++ b/engine-tests/src/tests/uniswap.rs @@ -50,7 +50,7 @@ fn test_uniswap_exact_output() { let (_result, profile) = context.add_equal_liquidity(LIQUIDITY_AMOUNT.into(), &token_a, &token_b); - test_utils::assert_gas_bound(profile.all_gas(), 33); + test_utils::assert_gas_bound(profile.all_gas(), 31); let wasm_fraction = 100 * profile.wasm_gas() / profile.all_gas(); assert!( 40 <= wasm_fraction && wasm_fraction <= 50, @@ -60,7 +60,7 @@ fn test_uniswap_exact_output() { let (_amount_in, profile) = context.exact_output_single(&token_a, &token_b, OUTPUT_AMOUNT.into()); - test_utils::assert_gas_bound(profile.all_gas(), 18); + test_utils::assert_gas_bound(profile.all_gas(), 17); let wasm_fraction = 100 * profile.wasm_gas() / profile.all_gas(); assert!( 45 <= wasm_fraction && wasm_fraction <= 55, From e6784d93797af11164dc759f4d3d29ff64787d82 Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Wed, 31 Aug 2022 16:31:57 +0200 Subject: [PATCH 5/5] Fix another gas cost --- engine-tests/src/tests/uniswap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine-tests/src/tests/uniswap.rs b/engine-tests/src/tests/uniswap.rs index 87242040a..8cf4839d1 100644 --- a/engine-tests/src/tests/uniswap.rs +++ b/engine-tests/src/tests/uniswap.rs @@ -60,7 +60,7 @@ fn test_uniswap_exact_output() { let (_amount_in, profile) = context.exact_output_single(&token_a, &token_b, OUTPUT_AMOUNT.into()); - test_utils::assert_gas_bound(profile.all_gas(), 17); + test_utils::assert_gas_bound(profile.all_gas(), 16); let wasm_fraction = 100 * profile.wasm_gas() / profile.all_gas(); assert!( 45 <= wasm_fraction && wasm_fraction <= 55,