From 6d861c8974b2adf979f06e8c0ae972ba78ca1193 Mon Sep 17 00:00:00 2001 From: Oleksandr Anyshchenko Date: Mon, 25 Sep 2023 15:59:46 +0100 Subject: [PATCH] Release 3.1.0 (#839) - Added the possibility to use native NEAR instead of wNEAR on Aurora by [@karim-en]. ([#750]) - Added hashchain integration by [@birchmd]. ([#831]) - Added functions for setting and getting metadata of ERC-20 contracts deployed with `deploy_erc20_token` transaction by [@aleksuss]. ([#837]) --------- Co-authored-by: Michael Birch Co-authored-by: Evgeny Ukhanov Co-authored-by: Karim --- CHANGES.md | 14 + Cargo.lock | 44 +- Cargo.toml | 12 +- Makefile.toml | 4 +- VERSION | 2 +- engine-hashchain/src/bloom.rs | 5 +- engine-hashchain/src/hashchain.rs | 1 + engine-hashchain/src/lib.rs | 3 + engine-hashchain/src/wrapped_io.rs | 116 +++ engine-precompiles/src/alt_bn256.rs | 12 +- engine-precompiles/src/lib.rs | 15 +- engine-precompiles/src/modexp.rs | 4 +- engine-precompiles/src/native.rs | 200 +++-- engine-precompiles/src/promise_result.rs | 2 +- .../src/json_snapshot/mod.rs | 6 +- .../src/relayer_db/mod.rs | 4 +- engine-standalone-storage/src/sync/mod.rs | 405 ++++----- engine-standalone-storage/src/sync/types.rs | 166 +++- engine-standalone-tracing/src/sputnik.rs | 2 +- engine-tests/Cargo.toml | 1 + .../src/tests/account_id_precompiles.rs | 6 +- engine-tests/src/tests/contract_call.rs | 4 + engine-tests/src/tests/erc20.rs | 71 +- engine-tests/src/tests/erc20_connector.rs | 168 +++- engine-tests/src/tests/hashchain.rs | 111 +++ engine-tests/src/tests/mod.rs | 1 + engine-tests/src/tests/one_inch.rs | 10 +- .../src/tests/prepaid_gas_precompile.rs | 10 +- .../src/tests/promise_results_precompile.rs | 26 +- engine-tests/src/tests/repro.rs | 4 +- engine-tests/src/tests/sanity.rs | 8 +- .../src/tests/standalone/call_tracer.rs | 39 +- .../src/tests/standalone/json_snapshot.rs | 4 +- engine-tests/src/tests/standalone/storage.rs | 1 + engine-tests/src/tests/standalone/sync.rs | 55 +- engine-tests/src/tests/standalone/tracing.rs | 1 + engine-tests/src/tests/transaction.rs | 4 +- engine-tests/src/tests/uniswap.rs | 4 +- engine-tests/src/tests/xcc.rs | 17 +- engine-tests/src/utils/mod.rs | 48 +- engine-tests/src/utils/standalone/mod.rs | 44 +- engine-tests/src/utils/workspace.rs | 31 + engine-types/src/parameters/connector.rs | 30 + engine-types/src/parameters/engine.rs | 7 + engine-types/src/parameters/promise.rs | 16 +- engine-types/src/storage.rs | 2 + engine-types/src/types/mod.rs | 20 +- engine-workspace/src/lib.rs | 2 +- engine/Cargo.toml | 8 +- engine/src/connector.rs | 2 +- engine/src/contract_methods/admin.rs | 404 +++++++++ engine/src/contract_methods/connector.rs | 476 ++++++++++ .../src/contract_methods/evm_transactions.rs | 142 +++ engine/src/contract_methods/mod.rs | 103 +++ engine/src/contract_methods/xcc.rs | 90 ++ engine/src/engine.rs | 154 +++- engine/src/errors.rs | 4 + engine/src/hashchain.rs | 115 +++ engine/src/lib.rs | 847 ++++++------------ engine/src/xcc.rs | 2 +- 60 files changed, 3044 insertions(+), 1065 deletions(-) create mode 100644 engine-hashchain/src/wrapped_io.rs create mode 100644 engine-tests/src/tests/hashchain.rs create mode 100644 engine/src/contract_methods/admin.rs create mode 100644 engine/src/contract_methods/connector.rs create mode 100644 engine/src/contract_methods/evm_transactions.rs create mode 100644 engine/src/contract_methods/mod.rs create mode 100644 engine/src/contract_methods/xcc.rs create mode 100644 engine/src/hashchain.rs diff --git a/CHANGES.md b/CHANGES.md index 6574d7d61..a176f1050 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.0] 2023-09-25 + +- Added the possibility to use native NEAR instead of wNEAR on Aurora by [@karim-en]. ([#750]) + +- Added hashchain integration by [@birchmd]. ([#831]) + +- Added functions for setting and getting metadata of ERC-20 contracts deployed with `deploy_erc20_token` transaction + by [@aleksuss]. ([#837]) + +[#750]: https://github.com/aurora-is-near/aurora-engine/pull/750 +[#831]: https://github.com/aurora-is-near/aurora-engine/pull/831 +[#837]: https://github.com/aurora-is-near/aurora-engine/pull/837 + ## [3.0.0] 2023-08-28 ### Fixes @@ -530,6 +543,7 @@ struct SubmitResult { [@guidovranken]: https://github.com/guidovranken [@hskang9]: https://github.com/hskang9 [@joshuajbouw]: https://github.com/joshuajbouw +[@karim-en]: https://github.com/karim-en [@mandreyel]: https://github.com/mandreyel [@matklad]: https://github.com/matklad [@mfornet]: https://github.com/mfornet diff --git a/Cargo.lock b/Cargo.lock index 9e3a1466f..a7a2c4213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,6 +329,7 @@ dependencies = [ name = "aurora-engine" version = "2.10.1" dependencies = [ + "aurora-engine-hashchain", "aurora-engine-modexp", "aurora-engine-precompiles", "aurora-engine-sdk", @@ -339,6 +340,7 @@ dependencies = [ "digest 0.10.7", "ethabi", "evm", + "function_name", "hex 0.4.3", "rlp", "serde", @@ -415,6 +417,7 @@ version = "1.0.0" dependencies = [ "anyhow", "aurora-engine", + "aurora-engine-hashchain", "aurora-engine-modexp", "aurora-engine-precompiles", "aurora-engine-sdk", @@ -1730,8 +1733,8 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "evm" -version = "0.39.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.38.3-aurora#4e2d27d8ff8e5a50a43ed49d86282decce9d39f9" +version = "0.39.1" +source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.39.1#0334a09d6b6e83ff3a8da992e33f29ba95e0c9fe" dependencies = [ "auto_impl", "environmental", @@ -1750,8 +1753,8 @@ dependencies = [ [[package]] name = "evm-core" -version = "0.39.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.38.3-aurora#4e2d27d8ff8e5a50a43ed49d86282decce9d39f9" +version = "0.39.1" +source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.39.1#0334a09d6b6e83ff3a8da992e33f29ba95e0c9fe" dependencies = [ "parity-scale-codec 3.6.4", "primitive-types 0.12.1", @@ -1761,8 +1764,8 @@ dependencies = [ [[package]] name = "evm-gasometer" -version = "0.39.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.38.3-aurora#4e2d27d8ff8e5a50a43ed49d86282decce9d39f9" +version = "0.39.1" +source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.39.1#0334a09d6b6e83ff3a8da992e33f29ba95e0c9fe" dependencies = [ "environmental", "evm-core", @@ -1772,8 +1775,8 @@ dependencies = [ [[package]] name = "evm-runtime" -version = "0.39.0" -source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.38.3-aurora#4e2d27d8ff8e5a50a43ed49d86282decce9d39f9" +version = "0.39.1" +source = "git+https://github.com/aurora-is-near/sputnikvm.git?tag=v0.39.1#0334a09d6b6e83ff3a8da992e33f29ba95e0c9fe" dependencies = [ "auto_impl", "environmental", @@ -1933,6 +1936,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "function_name" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1ab577a896d09940b5fe12ec5ae71f9d8211fff62c919c03a3750a9901e98a7" +dependencies = [ + "function_name-proc-macro", +] + +[[package]] +name = "function_name-proc-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673464e1e314dd67a0fd9544abc99e8eb28d0c7e3b69b033bcff9b2d00b87333" + [[package]] name = "funty" version = "1.1.0" @@ -2108,11 +2126,11 @@ checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "git2" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044" +checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.3.3", "libc", "libgit2-sys", "log", @@ -2614,9 +2632,9 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libgit2-sys" -version = "0.15.2+1.6.4" +version = "0.16.1+1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa" +checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index ec11bfc07..b315b9b34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ publish = false [workspace.dependencies] aurora-engine = { path = "engine", default-features = false } +aurora-engine-hashchain = { path = "engine-hashchain", default-features = false } aurora-engine-precompiles = { path = "engine-precompiles", default-features = false } aurora-engine-sdk = { path = "engine-sdk", default-features = false } aurora-engine-transactions = { path = "engine-transactions", default-features = false } @@ -31,12 +32,13 @@ byte-slice-cast = { version = "1", default-features = false } criterion = "0.5" digest = "0.10" ethabi = { version = "18", default-features = false } -evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.38.3-aurora", default-features = false } -evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.38.3-aurora", default-features = false, features = ["std"] } -evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.38.3-aurora", default-features = false, features = ["std", "tracing"] } -evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.38.3-aurora", default-features = false, features = ["std", "tracing"] } +evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.39.1", default-features = false } +evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.39.1", default-features = false, features = ["std"] } +evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.39.1", default-features = false, features = ["std", "tracing"] } +evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", tag = "v0.39.1", default-features = false, features = ["std", "tracing"] } fixed-hash = { version = "0.8.0", default-features = false} -git2 = "0.17" +function_name = "0.3.0" +git2 = "0.18" hex = { version = "0.4", default-features = false, features = ["alloc"] } ibig = { version = "0.3", default-features = false, features = ["num-traits"] } impl-serde = { version = "0.4.0", default-features = false} diff --git a/Makefile.toml b/Makefile.toml index cdffeb9e0..a125a1ee6 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -300,7 +300,7 @@ run_task = "build-engine-flow-docker" [tasks.build-docker] category = "Build" script = ''' -docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-de975ed75e0f6a840c7aeb57e3414959cb59bc00-amd64 ./scripts/docker-entrypoint.sh ${PROFILE} +docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-f033430c4fc619aaf8b88eb2fcb976bb8d24d65d-amd64 ./scripts/docker-entrypoint.sh ${PROFILE} ''' [tasks.build-xcc-router-docker-inner] @@ -312,7 +312,7 @@ run_task = "build-xcc-router-flow-docker" [tasks.build-xcc-docker] category = "Build" script = ''' -docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-de975ed75e0f6a840c7aeb57e3414959cb59bc00-amd64 ./scripts/docker-xcc-router-entrypoint.sh ${PROFILE} +docker run --volume $PWD:/host -w /host -i --rm nearprotocol/contract-builder:master-f033430c4fc619aaf8b88eb2fcb976bb8d24d65d-amd64 ./scripts/docker-xcc-router-entrypoint.sh ${PROFILE} ''' [tasks.test-contracts] diff --git a/VERSION b/VERSION index 4a36342fc..fd2a01863 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0 +3.1.0 diff --git a/engine-hashchain/src/bloom.rs b/engine-hashchain/src/bloom.rs index b8bd53203..4db7012fa 100644 --- a/engine-hashchain/src/bloom.rs +++ b/engine-hashchain/src/bloom.rs @@ -2,7 +2,10 @@ //! Link: //! //! Reimplemented here since there is a large mismatch in types and dependencies. -#![allow(clippy::expl_impl_clone_on_copy)] +#![allow( + clippy::expl_impl_clone_on_copy, +// TODO: rust-2023-08-24 clippy::incorrect_clone_impl_on_copy_type +)] use aurora_engine_sdk::keccak; use aurora_engine_types::borsh::{self, BorshDeserialize, BorshSerialize}; diff --git a/engine-hashchain/src/hashchain.rs b/engine-hashchain/src/hashchain.rs index 22942eb93..cdd85cba6 100644 --- a/engine-hashchain/src/hashchain.rs +++ b/engine-hashchain/src/hashchain.rs @@ -3,6 +3,7 @@ use aurora_engine_sdk::keccak; use aurora_engine_types::{ account_id::AccountId, borsh::{self, maybestd::io, BorshDeserialize, BorshSerialize}, + format, types::RawH256, Cow, Vec, }; diff --git a/engine-hashchain/src/lib.rs b/engine-hashchain/src/lib.rs index b7743dde7..39084c4f1 100644 --- a/engine-hashchain/src/lib.rs +++ b/engine-hashchain/src/lib.rs @@ -1,6 +1,9 @@ +#![cfg_attr(not(feature = "std"), no_std)] + pub mod bloom; pub mod error; pub mod hashchain; pub mod merkle; #[cfg(test)] mod tests; +pub mod wrapped_io; diff --git a/engine-hashchain/src/wrapped_io.rs b/engine-hashchain/src/wrapped_io.rs new file mode 100644 index 000000000..3fb75bd9e --- /dev/null +++ b/engine-hashchain/src/wrapped_io.rs @@ -0,0 +1,116 @@ +//! This module contains `CachedIO`, a light wrapper over any IO instance +//! which will cache the input read and output written by the underlying instance. +//! It has no impact on the storage access functions of the trait. +//! The purpose of this struct is to capture the input and output from the underlying +//! IO instance for the purpose of passing it to `Hashchain::add_block_tx`. + +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_types::Vec; +use core::cell::RefCell; + +#[derive(Debug, Clone, Copy)] +pub struct CachedIO<'cache, I> { + inner: I, + cache: &'cache RefCell, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum WrappedInput { + Input(Vec), + Wrapped(T), +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct IOCache { + pub input: Vec, + pub output: Vec, +} + +impl<'cache, I> CachedIO<'cache, I> { + pub fn new(io: I, cache: &'cache RefCell) -> Self { + Self { inner: io, cache } + } +} + +impl IOCache { + pub fn set_input(&mut self, value: Vec) { + self.input = value; + } + + pub fn set_output(&mut self, value: Vec) { + self.output = value; + } +} + +impl StorageIntermediate for WrappedInput { + fn len(&self) -> usize { + match self { + Self::Input(bytes) => bytes.len(), + Self::Wrapped(x) => x.len(), + } + } + + fn is_empty(&self) -> bool { + match self { + Self::Input(bytes) => bytes.is_empty(), + Self::Wrapped(x) => x.is_empty(), + } + } + + fn copy_to_slice(&self, buffer: &mut [u8]) { + match self { + Self::Input(bytes) => buffer.copy_from_slice(bytes), + Self::Wrapped(x) => x.copy_to_slice(buffer), + } + } +} + +impl<'cache, I: IO> IO for CachedIO<'cache, I> { + type StorageValue = WrappedInput; + + fn read_input(&self) -> Self::StorageValue { + let input = self.inner.read_input().to_vec(); + self.cache.borrow_mut().set_input(input.clone()); + WrappedInput::Input(input) + } + + fn return_output(&mut self, value: &[u8]) { + self.cache.borrow_mut().set_output(value.to_vec()); + self.inner.return_output(value); + } + + fn read_storage(&self, key: &[u8]) -> Option { + self.inner.read_storage(key).map(WrappedInput::Wrapped) + } + + fn storage_has_key(&self, key: &[u8]) -> bool { + self.inner.storage_has_key(key) + } + + fn write_storage(&mut self, key: &[u8], value: &[u8]) -> Option { + self.inner + .write_storage(key, value) + .map(WrappedInput::Wrapped) + } + + fn write_storage_direct( + &mut self, + key: &[u8], + value: Self::StorageValue, + ) -> Option { + match value { + WrappedInput::Wrapped(x) => self + .inner + .write_storage_direct(key, x) + .map(WrappedInput::Wrapped), + WrappedInput::Input(bytes) => self + .inner + .write_storage(key, &bytes) + .map(WrappedInput::Wrapped), + } + } + + fn remove_storage(&mut self, key: &[u8]) -> Option { + self.inner.remove_storage(key).map(WrappedInput::Wrapped) + } +} diff --git a/engine-precompiles/src/alt_bn256.rs b/engine-precompiles/src/alt_bn256.rs index 72e36e7d7..fd32e7179 100644 --- a/engine-precompiles/src/alt_bn256.rs +++ b/engine-precompiles/src/alt_bn256.rs @@ -189,8 +189,8 @@ impl Bn256Add { pub const ADDRESS: Address = make_address(0, 6); #[must_use] - pub fn new() -> Self { - Self(PhantomData::default()) + pub const fn new() -> Self { + Self(PhantomData) } } @@ -294,8 +294,8 @@ impl Bn256Mul { pub const ADDRESS: Address = make_address(0, 7); #[must_use] - pub fn new() -> Self { - Self(PhantomData::default()) + pub const fn new() -> Self { + Self(PhantomData) } } @@ -402,8 +402,8 @@ impl Bn256Pair { pub const ADDRESS: Address = make_address(0, 8); #[must_use] - pub fn new() -> Self { - Self(PhantomData::default()) + pub const fn new() -> Self { + Self(PhantomData) } } diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs index 49f475a8a..325d8d5c7 100644 --- a/engine-precompiles/src/lib.rs +++ b/engine-precompiles/src/lib.rs @@ -166,7 +166,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::Preco fn process_precompile( p: &dyn Precompile, - handle: &mut impl PrecompileHandle, + handle: &impl PrecompileHandle, ) -> Result { let input = handle.input(); let gas_limit = handle.gas_limit(); @@ -524,6 +524,19 @@ mod tests { unimplemented!() } + fn record_external_cost( + &mut self, + _ref_time: Option, + _proof_size: Option, + _storage_growth: Option, + ) -> Result<(), ExitError> { + unimplemented!() + } + + fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) { + unimplemented!() + } + fn remaining_gas(&self) -> u64 { unimplemented!() } diff --git a/engine-precompiles/src/modexp.rs b/engine-precompiles/src/modexp.rs index 9765b69b3..299b147fa 100644 --- a/engine-precompiles/src/modexp.rs +++ b/engine-precompiles/src/modexp.rs @@ -14,8 +14,8 @@ impl ModExp { pub const ADDRESS: Address = make_address(0, 5); #[must_use] - pub fn new() -> Self { - Self(PhantomData::default(), PhantomData::default()) + pub const fn new() -> Self { + Self(PhantomData, PhantomData) } } diff --git a/engine-precompiles/src/native.rs b/engine-precompiles/src/native.rs index 272a76cc3..8f08de6a2 100644 --- a/engine-precompiles/src/native.rs +++ b/engine-precompiles/src/native.rs @@ -1,25 +1,33 @@ use super::{EvmPrecompileResult, Precompile}; -use crate::prelude::{ - format, - parameters::{PromiseArgs, PromiseCreateArgs, WithdrawCallArgs}, - sdk::io::{StorageIntermediate, IO}, - storage::{bytes_to_key, KeyPrefix}, - types::{Address, Yocto}, - vec, BorshSerialize, Cow, String, ToString, Vec, U256, -}; #[cfg(feature = "error_refund")] -use crate::prelude::{ - parameters::{PromiseWithCallbackArgs, RefundCallArgs}, - types, +use crate::prelude::{parameters::RefundCallArgs, types}; +use crate::{ + prelude::{ + format, + parameters::{PromiseArgs, PromiseCreateArgs, WithdrawCallArgs}, + sdk::io::{StorageIntermediate, IO}, + storage::{bytes_to_key, KeyPrefix}, + str, + types::{Address, Yocto}, + vec, BorshSerialize, Cow, String, ToString, Vec, U256, + }, + xcc::state::get_wnear_address, }; use crate::prelude::types::EthGas; use crate::PrecompileOutput; -use aurora_engine_types::{account_id::AccountId, types::NEP141Wei}; +use aurora_engine_types::{ + account_id::AccountId, + parameters::{ + ExitToNearPrecompileCallbackCallArgs, PromiseWithCallbackArgs, TransferNearCallArgs, + }, + types::NEP141Wei, +}; use evm::backend::Log; use evm::{Context, ExitError}; const ERR_TARGET_TOKEN_NOT_FOUND: &str = "Target token not found"; +const UNWRAP_WNEAR_MSG: &str = "unwrap"; mod costs { use crate::prelude::types::{EthGas, NearGas}; @@ -35,9 +43,7 @@ mod costs { pub(super) const FT_TRANSFER_GAS: NearGas = NearGas::new(10_000_000_000_000); /// Value determined experimentally based on tests. - /// (No mainnet data available since this feature is not enabled) - #[cfg(feature = "error_refund")] - pub(super) const REFUND_ON_ERROR_GAS: NearGas = NearGas::new(5_000_000_000_000); + pub(super) const EXIT_TO_NEAR_CALLBACK_GAS: NearGas = NearGas::new(10_000_000_000_000); // TODO(#332): Determine the correct amount of gas pub(super) const WITHDRAWAL_GAS: NearGas = NearGas::new(100_000_000_000_000); @@ -242,6 +248,28 @@ fn validate_amount(amount: U256) -> Result<(), ExitError> { Ok(()) } +#[derive(Debug, PartialEq)] +struct Recipient<'a> { + receiver_account_id: AccountId, + message: Option<&'a str>, +} + +fn parse_recipient(recipient: &[u8]) -> Result, ExitError> { + let recipient = str::from_utf8(recipient) + .map_err(|_| ExitError::Other(Cow::from("ERR_INVALID_RECEIVER_ACCOUNT_ID")))?; + let (receiver_account_id, message) = recipient.split_once(':').map_or_else( + || (recipient, None), + |(recipient, msg)| (recipient, Some(msg)), + ); + + Ok(Recipient { + receiver_account_id: receiver_account_id + .parse() + .map_err(|_| ExitError::Other(Cow::from("ERR_INVALID_RECEIVER_ACCOUNT_ID")))?, + message, + }) +} + impl Precompile for ExitToNear { fn required_gas(_input: &[u8]) -> Result { Ok(costs::EXIT_TO_NEAR_GAS) @@ -300,10 +328,8 @@ impl Precompile for ExitToNear { #[cfg(not(feature = "error_refund"))] let mut input = parse_input(input)?; let current_account_id = self.current_account_id.clone(); - #[cfg(feature = "error_refund")] - let refund_on_error_target = current_account_id.clone(); - let (nep141_address, args, exit_event) = match flag { + let (nep141_address, args, exit_event, method, transfer_near_args) = match flag { 0x0 => { // ETH transfer // @@ -326,6 +352,8 @@ impl Precompile for ExitToNear { dest: dest_account.to_string(), amount: context.apparent_value, }, + "ft_transfer", + None, ) } else { return Err(ExitError::Other(Cow::from( @@ -355,29 +383,46 @@ impl Precompile for ExitToNear { input = &input[32..]; validate_amount(amount)?; + let recipient = parse_recipient(input)?; - if let Ok(receiver_account_id) = AccountId::try_from(input) { + let (args, method, transfer_near_args) = if recipient.message + == Some(UNWRAP_WNEAR_MSG) + && erc20_address == get_wnear_address(&self.io).raw() + { + ( + format!(r#"{{"amount": "{}"}}"#, amount.as_u128()), + "near_withdraw", + Some(TransferNearCallArgs { + target_account_id: recipient.receiver_account_id.clone(), + amount: amount.as_u128(), + }), + ) + } else { + // There is no way to inject json, given the encoding of both arguments + // as decimal and valid account id respectively. ( - nep141_address, - // There is no way to inject json, given the encoding of both arguments - // as decimal and valid account id respectively. format!( r#"{{"receiver_id": "{}", "amount": "{}", "memo": null}}"#, - receiver_account_id, + recipient.receiver_account_id, amount.as_u128() ), - events::ExitToNear { - sender: Address::new(erc20_address), - erc20_address: Address::new(erc20_address), - dest: receiver_account_id.to_string(), - amount, - }, + "ft_transfer", + None, ) - } else { - return Err(ExitError::Other(Cow::from( - "ERR_INVALID_RECEIVER_ACCOUNT_ID", - ))); - } + }; + + ( + nep141_address, + args, + events::ExitToNear { + sender: Address::new(erc20_address), + erc20_address: Address::new(erc20_address), + dest: recipient.receiver_account_id.to_string(), + amount, + }, + method, + transfer_near_args, + ) } _ => return Err(ExitError::Other(Cow::from("ERR_INVALID_FLAG"))), }; @@ -394,30 +439,37 @@ impl Precompile for ExitToNear { erc20_address, amount: types::u256_to_arr(&exit_event.amount), }; - #[cfg(feature = "error_refund")] - let refund_promise = PromiseCreateArgs { - target_account_id: refund_on_error_target, - method: "refund_on_error".to_string(), - args: refund_args.try_to_vec().unwrap(), - attached_balance: Yocto::new(0), - attached_gas: costs::REFUND_ON_ERROR_GAS, + + let callback_args = ExitToNearPrecompileCallbackCallArgs { + #[cfg(feature = "error_refund")] + refund: Some(refund_args), + #[cfg(not(feature = "error_refund"))] + refund: None, + transfer_near: transfer_near_args, }; + let transfer_promise = PromiseCreateArgs { target_account_id: nep141_address, - method: "ft_transfer".to_string(), + method: method.to_string(), args: args.as_bytes().to_vec(), attached_balance: Yocto::new(1), attached_gas: costs::FT_TRANSFER_GAS, }; - #[cfg(feature = "error_refund")] - let promise = PromiseArgs::Callback(PromiseWithCallbackArgs { - base: transfer_promise, - callback: refund_promise, - }); - #[cfg(not(feature = "error_refund"))] - let promise = PromiseArgs::Create(transfer_promise); - + let promise = if callback_args == ExitToNearPrecompileCallbackCallArgs::default() { + PromiseArgs::Create(transfer_promise) + } else { + PromiseArgs::Callback(PromiseWithCallbackArgs { + base: transfer_promise, + callback: PromiseCreateArgs { + target_account_id: self.current_account_id.clone(), + method: "exit_to_near_precompile_callback".to_string(), + args: callback_args.try_to_vec().unwrap(), + attached_balance: Yocto::new(0), + attached_gas: costs::EXIT_TO_NEAR_CALLBACK_GAS, + }, + }) + }; let promise_log = Log { address: exit_to_near::ADDRESS.raw(), topics: Vec::new(), @@ -620,8 +672,10 @@ impl Precompile for ExitToEthereum { #[cfg(test)] mod tests { - use super::{exit_to_ethereum, exit_to_near, validate_amount, validate_input_size}; - use crate::prelude::sdk::types::near_account_to_evm_address; + use super::{ + exit_to_ethereum, exit_to_near, parse_recipient, validate_amount, validate_input_size, + }; + use crate::{native::Recipient, prelude::sdk::types::near_account_to_evm_address}; use aurora_engine_types::U256; #[test] @@ -687,4 +741,46 @@ mod tests { fn test_exit_with_valid_amount() { validate_amount(U256::from(u128::MAX)).unwrap(); } + + #[test] + fn test_parse_recipient() { + assert_eq!( + parse_recipient(b"test.near").unwrap(), + Recipient { + receiver_account_id: "test.near".parse().unwrap(), + message: None + } + ); + + assert_eq!( + parse_recipient(b"test.near:unwrap").unwrap(), + Recipient { + receiver_account_id: "test.near".parse().unwrap(), + message: Some("unwrap") + } + ); + + assert_eq!( + parse_recipient(b"test.near:some_msg:with_extra_colon").unwrap(), + Recipient { + receiver_account_id: "test.near".parse().unwrap(), + message: Some("some_msg:with_extra_colon") + } + ); + + assert_eq!( + parse_recipient(b"test.near:").unwrap(), + Recipient { + receiver_account_id: "test.near".parse().unwrap(), + message: Some("") + } + ); + } + + #[test] + fn test_parse_invalid_recipient() { + assert!(parse_recipient(b"test@.near").is_err()); + assert!(parse_recipient(b"test@.near:msg").is_err()); + assert!(parse_recipient(&[0xc2]).is_err()); + } } diff --git a/engine-precompiles/src/promise_result.rs b/engine-precompiles/src/promise_result.rs index e54e15171..4f7811d2f 100644 --- a/engine-precompiles/src/promise_result.rs +++ b/engine-precompiles/src/promise_result.rs @@ -17,7 +17,7 @@ pub mod costs { /// This cost is always charged for calling this precompile. pub const PROMISE_RESULT_BASE_COST: EthGas = EthGas::new(111); /// This is the cost per byte of promise result data. - pub const PROMISE_RESULT_BYTE_COST: EthGas = EthGas::new(1); + pub const PROMISE_RESULT_BYTE_COST: EthGas = EthGas::new(2); } pub struct PromiseResult { diff --git a/engine-standalone-storage/src/json_snapshot/mod.rs b/engine-standalone-storage/src/json_snapshot/mod.rs index 67e9464a0..639c93a50 100644 --- a/engine-standalone-storage/src/json_snapshot/mod.rs +++ b/engine-standalone-storage/src/json_snapshot/mod.rs @@ -5,7 +5,7 @@ pub mod types; /// Write engine state directly into the Storage from a /// JSON snapshot (which can be extracted from a NEAR RPC node). pub fn initialize_engine_state( - storage: &mut Storage, + storage: &Storage, snapshot: types::JsonSnapshot, ) -> Result<(), error::Error> { // The snapshot is giving us a post-state, so we insert it right at the end of its block height. @@ -64,7 +64,7 @@ mod test { "contract.aurora.block51077328.json", ) .unwrap(); - let mut storage = crate::Storage::open("rocks_tmp/").unwrap(); - super::initialize_engine_state(&mut storage, snapshot).unwrap(); + let storage = crate::Storage::open("rocks_tmp/").unwrap(); + super::initialize_engine_state(&storage, snapshot).unwrap(); } } diff --git a/engine-standalone-storage/src/relayer_db/mod.rs b/engine-standalone-storage/src/relayer_db/mod.rs index 5e29a0488..e64dfb030 100644 --- a/engine-standalone-storage/src/relayer_db/mod.rs +++ b/engine-standalone-storage/src/relayer_db/mod.rs @@ -103,7 +103,7 @@ where env.random_seed = block_metadata.random_seed; let args = SubmitArgs { - tx_data: transaction_bytes, + tx_data: transaction_bytes.clone(), ..Default::default() }; let result = storage.with_engine_access(block_height, transaction_position, &[], |io| { @@ -154,6 +154,7 @@ where attached_near: 0, transaction: crate::sync::types::TransactionKind::Submit(tx), promise_data: Vec::new(), + raw_input: transaction_bytes, }; storage.set_transaction_included(tx_hash, &tx_msg, &diff)?; } @@ -265,6 +266,7 @@ mod test { attached_near: 0, transaction: TransactionKind::Unknown, promise_data: Vec::new(), + raw_input: Vec::new(), }, &diff, ) diff --git a/engine-standalone-storage/src/sync/mod.rs b/engine-standalone-storage/src/sync/mod.rs index 1164ab5c2..6ac1502fc 100644 --- a/engine-standalone-storage/src/sync/mod.rs +++ b/engine-standalone-storage/src/sync/mod.rs @@ -1,25 +1,17 @@ use crate::engine_state::EngineStateAccess; -use aurora_engine::parameters::SubmitArgs; -use aurora_engine::pausables::{ - EnginePrecompilesPauser, PausedPrecompilesManager, PrecompileFlags, -}; use aurora_engine::{ - connector, engine, + contract_methods, engine, parameters::{self, SubmitResult}, - state, xcc, }; use aurora_engine_modexp::ModExpAlgorithm; use aurora_engine_sdk::{ - env::{self, Env, DEFAULT_PREPAID_GAS}, + env::{self, DEFAULT_PREPAID_GAS}, io::IO, }; use aurora_engine_transactions::EthTransactionKind; use aurora_engine_types::{ - account_id::AccountId, - borsh::BorshDeserialize, - parameters::PromiseWithCallbackArgs, - types::{Address, Yocto}, - H256, + account_id::AccountId, borsh::BorshDeserialize, parameters::PromiseWithCallbackArgs, + types::Address, H256, }; use std::{io, str::FromStr}; @@ -160,12 +152,12 @@ pub fn parse_transaction_kind( })?; TransactionKind::RegisterRelayer(address) } - TransactionKindTag::RefundOnError => match promise_data.first().and_then(Option::as_ref) { - None => TransactionKind::RefundOnError(None), + TransactionKindTag::ExitToNear => match promise_data.first().and_then(Option::as_ref) { + None => TransactionKind::ExitToNear(None), Some(_) => { - let args = aurora_engine_types::parameters::RefundCallArgs::try_from_slice(&bytes) + let args = aurora_engine_types::parameters::ExitToNearPrecompileCallbackCallArgs::try_from_slice(&bytes) .map_err(f)?; - TransactionKind::RefundOnError(Some(args)) + TransactionKind::ExitToNear(Some(args)) } }, TransactionKindTag::SetConnectorData => { @@ -206,20 +198,31 @@ pub fn parse_transaction_kind( TransactionKindTag::PauseContract => TransactionKind::PauseContract, TransactionKindTag::ResumeContract => TransactionKind::ResumeContract, TransactionKindTag::SetKeyManager => { - let args = aurora_engine::parameters::RelayerKeyManagerArgs::try_from_slice(&bytes) - .map_err(f)?; + let args: parameters::RelayerKeyManagerArgs = serde_json::from_slice(bytes.as_slice()) + .map_err(|e| { + ParseTransactionKindError::failed_deserialization(tx_kind_tag, Some(e)) + })?; TransactionKind::SetKeyManager(args) } TransactionKindTag::AddRelayerKey => { - let args = - aurora_engine::parameters::RelayerKeyArgs::try_from_slice(&bytes).map_err(f)?; + let args = parameters::RelayerKeyArgs::try_from_slice(&bytes).map_err(f)?; TransactionKind::AddRelayerKey(args) } TransactionKindTag::RemoveRelayerKey => { - let args = - aurora_engine::parameters::RelayerKeyArgs::try_from_slice(&bytes).map_err(f)?; + let args = parameters::RelayerKeyArgs::try_from_slice(&bytes).map_err(f)?; TransactionKind::RemoveRelayerKey(args) } + TransactionKindTag::StartHashchain => { + let args = parameters::StartHashchainArgs::try_from_slice(&bytes).map_err(f)?; + TransactionKind::StartHashchain(args) + } + TransactionKindTag::SetErc20Metadata => { + let args: parameters::SetErc20MetadataArgs = + serde_json::from_slice(&bytes).map_err(|e| { + ParseTransactionKindError::failed_deserialization(tx_kind_tag, Some(e)) + })?; + TransactionKind::SetErc20Metadata(args) + } TransactionKindTag::Unknown => { return Err(ParseTransactionKindError::UnknownMethodName { name: method_name.into(), @@ -260,16 +263,21 @@ pub fn consume_message( let engine_account_id = storage.get_engine_account_id()?; let (tx_hash, diff, result) = storage - .with_engine_access(block_height, transaction_position, &[], |io| { - execute_transaction::<_, M, _>( - transaction_message.as_ref(), - block_height, - &block_metadata, - engine_account_id, - io, - EngineStateAccess::get_transaction_diff, - ) - }) + .with_engine_access( + block_height, + transaction_position, + &transaction_message.raw_input, + |io| { + execute_transaction::<_, M, _>( + transaction_message.as_ref(), + block_height, + &block_metadata, + engine_account_id, + io, + EngineStateAccess::get_transaction_diff, + ) + }, + ) .result; let outcome = TransactionIncludedOutcome { hash: tx_hash, @@ -293,16 +301,21 @@ pub fn execute_transaction_message( let block_height = storage.get_block_height_by_hash(block_hash)?; let block_metadata = storage.get_block_metadata(block_hash)?; let engine_account_id = storage.get_engine_account_id()?; - let result = storage.with_engine_access(block_height, transaction_position, &[], |io| { - execute_transaction::<_, M, _>( - &transaction_message, - block_height, - &block_metadata, - engine_account_id, - io, - EngineStateAccess::get_transaction_diff, - ) - }); + let result = storage.with_engine_access( + block_height, + transaction_position, + &transaction_message.raw_input, + |io| { + execute_transaction::<_, M, _>( + &transaction_message, + block_height, + &block_metadata, + engine_account_id, + io, + EngineStateAccess::get_transaction_diff, + ) + }, + ); let (tx_hash, diff, maybe_result) = result.result; let outcome = TransactionIncludedOutcome { hash: tx_hash, @@ -332,8 +345,6 @@ where { let signer_account_id = transaction_message.signer.clone(); let predecessor_account_id = transaction_message.caller.clone(); - let relayer_address = - aurora_engine_sdk::types::near_account_to_evm_address(predecessor_account_id.as_bytes()); let near_receipt_id = transaction_message.near_receipt_id; let current_account_id = engine_account_id; let env = env::Fixed { @@ -356,23 +367,8 @@ where }; let tx_data: Vec = tx.into(); let tx_hash = aurora_engine_sdk::keccak(&tx_data); - let args = SubmitArgs { - tx_data, - ..Default::default() - }; - let result = state::get_state(&io) - .map(|engine_state| { - let submit_result = engine::submit_with_alt_modexp::<_, _, _, M>( - io, - &env, - &args, - engine_state, - env.current_account_id(), - relayer_address, - &mut handler, - ); - Some(TransactionExecutionResult::Submit(submit_result)) - }) + let result = contract_methods::evm_transactions::submit(io, &env, &mut handler) + .map(|submit_result| Some(TransactionExecutionResult::Submit(Ok(submit_result)))) .map_err(Into::into); (tx_hash, result) @@ -382,31 +378,17 @@ where promise_data: &transaction_message.promise_data, }; let tx_hash = aurora_engine_sdk::keccak(&args.tx_data); - let result = state::get_state(&io) - .map(|engine_state| { - let submit_result = engine::submit_with_alt_modexp::<_, _, _, M>( - io, - &env, - args, - engine_state, - env.current_account_id(), - relayer_address, - &mut handler, - ); - Some(TransactionExecutionResult::Submit(submit_result)) - }) - .map_err(Into::into); + let result = + contract_methods::evm_transactions::submit_with_args(io, &env, &mut handler) + .map(|submit_result| { + Some(TransactionExecutionResult::Submit(Ok(submit_result))) + }) + .map_err(Into::into); (tx_hash, result) } other => { - let result = non_submit_execute::( - other, - io, - env, - relayer_address, - &transaction_message.promise_data, - ); + let result = non_submit_execute(other, io, &env, &transaction_message.promise_data); (near_receipt_id, result) } }; @@ -420,290 +402,226 @@ where /// The `submit` transaction kind is special because it is the only one where the transaction hash /// differs from the NEAR receipt hash. #[allow(clippy::too_many_lines)] -fn non_submit_execute( +fn non_submit_execute( transaction: &TransactionKind, - mut io: I, - env: env::Fixed, - relayer_address: Address, + io: I, + env: &env::Fixed, promise_data: &[Option>], ) -> Result, error::Error> { let result = match transaction { - TransactionKind::Call(args) => { + TransactionKind::Call(_) => { // We can ignore promises in the standalone engine (see above) let mut handler = crate::promise::NoScheduler { promise_data }; - let mut engine: engine::Engine<_, _, M> = - engine::Engine::new(relayer_address, env.current_account_id(), io, &env)?; + let result = contract_methods::evm_transactions::call(io, env, &mut handler)?; - let result = engine.call_with_args(args.clone(), &mut handler); - - Some(TransactionExecutionResult::Submit(result)) + Some(TransactionExecutionResult::Submit(Ok(result))) } - TransactionKind::Deploy(input) => { + TransactionKind::Deploy(_) => { // We can ignore promises in the standalone engine (see above) let mut handler = crate::promise::NoScheduler { promise_data }; - let mut engine: engine::Engine<_, _, M> = - engine::Engine::new(relayer_address, env.current_account_id(), io, &env)?; - - let result = engine.deploy_code_with_input(input.clone(), &mut handler); + let result = contract_methods::evm_transactions::deploy_code(io, env, &mut handler)?; - Some(TransactionExecutionResult::Submit(result)) + Some(TransactionExecutionResult::Submit(Ok(result))) } - TransactionKind::DeployErc20(args) => { + TransactionKind::DeployErc20(_) => { // No promises can be created by `deploy_erc20_token` let mut handler = crate::promise::NoScheduler { promise_data }; - let result = engine::deploy_erc20_token(args.clone(), io, &env, &mut handler)?; + let result = contract_methods::connector::deploy_erc20_token(io, env, &mut handler)?; Some(TransactionExecutionResult::DeployErc20(result)) } - TransactionKind::FtOnTransfer(args) => { + TransactionKind::FtOnTransfer(_) => { // No promises can be created by `ft_on_transfer` let mut handler = crate::promise::NoScheduler { promise_data }; - let mut engine: engine::Engine<_, _, M> = - engine::Engine::new(relayer_address, env.current_account_id(), io, &env)?; - - if env.predecessor_account_id == env.current_account_id { - connector::EthConnectorContract::init_instance(io)? - .ft_on_transfer(&engine, args)?; - } else { - engine.receive_erc20_tokens( - &env.predecessor_account_id, - args, - &env.current_account_id, - &mut handler, - ); - } + contract_methods::connector::ft_on_transfer(io, env, &mut handler)?; None } - TransactionKind::FtTransferCall(args) => { - let mut connector = connector::EthConnectorContract::init_instance(io)?; - let promise_args = connector.ft_transfer_call( - env.predecessor_account_id.clone(), - env.current_account_id.clone(), - args.clone(), - env.prepaid_gas, - )?; + TransactionKind::FtTransferCall(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + let promise_args = + contract_methods::connector::ft_transfer_call(io, env, &mut handler)?; Some(TransactionExecutionResult::Promise(promise_args)) } - TransactionKind::ResolveTransfer(args, promise_result) => { - let mut connector = connector::EthConnectorContract::init_instance(io)?; - connector.ft_resolve_transfer(args, promise_result.clone()); + TransactionKind::ResolveTransfer(_, _) => { + let handler = crate::promise::NoScheduler { promise_data }; + contract_methods::connector::ft_resolve_transfer(io, env, &handler)?; None } - TransactionKind::FtTransfer(args) => { - let mut connector = connector::EthConnectorContract::init_instance(io)?; - connector.ft_transfer(&env.predecessor_account_id, args)?; + TransactionKind::FtTransfer(_) => { + contract_methods::connector::ft_transfer(io, env)?; None } - TransactionKind::Withdraw(args) => { - let mut connector = connector::EthConnectorContract::init_instance(io)?; - connector.withdraw_eth_from_near( - &env.current_account_id, - &env.predecessor_account_id, - args, - )?; + TransactionKind::Withdraw(_) => { + contract_methods::connector::withdraw(io, env)?; None } - TransactionKind::Deposit(raw_proof) => { - let connector_contract = connector::EthConnectorContract::init_instance(io)?; - let promise_args = connector_contract.deposit( - raw_proof.clone(), - env.current_account_id(), - env.predecessor_account_id(), - )?; + TransactionKind::Deposit(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + let promise_args = contract_methods::connector::deposit(io, env, &mut handler)?; Some(TransactionExecutionResult::Promise(promise_args)) } - TransactionKind::FinishDeposit(finish_args) => { - let mut connector = connector::EthConnectorContract::init_instance(io)?; - let maybe_promise_args = connector.finish_deposit( - env.predecessor_account_id(), - env.current_account_id(), - finish_args.clone(), - env.prepaid_gas, - )?; + TransactionKind::FinishDeposit(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + let maybe_promise_args = + contract_methods::connector::finish_deposit(io, env, &mut handler)?; maybe_promise_args.map(TransactionExecutionResult::Promise) } - TransactionKind::StorageDeposit(args) => { - let mut connector = connector::EthConnectorContract::init_instance(io)?; - let _promise = connector.storage_deposit( - env.predecessor_account_id, - Yocto::new(env.attached_deposit), - args.clone(), - )?; + TransactionKind::StorageDeposit(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + contract_methods::connector::storage_deposit(io, env, &mut handler)?; None } - TransactionKind::StorageUnregister(force) => { - let mut connector = connector::EthConnectorContract::init_instance(io)?; - let _promise = connector.storage_unregister(env.predecessor_account_id, *force)?; + TransactionKind::StorageUnregister(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + contract_methods::connector::storage_unregister(io, env, &mut handler)?; None } - TransactionKind::StorageWithdraw(args) => { - let mut connector = connector::EthConnectorContract::init_instance(io)?; - connector.storage_withdraw(&env.predecessor_account_id, args)?; + TransactionKind::StorageWithdraw(_) => { + contract_methods::connector::storage_withdraw(io, env)?; None } - TransactionKind::SetPausedFlags(args) => { - let mut connector = connector::EthConnectorContract::init_instance(io)?; - connector.set_paused_flags(args); + TransactionKind::SetPausedFlags(_) => { + contract_methods::connector::set_paused_flags(io, env)?; None } - TransactionKind::RegisterRelayer(evm_address) => { - let mut engine: engine::Engine<_, _, M> = - engine::Engine::new(relayer_address, env.current_account_id(), io, &env)?; - engine.register_relayer(env.predecessor_account_id.as_bytes(), *evm_address); + TransactionKind::RegisterRelayer(_) => { + contract_methods::admin::register_relayer(io, env)?; None } - TransactionKind::RefundOnError(maybe_args) => { - let result: Result, state::EngineStateError> = - maybe_args - .clone() - .map(|args| { - let mut handler = crate::promise::NoScheduler { promise_data }; - let engine_state = state::get_state(&io)?; - let result = - engine::refund_on_error(io, &env, engine_state, &args, &mut handler); - Ok(TransactionExecutionResult::Submit(result)) - }) - .transpose(); + TransactionKind::ExitToNear(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + let maybe_result = contract_methods::connector::exit_to_near_precompile_callback( + io, + env, + &mut handler, + )?; - result? + maybe_result.map(|submit_result| TransactionExecutionResult::Submit(Ok(submit_result))) } - TransactionKind::SetConnectorData(args) => { - let mut connector_io = io; - connector::set_contract_data(&mut connector_io, args.clone())?; + TransactionKind::SetConnectorData(_) => { + contract_methods::connector::set_eth_connector_contract_data(io, env)?; None } - TransactionKind::NewConnector(args) => { - connector::EthConnectorContract::create_contract( - io, - &env.current_account_id, - args.clone(), - )?; + TransactionKind::NewConnector(_) => { + contract_methods::connector::new_eth_connector(io, env)?; None } - TransactionKind::NewEngine(args) => { - state::set_state(&mut io, &args.clone().into())?; + TransactionKind::NewEngine(_) => { + contract_methods::admin::new(io, env)?; None } - TransactionKind::FactoryUpdate(bytecode) => { - let router_bytecode = xcc::RouterCode::borrowed(bytecode); - xcc::update_router_code(&mut io, &router_bytecode); + TransactionKind::FactoryUpdate(_) => { + contract_methods::xcc::factory_update(io, env)?; None } - TransactionKind::FactoryUpdateAddressVersion(args) => { - xcc::set_code_version_of_address(&mut io, &args.address, args.version); + TransactionKind::FactoryUpdateAddressVersion(_) => { + let handler = crate::promise::NoScheduler { promise_data }; + contract_methods::xcc::factory_update_address_version(io, env, &handler)?; None } - TransactionKind::FactorySetWNearAddress(address) => { - xcc::set_wnear_address(&mut io, address); + TransactionKind::FactorySetWNearAddress(_) => { + contract_methods::xcc::factory_set_wnear_address(io, env)?; None } - TransactionKind::FundXccSubAccound(args) => { + TransactionKind::FundXccSubAccound(_) => { let mut handler = crate::promise::NoScheduler { promise_data }; - xcc::fund_xcc_sub_account(&io, &mut handler, &env, args.clone())?; + contract_methods::xcc::fund_xcc_sub_account(io, env, &mut handler)?; None } TransactionKind::Unknown => None, // Not handled in this function; is handled by the general `execute_transaction` function TransactionKind::Submit(_) | TransactionKind::SubmitWithArgs(_) => unreachable!(), - TransactionKind::PausePrecompiles(args) => { - let precompiles_to_pause = PrecompileFlags::from_bits_truncate(args.paused_mask); - - let mut pauser = EnginePrecompilesPauser::from_io(io); - pauser.pause_precompiles(precompiles_to_pause); + TransactionKind::PausePrecompiles(_) => { + contract_methods::admin::pause_precompiles(io, env)?; None } - TransactionKind::ResumePrecompiles(args) => { - let precompiles_to_resume = PrecompileFlags::from_bits_truncate(args.paused_mask); - - let mut pauser = EnginePrecompilesPauser::from_io(io); - pauser.resume_precompiles(precompiles_to_resume); + TransactionKind::ResumePrecompiles(_) => { + contract_methods::admin::resume_precompiles(io, env)?; None } - TransactionKind::SetOwner(args) => { - let mut prev = state::get_state(&io)?; - - prev.owner_id = args.clone().new_owner; - state::set_state(&mut io, &prev)?; + TransactionKind::SetOwner(_) => { + contract_methods::admin::set_owner(io, env)?; None } - TransactionKind::SetUpgradeDelayBlocks(args) => { - let mut prev = state::get_state(&io)?; - - prev.upgrade_delay_blocks = args.upgrade_delay_blocks; - state::set_state(&mut io, &prev)?; + TransactionKind::SetUpgradeDelayBlocks(_) => { + contract_methods::admin::set_upgrade_delay_blocks(io, env)?; None } TransactionKind::PauseContract => { - let mut prev = state::get_state(&io)?; - - prev.is_paused = true; - state::set_state(&mut io, &prev)?; + contract_methods::admin::pause_contract(io, env)?; None } TransactionKind::ResumeContract => { - let mut prev = state::get_state(&io)?; + contract_methods::admin::resume_contract(io, env)?; - prev.is_paused = false; - state::set_state(&mut io, &prev)?; + None + } + TransactionKind::SetKeyManager(_) => { + contract_methods::admin::set_key_manager(io, env)?; None } - TransactionKind::SetKeyManager(args) => { - let mut prev = state::get_state(&io)?; + TransactionKind::AddRelayerKey(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + contract_methods::admin::add_relayer_key(io, env, &mut handler)?; - prev.key_manager = args.key_manager.clone(); - state::set_state(&mut io, &prev)?; + None + } + TransactionKind::RemoveRelayerKey(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + contract_methods::admin::remove_relayer_key(io, env, &mut handler)?; None } - TransactionKind::AddRelayerKey(args) => { - engine::add_function_call_key(&mut io, &args.public_key); + TransactionKind::StartHashchain(_) => { + contract_methods::admin::start_hashchain(io, env)?; None } - TransactionKind::RemoveRelayerKey(args) => { - engine::remove_function_call_key(&mut io, &args.public_key)?; + TransactionKind::SetErc20Metadata(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + contract_methods::connector::set_erc20_metadata(io, env, &mut handler)?; None } @@ -754,7 +672,7 @@ pub enum TransactionExecutionResult { } pub mod error { - use aurora_engine::{connector, engine, fungible_token, state, xcc}; + use aurora_engine::{connector, contract_methods, engine, fungible_token, state, xcc}; #[derive(Debug)] pub enum Error { @@ -771,6 +689,7 @@ pub mod error { ConnectorInit(connector::error::InitContractError), ConnectorStorage(connector::error::StorageReadError), FundXccError(xcc::FundXccError), + ContractError(contract_methods::ContractError), } impl From for Error { @@ -850,4 +769,10 @@ pub mod error { Self::FundXccError(e) } } + + impl From for Error { + fn from(e: contract_methods::ContractError) -> Self { + Self::ContractError(e) + } + } } diff --git a/engine-standalone-storage/src/sync/types.rs b/engine-standalone-storage/src/sync/types.rs index cab2209a8..54b876aad 100644 --- a/engine-standalone-storage/src/sync/types.rs +++ b/engine-standalone-storage/src/sync/types.rs @@ -49,6 +49,8 @@ pub struct TransactionMessage { /// Results from previous NEAR receipts /// (only present when this transaction is a callback of another transaction). pub promise_data: Vec>>, + /// Raw bytes passed as input when executed in the Near Runtime. + pub raw_input: Vec, } impl TransactionMessage { @@ -118,8 +120,8 @@ pub enum TransactionKind { SetPausedFlags(parameters::PauseEthConnectorCallArgs), /// Ad entry mapping from address to relayer NEAR account RegisterRelayer(Address), - /// Called if exist precompiles fail - RefundOnError(Option), + /// Callback called by ExitToNear precompile, also can refund on fail + ExitToNear(Option), /// Update eth-connector config SetConnectorData(parameters::SetContractDataCallArgs), /// Initialize eth-connector @@ -142,6 +144,9 @@ pub enum TransactionKind { AddRelayerKey(parameters::RelayerKeyArgs), /// Remove the relayer public function call access key RemoveRelayerKey(parameters::RelayerKeyArgs), + StartHashchain(parameters::StartHashchainArgs), + /// Set metadata of ERC-20 contract. + SetErc20Metadata(parameters::SetErc20MetadataArgs), /// Sentinel kind for cases where a NEAR receipt caused a /// change in Aurora state, but we failed to parse the Action. Unknown, @@ -281,12 +286,15 @@ impl TransactionKind { } } } - Self::RefundOnError(maybe_args) => { + Self::ExitToNear(maybe_args) => { + let method_name = "exit_to_near_precompile_callback"; maybe_args.map_or_else( - || Self::no_evm_execution("refund_on_error"), + || Self::no_evm_execution(method_name), |args| { - args.erc20_address.map_or_else( - || { + args.refund.map_or_else( + || Self::no_evm_execution(method_name), + |args| { + args.erc20_address.map_or_else(|| { // ETH refund let value = Wei::new(U256::from_big_endian(&args.amount)); let from = aurora_engine_precompiles::native::exit_to_near::ADDRESS; @@ -337,6 +345,8 @@ impl TransactionKind { } }, ) + }, + ) }, ) } @@ -370,6 +380,8 @@ impl TransactionKind { Self::SetKeyManager(_) => Self::no_evm_execution("set_key_manager"), Self::AddRelayerKey(_) => Self::no_evm_execution("add_relayer_key"), Self::RemoveRelayerKey(_) => Self::no_evm_execution("remove_relayer_key"), + Self::StartHashchain(_) => Self::no_evm_execution("start_hashchain"), + Self::SetErc20Metadata(_) => Self::no_evm_execution("set_erc20_metadata"), } } @@ -446,8 +458,8 @@ pub enum TransactionKindTag { SetPausedFlags, #[strum(serialize = "register_relayer")] RegisterRelayer, - #[strum(serialize = "refund_on_error")] - RefundOnError, + #[strum(serialize = "exit_to_near_precompile_callback")] + ExitToNear, #[strum(serialize = "set_eth_connector_contract_data")] SetConnectorData, #[strum(serialize = "new_eth_connector")] @@ -478,9 +490,63 @@ pub enum TransactionKindTag { AddRelayerKey, #[strum(serialize = "remove_relayer_key")] RemoveRelayerKey, + #[strum(serialize = "start_hashchain")] + StartHashchain, + #[strum(serialize = "set_erc20_metadata")] + SetErc20Metadata, Unknown, } +impl TransactionKind { + #[must_use] + pub fn raw_bytes(&self) -> Vec { + match self { + Self::Submit(tx) => tx.into(), + Self::SubmitWithArgs(args) => args.try_to_vec().unwrap_or_default(), + Self::Call(args) => args.try_to_vec().unwrap_or_default(), + Self::PausePrecompiles(args) | Self::ResumePrecompiles(args) => { + args.try_to_vec().unwrap_or_default() + } + Self::Deploy(bytes) | Self::Deposit(bytes) | Self::FactoryUpdate(bytes) => { + bytes.clone() + } + Self::DeployErc20(args) => args.try_to_vec().unwrap_or_default(), + Self::FtOnTransfer(args) => serde_json::to_vec(args).unwrap_or_default(), + Self::FtTransferCall(args) => serde_json::to_vec(args).unwrap_or_default(), + Self::FinishDeposit(args) => args.try_to_vec().unwrap_or_default(), + Self::ResolveTransfer(args, _) => args.try_to_vec().unwrap_or_default(), + Self::FtTransfer(args) => serde_json::to_vec(args).unwrap_or_default(), + Self::Withdraw(args) => args.try_to_vec().unwrap_or_default(), + Self::StorageDeposit(args) => serde_json::to_vec(args).unwrap_or_default(), + Self::StorageUnregister(args) => serde_json::to_vec(args).unwrap_or_default(), + Self::StorageWithdraw(args) => serde_json::to_vec(args).unwrap_or_default(), + Self::SetOwner(args) => args.try_to_vec().unwrap_or_default(), + Self::SetUpgradeDelayBlocks(args) => args.try_to_vec().unwrap_or_default(), + Self::SetPausedFlags(args) => args.try_to_vec().unwrap_or_default(), + Self::RegisterRelayer(address) | Self::FactorySetWNearAddress(address) => { + address.as_bytes().to_vec() + } + Self::ExitToNear(maybe_args) => maybe_args + .as_ref() + .and_then(|args| args.try_to_vec().ok()) + .unwrap_or_default(), + Self::NewConnector(args) | Self::SetConnectorData(args) => { + args.try_to_vec().unwrap_or_default() + } + Self::NewEngine(args) => args.try_to_vec().unwrap_or_default(), + Self::FactoryUpdateAddressVersion(args) => args.try_to_vec().unwrap_or_default(), + Self::FundXccSubAccound(args) => args.try_to_vec().unwrap_or_default(), + Self::PauseContract | Self::ResumeContract | Self::Unknown => Vec::new(), + Self::SetKeyManager(args) => args.try_to_vec().unwrap_or_default(), + Self::AddRelayerKey(args) | Self::RemoveRelayerKey(args) => { + args.try_to_vec().unwrap_or_default() + } + Self::StartHashchain(args) => args.try_to_vec().unwrap_or_default(), + Self::SetErc20Metadata(args) => serde_json::to_vec(args).unwrap_or_default(), + } + } +} + /// Used to make sure `TransactionKindTag` is kept in sync with `TransactionKind` impl From<&TransactionKind> for TransactionKindTag { fn from(tx: &TransactionKind) -> Self { @@ -503,7 +569,7 @@ impl From<&TransactionKind> for TransactionKindTag { TransactionKind::StorageWithdraw(_) => Self::StorageWithdraw, TransactionKind::SetPausedFlags(_) => Self::SetPausedFlags, TransactionKind::RegisterRelayer(_) => Self::RegisterRelayer, - TransactionKind::RefundOnError(_) => Self::RefundOnError, + TransactionKind::ExitToNear(_) => Self::ExitToNear, TransactionKind::SetConnectorData(_) => Self::SetConnectorData, TransactionKind::NewConnector(_) => Self::NewConnector, TransactionKind::NewEngine(_) => Self::NewEngine, @@ -519,6 +585,8 @@ impl From<&TransactionKind> for TransactionKindTag { TransactionKind::SetKeyManager(_) => Self::SetKeyManager, TransactionKind::AddRelayerKey(_) => Self::AddRelayerKey, TransactionKind::RemoveRelayerKey(_) => Self::RemoveRelayerKey, + TransactionKind::StartHashchain(_) => Self::StartHashchain, + TransactionKind::SetErc20Metadata(_) => Self::SetErc20Metadata, TransactionKind::Unknown => Self::Unknown, } } @@ -543,6 +611,7 @@ impl From<&TransactionKind> for TransactionKindTag { enum BorshableTransactionMessage<'a> { V1(BorshableTransactionMessageV1<'a>), V2(BorshableTransactionMessageV2<'a>), + V3(BorshableTransactionMessageV3<'a>), } #[derive(BorshDeserialize, BorshSerialize)] @@ -570,9 +639,23 @@ struct BorshableTransactionMessageV2<'a> { pub promise_data: Cow<'a, Vec>>>, } +#[derive(BorshDeserialize, BorshSerialize)] +struct BorshableTransactionMessageV3<'a> { + pub block_hash: [u8; 32], + pub near_receipt_id: [u8; 32], + pub position: u16, + pub succeeded: bool, + pub signer: Cow<'a, AccountId>, + pub caller: Cow<'a, AccountId>, + pub attached_near: u128, + pub transaction: BorshableTransactionKind<'a>, + pub promise_data: Cow<'a, Vec>>>, + pub raw_input: Cow<'a, Vec>, +} + impl<'a> From<&'a TransactionMessage> for BorshableTransactionMessage<'a> { fn from(t: &'a TransactionMessage) -> Self { - Self::V2(BorshableTransactionMessageV2 { + Self::V3(BorshableTransactionMessageV3 { block_hash: t.block_hash.0, near_receipt_id: t.near_receipt_id.0, position: t.position, @@ -582,6 +665,7 @@ impl<'a> From<&'a TransactionMessage> for BorshableTransactionMessage<'a> { attached_near: t.attached_near, transaction: (&t.transaction).into(), promise_data: Cow::Borrowed(&t.promise_data), + raw_input: Cow::Borrowed(&t.raw_input), }) } } @@ -591,18 +675,39 @@ impl<'a> TryFrom> for TransactionMessage { fn try_from(t: BorshableTransactionMessage<'a>) -> Result { match t { - BorshableTransactionMessage::V1(t) => Ok(Self { - block_hash: H256(t.block_hash), - near_receipt_id: H256(t.near_receipt_id), - position: t.position, - succeeded: t.succeeded, - signer: t.signer.into_owned(), - caller: t.caller.into_owned(), - attached_near: t.attached_near, - transaction: t.transaction.try_into()?, - promise_data: Vec::new(), - }), - BorshableTransactionMessage::V2(t) => Ok(Self { + BorshableTransactionMessage::V1(t) => { + let transaction: TransactionKind = t.transaction.try_into()?; + let raw_input = transaction.raw_bytes(); + Ok(Self { + block_hash: H256(t.block_hash), + near_receipt_id: H256(t.near_receipt_id), + position: t.position, + succeeded: t.succeeded, + signer: t.signer.into_owned(), + caller: t.caller.into_owned(), + attached_near: t.attached_near, + transaction, + promise_data: Vec::new(), + raw_input, + }) + } + BorshableTransactionMessage::V2(t) => { + let transaction: TransactionKind = t.transaction.try_into()?; + let raw_input = transaction.raw_bytes(); + Ok(Self { + block_hash: H256(t.block_hash), + near_receipt_id: H256(t.near_receipt_id), + position: t.position, + succeeded: t.succeeded, + signer: t.signer.into_owned(), + caller: t.caller.into_owned(), + attached_near: t.attached_near, + transaction, + promise_data: t.promise_data.into_owned(), + raw_input, + }) + } + BorshableTransactionMessage::V3(t) => Ok(Self { block_hash: H256(t.block_hash), near_receipt_id: H256(t.near_receipt_id), position: t.position, @@ -612,6 +717,7 @@ impl<'a> TryFrom> for TransactionMessage { attached_near: t.attached_near, transaction: t.transaction.try_into()?, promise_data: t.promise_data.into_owned(), + raw_input: t.raw_input.into_owned(), }), } } @@ -641,7 +747,9 @@ enum BorshableTransactionKind<'a> { StorageWithdraw(Cow<'a, parameters::StorageWithdrawCallArgs>), SetPausedFlags(Cow<'a, parameters::PauseEthConnectorCallArgs>), RegisterRelayer(Cow<'a, Address>), - RefundOnError(Cow<'a, Option>), + ExitToNear( + Cow<'a, Option>, + ), SetConnectorData(Cow<'a, parameters::SetContractDataCallArgs>), NewConnector(Cow<'a, parameters::InitCallArgs>), NewEngine(Cow<'a, parameters::NewCallArgs>), @@ -660,6 +768,8 @@ enum BorshableTransactionKind<'a> { SetKeyManager(Cow<'a, parameters::RelayerKeyManagerArgs>), AddRelayerKey(Cow<'a, parameters::RelayerKeyArgs>), RemoveRelayerKey(Cow<'a, parameters::RelayerKeyArgs>), + StartHashchain(Cow<'a, parameters::StartHashchainArgs>), + SetErc20Metadata(Cow<'a, parameters::SetErc20MetadataArgs>), } impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { @@ -687,7 +797,7 @@ impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { TransactionKind::StorageWithdraw(x) => Self::StorageWithdraw(Cow::Borrowed(x)), TransactionKind::SetPausedFlags(x) => Self::SetPausedFlags(Cow::Borrowed(x)), TransactionKind::RegisterRelayer(x) => Self::RegisterRelayer(Cow::Borrowed(x)), - TransactionKind::RefundOnError(x) => Self::RefundOnError(Cow::Borrowed(x)), + TransactionKind::ExitToNear(x) => Self::ExitToNear(Cow::Borrowed(x)), TransactionKind::SetConnectorData(x) => Self::SetConnectorData(Cow::Borrowed(x)), TransactionKind::NewConnector(x) => Self::NewConnector(Cow::Borrowed(x)), TransactionKind::NewEngine(x) => Self::NewEngine(Cow::Borrowed(x)), @@ -711,6 +821,8 @@ impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { TransactionKind::SetKeyManager(x) => Self::SetKeyManager(Cow::Borrowed(x)), TransactionKind::AddRelayerKey(x) => Self::AddRelayerKey(Cow::Borrowed(x)), TransactionKind::RemoveRelayerKey(x) => Self::RemoveRelayerKey(Cow::Borrowed(x)), + TransactionKind::StartHashchain(x) => Self::StartHashchain(Cow::Borrowed(x)), + TransactionKind::SetErc20Metadata(x) => Self::SetErc20Metadata(Cow::Borrowed(x)), } } } @@ -749,7 +861,7 @@ impl<'a> TryFrom> for TransactionKind { BorshableTransactionKind::RegisterRelayer(x) => { Ok(Self::RegisterRelayer(x.into_owned())) } - BorshableTransactionKind::RefundOnError(x) => Ok(Self::RefundOnError(x.into_owned())), + BorshableTransactionKind::ExitToNear(x) => Ok(Self::ExitToNear(x.into_owned())), BorshableTransactionKind::SetConnectorData(x) => { Ok(Self::SetConnectorData(x.into_owned())) } @@ -783,6 +895,10 @@ impl<'a> TryFrom> for TransactionKind { BorshableTransactionKind::RemoveRelayerKey(x) => { Ok(Self::RemoveRelayerKey(x.into_owned())) } + BorshableTransactionKind::StartHashchain(x) => Ok(Self::StartHashchain(x.into_owned())), + BorshableTransactionKind::SetErc20Metadata(x) => { + Ok(Self::SetErc20Metadata(x.into_owned())) + } } } } diff --git a/engine-standalone-tracing/src/sputnik.rs b/engine-standalone-tracing/src/sputnik.rs index 0a3f02fec..9913e4d7c 100644 --- a/engine-standalone-tracing/src/sputnik.rs +++ b/engine-standalone-tracing/src/sputnik.rs @@ -132,7 +132,7 @@ impl evm_runtime::tracing::EventListener for TransactionTraceBuilder { return_value, } => { match result { - Ok(_) => { + Ok(()) => { // Step completed, push current log into the record self.logs.push(self.current.clone()); } diff --git a/engine-tests/Cargo.toml b/engine-tests/Cargo.toml index bc6c53197..e69eb1624 100644 --- a/engine-tests/Cargo.toml +++ b/engine-tests/Cargo.toml @@ -12,6 +12,7 @@ autobenches = false [dev-dependencies] aurora-engine = { workspace = true, features = ["std", "tracing", "impl-serde"] } +aurora-engine-hashchain = { workspace = true, features = ["std"] } aurora-engine-modexp = { workspace = true, features = ["std"] } aurora-engine-precompiles = { workspace = true, features = ["std"] } aurora-engine-sdk = { workspace = true, features = ["std"] } diff --git a/engine-tests/src/tests/account_id_precompiles.rs b/engine-tests/src/tests/account_id_precompiles.rs index 6f21a86cd..ad99590f4 100644 --- a/engine-tests/src/tests/account_id_precompiles.rs +++ b/engine-tests/src/tests/account_id_precompiles.rs @@ -1,14 +1,10 @@ -use crate::utils::{self, standalone}; +use crate::utils; use aurora_engine::parameters::SubmitResult; #[test] fn test_account_id_precompiles() { let mut signer = utils::Signer::random(); let mut runner = utils::deploy_runner(); - let mut standalone = standalone::StandaloneRunner::default(); - - standalone.init_evm(); - runner.standalone_runner = Some(standalone); let constructor = utils::solidity::ContractConstructor::compile_from_source( "src/tests/res", diff --git a/engine-tests/src/tests/contract_call.rs b/engine-tests/src/tests/contract_call.rs index f8856cb79..66de7eda9 100644 --- a/engine-tests/src/tests/contract_call.rs +++ b/engine-tests/src/tests/contract_call.rs @@ -6,6 +6,10 @@ use crate::utils::{self, AuroraRunner, Signer, DEFAULT_AURORA_ACCOUNT_ID}; fn setup_test() -> (AuroraRunner, Signer, Address, Tester) { let mut runner = AuroraRunner::new(); + let wnear_token_address = runner.deploy_erc20_token("wrap.testnet"); + runner + .factory_set_wnear_address(wnear_token_address) + .unwrap(); let token = runner.deploy_erc20_token("tt.testnet"); let mut signer = Signer::random(); runner.create_address( diff --git a/engine-tests/src/tests/erc20.rs b/engine-tests/src/tests/erc20.rs index bcb373d23..7f711a30a 100644 --- a/engine-tests/src/tests/erc20.rs +++ b/engine-tests/src/tests/erc20.rs @@ -8,6 +8,7 @@ use crate::utils::{ use aurora_engine::engine::EngineErrorKind; use aurora_engine::parameters::TransactionStatus; use aurora_engine_sdk as sdk; +use aurora_engine_types::parameters::connector::{Erc20Metadata, SetErc20MetadataArgs}; use bstr::ByteSlice; use libsecp256k1::SecretKey; @@ -22,7 +23,7 @@ fn erc20_mint() { // Validate pre-state assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) + get_address_erc20_balance(&runner, &source_account, dest_address, &contract) ); // Do mint transaction @@ -35,7 +36,7 @@ fn erc20_mint() { // Validate post-state assert_eq!( U256::from(mint_amount), - get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) + get_address_erc20_balance(&runner, &source_account, dest_address, &contract) ); } @@ -49,7 +50,7 @@ fn erc20_mint_out_of_gas() { // Validate pre-state assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) + get_address_erc20_balance(&runner, &source_account, dest_address, &contract) ); // Try mint transaction @@ -131,11 +132,11 @@ fn erc20_transfer_success() { // Validate pre-state assert_eq!( U256::from(INITIAL_BALANCE), - get_address_erc20_balance(&mut runner, &source_account, source_address, &contract) + get_address_erc20_balance(&runner, &source_account, source_address, &contract) ); assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) + get_address_erc20_balance(&runner, &source_account, dest_address, &contract) ); // Do transfer @@ -149,11 +150,11 @@ fn erc20_transfer_success() { // Validate post-state assert_eq!( U256::from(INITIAL_BALANCE - TRANSFER_AMOUNT), - get_address_erc20_balance(&mut runner, &source_account, source_address, &contract) + get_address_erc20_balance(&runner, &source_account, source_address, &contract) ); assert_eq!( U256::from(TRANSFER_AMOUNT), - get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) + get_address_erc20_balance(&runner, &source_account, dest_address, &contract) ); } @@ -170,11 +171,11 @@ fn erc20_transfer_insufficient_balance() { // Validate pre-state assert_eq!( U256::from(INITIAL_BALANCE), - get_address_erc20_balance(&mut runner, &source_account, source_address, &contract) + get_address_erc20_balance(&runner, &source_account, source_address, &contract) ); assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) + get_address_erc20_balance(&runner, &source_account, dest_address, &contract) ); // Do transfer @@ -189,11 +190,11 @@ fn erc20_transfer_insufficient_balance() { // Validate post-state assert_eq!( U256::from(INITIAL_BALANCE), - get_address_erc20_balance(&mut runner, &source_account, source_address, &contract) + get_address_erc20_balance(&runner, &source_account, source_address, &contract) ); assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) + get_address_erc20_balance(&runner, &source_account, dest_address, &contract) ); } @@ -237,8 +238,54 @@ fn deploy_erc_20_out_of_gas() { ); } +#[test] +fn test_erc20_get_and_set_metadata() { + let mut runner = utils::deploy_runner(); + let erc20_address = runner.deploy_erc20_token("token"); + let caller = runner.aurora_account_id.clone(); + let result = runner.one_shot().call( + "get_erc20_metadata", + &caller, + erc20_address.as_bytes().to_vec(), + ); + + assert!(result.is_ok()); + + let metadata: Erc20Metadata = + serde_json::from_slice(&result.unwrap().return_data.as_value().unwrap()).unwrap(); + assert_eq!(metadata, Erc20Metadata::default()); + + let new_metadata = Erc20Metadata { + name: "USD Token".to_string(), + symbol: "USDT".to_string(), + decimals: 20, + }; + + let result = runner.call( + "set_erc20_metadata", + &caller, + serde_json::to_vec(&SetErc20MetadataArgs { + erc20_address, + erc20_metadata: new_metadata.clone(), + }) + .unwrap(), + ); + assert!(result.is_ok()); + + let result = runner.one_shot().call( + "get_erc20_metadata", + &caller, + erc20_address.as_bytes().to_vec(), + ); + assert!(result.is_ok()); + + let metadata: Erc20Metadata = + serde_json::from_slice(&result.unwrap().return_data.as_value().unwrap()).unwrap(); + assert_eq!(metadata, new_metadata); +} + fn get_address_erc20_balance( - runner: &mut utils::AuroraRunner, + runner: &utils::AuroraRunner, signer: &Signer, address: Address, contract: &ERC20, diff --git a/engine-tests/src/tests/erc20_connector.rs b/engine-tests/src/tests/erc20_connector.rs index 8578ece1b..c85e2a86b 100644 --- a/engine-tests/src/tests/erc20_connector.rs +++ b/engine-tests/src/tests/erc20_connector.rs @@ -208,6 +208,17 @@ impl AuroraRunner { relayer_address.try_to_vec().unwrap(), ) } + + pub fn factory_set_wnear_address( + &mut self, + wnear_address: Address, + ) -> Result { + self.make_call( + "factory_set_wnear_address", + DEFAULT_AURORA_ACCOUNT_ID, + wnear_address.try_to_vec().unwrap(), + ) + } } #[test] @@ -380,11 +391,12 @@ mod workspace { use crate::utils::solidity::exit_precompile::TesterConstructor; use crate::utils::workspace::{ create_sub_account, deploy_engine, deploy_erc20_from_nep_141, deploy_nep_141, - nep_141_balance_of, transfer_nep_141_to_erc_20, + nep_141_balance_of, transfer_nep_141, transfer_nep_141_to_erc_20, }; use aurora_engine::parameters::{CallArgs, FunctionCallArgsV2}; use aurora_engine_types::parameters::engine::TransactionStatus; use aurora_engine_workspace::account::Account; + use aurora_engine_workspace::types::ExecutionFinalResult; use aurora_engine_workspace::{parse_near, EngineContract, RawContract}; const FT_TOTAL_SUPPLY: u128 = 1_000_000; @@ -467,6 +479,7 @@ mod workspace { nep_141, erc20, aurora, + .. } = test_exit_to_near_common().await.unwrap(); // Call exit function on ERC-20; observe ERC-20 burned + NEP-141 transferred @@ -477,7 +490,8 @@ mod workspace { &erc20, &aurora, ) - .await; + .await + .unwrap(); assert_eq!( nep_141_balance_of(&nep_141, &ft_owner.id()).await, @@ -493,6 +507,98 @@ mod workspace { ); } + #[tokio::test] + async fn test_exit_to_near_wnear_unwrapped() { + // Deploy Aurora; deploy wnear; bridge wnear to ERC-20 on Aurora + let TestExitToNearContext { + ft_owner, + ft_owner_address, + aurora, + wnear, + wnear_erc20, + .. + } = test_exit_to_near_common().await.unwrap(); + + let ft_owner_balance = aurora.node.get_balance(&ft_owner.id()).await.unwrap(); + + // Call exit function on ERC-20; observe ERC-20 burned + near tokens transferred + let result = exit_to_near( + &ft_owner, + &format!("{}:unwrap", ft_owner.id().as_ref()), + FT_EXIT_AMOUNT, + &wnear_erc20, + &aurora, + ) + .await; + let total_tokens_burnt: u128 = result.outcomes().iter().map(|o| o.tokens_burnt).sum(); + + // Check that the wnear tokens are properly unwrapped and transferred to `ft_owner` + assert_eq!( + aurora.node.get_balance(&ft_owner.id()).await.unwrap(), + ft_owner_balance - total_tokens_burnt + FT_EXIT_AMOUNT + ); + + // Check wnear balances + assert_eq!( + nep_141_balance_of(&wnear, &ft_owner.id()).await, + FT_TOTAL_SUPPLY - FT_TRANSFER_AMOUNT + ); + assert_eq!( + nep_141_balance_of(&wnear, &aurora.id()).await, + FT_TRANSFER_AMOUNT - FT_EXIT_AMOUNT + ); + assert_eq!( + erc20_balance(&wnear_erc20, ft_owner_address, &aurora).await, + (FT_TRANSFER_AMOUNT - FT_EXIT_AMOUNT).into() + ); + } + + #[tokio::test] + async fn test_exit_to_near_wnear() { + // Deploy Aurora; deploy wnear; bridge wnear to ERC-20 on Aurora + let TestExitToNearContext { + ft_owner, + ft_owner_address, + aurora, + wnear, + wnear_erc20, + .. + } = test_exit_to_near_common().await.unwrap(); + + let ft_owner_balance = aurora.node.get_balance(&ft_owner.id()).await.unwrap(); + + // Call exit function on ERC-20; observe ERC-20 burned + wnear tokens transferred + let result = exit_to_near( + &ft_owner, + ft_owner.id().as_ref(), + FT_EXIT_AMOUNT, + &wnear_erc20, + &aurora, + ) + .await; + let total_tokens_burnt: u128 = result.outcomes().iter().map(|o| o.tokens_burnt).sum(); + + // Check that there were no near tokens transferred to `ft_owner` + assert_eq!( + aurora.node.get_balance(&ft_owner.id()).await.unwrap(), + ft_owner_balance - total_tokens_burnt + ); + + // Check wnear balances + assert_eq!( + nep_141_balance_of(&wnear, &ft_owner.id()).await, + FT_TOTAL_SUPPLY - FT_TRANSFER_AMOUNT + FT_EXIT_AMOUNT + ); + assert_eq!( + nep_141_balance_of(&wnear, &aurora.id()).await, + FT_TRANSFER_AMOUNT - FT_EXIT_AMOUNT + ); + assert_eq!( + erc20_balance(&wnear_erc20, ft_owner_address, &aurora).await, + (FT_TRANSFER_AMOUNT - FT_EXIT_AMOUNT).into() + ); + } + #[tokio::test] async fn test_exit_to_near_refund() { // Deploy Aurora; deploy NEP-141; bridge NEP-141 to ERC-20 on Aurora @@ -502,6 +608,7 @@ mod workspace { nep_141, erc20, aurora, + .. } = test_exit_to_near_common().await.unwrap(); // Call exit on ERC-20; ft_transfer promise fails; expect refund on Aurora; @@ -513,7 +620,8 @@ mod workspace { &erc20, &aurora, ) - .await; + .await + .unwrap(); assert_eq!( nep_141_balance_of(&nep_141, &ft_owner.id()).await, @@ -692,6 +800,7 @@ mod workspace { }) } + #[allow(clippy::cognitive_complexity)] async fn test_exit_to_near_common() -> anyhow::Result { // 1. deploy Aurora let aurora = deploy_engine().await; @@ -707,9 +816,51 @@ mod workspace { .await?; assert!(result.is_success()); + // 3. Deploy wnear and set wnear address + + let wnear = crate::tests::xcc::workspace::deploy_wnear(&aurora).await?; + let wnear_erc20 = deploy_erc20_from_nep_141(wnear.id().as_ref(), &aurora).await?; + aurora + .factory_set_wnear_address(wnear_erc20.0.address) + .transact() + .await?; + + // 4. Transfer wnear to `ft_owner` and bridge it to aurora + transfer_nep_141( + &wnear.id(), + &aurora.root(), + ft_owner.id().as_ref(), + FT_TOTAL_SUPPLY, + ) + .await?; + + transfer_nep_141_to_erc_20( + &wnear, + &wnear_erc20, + &ft_owner, + ft_owner_address, + FT_TRANSFER_AMOUNT, + &aurora, + ) + .await?; + + assert_eq!( + nep_141_balance_of(&wnear, &ft_owner.id()).await, + FT_TOTAL_SUPPLY - FT_TRANSFER_AMOUNT + ); + assert_eq!( + nep_141_balance_of(&wnear, &aurora.id()).await, + FT_TRANSFER_AMOUNT + ); + assert_eq!( + erc20_balance(&wnear_erc20, ft_owner_address, &aurora).await, + FT_TRANSFER_AMOUNT.into() + ); + + // 5. Deploy NEP-141 let nep_141_account = create_sub_account(&aurora.root(), FT_ACCOUNT, parse_near!("50 N")).await?; - // 3. Deploy NEP-141 + let nep_141 = deploy_nep_141(&nep_141_account, &ft_owner, FT_TOTAL_SUPPLY, &aurora) .await .map_err(|e| anyhow::anyhow!("Couldn't deploy NEP-141: {e}"))?; @@ -719,7 +870,7 @@ mod workspace { FT_TOTAL_SUPPLY ); - // 4. Deploy ERC-20 from NEP-141 and bridge value to Aurora + // 6. Deploy ERC-20 from NEP-141 and bridge value to Aurora let erc20 = deploy_erc20_from_nep_141(nep_141.id().as_ref(), &aurora) .await .map_err(|e| anyhow::anyhow!("Couldn't deploy ERC-20 from NEP-141: {e}"))?; @@ -754,6 +905,8 @@ mod workspace { nep_141, erc20, aurora, + wnear, + wnear_erc20, }) } @@ -763,7 +916,7 @@ mod workspace { amount: u128, erc20: &ERC20, aurora: &EngineContract, - ) { + ) -> ExecutionFinalResult { let input = build_input( "withdrawToNear(bytes,uint256)", &[ @@ -784,6 +937,7 @@ mod workspace { .await .unwrap(); assert!(result.is_success()); + result } async fn eth_balance_of(address: Address, aurora: &EngineContract) -> Wei { @@ -812,6 +966,8 @@ mod workspace { nep_141: RawContract, erc20: ERC20, aurora: EngineContract, + wnear: RawContract, + wnear_erc20: ERC20, } struct TestExitToNearEthContext { diff --git a/engine-tests/src/tests/hashchain.rs b/engine-tests/src/tests/hashchain.rs new file mode 100644 index 000000000..338d8f2bc --- /dev/null +++ b/engine-tests/src/tests/hashchain.rs @@ -0,0 +1,111 @@ +use crate::utils; +use aurora_engine::parameters::{StartHashchainArgs, SubmitResult, TransactionStatus}; +use aurora_engine_hashchain::bloom::Bloom; +use aurora_engine_transactions::legacy::TransactionLegacy; +use aurora_engine_types::{ + borsh::BorshSerialize, + types::{Address, Wei}, + H256, U256, +}; + +#[test] +fn test_hashchain() { + let (mut runner, mut signer, _) = crate::tests::sanity::initialize_transfer(); + + // The tests initialize the hashchain with the default value. + let hc = get_latest_hashchain(&runner); + // Hashchain starts 2 heights lower than the current context height because + // at `hc.block_height + 1` the `start_hashchain` is submitted and at + // `hc.block_height + 2` the signer address is created in the EVM state. + assert_eq!(hc.block_height, runner.context.block_height - 2); + assert_eq!(hc.hashchain, hex::encode(H256::default())); + + // Execute a transaction and the hashchain changes + let transaction = TransactionLegacy { + nonce: signer.use_nonce().into(), + gas_price: U256::zero(), + gas_limit: u64::MAX.into(), + to: Some(Address::from_array([1u8; 20])), + value: Wei::zero(), + data: Vec::new(), + }; + let signed_transaction = + utils::sign_transaction(transaction, Some(runner.chain_id), &signer.secret_key); + let input = rlp::encode(&signed_transaction).to_vec(); + let output = SubmitResult::new(TransactionStatus::Succeed(Vec::new()), 21_000, Vec::new()) + .try_to_vec() + .unwrap(); + + let expected_hc = { + let start_hc_args = StartHashchainArgs { + block_height: hc.block_height, + block_hashchain: [0u8; 32], + }; + let mut block_height = hc.block_height + 1; + let mut hc = aurora_engine_hashchain::hashchain::Hashchain::new( + aurora_engine_types::types::u256_to_arr(&runner.chain_id.into()), + runner.aurora_account_id.parse().unwrap(), + block_height, + H256::default().0, + ); + // First transaction is always `start_hashchain` + hc.add_block_tx( + block_height, + "start_hashchain", + &start_hc_args.try_to_vec().unwrap(), + &[], + &Bloom::default(), + ) + .unwrap(); + // Skip a block height because at block_height + 1 there is no "real" transaction + // (that height is skipped when the signer address is directly inserted into the EVM state) + block_height += 2; + hc.move_to_block(block_height).unwrap(); + // Insert the `submit` transaction we care about + hc.add_block_tx(block_height, "submit", &input, &output, &Bloom::default()) + .unwrap(); + hc.move_to_block(block_height + 1).unwrap(); + hc.get_previous_block_hashchain() + }; + + runner + .evm_submit(&signed_transaction, "relay.aurora") + .unwrap(); + // Need to submit a second transaction to trigger hashchain computation on + // the previous block (which contains the previous transaction) + runner + .submit_with_signer(&mut signer, |nonce| TransactionLegacy { + nonce, + gas_price: U256::zero(), + gas_limit: u64::MAX.into(), + to: None, + value: Wei::zero(), + data: Vec::new(), + }) + .unwrap(); + + let hc = get_latest_hashchain(&runner); + assert_eq!(hc.block_height, runner.context.block_height - 1); + assert_eq!(hc.hashchain, hex::encode(expected_hc)); +} + +fn get_latest_hashchain(runner: &utils::AuroraRunner) -> HashchainView { + let outcome = runner + .one_shot() + .call("get_latest_hashchain", "any.near", Vec::new()) + .unwrap(); + let return_data = outcome.return_data.as_value().unwrap(); + let result: HashchainViewResult = serde_json::from_slice(&return_data).unwrap(); + result.result.unwrap() +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +struct HashchainViewResult { + result: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +struct HashchainView { + block_height: u64, + hashchain: String, +} diff --git a/engine-tests/src/tests/mod.rs b/engine-tests/src/tests/mod.rs index 7adb5cb1c..87fdc8640 100644 --- a/engine-tests/src/tests/mod.rs +++ b/engine-tests/src/tests/mod.rs @@ -5,6 +5,7 @@ mod erc20; mod erc20_connector; mod eth_connector; mod ghsa_3p69_m8gg_fwmf; +mod hashchain; #[cfg(feature = "meta-call")] mod meta_parsing; pub mod modexp; diff --git a/engine-tests/src/tests/one_inch.rs b/engine-tests/src/tests/one_inch.rs index 143092e90..b5670a965 100644 --- a/engine-tests/src/tests/one_inch.rs +++ b/engine-tests/src/tests/one_inch.rs @@ -58,7 +58,7 @@ fn test_1inch_liquidity_protocol() { }, ); assert!(result.gas_used >= 302_000); // more than 302k EVM gas used - assert_gas_bound(profile.all_gas(), 22); + assert_gas_bound(profile.all_gas(), 23); // Same here helper.runner.context.block_timestamp += 10_000_000 * 1_000_000_000; @@ -100,13 +100,13 @@ fn test_1_inch_limit_order_deploy() { // more than 3.5 million Ethereum gas used assert!(result.gas_used > 3_500_000); - // less than 10 NEAR Tgas used - assert_gas_bound(profile.all_gas(), 8); + // less than 9 NEAR Tgas used + assert_gas_bound(profile.all_gas(), 9); // at least 45% of which is from wasm execution let wasm_fraction = 100 * profile.wasm_gas() / profile.all_gas(); assert!( - (50..=60).contains(&wasm_fraction), - "{wasm_fraction}% is not between 50% and 60%", + (45..=55).contains(&wasm_fraction), + "{wasm_fraction}% is not between 45% and 55%", ); } diff --git a/engine-tests/src/tests/prepaid_gas_precompile.rs b/engine-tests/src/tests/prepaid_gas_precompile.rs index 6330ade9d..55cc55a13 100644 --- a/engine-tests/src/tests/prepaid_gas_precompile.rs +++ b/engine-tests/src/tests/prepaid_gas_precompile.rs @@ -1,4 +1,4 @@ -use crate::utils::{self, standalone}; +use crate::utils; use aurora_engine_precompiles::prepaid_gas; use aurora_engine_transactions::legacy::TransactionLegacy; use aurora_engine_types::{types::Wei, U256}; @@ -8,10 +8,10 @@ fn test_prepaid_gas_precompile() { const EXPECTED_VALUE: u64 = 157_277_246_352_223; let mut signer = utils::Signer::random(); let mut runner = utils::deploy_runner(); - let mut standalone = standalone::StandaloneRunner::default(); - - standalone.init_evm(); - runner.standalone_runner = Some(standalone); + // The standalone runner gets the wrong answer because the prepaid gas is not + // captured in the `TransactionMessage` type. + // TODO: capture prepaid gas in `TransacitonMessage` + runner.standalone_runner = None; let transaction = TransactionLegacy { nonce: signer.use_nonce().into(), diff --git a/engine-tests/src/tests/promise_results_precompile.rs b/engine-tests/src/tests/promise_results_precompile.rs index 14c39f764..a3fbf92c1 100644 --- a/engine-tests/src/tests/promise_results_precompile.rs +++ b/engine-tests/src/tests/promise_results_precompile.rs @@ -1,4 +1,4 @@ -use crate::utils::{self, standalone}; +use crate::utils; use aurora_engine_precompiles::promise_result::{self, costs}; use aurora_engine_transactions::legacy::TransactionLegacy; use aurora_engine_types::borsh::BorshSerialize; @@ -14,9 +14,6 @@ fn test_promise_results_precompile() { let mut signer = utils::Signer::random(); let mut runner = utils::deploy_runner(); - let mut standalone = standalone::StandaloneRunner::default(); - standalone.init_evm(); - let promise_results = vec![ PromiseResult::Successful(hex::decode("deadbeef").unwrap()), PromiseResult::Failed, @@ -36,12 +33,6 @@ fn test_promise_results_precompile() { .submit_transaction(&signer.secret_key, transaction) .unwrap(); - let standalone_result = standalone - .submit_raw("submit", &runner.context, &promise_results) - .unwrap(); - - assert_eq!(result, standalone_result); - assert_eq!( utils::unwrap_success(result), promise_results.try_to_vec().unwrap(), @@ -51,11 +42,14 @@ fn test_promise_results_precompile() { #[test] fn test_promise_result_gas_cost() { let mut runner = utils::deploy_runner(); - let mut standalone = standalone::StandaloneRunner::default(); - standalone.init_evm(); - runner.standalone_runner = Some(standalone); let mut signer = utils::Signer::random(); - runner.context.block_height = aurora_engine::engine::ZERO_ADDRESS_FIX_HEIGHT + 1; + // Skip to later block height and re-init hashchain + let account_id = runner.aurora_account_id.clone(); + utils::init_hashchain( + &mut runner, + &account_id, + Some(aurora_engine::engine::ZERO_ADDRESS_FIX_HEIGHT + 1), + ); // Baseline transaction that does essentially nothing. let (_, baseline) = runner @@ -127,13 +121,13 @@ fn test_promise_result_gas_cost() { let total_gas1 = y1 + baseline.all_gas(); let total_gas2 = y2 + baseline.all_gas(); assert!( - utils::within_x_percent(6, evm1, total_gas1 / NEAR_GAS_PER_EVM), + utils::within_x_percent(15, evm1, total_gas1 / NEAR_GAS_PER_EVM), "Incorrect EVM gas used. Expected: {} Actual: {}", evm1, total_gas1 / NEAR_GAS_PER_EVM ); assert!( - utils::within_x_percent(6, evm2, total_gas2 / NEAR_GAS_PER_EVM), + utils::within_x_percent(15, evm2, total_gas2 / NEAR_GAS_PER_EVM), "Incorrect EVM gas used. Expected: {} Actual: {}", evm2, total_gas2 / NEAR_GAS_PER_EVM diff --git a/engine-tests/src/tests/repro.rs b/engine-tests/src/tests/repro.rs index f9a6e9221..5776df0cb 100644 --- a/engine-tests/src/tests/repro.rs +++ b/engine-tests/src/tests/repro.rs @@ -26,7 +26,7 @@ fn repro_GdASJ3KESs() { block_timestamp: 1_645_717_564_644_206_730, input_path: "src/tests/res/input_GdASJ3KESs.hex", evm_gas_used: 706_713, - near_gas_used: 120, + near_gas_used: 121, }); } @@ -169,7 +169,7 @@ fn repro_common(context: &ReproContext) { // Also validate the SubmitResult in the standalone engine let mut standalone = standalone::StandaloneRunner::default(); - json_snapshot::initialize_engine_state(&mut standalone.storage, snapshot).unwrap(); + json_snapshot::initialize_engine_state(&standalone.storage, snapshot).unwrap(); let standalone_result = standalone .submit_raw("submit", &runner.context, &[]) .unwrap(); diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs index ec8514719..302930550 100644 --- a/engine-tests/src/tests/sanity.rs +++ b/engine-tests/src/tests/sanity.rs @@ -681,10 +681,12 @@ fn test_num_wasm_functions() { let module = walrus::ModuleConfig::default() .parse(runner.code.code()) .unwrap(); - let num_functions = module.funcs.iter().count(); + let expected_number = 1464; + let actual_number = module.funcs.iter().count(); + assert!( - num_functions <= 1440, - "{num_functions} is not less than 1440", + actual_number <= expected_number, + "{actual_number} is not less than {expected_number}", ); } diff --git a/engine-tests/src/tests/standalone/call_tracer.rs b/engine-tests/src/tests/standalone/call_tracer.rs index 82acb8d10..78d973d22 100644 --- a/engine-tests/src/tests/standalone/call_tracer.rs +++ b/engine-tests/src/tests/standalone/call_tracer.rs @@ -322,13 +322,20 @@ fn test_trace_precompiles_with_subcalls() { let storage = &mut runner.storage; let env = &runner.env; - let mut tx = - standalone::StandaloneRunner::template_tx_msg(storage, env, 0, H256::default(), &[]); - tx.transaction = sync::types::TransactionKind::DeployErc20( + let tx_kind = sync::types::TransactionKind::DeployErc20( aurora_engine::parameters::DeployErc20TokenArgs { nep141: "wrap.near".parse().unwrap(), }, ); + let mut tx = standalone::StandaloneRunner::template_tx_msg( + storage, + env, + 0, + H256::default(), + &[], + tx_kind.raw_bytes(), + ); + tx.transaction = tx_kind; let mut outcome = sync::execute_transaction_message::(storage, tx).unwrap(); let key = storage::bytes_to_key(storage::KeyPrefix::Nep141Erc20Map, b"wrap.near"); outcome.diff.modify(key, wnear_address.as_bytes().to_vec()); @@ -347,9 +354,16 @@ fn test_trace_precompiles_with_subcalls() { let storage = &mut runner.storage; let env = &runner.env; - let mut tx = - standalone::StandaloneRunner::template_tx_msg(storage, env, 0, H256::default(), &[]); - tx.transaction = sync::types::TransactionKind::FactoryUpdate(xcc_router_bytes); + let tx_kind = sync::types::TransactionKind::FactoryUpdate(xcc_router_bytes); + let mut tx = standalone::StandaloneRunner::template_tx_msg( + storage, + env, + 0, + H256::default(), + &[], + tx_kind.raw_bytes(), + ); + tx.transaction = tx_kind; tx }; let outcome = @@ -360,9 +374,16 @@ fn test_trace_precompiles_with_subcalls() { let storage = &mut runner.storage; let env = &runner.env; - let mut tx = - standalone::StandaloneRunner::template_tx_msg(storage, env, 0, H256::default(), &[]); - tx.transaction = sync::types::TransactionKind::FactorySetWNearAddress(wnear_address); + let tx_kind = sync::types::TransactionKind::FactorySetWNearAddress(wnear_address); + let mut tx = standalone::StandaloneRunner::template_tx_msg( + storage, + env, + 0, + H256::default(), + &[], + tx_kind.raw_bytes(), + ); + tx.transaction = tx_kind; tx }; let outcome = diff --git a/engine-tests/src/tests/standalone/json_snapshot.rs b/engine-tests/src/tests/standalone/json_snapshot.rs index be55c8d58..e2702eca2 100644 --- a/engine-tests/src/tests/standalone/json_snapshot.rs +++ b/engine-tests/src/tests/standalone/json_snapshot.rs @@ -15,7 +15,7 @@ fn test_consume_snapshot() { ) .unwrap(); let mut runner = standalone::StandaloneRunner::default(); - json_snapshot::initialize_engine_state(&mut runner.storage, snapshot.clone()).unwrap(); + json_snapshot::initialize_engine_state(&runner.storage, snapshot.clone()).unwrap(); // check accounts to see they were written properly runner.env.block_height = snapshot.result.block_height + 1; @@ -53,7 +53,7 @@ fn test_produce_snapshot() { .storage .set_engine_account_id(&"aurora".parse().unwrap()) .unwrap(); - json_snapshot::initialize_engine_state(&mut runner.storage, snapshot.clone()).unwrap(); + json_snapshot::initialize_engine_state(&runner.storage, snapshot.clone()).unwrap(); // add a couple more transactions that write some extra keys runner.env.block_height = snapshot.result.block_height + 1; diff --git a/engine-tests/src/tests/standalone/storage.rs b/engine-tests/src/tests/standalone/storage.rs index 5671551a8..e72c79500 100644 --- a/engine-tests/src/tests/standalone/storage.rs +++ b/engine-tests/src/tests/standalone/storage.rs @@ -272,6 +272,7 @@ fn test_transaction_index() { attached_near: 0, transaction: TransactionKind::Unknown, promise_data: Vec::new(), + raw_input: Vec::new(), }; let tx_included = engine_standalone_storage::TransactionIncluded { block_hash, diff --git a/engine-tests/src/tests/standalone/sync.rs b/engine-tests/src/tests/standalone/sync.rs index 681ecc252..3fdbf3337 100644 --- a/engine-tests/src/tests/standalone/sync.rs +++ b/engine-tests/src/tests/standalone/sync.rs @@ -45,6 +45,8 @@ fn test_consume_deposit_message() { let recipient_address = Address::new(H160([22u8; 20])); let deposit_amount = Wei::new_u64(123_456_789); let proof = mock_proof(recipient_address, deposit_amount); + let tx_kind = sync::types::TransactionKind::Deposit(proof.try_to_vec().unwrap()); + let raw_input = tx_kind.raw_bytes(); let transaction_message = sync::types::TransactionMessage { block_hash: block_message.hash, @@ -54,8 +56,9 @@ fn test_consume_deposit_message() { signer: runner.env.signer_account_id(), caller: runner.env.predecessor_account_id(), attached_near: 0, - transaction: sync::types::TransactionKind::Deposit(proof.try_to_vec().unwrap()), + transaction: tx_kind, promise_data: Vec::new(), + raw_input, }; let outcome = sync::consume_message::( @@ -76,6 +79,8 @@ fn test_consume_deposit_message() { } other => panic!("Unexpected result {other:?}"), }; + let tx_kind = sync::types::TransactionKind::FinishDeposit(finish_deposit_args); + let raw_input = tx_kind.raw_bytes(); // Now executing aurora callbacks, so predecessor_account_id = current_account_id runner.env.predecessor_account_id = runner.env.current_account_id.clone(); @@ -87,8 +92,11 @@ fn test_consume_deposit_message() { signer: runner.env.signer_account_id(), caller: runner.env.predecessor_account_id(), attached_near: 0, - transaction: sync::types::TransactionKind::FinishDeposit(finish_deposit_args), - promise_data: Vec::new(), + transaction: tx_kind, + // Need to pass the result of calling the proof verifier + // (which is `true` because the proof is valid in this case). + promise_data: vec![Some(true.try_to_vec().unwrap())], + raw_input, }; let outcome = sync::consume_message::( @@ -109,6 +117,8 @@ fn test_consume_deposit_message() { } other => panic!("Unexpected result {other:?}"), }; + let tx_kind = sync::types::TransactionKind::FtOnTransfer(ft_on_transfer_args); + let raw_input = tx_kind.raw_bytes(); let transaction_message = sync::types::TransactionMessage { block_hash: block_message.hash, @@ -118,8 +128,9 @@ fn test_consume_deposit_message() { signer: runner.env.signer_account_id(), caller: runner.env.predecessor_account_id(), attached_near: 0, - transaction: sync::types::TransactionKind::FtOnTransfer(ft_on_transfer_args), + transaction: tx_kind, promise_data: Vec::new(), + raw_input, }; let outcome = sync::consume_message::( @@ -140,6 +151,8 @@ fn test_consume_deploy_message() { let code = b"hello_world!".to_vec(); let input = utils::create_deploy_transaction(code.clone(), U256::zero()).data; + let tx_kind = sync::types::TransactionKind::Deploy(input); + let raw_input = tx_kind.raw_bytes(); let transaction_message = sync::types::TransactionMessage { block_hash: block_message.hash, @@ -149,8 +162,9 @@ fn test_consume_deploy_message() { signer: runner.env.signer_account_id(), caller: runner.env.predecessor_account_id(), attached_near: 0, - transaction: sync::types::TransactionKind::Deploy(input), + transaction: tx_kind, promise_data: Vec::new(), + raw_input, }; let outcome = sync::consume_message::( @@ -194,6 +208,8 @@ fn test_consume_deploy_erc20_message() { let args = aurora_engine::parameters::DeployErc20TokenArgs { nep141: token.clone(), }; + let tx_kind = sync::types::TransactionKind::DeployErc20(args); + let raw_input = tx_kind.raw_bytes(); let transaction_message = sync::types::TransactionMessage { block_hash: block_message.hash, near_receipt_id: H256([8u8; 32]), @@ -202,8 +218,9 @@ fn test_consume_deploy_erc20_message() { signer: runner.env.signer_account_id(), caller: runner.env.predecessor_account_id(), attached_near: 0, - transaction: sync::types::TransactionKind::DeployErc20(args), + transaction: tx_kind, promise_data: Vec::new(), + raw_input, }; // Deploy ERC-20 (this would be the flow for bridging a new NEP-141 to Aurora) @@ -233,6 +250,8 @@ fn test_consume_deploy_erc20_message() { amount: Balance::new(mint_amount), msg: hex::encode(dest_address.as_bytes()), }; + let tx_kind = sync::types::TransactionKind::FtOnTransfer(args); + let raw_input = tx_kind.raw_bytes(); let transaction_message = sync::types::TransactionMessage { block_hash, near_receipt_id: H256([8u8; 32]), @@ -241,8 +260,9 @@ fn test_consume_deploy_erc20_message() { signer: runner.env.signer_account_id(), caller: runner.env.predecessor_account_id(), attached_near: 0, - transaction: sync::types::TransactionKind::FtOnTransfer(args), + transaction: tx_kind, promise_data: Vec::new(), + raw_input, }; // Mint new tokens (via ft_on_transfer flow, same as the bridge) @@ -291,6 +311,8 @@ fn test_consume_ft_on_transfer_message() { ] .concat(), }; + let tx_kind = sync::types::TransactionKind::FtOnTransfer(args); + let raw_input = tx_kind.raw_bytes(); let transaction_message = sync::types::TransactionMessage { block_hash: block_message.hash, near_receipt_id: H256([8u8; 32]), @@ -299,8 +321,9 @@ fn test_consume_ft_on_transfer_message() { signer: runner.env.signer_account_id(), caller: runner.env.predecessor_account_id(), attached_near: 0, - transaction: sync::types::TransactionKind::FtOnTransfer(args), + transaction: tx_kind, promise_data: Vec::new(), + raw_input, }; let outcome = sync::consume_message::( @@ -333,6 +356,11 @@ fn test_consume_call_message() { utils::standalone::mocks::insert_block(&mut runner.storage, runner.env.block_height); let block_hash = utils::standalone::mocks::compute_block_hash(runner.env.block_height); + let tx_kind = sync::types::TransactionKind::Call(simple_transfer_args( + recipient_address, + transfer_amount, + )); + let raw_input = tx_kind.raw_bytes(); let transaction_message = sync::types::TransactionMessage { block_hash, near_receipt_id: H256([8u8; 32]), @@ -341,11 +369,9 @@ fn test_consume_call_message() { signer: runner.env.signer_account_id(), caller: runner.env.predecessor_account_id(), attached_near: 0, - transaction: sync::types::TransactionKind::Call(simple_transfer_args( - recipient_address, - transfer_amount, - )), + transaction: tx_kind, promise_data: Vec::new(), + raw_input, }; let outcome = sync::consume_message::( @@ -386,6 +412,8 @@ fn test_consume_submit_message() { utils::sign_transaction(transaction, Some(runner.chain_id), &signer.secret_key); let eth_transaction = crate::prelude::transactions::EthTransactionKind::Legacy(signed_transaction); + let tx_kind = sync::types::TransactionKind::Submit(eth_transaction); + let raw_input = tx_kind.raw_bytes(); let transaction_message = sync::types::TransactionMessage { block_hash, @@ -395,8 +423,9 @@ fn test_consume_submit_message() { signer: runner.env.signer_account_id(), caller: runner.env.predecessor_account_id(), attached_near: 0, - transaction: sync::types::TransactionKind::Submit(eth_transaction), + transaction: tx_kind, promise_data: Vec::new(), + raw_input, }; let outcome = sync::consume_message::( diff --git a/engine-tests/src/tests/standalone/tracing.rs b/engine-tests/src/tests/standalone/tracing.rs index 7424d8c29..153f00ef5 100644 --- a/engine-tests/src/tests/standalone/tracing.rs +++ b/engine-tests/src/tests/standalone/tracing.rs @@ -72,6 +72,7 @@ fn test_evm_tracing_with_storage() { attached_near: 0, transaction: engine_standalone_storage::sync::types::TransactionKind::Unknown, promise_data: Vec::new(), + raw_input: Vec::new(), }, diff, maybe_result: Ok(None), diff --git a/engine-tests/src/tests/transaction.rs b/engine-tests/src/tests/transaction.rs index 9c8204398..13282da3f 100644 --- a/engine-tests/src/tests/transaction.rs +++ b/engine-tests/src/tests/transaction.rs @@ -141,7 +141,7 @@ fn test_access_list_tx_encoding_decoding() { let signed_tx = utils::sign_access_list_transaction(transaction, &secret_key); let bytes: Vec = iter::once(eip_2930::TYPE_BYTE) - .chain(rlp::encode(&signed_tx).into_iter()) + .chain(rlp::encode(&signed_tx)) .collect(); let expected_bytes = hex::decode("01f8f901800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d87830186a000f893f85994095e7baea6a6c7c4c2dfeb977efac326af552d87f842a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001f794195e7baea6a6c7c4c2dfeb977efac326af552d87e1a0000000000000000000000000000000000000000000000000000000000000000080a011c97e0bb8a356fe4f49b37863d059c6fe8cd3214a6ac06a8387a2f6f0b75f60a0212368a1097da30806edfd13d9c35662e1baee939235eb25de867980bd0eda26").unwrap(); @@ -163,7 +163,7 @@ fn test_access_list_tx_encoding_decoding() { fn encode_tx(signed_tx: &SignedTransaction1559) -> Vec { iter::once(eip_1559::TYPE_BYTE) - .chain(rlp::encode(signed_tx).into_iter()) + .chain(rlp::encode(signed_tx)) .collect() } diff --git a/engine-tests/src/tests/uniswap.rs b/engine-tests/src/tests/uniswap.rs index 71b9958d9..ae4dfe522 100644 --- a/engine-tests/src/tests/uniswap.rs +++ b/engine-tests/src/tests/uniswap.rs @@ -38,7 +38,7 @@ fn test_uniswap_input_multihop() { let (_amount_out, _evm_gas, profile) = context.exact_input(&tokens, INPUT_AMOUNT.into()); - assert_eq!(112, profile.all_gas() / 1_000_000_000_000); + assert_eq!(114, profile.all_gas() / 1_000_000_000_000); } #[test] @@ -282,7 +282,7 @@ impl UniswapTestContext { token_path: &[ERC20], amount_in: U256, ) -> (U256, u64, ExecutionProfile) { - for token in token_path.iter() { + for token in token_path { self.approve_erc20(token, self.swap_router.0.address, U256::MAX); } let params = Self::exact_input_params(amount_in, token_path); diff --git a/engine-tests/src/tests/xcc.rs b/engine-tests/src/tests/xcc.rs index 0b16e5335..c70e71060 100644 --- a/engine-tests/src/tests/xcc.rs +++ b/engine-tests/src/tests/xcc.rs @@ -27,9 +27,16 @@ fn test_xcc_eth_gas_cost() { let _res = runner.call("factory_update", DEFAULT_AURORA_ACCOUNT_ID, xcc_wasm_bytes); let mut signer = utils::Signer::random(); let mut baseline_signer = utils::Signer::random(); - runner.context.block_height = aurora_engine::engine::ZERO_ADDRESS_FIX_HEIGHT + 1; + // Skip to later block height and re-init hashchain + let account_id = runner.aurora_account_id.clone(); + utils::init_hashchain( + &mut runner, + &account_id, + Some(aurora_engine::engine::ZERO_ADDRESS_FIX_HEIGHT + 1), + ); + // Need to use for engine's deployment! - let wnear_erc20 = deploy_erc20(&mut runner, &mut signer); + let wnear_erc20 = deploy_erc20(&mut runner, &signer); approve_erc20( &wnear_erc20, cross_contract_call::ADDRESS, @@ -295,7 +302,7 @@ fn deploy_router() -> AuroraRunner { router } -fn deploy_erc20(runner: &mut AuroraRunner, signer: &mut utils::Signer) -> ERC20 { +fn deploy_erc20(runner: &mut AuroraRunner, signer: &utils::Signer) -> ERC20 { let engine_account = runner.aurora_account_id.clone(); let args = aurora_engine::parameters::DeployErc20TokenArgs { nep141: "wrap.near".parse().unwrap(), @@ -375,7 +382,7 @@ fn make_fib_promise(n: usize, account_id: &AccountId) -> NearPromise { } } -mod workspace { +pub mod workspace { use crate::tests::xcc::{check_fib_result, WNEAR_AMOUNT}; use crate::utils; use crate::utils::workspace::{ @@ -847,7 +854,7 @@ mod workspace { aurora.ft_balance_of(&aurora.id()).await.unwrap().result.0 } - async fn deploy_wnear(aurora: &EngineContract) -> anyhow::Result { + pub async fn deploy_wnear(aurora: &EngineContract) -> anyhow::Result { let contract_bytes = std::fs::read("src/tests/res/w_near.wasm").unwrap(); let wrap_account = create_sub_account(&aurora.root(), "wrap", STORAGE_AMOUNT).await?; let contract = wrap_account.deploy(&contract_bytes).await?; diff --git a/engine-tests/src/utils/mod.rs b/engine-tests/src/utils/mod.rs index b314d01be..047379ee4 100644 --- a/engine-tests/src/utils/mod.rs +++ b/engine-tests/src/utils/mod.rs @@ -21,7 +21,8 @@ use std::borrow::Cow; use crate::prelude::fungible_token::{FungibleToken, FungibleTokenMetadata}; use crate::prelude::parameters::{ - InitCallArgs, LegacyNewCallArgs, SubmitResult, TransactionStatus, + InitCallArgs, LegacyNewCallArgs, RelayerKeyManagerArgs, StartHashchainArgs, SubmitResult, + TransactionStatus, }; use crate::prelude::transactions::{ eip_1559::{self, SignedTransaction1559, Transaction1559}, @@ -619,9 +620,10 @@ impl ExecutionProfile { pub fn deploy_runner() -> AuroraRunner { let mut runner = AuroraRunner::default(); + let aurora_account_id = str_to_account_id(runner.aurora_account_id.as_str()); let args = LegacyNewCallArgs { chain_id: crate::prelude::u256_to_arr(&U256::from(runner.chain_id)), - owner_id: str_to_account_id(runner.aurora_account_id.as_str()), + owner_id: aurora_account_id.clone(), bridge_prover_id: str_to_account_id("bridge_prover.near"), upgrade_delay_blocks: 1, }; @@ -637,12 +639,52 @@ pub fn deploy_runner() -> AuroraRunner { metadata: FungibleTokenMetadata::default(), }; let result = runner.call("new_eth_connector", &account_id, args.try_to_vec().unwrap()); + assert!(result.is_ok()); + // Need to set a key manager because that is the only account that can initialize the hashchain + let args = RelayerKeyManagerArgs { + key_manager: Some(aurora_account_id), + }; + let result: Result = runner.call( + "set_key_manager", + &account_id, + serde_json::to_vec(&args).unwrap(), + ); assert!(result.is_ok()); + init_hashchain(&mut runner, &account_id, None); runner } +pub fn init_hashchain( + runner: &mut AuroraRunner, + caller_account_id: &str, + block_height: Option, +) { + // Set up hashchain: + // 1. Pause contract (hashchain can only be started if contract is paused first) + // 2. Start hashchain + + let result: Result = + runner.call("pause_contract", caller_account_id, Vec::new()); + assert!(result.is_ok()); + + if let Some(h) = block_height { + runner.context.block_height = h; + } + + let args = StartHashchainArgs { + block_height: runner.context.block_height, + block_hashchain: [0u8; 32], + }; + let result = runner.call( + "start_hashchain", + caller_account_id, + args.try_to_vec().unwrap(), + ); + assert!(result.is_ok()); +} + pub fn transfer(to: Address, amount: Wei, nonce: U256) -> TransactionLegacy { TransactionLegacy { nonce, @@ -668,7 +710,7 @@ pub fn create_deploy_transaction(contract_bytes: Vec, nonce: U256) -> Transa let data = hex::decode(init_code) .unwrap() .into_iter() - .chain(contract_bytes.into_iter()) + .chain(contract_bytes) .collect(); TransactionLegacy { diff --git a/engine-tests/src/utils/standalone/mod.rs b/engine-tests/src/utils/standalone/mod.rs index 0663d5a30..cecc4bc2b 100644 --- a/engine-tests/src/utils/standalone/mod.rs +++ b/engine-tests/src/utils/standalone/mod.rs @@ -43,7 +43,7 @@ impl StandaloneRunner { .unwrap(); env.block_height += 1; let transaction_hash = H256::zero(); - let tx_msg = Self::template_tx_msg(storage, env, 0, transaction_hash, &[]); + let tx_msg = Self::template_tx_msg(storage, env, 0, transaction_hash, &[], Vec::new()); let result = storage.with_engine_access(env.block_height, 0, &[], |io| { mocks::init_evm(io, env, chain_id); }); @@ -77,7 +77,7 @@ impl StandaloneRunner { }; env.block_height += 1; - let tx_msg = Self::template_tx_msg(storage, env, 0, transaction_hash, &[]); + let tx_msg = Self::template_tx_msg(storage, env, 0, transaction_hash, &[], Vec::new()); let result = storage.with_engine_access(env.block_height, 0, &[], |io| { mocks::mint_evm_account(address, balance, nonce, code, io, env); @@ -97,7 +97,7 @@ impl StandaloneRunner { signer: &mut utils::Signer, amount: Wei, dest: Address, - ) -> Result { + ) -> Result { let tx = TransactionLegacy { nonce: signer.use_nonce().into(), gas_price: U256::zero(), @@ -113,7 +113,7 @@ impl StandaloneRunner { &mut self, account: &SecretKey, transaction: TransactionLegacy, - ) -> Result { + ) -> Result { let storage = &mut self.storage; let env = &mut self.env; env.block_height += 1; @@ -133,7 +133,7 @@ impl StandaloneRunner { pub fn submit_raw_transaction_bytes( &mut self, transaction_bytes: &[u8], - ) -> Result { + ) -> Result { self.env.predecessor_account_id = "some-account.near".parse().unwrap(); let storage = &mut self.storage; let env = &mut self.env; @@ -163,7 +163,14 @@ impl StandaloneRunner { let transaction_bytes = rlp::encode(signed_tx).to_vec(); let transaction_hash = aurora_engine_sdk::keccak(&transaction_bytes); - let mut tx_msg = Self::template_tx_msg(storage, env, 0, transaction_hash, &[]); + let mut tx_msg = Self::template_tx_msg( + storage, + env, + 0, + transaction_hash, + &[], + transaction_bytes.clone(), + ); tx_msg.position = transaction_position; tx_msg.transaction = TransactionKind::Submit(transaction_bytes.as_slice().try_into().unwrap()); @@ -196,6 +203,9 @@ impl StandaloneRunner { env.current_account_id = ctx.current_account_id.as_ref().parse().unwrap(); env.signer_account_id = ctx.signer_account_id.as_ref().parse().unwrap(); env.prepaid_gas = NearGas::new(ctx.prepaid_gas); + if ctx.random_seed.len() == 32 { + env.random_seed = H256::from_slice(&ctx.random_seed); + } let promise_data: Vec<_> = promise_results .iter() @@ -218,7 +228,14 @@ impl StandaloneRunner { }; let storage = &mut self.storage; - let mut tx_msg = Self::template_tx_msg(storage, &env, 0, transaction_hash, promise_results); + let mut tx_msg = Self::template_tx_msg( + storage, + &env, + 0, + transaction_hash, + promise_results, + ctx.input.clone(), + ); tx_msg.transaction = transaction_kind; let outcome = sync::execute_transaction_message::(storage, tx_msg).unwrap(); @@ -279,6 +296,7 @@ impl StandaloneRunner { transaction_position: u16, transaction_hash: H256, promise_results: &[PromiseResult], + raw_input: Vec, ) -> TransactionMessage { let block_hash = mocks::compute_block_hash(env.block_height); let block_metadata = BlockMetadata { @@ -305,6 +323,7 @@ impl StandaloneRunner { attached_near: env.attached_deposit, transaction: TransactionKind::Unknown, promise_data, + raw_input, } } @@ -312,10 +331,10 @@ impl StandaloneRunner { transaction_bytes: &[u8], transaction_position: u16, storage: &mut Storage, - env: &mut env::Fixed, + env: &env::Fixed, cumulative_diff: &mut Diff, promise_results: &[PromiseResult], - ) -> Result { + ) -> Result { let transaction_hash = aurora_engine_sdk::keccak(transaction_bytes); let mut tx_msg = Self::template_tx_msg( storage, @@ -323,6 +342,7 @@ impl StandaloneRunner { transaction_position, transaction_hash, promise_results, + transaction_bytes.to_vec(), ); tx_msg.transaction = TransactionKind::Submit(transaction_bytes.try_into().unwrap()); @@ -336,9 +356,9 @@ impl StandaloneRunner { fn unwrap_result( outcome: sync::TransactionIncludedOutcome, -) -> Result { - match outcome.maybe_result.unwrap().unwrap() { - sync::TransactionExecutionResult::Submit(result) => result, +) -> Result { + match outcome.maybe_result?.unwrap() { + sync::TransactionExecutionResult::Submit(result) => result.map_err(Into::into), sync::TransactionExecutionResult::Promise(_) => panic!("Unexpected promise."), sync::TransactionExecutionResult::DeployErc20(_) => panic!("Unexpected DeployErc20."), } diff --git a/engine-tests/src/utils/workspace.rs b/engine-tests/src/utils/workspace.rs index 58008edad..bfdcc3072 100644 --- a/engine-tests/src/utils/workspace.rs +++ b/engine-tests/src/utils/workspace.rs @@ -129,3 +129,34 @@ pub async fn deploy_nep_141( Ok(nep141) } + +pub async fn transfer_nep_141( + nep_141: &AccountId, + source: &Account, + dest: &str, + amount: u128, +) -> anyhow::Result<()> { + let result = source + .call(nep_141, "storage_deposit") + .args_json(json!({ + "account_id": dest, + })) + .deposit(STORAGE_AMOUNT) + .transact() + .await?; + assert!(result.is_success()); + + let result = source + .call(nep_141, "ft_transfer") + .args_json(json!({ + "receiver_id": dest, + "amount": amount.to_string(), + "memo": "null", + })) + .deposit(1) + .transact() + .await?; + assert!(result.is_success()); + + Ok(()) +} diff --git a/engine-types/src/parameters/connector.rs b/engine-types/src/parameters/connector.rs index 5fa2483ce..5b3449ea3 100644 --- a/engine-types/src/parameters/connector.rs +++ b/engine-types/src/parameters/connector.rs @@ -220,6 +220,36 @@ impl rlp::Encodable for LogEntry { } } +/// Parameters for `set_erc20_metadata` function. +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct SetErc20MetadataArgs { + /// Address of the ERC-20 contract. + pub erc20_address: Address, + /// Metadata of the ERC-20 contract. + pub erc20_metadata: Erc20Metadata, +} + +/// Metadata of ERC-20 contract. +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Erc20Metadata { + /// Name of the token. + pub name: String, + /// Symbol of the token. + pub symbol: String, + /// Number of decimals. + pub decimals: u8, +} + +impl Default for Erc20Metadata { + fn default() -> Self { + Self { + name: "Empty".to_string(), + symbol: "EMPTY".to_string(), + decimals: 0, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/engine-types/src/parameters/engine.rs b/engine-types/src/parameters/engine.rs index 7b4820199..76d641842 100644 --- a/engine-types/src/parameters/engine.rs +++ b/engine-types/src/parameters/engine.rs @@ -99,6 +99,13 @@ pub struct SubmitArgs { pub gas_token_address: Option
, } +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct StartHashchainArgs { + pub block_height: u64, + pub block_hashchain: RawH256, +} + /// Borsh-encoded parameters for the `begin_chain` function. #[cfg(feature = "evm_bully")] #[derive(BorshSerialize, BorshDeserialize)] diff --git a/engine-types/src/parameters/promise.rs b/engine-types/src/parameters/promise.rs index 8820bbaa3..1ec0c2c20 100644 --- a/engine-types/src/parameters/promise.rs +++ b/engine-types/src/parameters/promise.rs @@ -304,7 +304,21 @@ pub struct RefundCallArgs { pub amount: RawU256, } -/// Args passed to the the cross contract call precompile. +// Transfer near tokens call args +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] +pub struct TransferNearCallArgs { + pub target_account_id: AccountId, + pub amount: u128, +} + +/// Exit to near precompile callback call args +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq, Default)] +pub struct ExitToNearPrecompileCallbackCallArgs { + pub refund: Option, + pub transfer_near: Option, +} + +/// Args passed to the cross contract call precompile. /// That precompile is used by Aurora contracts to make calls to the broader NEAR ecosystem. /// See `https://github.com/aurora-is-near/AIPs/pull/2` for design details. #[derive(Debug, BorshSerialize, BorshDeserialize)] diff --git a/engine-types/src/storage.rs b/engine-types/src/storage.rs index 4dfa80e2d..ae36d1046 100644 --- a/engine-types/src/storage.rs +++ b/engine-types/src/storage.rs @@ -33,6 +33,7 @@ pub enum KeyPrefix { Erc20Nep141Map = 0x9, CrossContractCall = 0xa, RelayerFunctionCallKey = 0xb, + Hashchain = 0xc, } impl From for u8 { @@ -50,6 +51,7 @@ impl From for u8 { KeyPrefix::Erc20Nep141Map => 0x9, KeyPrefix::CrossContractCall => 0xa, KeyPrefix::RelayerFunctionCallKey => 0xb, + KeyPrefix::Hashchain => 0xc, } } } diff --git a/engine-types/src/types/mod.rs b/engine-types/src/types/mod.rs index abfc2816f..87db146e9 100644 --- a/engine-types/src/types/mod.rs +++ b/engine-types/src/types/mod.rs @@ -10,7 +10,7 @@ pub mod fee; pub mod gas; pub mod wei; -pub use address::*; +pub use address::{make_address, Address}; pub use balance::*; pub use fee::*; pub use gas::*; @@ -22,11 +22,21 @@ pub type RawH256 = [u8; 32]; // Unformatted binary data of fixed length. pub type StorageUsage = u64; -/// Selector to call mint function in ERC 20 contract -/// -/// `keccak("mint(address,uint256)".as_bytes())[..4];` -#[allow(dead_code)] +/// Selector to call `mint` function in ERC 20 contract. +/// `keccak(b"mint(address,uint256)")[..4];` pub const ERC20_MINT_SELECTOR: &[u8] = &[64, 193, 15, 25]; +/// Selector to call `setMetadata` function in ERC-20 contact. +/// `keccak(b"setMetadata(string,string,uint8)")[..4];` +pub const ERC20_SET_METADATA_SELECTOR: &[u8] = &[55, 210, 194, 244]; +/// Selector to call `name` function in ERC-20 contact. +/// `keccak(b"name()")[..4];` +pub const ERC20_NAME_SELECTOR: &[u8] = &[6, 253, 222, 3]; +/// Selector to call `symbol` function in ERC-20 contact. +/// `keccak(b"symbol()")[..4];` +pub const ERC20_SYMBOL_SELECTOR: &[u8] = &[149, 216, 155, 65]; +/// Selector to call `digits` function in ERC-20 contact. +/// `keccak(b"digits()")[..4];` +pub const ERC20_DIGITS_SELECTOR: &[u8] = &[49, 60, 229, 103]; #[derive(Debug)] pub enum AddressValidationError { diff --git a/engine-workspace/src/lib.rs b/engine-workspace/src/lib.rs index bea07ef9f..cc0b7ab22 100644 --- a/engine-workspace/src/lib.rs +++ b/engine-workspace/src/lib.rs @@ -17,7 +17,7 @@ pub mod result; pub mod transaction; pub mod types { - pub use workspaces::result::ExecutionOutcome; + pub use workspaces::result::{ExecutionFinalResult, ExecutionOutcome}; pub use workspaces::types::{KeyType, SecretKey}; } diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 4b553ea3b..8db44dc1f 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aurora-engine" -version = "3.0.0" +version = "3.1.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -14,6 +14,7 @@ autobenches = false crate-type = ["cdylib", "rlib"] [dependencies] +aurora-engine-hashchain.workspace = true aurora-engine-modexp.workspace = true aurora-engine-precompiles.workspace = true aurora-engine-transactions.workspace = true @@ -22,6 +23,7 @@ aurora-engine-sdk.workspace = true bitflags.workspace = true ethabi.workspace = true evm.workspace = true +function_name.workspace = true hex.workspace = true rlp.workspace = true serde.workspace = true @@ -37,9 +39,9 @@ test-case.workspace = true [features] default = ["std"] -std = ["aurora-engine-types/std", "aurora-engine-sdk/std", "aurora-engine-precompiles/std", "aurora-engine-transactions/std", "ethabi/std", "evm/std", "hex/std", "rlp/std", "serde/std", "serde_json/std"] +std = ["aurora-engine-types/std", "aurora-engine-hashchain/std", "aurora-engine-sdk/std", "aurora-engine-precompiles/std", "aurora-engine-transactions/std", "ethabi/std", "evm/std", "hex/std", "rlp/std", "serde/std", "serde_json/std"] contract = ["aurora-engine-sdk/contract", "aurora-engine-precompiles/contract"] -borsh-compat = ["aurora-engine-types/borsh-compat", "aurora-engine-sdk/borsh-compat", "aurora-engine-precompiles/borsh-compat"] +borsh-compat = ["aurora-engine-types/borsh-compat", "aurora-engine-hashchain/borsh-compat", "aurora-engine-sdk/borsh-compat", "aurora-engine-precompiles/borsh-compat"] evm_bully = [] log = ["aurora-engine-sdk/log", "aurora-engine-precompiles/log"] tracing = ["evm/tracing"] diff --git a/engine/src/connector.rs b/engine/src/connector.rs index 2616938b2..5925f311d 100644 --- a/engine/src/connector.rs +++ b/engine/src/connector.rs @@ -58,7 +58,7 @@ pub struct EthConnector { /// The account id of the Prover NEAR smart contract. It used in the Deposit flow for verifying /// a log entry from incoming proof. pub prover_account: AccountId, - /// It is Ethereum address used in the Deposit and Withdraw logic. + /// It is Ethereum address used in the Deposit and Withdraw logic. pub eth_custodian_address: Address, } diff --git a/engine/src/contract_methods/admin.rs b/engine/src/contract_methods/admin.rs new file mode 100644 index 000000000..05c184ba8 --- /dev/null +++ b/engine/src/contract_methods/admin.rs @@ -0,0 +1,404 @@ +//! This module contains implementations for all top-level functions in the Aurora Engine +//! smart contract. All functions return `Result<(), ContractError>` because any output +//! is returned via the `IO` object and none of these functions are intended to panic. +//! Conditions which would cause the smart contract to panic are captured in the `ContractError`. +//! The actual panic happens via the `sdk_unwrap()` call where these functions are used in `lib.rs`. +//! The reason to isolate these implementations is so that they can be shared between both +//! the smart contract and the standalone. + +use crate::{ + connector::EthConnectorContract, + contract_methods::{ + predecessor_address, require_key_manager_only, require_owner_only, require_paused, + require_running, ContractError, + }, + engine::{self, Engine}, + errors, + hashchain::with_hashchain, + pausables::{ + Authorizer, EngineAuthorizer, EnginePrecompilesPauser, PausedPrecompilesChecker, + PausedPrecompilesManager, PrecompileFlags, + }, + state, +}; +use aurora_engine_hashchain::{bloom::Bloom, hashchain::Hashchain}; +use aurora_engine_modexp::AuroraModExp; +use aurora_engine_sdk::{ + env::Env, + error::ReadU64Error, + io::{StorageIntermediate, IO}, + promise::PromiseHandler, +}; +use aurora_engine_types::{ + borsh::BorshDeserialize, + parameters::{ + engine::{ + NewCallArgs, PausePrecompilesCallArgs, RelayerKeyArgs, RelayerKeyManagerArgs, + SetOwnerArgs, SetUpgradeDelayBlocksArgs, StartHashchainArgs, + }, + promise::{PromiseAction, PromiseBatchAction}, + }, + storage::{self, KeyPrefix}, + types::{Address, Yocto}, + vec, +}; +use function_name::named; + +const CODE_KEY: &[u8; 4] = b"CODE"; +const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; + +#[named] +pub fn new(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + if state::get_state(&io).is_ok() { + return Err(b"ERR_ALREADY_INITIALIZED".into()); + } + + let bytes = io.read_input().to_vec(); + let args = NewCallArgs::deserialize(&bytes).map_err(|_| errors::ERR_BORSH_DESERIALIZE)?; + state::set_state(&mut io, &args.into())?; + Ok(()) + }) +} + +pub fn get_version(mut io: I) -> Result<(), ContractError> { + let version = option_env!("NEAR_EVM_VERSION") + .map_or(&include_bytes!("../../../VERSION")[..], str::as_bytes); + io.return_output(version); + Ok(()) +} + +pub fn get_owner(mut io: I) -> Result<(), ContractError> { + let state = state::get_state(&io)?; + io.return_output(state.owner_id.as_bytes()); + Ok(()) +} + +#[named] +pub fn set_owner(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let mut state = state::get_state(&io)?; + + require_running(&state)?; + require_owner_only(&state, &env.predecessor_account_id())?; + + let args: SetOwnerArgs = io.read_input_borsh()?; + if state.owner_id == args.new_owner { + return Err(errors::ERR_SAME_OWNER.into()); + } + + state.owner_id = args.new_owner; + state::set_state(&mut io, &state)?; + + Ok(()) + }) +} + +pub fn get_bridge_prover(mut io: I) -> Result<(), ContractError> { + let connector = EthConnectorContract::init_instance(io)?; + io.return_output(connector.get_bridge_prover().as_bytes()); + Ok(()) +} + +pub fn get_chain_id(mut io: I) -> Result<(), ContractError> { + io.return_output(&state::get_state(&io)?.chain_id); + Ok(()) +} + +pub fn get_upgrade_delay_blocks(mut io: I) -> Result<(), ContractError> { + let state = state::get_state(&io)?; + io.return_output(&state.upgrade_delay_blocks.to_le_bytes()); + Ok(()) +} + +#[named] +pub fn set_upgrade_delay_blocks(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let mut state = state::get_state(&io)?; + require_running(&state)?; + require_owner_only(&state, &env.predecessor_account_id())?; + let args: SetUpgradeDelayBlocksArgs = io.read_input_borsh()?; + state.upgrade_delay_blocks = args.upgrade_delay_blocks; + state::set_state(&mut io, &state)?; + Ok(()) + }) +} + +pub fn get_upgrade_index(mut io: I) -> Result<(), ContractError> { + let index = internal_get_upgrade_index(&io)?; + io.return_output(&index.to_le_bytes()); + Ok(()) +} + +#[named] +pub fn stage_upgrade(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let delay_block_height = env.block_height() + state.upgrade_delay_blocks; + require_owner_only(&state, &env.predecessor_account_id())?; + io.read_input_and_store(&storage::bytes_to_key(KeyPrefix::Config, CODE_KEY)); + io.write_storage( + &storage::bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY), + &delay_block_height.to_le_bytes(), + ); + Ok(()) + }) +} + +#[named] +pub fn resume_precompiles(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let predecessor_account_id = env.predecessor_account_id(); + + require_owner_only(&state, &predecessor_account_id)?; + + let args: PausePrecompilesCallArgs = io.read_input_borsh()?; + let flags = PrecompileFlags::from_bits_truncate(args.paused_mask); + let mut pauser = EnginePrecompilesPauser::from_io(io); + pauser.resume_precompiles(flags); + Ok(()) + }) +} + +#[named] +pub fn pause_precompiles(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + let authorizer: EngineAuthorizer = engine::get_authorizer(&io); + + if !authorizer.is_authorized(&env.predecessor_account_id()) { + return Err(b"ERR_UNAUTHORIZED".into()); + } + + let args: PausePrecompilesCallArgs = io.read_input_borsh()?; + let flags = PrecompileFlags::from_bits_truncate(args.paused_mask); + let mut pauser = EnginePrecompilesPauser::from_io(io); + pauser.pause_precompiles(flags); + Ok(()) + }) +} + +pub fn paused_precompiles(mut io: I) -> Result<(), ContractError> { + let pauser = EnginePrecompilesPauser::from_io(io); + let data = pauser.paused().bits().to_le_bytes(); + io.return_output(&data[..]); + Ok(()) +} + +#[named] +pub fn pause_contract(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let mut state = state::get_state(&io)?; + require_owner_only(&state, &env.predecessor_account_id())?; + require_running(&state)?; + state.is_paused = true; + state::set_state(&mut io, &state)?; + Ok(()) + }) +} + +#[named] +pub fn resume_contract(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let mut state = state::get_state(&io)?; + require_owner_only(&state, &env.predecessor_account_id())?; + require_paused(&state)?; + state.is_paused = false; + state::set_state(&mut io, &state)?; + Ok(()) + }) +} + +#[named] +pub fn set_key_manager(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let mut state = state::get_state(&io)?; + + require_running(&state)?; + require_owner_only(&state, &env.predecessor_account_id())?; + + let key_manager = + serde_json::from_slice::(&io.read_input().to_vec()) + .map(|args| args.key_manager) + .map_err(|_| errors::ERR_JSON_DESERIALIZE)?; + + if state.key_manager == key_manager { + return Err(errors::ERR_SAME_KEY_MANAGER.into()); + } + + state.key_manager = key_manager; + state::set_state(&mut io, &state)?; + + Ok(()) + }) +} + +#[named] +pub fn add_relayer_key( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + + require_running(&state)?; + require_key_manager_only(&state, &env.predecessor_account_id())?; + + let public_key = serde_json::from_slice::(&io.read_input().to_vec()) + .map(|args| args.public_key) + .map_err(|_| errors::ERR_JSON_DESERIALIZE)?; + let allowance = Yocto::new(env.attached_deposit()); + aurora_engine_sdk::log!("attached key allowance: {allowance}"); + + if allowance.as_u128() < 100 { + // TODO: Clarify the minimum amount if check is needed then change error type + return Err(errors::ERR_NOT_ALLOWED.into()); + } + + engine::add_function_call_key(&mut io, &public_key); + + let current_account_id = env.current_account_id(); + let action = PromiseAction::AddFunctionCallKey { + public_key, + allowance, + nonce: 0, // not actually used - depends on block height + receiver_id: current_account_id.clone(), + function_names: "call,submit,submit_with_args".into(), + }; + let promise = PromiseBatchAction { + target_account_id: current_account_id, + actions: vec![action], + }; + + let promise_id = unsafe { handler.promise_create_batch(&promise) }; + handler.promise_return(promise_id); + + Ok(()) + }) +} + +#[named] +pub fn remove_relayer_key( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + + require_running(&state)?; + require_key_manager_only(&state, &env.predecessor_account_id())?; + + let args: RelayerKeyArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(|_| errors::ERR_JSON_DESERIALIZE)?; + + engine::remove_function_call_key(&mut io, &args.public_key)?; + + let action = PromiseAction::DeleteKey { + public_key: args.public_key, + }; + let promise = PromiseBatchAction { + target_account_id: env.current_account_id(), + actions: vec![action], + }; + + let promise_id = unsafe { handler.promise_create_batch(&promise) }; + handler.promise_return(promise_id); + + Ok(()) + }) +} + +#[named] +pub fn register_relayer(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let relayer_address = io.read_input_arr20()?; + + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(&predecessor_account_id), + current_account_id, + io, + env, + ); + engine.register_relayer( + predecessor_account_id.as_bytes(), + Address::from_array(relayer_address), + ); + Ok(()) + }) +} + +#[named] +pub fn start_hashchain(mut io: I, env: &E) -> Result<(), ContractError> { + let mut state = state::get_state(&io)?; + require_paused(&state)?; + require_key_manager_only(&state, &env.predecessor_account_id())?; + + let input = io.read_input().to_vec(); + let args = StartHashchainArgs::try_from_slice(&input).map_err(|_| errors::ERR_SERIALIZE)?; + let block_height = env.block_height(); + + // Starting hashchain must be for an earlier block + if block_height < args.block_height { + return Err(errors::ERR_ARGS.into()); + } + + let mut hashchain = Hashchain::new( + state.chain_id, + env.current_account_id(), + args.block_height + 1, + args.block_hashchain, + ); + + if hashchain.get_current_block_height() < block_height { + hashchain.move_to_block(block_height)?; + } + + hashchain.add_block_tx( + block_height, + function_name!(), + &input, + &[], + &Bloom::default(), + )?; + crate::hashchain::save_hashchain(&mut io, &hashchain)?; + + state.is_paused = false; + state::set_state(&mut io, &state)?; + + Ok(()) +} + +pub fn get_latest_hashchain(io: &mut I) -> Result<(), ContractError> { + let result = crate::hashchain::read_current_hashchain(io)?.map(|hc| { + let block_height = hc.get_current_block_height() - 1; + let hashchain = hex::encode(hc.get_previous_block_hashchain()); + serde_json::json!({ + "block_height": block_height, + "hashchain": hashchain, + }) + }); + + let bytes = serde_json::to_vec(&serde_json::json!({ "result": result })) + .map_err(|_| errors::ERR_SERIALIZE)?; + io.return_output(&bytes); + + Ok(()) +} + +fn internal_get_upgrade_index(io: &I) -> Result { + match io.read_u64(&storage::bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY)) { + Ok(index) => Ok(index), + Err(ReadU64Error::InvalidU64) => Err(errors::ERR_INVALID_UPGRADE.into()), + Err(ReadU64Error::MissingValue) => Err(errors::ERR_NO_UPGRADE.into()), + } +} diff --git a/engine/src/contract_methods/connector.rs b/engine/src/contract_methods/connector.rs new file mode 100644 index 000000000..999e17116 --- /dev/null +++ b/engine/src/contract_methods/connector.rs @@ -0,0 +1,476 @@ +use crate::{ + connector::{self, EthConnectorContract}, + contract_methods::{predecessor_address, require_owner_only, require_running, ContractError}, + engine::{self, Engine}, + errors, + hashchain::with_hashchain, + state, +}; +use aurora_engine_modexp::AuroraModExp; +use aurora_engine_sdk::{ + env::Env, + io::{StorageIntermediate, IO}, + promise::PromiseHandler, +}; +use aurora_engine_types::parameters::{ + ExitToNearPrecompileCallbackCallArgs, PromiseAction, PromiseBatchAction, +}; +use aurora_engine_types::{ + borsh::{BorshDeserialize, BorshSerialize}, + parameters::{ + connector::{ + InitCallArgs, NEP141FtOnTransferArgs, ResolveTransferCallArgs, SetContractDataCallArgs, + SetErc20MetadataArgs, StorageDepositCallArgs, StorageWithdrawCallArgs, + TransferCallArgs, TransferCallCallArgs, + }, + engine::{ + errors::ParseTypeFromJsonError, DeployErc20TokenArgs, PauseEthConnectorCallArgs, + SubmitResult, + }, + PromiseWithCallbackArgs, + }, + types::{Address, PromiseResult, Yocto}, + vec, Vec, +}; +use function_name::named; + +#[named] +pub fn ft_on_transfer( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(&predecessor_account_id), + current_account_id.clone(), + io, + env, + ); + + let args: NEP141FtOnTransferArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + + if predecessor_account_id == current_account_id { + EthConnectorContract::init_instance(io)?.ft_on_transfer(&engine, &args)?; + } else { + engine.receive_erc20_tokens( + &predecessor_account_id, + &args, + ¤t_account_id, + handler, + ); + } + Ok(()) + }) +} + +#[named] +pub fn deploy_erc20_token( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_hashchain(io, env, function_name!(), |mut io| { + require_running(&state::get_state(&io)?)?; + // Id of the NEP141 token in Near + let args: DeployErc20TokenArgs = io.read_input_borsh()?; + + let address = engine::deploy_erc20_token(args, io, env, handler)?; + + io.return_output( + &address + .as_bytes() + .try_to_vec() + .map_err(|_| errors::ERR_SERIALIZE)?, + ); + Ok(address) + }) +} + +#[named] +pub fn exit_to_near_precompile_callback( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + env.assert_private_call()?; + + // This function should only be called as the callback of + // exactly one promise. + if handler.promise_results_count() != 1 { + return Err(errors::ERR_PROMISE_COUNT.into()); + } + + let args: ExitToNearPrecompileCallbackCallArgs = io.read_input_borsh()?; + + let maybe_result = if let Some(PromiseResult::Successful(_)) = handler.promise_result(0) { + if let Some(args) = args.transfer_near { + let action = PromiseAction::Transfer { + amount: Yocto::new(args.amount), + }; + let promise = PromiseBatchAction { + target_account_id: args.target_account_id, + actions: vec![action], + }; + + // Safety: this call is safe because it comes from the exit to near precompile, not users. + // The call is to transfer the unwrapped wNEAR tokens. + let promise_id = unsafe { handler.promise_create_batch(&promise) }; + handler.promise_return(promise_id); + } + + None + } else if let Some(args) = args.refund { + // Exit call failed; need to refund tokens + let refund_result = engine::refund_on_error(io, env, state, &args, handler)?; + + if !refund_result.status.is_ok() { + return Err(errors::ERR_REFUND_FAILURE.into()); + } + + Some(refund_result) + } else { + None + }; + + Ok(maybe_result) + }) +} + +#[named] +pub fn new_eth_connector(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + // Only the owner can initialize the EthConnector + let is_private = env.assert_private_call(); + if is_private.is_err() { + require_owner_only(&state, &env.predecessor_account_id())?; + } + + let args: InitCallArgs = io.read_input_borsh()?; + let owner_id = env.current_account_id(); + + EthConnectorContract::create_contract(io, &owner_id, args)?; + Ok(()) + }) +} + +#[named] +pub fn set_eth_connector_contract_data( + io: I, + env: &E, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + require_running(&state)?; + // Only the owner can set the EthConnector contract data + let is_private = env.assert_private_call(); + if is_private.is_err() { + require_owner_only(&state, &env.predecessor_account_id())?; + } + + let args: SetContractDataCallArgs = io.read_input_borsh()?; + connector::set_contract_data(&mut io, args)?; + Ok(()) + }) +} + +#[named] +pub fn withdraw(io: I, env: &E) -> Result, ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let args = io.read_input_borsh()?; + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let result = EthConnectorContract::init_instance(io)?.withdraw_eth_from_near( + ¤t_account_id, + &predecessor_account_id, + &args, + )?; + let result_bytes = result.try_to_vec().map_err(|_| errors::ERR_SERIALIZE)?; + + // We only return the output via IO in the case of standalone. + // In the case of contract we intentionally avoid IO to call Wasm directly. + #[cfg(not(feature = "contract"))] + { + let mut io = io; + io.return_output(&result_bytes); + } + + Ok(result_bytes) + }) +} + +#[named] +pub fn deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + let raw_proof = io.read_input().to_vec(); + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let promise_args = EthConnectorContract::init_instance(io)?.deposit( + raw_proof, + current_account_id, + predecessor_account_id, + )?; + // Safety: this call is safe because it comes from the eth-connector, not users. + // The call is to verify the user-supplied proof for the deposit, with `finish_deposit` + // as a callback. + let promise_id = unsafe { handler.promise_create_with_callback(&promise_args) }; + handler.promise_return(promise_id); + Ok(promise_args) + }) +} + +#[named] +pub fn finish_deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result, ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_private_call()?; + + // Check result from proof verification call + if handler.promise_results_count() != 1 { + return Err(errors::ERR_PROMISE_COUNT.into()); + } + let promise_result = match handler.promise_result(0) { + Some(PromiseResult::Successful(bytes)) => { + bool::try_from_slice(&bytes).map_err(|_| errors::ERR_PROMISE_ENCODING)? + } + _ => return Err(errors::ERR_PROMISE_FAILED.into()), + }; + if !promise_result { + return Err(errors::ERR_VERIFY_PROOF.into()); + } + + let data = io.read_input_borsh()?; + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let maybe_promise_args = EthConnectorContract::init_instance(io)?.finish_deposit( + predecessor_account_id, + current_account_id, + data, + env.prepaid_gas(), + )?; + + if let Some(promise_args) = maybe_promise_args.as_ref() { + // Safety: this call is safe because it comes from the eth-connector, not users. + // The call will be to the Engine's ft_transfer_call`, which is needed as part + // of the bridge flow (if depositing ETH to an Aurora address). + let promise_id = unsafe { handler.promise_create_with_callback(promise_args) }; + handler.promise_return(promise_id); + } + + Ok(maybe_promise_args) + }) +} + +#[named] +pub fn ft_transfer(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let predecessor_account_id = env.predecessor_account_id(); + let args: TransferCallArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + EthConnectorContract::init_instance(io)?.ft_transfer(&predecessor_account_id, &args)?; + Ok(()) + }) +} + +#[named] +pub fn ft_resolve_transfer( + io: I, + env: &E, + handler: &H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + + env.assert_private_call()?; + if handler.promise_results_count() != 1 { + return Err(errors::ERR_PROMISE_COUNT.into()); + } + + let args: ResolveTransferCallArgs = io.read_input().to_value()?; + let promise_result = handler + .promise_result(0) + .ok_or(errors::ERR_PROMISE_ENCODING)?; + + EthConnectorContract::init_instance(io)?.ft_resolve_transfer(&args, promise_result); + Ok(()) + }) +} + +#[named] +pub fn ft_transfer_call( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + // Check is payable + env.assert_one_yocto()?; + + let args: TransferCallCallArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + let promise_args = EthConnectorContract::init_instance(io)?.ft_transfer_call( + predecessor_account_id, + current_account_id, + args, + env.prepaid_gas(), + )?; + // Safety: this call is safe. It is required by the NEP-141 spec that `ft_transfer_call` + // creates a call to another contract's `ft_on_transfer` method. + let promise_id = unsafe { handler.promise_create_with_callback(&promise_args) }; + handler.promise_return(promise_id); + Ok(promise_args) + }) +} + +#[named] +pub fn storage_deposit( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + let args: StorageDepositCallArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + let predecessor_account_id = env.predecessor_account_id(); + let amount = Yocto::new(env.attached_deposit()); + let maybe_promise = EthConnectorContract::init_instance(io)?.storage_deposit( + predecessor_account_id, + amount, + args, + )?; + if let Some(promise) = maybe_promise { + // Safety: This call is safe. It is only a transfer back to the user in the case + // that they over paid for their deposit. + unsafe { handler.promise_create_batch(&promise) }; + } + Ok(()) + }) +} + +#[named] +pub fn storage_unregister( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let predecessor_account_id = env.predecessor_account_id(); + let force = serde_json::from_slice::(&io.read_input().to_vec()) + .ok() + .and_then(|args| args["force"].as_bool()); + let maybe_promise = EthConnectorContract::init_instance(io)? + .storage_unregister(predecessor_account_id, force)?; + if let Some(promise) = maybe_promise { + // Safety: This call is safe. It is only a transfer back to the user for their deposit. + unsafe { handler.promise_create_batch(&promise) }; + } + Ok(()) + }) +} + +#[named] +pub fn storage_withdraw(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + require_running(&state::get_state(&io)?)?; + env.assert_one_yocto()?; + let args: StorageWithdrawCallArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + let predecessor_account_id = env.predecessor_account_id(); + EthConnectorContract::init_instance(io)? + .storage_withdraw(&predecessor_account_id, &args)?; + Ok(()) + }) +} + +#[named] +pub fn set_paused_flags(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let is_private = env.assert_private_call(); + if is_private.is_err() { + require_owner_only(&state, &env.predecessor_account_id())?; + } + let args: PauseEthConnectorCallArgs = io.read_input_borsh()?; + EthConnectorContract::init_instance(io)?.set_paused_flags(&args); + Ok(()) + }) +} + +#[named] +pub fn set_erc20_metadata( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + // TODO: Define special role for this transaction. Potentially via multisig? + let is_private = env.assert_private_call(); + if is_private.is_err() { + require_owner_only(&state, &env.predecessor_account_id())?; + } + + let args: SetErc20MetadataArgs = serde_json::from_slice(&io.read_input().to_vec()) + .map_err(Into::::into)?; + let current_account_id = env.current_account_id(); + let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(&env.predecessor_account_id()), + current_account_id, + io, + env, + ); + let result = engine.set_erc20_metadata(args.erc20_address, args.erc20_metadata, handler)?; + + Ok(result) + }) +} + +pub fn get_erc20_metadata(mut io: I, env: &E) -> Result<(), ContractError> { + let erc20_address = io.read_input_arr20().map(Address::from_array)?; + let state = state::get_state(&io)?; + let current_account_id = env.current_account_id(); + let engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(&env.predecessor_account_id()), + current_account_id, + io, + env, + ); + let metadata = engine.get_erc20_metadata(erc20_address)?; + + io.return_output(&serde_json::to_vec(&metadata).map_err(|_| errors::ERR_SERIALIZE)?); + Ok(()) +} diff --git a/engine/src/contract_methods/evm_transactions.rs b/engine/src/contract_methods/evm_transactions.rs new file mode 100644 index 000000000..fe713dca8 --- /dev/null +++ b/engine/src/contract_methods/evm_transactions.rs @@ -0,0 +1,142 @@ +use crate::{ + contract_methods::{predecessor_address, require_running, ContractError}, + engine::{self, Engine}, + errors, + hashchain::with_logs_hashchain, + state, +}; +use aurora_engine_modexp::AuroraModExp; +use aurora_engine_sdk::{ + env::Env, + io::{StorageIntermediate, IO}, + promise::PromiseHandler, +}; +use aurora_engine_types::{ + borsh::BorshSerialize, + parameters::engine::{CallArgs, SubmitArgs, SubmitResult}, +}; +use function_name::named; + +#[named] +pub fn deploy_code( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_logs_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let input = io.read_input().to_vec(); + let current_account_id = env.current_account_id(); + let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(&env.predecessor_account_id()), + current_account_id, + io, + env, + ); + let result = engine.deploy_code_with_input(input, handler)?; + let result_bytes = result.try_to_vec().map_err(|_| errors::ERR_SERIALIZE)?; + io.return_output(&result_bytes); + Ok(result) + }) +} + +#[named] +pub fn call( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_logs_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let bytes = io.read_input().to_vec(); + let args = CallArgs::deserialize(&bytes).ok_or(errors::ERR_BORSH_DESERIALIZE)?; + let current_account_id = env.current_account_id(); + let predecessor_account_id = env.predecessor_account_id(); + + // During the XCC flow the Engine will call itself to move wNEAR + // to the user's sub-account. We do not want this move to happen + // if prior promises in the flow have failed. + if current_account_id == predecessor_account_id { + let check_promise: Result<(), &[u8]> = match handler.promise_result_check() { + Some(true) | None => Ok(()), + Some(false) => Err(b"ERR_CALLBACK_OF_FAILED_PROMISE"), + }; + check_promise?; + } + + let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(&predecessor_account_id), + current_account_id, + io, + env, + ); + let result = engine.call_with_args(args, handler)?; + let result_bytes = result.try_to_vec().map_err(|_| errors::ERR_SERIALIZE)?; + io.return_output(&result_bytes); + Ok(result) + }) +} + +#[named] +pub fn submit( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_logs_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let tx_data = io.read_input().to_vec(); + let current_account_id = env.current_account_id(); + let relayer_address = predecessor_address(&env.predecessor_account_id()); + let args = SubmitArgs { + tx_data, + ..Default::default() + }; + let result = engine::submit( + io, + env, + &args, + state, + current_account_id, + relayer_address, + handler, + )?; + let result_bytes = result.try_to_vec().map_err(|_| errors::ERR_SERIALIZE)?; + io.return_output(&result_bytes); + + Ok(result) + }) +} + +#[named] +pub fn submit_with_args( + io: I, + env: &E, + handler: &mut H, +) -> Result { + with_logs_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + require_running(&state)?; + let args: SubmitArgs = io.read_input_borsh()?; + let current_account_id = env.current_account_id(); + let relayer_address = predecessor_address(&env.predecessor_account_id()); + let result = engine::submit( + io, + env, + &args, + state, + current_account_id, + relayer_address, + handler, + )?; + let result_bytes = result.try_to_vec().map_err(|_| errors::ERR_SERIALIZE)?; + io.return_output(&result_bytes); + + Ok(result) + }) +} diff --git a/engine/src/contract_methods/mod.rs b/engine/src/contract_methods/mod.rs new file mode 100644 index 000000000..3a0c949e9 --- /dev/null +++ b/engine/src/contract_methods/mod.rs @@ -0,0 +1,103 @@ +//! This module contains implementations for all top-level functions in the Aurora Engine +//! smart contract. All functions return `Result<(), ContractError>` because any output +//! is returned via the `IO` object and none of these functions are intended to panic. +//! Conditions which would cause the smart contract to panic are captured in the `ContractError`. +//! The actual panic happens via the `sdk_unwrap()` call where these functions are used in `lib.rs`. +//! The reason to isolate these implementations is so that they can be shared between both +//! the smart contract and the standalone. + +use crate::{errors, state}; +use aurora_engine_types::{account_id::AccountId, fmt, types::Address, Box}; + +pub mod admin; +pub mod connector; +pub mod evm_transactions; +pub mod xcc; + +pub struct ContractError { + pub message: Box + Send + Sync>, +} + +impl ContractError { + #[must_use] + pub fn msg(self) -> ErrorMessage { + ErrorMessage { + message: self.message, + } + } +} + +impl fmt::Debug for ContractError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = aurora_engine_types::str::from_utf8(self.message.as_ref().as_ref()) + .unwrap_or("NON_PRINTABLE_ERROR"); + f.debug_struct("ContractError") + .field("message", &message) + .finish() + } +} + +impl + Send + Sync + 'static> From for ContractError { + fn from(value: T) -> Self { + Self { + message: Box::new(value), + } + } +} + +/// This type is structurally the same as `ContractError`, but +/// importantly `ContractError` implements `From>` +/// for easy usage in this module's function implementations, while +/// `ErrorMessage` implements `AsRef<[u8]>` for compatibility with +/// `sdk_unwrap`. +pub struct ErrorMessage { + pub message: Box>, +} + +impl AsRef<[u8]> for ErrorMessage { + fn as_ref(&self) -> &[u8] { + self.message.as_ref().as_ref() + } +} + +fn require_running(state: &state::EngineState) -> Result<(), ContractError> { + if state.is_paused { + return Err(errors::ERR_PAUSED.into()); + } + Ok(()) +} + +fn require_paused(state: &state::EngineState) -> Result<(), ContractError> { + if !state.is_paused { + return Err(errors::ERR_RUNNING.into()); + } + Ok(()) +} + +fn require_owner_only( + state: &state::EngineState, + predecessor_account_id: &AccountId, +) -> Result<(), ContractError> { + if &state.owner_id != predecessor_account_id { + return Err(errors::ERR_NOT_ALLOWED.into()); + } + Ok(()) +} + +fn require_key_manager_only( + state: &state::EngineState, + predecessor_account_id: &AccountId, +) -> Result<(), ContractError> { + let key_manager = state + .key_manager + .as_ref() + .ok_or(errors::ERR_KEY_MANAGER_IS_NOT_SET)?; + if key_manager != predecessor_account_id { + return Err(errors::ERR_NOT_ALLOWED.into()); + } + Ok(()) +} + +fn predecessor_address(predecessor_account_id: &AccountId) -> Address { + aurora_engine_sdk::types::near_account_to_evm_address(predecessor_account_id.as_bytes()) +} diff --git a/engine/src/contract_methods/xcc.rs b/engine/src/contract_methods/xcc.rs new file mode 100644 index 000000000..2a265b93d --- /dev/null +++ b/engine/src/contract_methods/xcc.rs @@ -0,0 +1,90 @@ +use crate::{ + contract_methods::{require_owner_only, require_running, ContractError}, + errors, + hashchain::with_hashchain, + state, xcc, +}; +use aurora_engine_sdk::{ + env::Env, + io::{StorageIntermediate, IO}, + promise::PromiseHandler, +}; +use aurora_engine_types::{borsh::BorshSerialize, types::Address}; +use function_name::named; + +#[named] +pub fn factory_update(io: I, env: &E) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + require_running(&state)?; + require_owner_only(&state, &env.predecessor_account_id())?; + let bytes = io.read_input().to_vec(); + let router_bytecode = xcc::RouterCode::new(bytes); + xcc::update_router_code(&mut io, &router_bytecode); + Ok(()) + }) +} + +#[named] +pub fn factory_update_address_version( + io: I, + env: &E, + handler: &H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + require_running(&state::get_state(&io)?)?; + // The function is only set to be private, otherwise callback error will happen. + env.assert_private_call()?; + let check_deploy: Result<(), &[u8]> = match handler.promise_result_check() { + Some(true) => Ok(()), + Some(false) => Err(b"ERR_ROUTER_DEPLOY_FAILED"), + None => Err(b"ERR_ROUTER_UPDATE_NOT_CALLBACK"), + }; + check_deploy?; + let args: xcc::AddressVersionUpdateArgs = io.read_input_borsh()?; + xcc::set_code_version_of_address(&mut io, &args.address, args.version); + Ok(()) + }) +} + +#[named] +pub fn factory_set_wnear_address( + io: I, + env: &E, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |mut io| { + let state = state::get_state(&io)?; + require_running(&state)?; + require_owner_only(&state, &env.predecessor_account_id())?; + let address = io.read_input_arr20()?; + xcc::set_wnear_address(&mut io, &Address::from_array(address)); + Ok(()) + }) +} + +pub fn factory_get_wnear_address(mut io: I) -> Result<(), ContractError> { + let address = aurora_engine_precompiles::xcc::state::get_wnear_address(&io); + let bytes = address.try_to_vec().map_err(|_| errors::ERR_SERIALIZE)?; + io.return_output(&bytes); + Ok(()) +} + +#[named] +pub fn fund_xcc_sub_account( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + // This method can only be called by the owner because it allows specifying the + // account ID of the wNEAR account. This information must be accurate for the + // sub-account to work properly, therefore this method can only be called by + // a trusted user. + require_owner_only(&state, &env.predecessor_account_id())?; + let args: xcc::FundXccArgs = io.read_input_borsh()?; + xcc::fund_xcc_sub_account(&io, handler, env, args)?; + Ok(()) + }) +} diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 180ca8c5d..abee82159 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -28,12 +28,15 @@ use crate::prelude::precompiles::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, KeyPrefix, PromiseArgs, PromiseCreateArgs, ToString, Vec, Wei, - Yocto, ERC20_MINT_SELECTOR, H160, H256, U256, + BTreeMap, BorshDeserialize, KeyPrefix, PromiseArgs, PromiseCreateArgs, Vec, Wei, Yocto, + ERC20_DIGITS_SELECTOR, ERC20_MINT_SELECTOR, ERC20_NAME_SELECTOR, ERC20_SET_METADATA_SELECTOR, + ERC20_SYMBOL_SELECTOR, H160, H256, U256, }; use crate::state::EngineState; use aurora_engine_modexp::{AuroraModExp, ModExpAlgorithm}; use aurora_engine_precompiles::PrecompileConstructorContext; +use aurora_engine_types::parameters::connector::Erc20Metadata; +use aurora_engine_types::parameters::engine::FunctionCallArgsV2; use core::cell::RefCell; use core::iter::once; @@ -342,6 +345,25 @@ impl AsRef<[u8]> for RegisterTokenError { } } +#[derive(Debug)] +pub enum ReadMetadataError { + DecodeError, + WrongType, + NoValue, + EngineError(EngineErrorKind), +} + +impl AsRef<[u8]> for ReadMetadataError { + fn as_ref(&self) -> &[u8] { + match self { + Self::DecodeError => errors::ERR_DECODING_TOKEN, + Self::WrongType => errors::ERR_WRONG_TOKEN_TYPE, + Self::NoValue => errors::ERR_TOKEN_NO_VALUE, + Self::EngineError(e) => e.as_ref(), + } + } +} + pub struct StackExecutorParams<'a, I, E, H> { precompiles: Precompiles<'a, I, E, H>, gas_limit: u64, @@ -423,7 +445,7 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { account_info_cache: RefCell::new(FullCache::default()), contract_code_cache: RefCell::new(FullCache::default()), contract_storage_cache: RefCell::new(FullCache::default()), - modexp_algorithm: PhantomData::default(), + modexp_algorithm: PhantomData, } } @@ -591,9 +613,9 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { let contract = &args.address; let value = U256::from_big_endian(&args.amount); // View calls cannot interact with promises - let mut handler = aurora_engine_sdk::promise::Noop; + let handler = aurora_engine_sdk::promise::Noop; let pause_flags = EnginePrecompilesPauser::from_io(self.io).paused(); - let precompiles = self.create_precompiles(pause_flags, &mut handler); + let precompiles = self.create_precompiles(pause_flags, &handler); let executor_params = StackExecutorParams::new(u64::MAX, precompiles); self.view( @@ -784,10 +806,73 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { self.io.return_output(b"\"0\""); } + /// Read metadata of ERC-20 contract. + pub fn get_erc20_metadata( + &self, + erc20_address: Address, + ) -> Result { + let name = self + .view_with_selector( + erc20_address, + ERC20_NAME_SELECTOR, + &[ethabi::ParamType::String], + )? + .into_string() + .ok_or(ReadMetadataError::WrongType)?; + let symbol = self + .view_with_selector( + erc20_address, + ERC20_SYMBOL_SELECTOR, + &[ethabi::ParamType::String], + )? + .into_string() + .ok_or(ReadMetadataError::WrongType)?; + let decimals = self + .view_with_selector( + erc20_address, + ERC20_DIGITS_SELECTOR, + &[ethabi::ParamType::Uint(8)], + )? + .into_uint() + .ok_or(ReadMetadataError::WrongType)? + .try_into() + .map_err(|_| ReadMetadataError::WrongType)?; + + Ok(Erc20Metadata { + name, + symbol, + decimals, + }) + } + + /// Set metadata of ERC-20 contract. + pub fn set_erc20_metadata( + &mut self, + erc20_address: Address, + erc20_metadata: Erc20Metadata, + handler: &mut P, + ) -> EngineResult { + let args = ethabi::encode(&[ + ethabi::Token::String(erc20_metadata.name), + ethabi::Token::String(erc20_metadata.symbol), + ethabi::Token::Uint(erc20_metadata.decimals.into()), + ]); + let input = [ERC20_SET_METADATA_SELECTOR, &args].concat(); + + self.call_with_args( + CallArgs::V2(FunctionCallArgsV2 { + contract: erc20_address, + value: [0; 32], + input, + }), + handler, + ) + } + fn create_precompiles( &self, pause_flags: PrecompileFlags, - handler: &mut P, + handler: &P, ) -> Precompiles<'env, I, E, P::ReadOnly> { let current_account_id = self.current_account_id.clone(); let random_seed = self.env.random_seed(); @@ -821,6 +906,30 @@ impl<'env, I: IO + Copy, E: Env, M: ModExpAlgorithm> Engine<'env, I, E, M> { all_precompiles: precompiles.all_precompiles, } } + + fn view_with_selector( + &self, + contract_address: Address, + selector: &[u8], + output_types: &[ethabi::ParamType], + ) -> Result { + let result = self.view_with_args(ViewCallArgs { + sender: self.origin, + address: contract_address, + amount: [0; 32], + input: selector.to_vec(), + }); + + let output = match result.map_err(ReadMetadataError::EngineError)? { + TransactionStatus::Succeed(bytes) => bytes, + _ => Vec::new(), + }; + + ethabi::decode(output_types, &output) + .map_err(|_| ReadMetadataError::DecodeError)? + .pop() + .ok_or(ReadMetadataError::NoValue) + } } pub fn submit( @@ -998,7 +1107,7 @@ pub fn refund_on_error( let erc20_admin_address = current_address(¤t_account_id); let mut engine: Engine<_, _> = Engine::new_with_state(state, erc20_admin_address, current_account_id, io, env); - let erc20_address = erc20_address; + let refund_address = args.recipient_address; let amount = U256::from_big_endian(&args.amount); let input = setup_refund_on_error_input(amount, refund_address); @@ -1123,11 +1232,12 @@ pub fn setup_deploy_erc20_input(current_account_id: &AccountId) -> Vec { let erc20_contract = include_bytes!("../../etc/eth-contracts/res/EvmErc20.bin"); let erc20_admin_address = current_address(current_account_id); + let erc20_metadata = Erc20Metadata::default(); let deploy_args = ethabi::encode(&[ - ethabi::Token::String("Empty".to_string()), - ethabi::Token::String("EMPTY".to_string()), - ethabi::Token::Uint(ethabi::Uint::from(0)), + ethabi::Token::String(erc20_metadata.name), + ethabi::Token::String(erc20_metadata.symbol), + ethabi::Token::Uint(erc20_metadata.decimals.into()), ethabi::Token::Address(erc20_admin_address.raw()), ]); @@ -2028,6 +2138,30 @@ mod tests { assert_eq!(expected_address, actual_address); } + #[test] + fn test_get_erc20_metadata() { + let env = Fixed::default(); + let origin = aurora_engine_sdk::types::near_account_to_evm_address( + env.predecessor_account_id().as_bytes(), + ); + let current_account_id = AccountId::default(); + let storage = RefCell::new(Storage::default()); + let mut io = StoragePointer(&storage); + add_balance(&mut io, &origin, Wei::new_u64(22000)).unwrap(); + let state = EngineState::default(); + state::set_state(&mut io, &state).unwrap(); + + let engine: Engine<_, _> = + Engine::new_with_state(state, origin, current_account_id, io, &env); + let nep141 = AccountId::new("testcoin").unwrap(); + let mut handler = Noop; + let args = DeployErc20TokenArgs { nep141 }; + let erc20_address = deploy_erc20_token(args, io, &env, &mut handler).unwrap(); + let metadata = engine.get_erc20_metadata(erc20_address).unwrap(); + + assert_eq!(metadata, Erc20Metadata::default()); + } + #[test] fn test_gas_charge_for_empty_transaction_is_zero() { let origin = Address::zero(); diff --git a/engine/src/errors.rs b/engine/src/errors.rs index f1e2d1138..7c6823da7 100644 --- a/engine/src/errors.rs +++ b/engine/src/errors.rs @@ -94,3 +94,7 @@ pub const ERR_SAME_KEY_MANAGER: &[u8] = b"ERR_SAME_KEY_MANAGER"; pub const ERR_FUNCTION_CALL_KEY_NOT_FOUND: &[u8] = b"ERR_FUNCTION_CALL_KEY_NOT_FOUND"; pub const ERR_KEY_MANAGER_IS_NOT_SET: &[u8] = b"ERR_KEY_MANAGER_IS_NOT_SET"; pub const ERR_ACCOUNTS_COUNTER_OVERFLOW: &str = "ERR_ACCOUNTS_COUNTER_OVERFLOW"; +pub const ERR_DECODING_TOKEN: &[u8] = b"ERR_DECODING_TOKEN"; +pub const ERR_GETTING_TOKEN: &[u8] = b"ERR_GETTING_TOKEN"; +pub const ERR_WRONG_TOKEN_TYPE: &[u8] = b"ERR_WRONG_TOKEN_TYPE"; +pub const ERR_TOKEN_NO_VALUE: &[u8] = b"ERR_TOKEN_NO_VALUE"; diff --git a/engine/src/hashchain.rs b/engine/src/hashchain.rs new file mode 100644 index 000000000..c482bdd69 --- /dev/null +++ b/engine/src/hashchain.rs @@ -0,0 +1,115 @@ +use crate::contract_methods::ContractError; +use aurora_engine_hashchain::{ + bloom::{self, Bloom}, + error::BlockchainHashchainError, + hashchain::Hashchain, + wrapped_io::{CachedIO, IOCache}, +}; +use aurora_engine_sdk::{ + env::Env, + io::{StorageIntermediate, IO}, +}; +use aurora_engine_types::{ + parameters::engine::SubmitResult, + storage::{self, KeyPrefix}, +}; +use core::cell::RefCell; + +pub const HASHCHAIN_STATE: &[u8] = b"HC_STATE"; + +pub fn with_hashchain( + mut io: I, + env: &E, + function_name: &str, + f: F, +) -> Result +where + I: IO + Copy, + E: Env, + F: for<'a> FnOnce(CachedIO<'a, I>) -> Result, +{ + let block_height = env.block_height(); + let maybe_hashchain = load_hashchain(&io, block_height)?; + + let cache = RefCell::new(IOCache::default()); + let hashchain_io = CachedIO::new(io, &cache); + let result = f(hashchain_io)?; + + if let Some(mut hashchain) = maybe_hashchain { + let cache_ref = cache.borrow(); + hashchain.add_block_tx( + block_height, + function_name, + &cache_ref.input, + &cache_ref.output, + &Bloom::default(), + )?; + save_hashchain(&mut io, &hashchain)?; + } + + Ok(result) +} + +pub fn with_logs_hashchain( + mut io: I, + env: &E, + function_name: &str, + f: F, +) -> Result +where + I: IO + Copy, + E: Env, + F: for<'a> FnOnce(CachedIO<'a, I>) -> Result, +{ + let block_height = env.block_height(); + let maybe_hashchain = load_hashchain(&io, block_height)?; + + let cache = RefCell::new(IOCache::default()); + let hashchain_io = CachedIO::new(io, &cache); + let result = f(hashchain_io)?; + + if let Some(mut hashchain) = maybe_hashchain { + let log_bloom = bloom::get_logs_bloom(&result.logs); + let cache_ref = cache.borrow(); + hashchain.add_block_tx( + block_height, + function_name, + &cache_ref.input, + &cache_ref.output, + &log_bloom, + )?; + save_hashchain(&mut io, &hashchain)?; + } + + Ok(result) +} + +fn load_hashchain(io: &I, block_height: u64) -> Result, ContractError> { + let mut maybe_hashchain = read_current_hashchain(io)?; + if let Some(hashchain) = maybe_hashchain.as_mut() { + if block_height > hashchain.get_current_block_height() { + hashchain.move_to_block(block_height)?; + } + } + Ok(maybe_hashchain) +} + +pub fn read_current_hashchain(io: &I) -> Result, ContractError> { + let key = storage::bytes_to_key(KeyPrefix::Hashchain, HASHCHAIN_STATE); + let maybe_hashchain = io.read_storage(&key).map_or(Ok(None), |value| { + let bytes = value.to_vec(); + Hashchain::try_deserialize(&bytes) + .map(Some) + .map_err(|_| BlockchainHashchainError::DeserializationFailed) + })?; + Ok(maybe_hashchain) +} + +pub fn save_hashchain(io: &mut I, hashchain: &Hashchain) -> Result<(), ContractError> { + let key = storage::bytes_to_key(KeyPrefix::Hashchain, HASHCHAIN_STATE); + let bytes = hashchain + .try_serialize() + .map_err(|_| BlockchainHashchainError::SerializationFailed)?; + io.write_storage(&key, &bytes); + Ok(()) +} diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 5fac26883..13aa7702f 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -31,10 +31,12 @@ pub mod accounting; pub mod admin_controlled; #[cfg_attr(feature = "contract", allow(dead_code))] pub mod connector; +pub mod contract_methods; pub mod deposit_event; pub mod engine; pub mod errors; pub mod fungible_token; +pub mod hashchain; pub mod pausables; mod prelude; pub mod state; @@ -77,42 +79,26 @@ pub unsafe fn on_alloc_error(_: core::alloc::Layout) -> ! { #[cfg(feature = "contract")] mod contract { - use parameters::{SetOwnerArgs, SetUpgradeDelayBlocksArgs}; - use crate::connector::{self, EthConnectorContract}; use crate::engine::{self, Engine}; use crate::parameters::{ - self, CallArgs, DeployErc20TokenArgs, FungibleTokenMetadata, GetErc20FromNep141CallArgs, - GetStorageAtArgs, InitCallArgs, IsUsedProofCallArgs, NEP141FtOnTransferArgs, NewCallArgs, - PauseEthConnectorCallArgs, PausePrecompilesCallArgs, ResolveTransferCallArgs, - SetContractDataCallArgs, StorageDepositCallArgs, StorageWithdrawCallArgs, SubmitArgs, - TransferCallCallArgs, ViewCallArgs, + self, FungibleTokenMetadata, GetErc20FromNep141CallArgs, GetStorageAtArgs, + IsUsedProofCallArgs, ViewCallArgs, }; #[cfg(feature = "evm_bully")] use crate::parameters::{BeginBlockArgs, BeginChainArgs}; - use crate::pausables::{ - Authorizer, EnginePrecompilesPauser, PausedPrecompilesChecker, PausedPrecompilesManager, - PrecompileFlags, - }; - use crate::prelude::account_id::AccountId; - use crate::prelude::parameters::RefundCallArgs; - use crate::prelude::sdk::types::{ - near_account_to_evm_address, SdkExpect, SdkProcess, SdkUnwrap, - }; + use crate::prelude::sdk::types::{SdkExpect, SdkUnwrap}; use crate::prelude::storage::{bytes_to_key, KeyPrefix}; - use crate::prelude::{ - sdk, u256_to_arr, vec, Address, PromiseResult, ToString, Yocto, ERR_FAILED_PARSE, H256, + use crate::prelude::{sdk, u256_to_arr, Address, ERR_FAILED_PARSE, H256}; + use crate::{ + contract_methods::{self, ContractError}, + errors, state, }; - 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}; - use aurora_engine_sdk::promise::PromiseHandler; - use aurora_engine_sdk::types::ExpectUtf8; - use aurora_engine_types::borsh::{BorshDeserialize, BorshSerialize}; + use aurora_engine_types::borsh::BorshSerialize; use aurora_engine_types::parameters::engine::errors::ParseTypeFromJsonError; - use aurora_engine_types::parameters::engine::{RelayerKeyArgs, RelayerKeyManagerArgs}; - use aurora_engine_types::parameters::{PromiseAction, PromiseBatchAction}; #[cfg(feature = "integration-test")] use crate::prelude::NearGas; @@ -120,6 +106,7 @@ mod contract { const CODE_KEY: &[u8; 4] = b"CODE"; const CODE_STAGE_KEY: &[u8; 10] = b"CODE_STAGE"; + // TODO: rust-2023-08-24 #[allow(clippy::empty_line_after_doc_comments)] /// /// ADMINISTRATIVE METHODS /// @@ -128,108 +115,99 @@ mod contract { /// Should be called on deployment. #[no_mangle] pub extern "C" fn new() { - let mut io = Runtime; - - if state::get_state(&io).is_ok() { - sdk::panic_utf8(b"ERR_ALREADY_INITIALIZED"); - } - - let bytes = io.read_input().to_vec(); - let args = NewCallArgs::deserialize(&bytes).sdk_expect(errors::ERR_BORSH_DESERIALIZE); - state::set_state(&mut io, &args.into()).sdk_unwrap(); + let io = Runtime; + let env = Runtime; + contract_methods::admin::new(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Get version of the contract. #[no_mangle] pub extern "C" fn get_version() { - let mut io = Runtime; - let version = option_env!("NEAR_EVM_VERSION") - .map_or(&include_bytes!("../../VERSION")[..], str::as_bytes); - io.return_output(version); + let io = Runtime; + contract_methods::admin::get_version(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Get owner account id for this contract. #[no_mangle] 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()); + let io = Runtime; + contract_methods::admin::get_owner(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Set owner account id for this contract. #[no_mangle] pub extern "C" fn set_owner() { - let mut io = Runtime; - let mut state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - require_owner_only(&state, &io.predecessor_account_id()); - let args: SetOwnerArgs = io.read_input_borsh().sdk_unwrap(); - if state.owner_id == args.new_owner { - sdk::panic_utf8(errors::ERR_SAME_OWNER); - } else { - state.owner_id = args.new_owner; - state::set_state(&mut io, &state).sdk_unwrap(); - } + let io = Runtime; + let env = Runtime; + contract_methods::admin::set_owner(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Get bridge prover id for this contract. #[no_mangle] pub extern "C" fn get_bridge_prover() { - let mut io = Runtime; - let connector = EthConnectorContract::init_instance(io).sdk_unwrap(); - io.return_output(connector.get_bridge_prover().as_bytes()); + let io = Runtime; + contract_methods::admin::get_bridge_prover(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Get chain id for this 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); + let io = Runtime; + contract_methods::admin::get_chain_id(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn get_upgrade_delay_blocks() { - let mut io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - io.return_output(&state.upgrade_delay_blocks.to_le_bytes()); + let io = Runtime; + contract_methods::admin::get_upgrade_delay_blocks(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn set_upgrade_delay_blocks() { - let mut io = Runtime; - let mut state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - require_owner_only(&state, &io.predecessor_account_id()); - let args: SetUpgradeDelayBlocksArgs = io.read_input_borsh().sdk_unwrap(); - state.upgrade_delay_blocks = args.upgrade_delay_blocks; - state::set_state(&mut io, &state).sdk_unwrap(); + let io = Runtime; + let env = Runtime; + contract_methods::admin::set_upgrade_delay_blocks(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn get_upgrade_index() { - let mut io = Runtime; - let index = internal_get_upgrade_index(); - io.return_output(&index.to_le_bytes()); + let io = Runtime; + contract_methods::admin::get_upgrade_index(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Stage new code for deployment. #[no_mangle] pub extern "C" fn stage_upgrade() { - let mut io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - let delay_block_height = io.block_height() + state.upgrade_delay_blocks; - require_owner_only(&state, &io.predecessor_account_id()); - io.read_input_and_store(&bytes_to_key(KeyPrefix::Config, CODE_KEY)); - io.write_storage( - &bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY), - &delay_block_height.to_le_bytes(), - ); + let io = Runtime; + let env = Runtime; + contract_methods::admin::stage_upgrade(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Deploy staged upgrade. #[no_mangle] pub extern "C" fn deploy_upgrade() { + // This function is intentionally not implemented in `contract_methods` + // because it only make sense in the context of the Near runtime. let mut io = Runtime; let state = state::get_state(&io).sdk_unwrap(); require_running(&state); @@ -257,71 +235,53 @@ mod contract { #[no_mangle] pub extern "C" fn resume_precompiles() { let io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - let predecessor_account_id = io.predecessor_account_id(); - - require_owner_only(&state, &predecessor_account_id); - - let args: PausePrecompilesCallArgs = io.read_input_borsh().sdk_unwrap(); - let flags = PrecompileFlags::from_bits_truncate(args.paused_mask); - let mut pauser = EnginePrecompilesPauser::from_io(io); - pauser.resume_precompiles(flags); + let env = Runtime; + contract_methods::admin::resume_precompiles(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Pauses a precompile. #[no_mangle] pub extern "C" fn pause_precompiles() { let io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - let authorizer: pausables::EngineAuthorizer = engine::get_authorizer(&io); - - if !authorizer.is_authorized(&io.predecessor_account_id()) { - sdk::panic_utf8(b"ERR_UNAUTHORIZED"); - } - - let args: PausePrecompilesCallArgs = io.read_input_borsh().sdk_unwrap(); - let flags = PrecompileFlags::from_bits_truncate(args.paused_mask); - let mut pauser = EnginePrecompilesPauser::from_io(io); - pauser.pause_precompiles(flags); + let env = Runtime; + contract_methods::admin::pause_precompiles(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Returns an unsigned integer where each 1-bit means that a precompile corresponding to that bit is paused and /// 0-bit means not paused. #[no_mangle] pub extern "C" fn paused_precompiles() { - let mut io = Runtime; - let pauser = EnginePrecompilesPauser::from_io(io); - let data = pauser.paused().bits().to_le_bytes(); - io.return_output(&data[..]); + let io = Runtime; + contract_methods::admin::paused_precompiles(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Sets the flag to pause the contract. #[no_mangle] pub extern "C" fn pause_contract() { - let mut io = Runtime; - let mut state = state::get_state(&io).sdk_unwrap(); - require_owner_only(&state, &io.predecessor_account_id()); - if state.is_paused { - sdk::panic_utf8(errors::ERR_PAUSED); - } - state.is_paused = true; - state::set_state(&mut io, &state).sdk_unwrap(); + let io = Runtime; + let env = Runtime; + contract_methods::admin::pause_contract(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Sets the flag to resume the contract. #[no_mangle] pub extern "C" fn resume_contract() { - let mut io = Runtime; - let mut state = state::get_state(&io).sdk_unwrap(); - require_owner_only(&state, &io.predecessor_account_id()); - if !state.is_paused { - sdk::panic_utf8(errors::ERR_RUNNING); - } - state.is_paused = false; - state::set_state(&mut io, &state).sdk_unwrap(); + let io = Runtime; + let env = Runtime; + contract_methods::admin::resume_contract(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } + // TODO: rust-2023-08-24 #[allow(clippy::empty_line_after_doc_comments)] /// /// MUTATIVE METHODS /// @@ -330,54 +290,22 @@ mod contract { #[no_mangle] pub extern "C" fn deploy_code() { let io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - let input = io.read_input().to_vec(); - let current_account_id = io.current_account_id(); - let mut engine: Engine<_, _> = Engine::new( - predecessor_address(&io.predecessor_account_id()), - current_account_id, - io, - &io, - ) - .sdk_unwrap(); - Engine::deploy_code_with_input(&mut engine, input, &mut Runtime) - .map(|res| res.try_to_vec().sdk_expect(errors::ERR_SERIALIZE)) - .sdk_process(); - // TODO: charge for storage + let env = Runtime; + let mut handler = Runtime; + contract_methods::evm_transactions::deploy_code(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Call method on the EVM contract. #[no_mangle] pub extern "C" fn call() { let io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - let bytes = io.read_input().to_vec(); - let args = CallArgs::deserialize(&bytes).sdk_expect(errors::ERR_BORSH_DESERIALIZE); - let current_account_id = io.current_account_id(); - let predecessor_account_id = io.predecessor_account_id(); - - // During the XCC flow the Engine will call itself to move wNEAR - // to the user's sub-account. We do not want this move to happen - // if prior promises in the flow have failed. - if current_account_id == predecessor_account_id { - let check_promise: Result<(), &[u8]> = match io.promise_result_check() { - Some(true) | None => Ok(()), - Some(false) => Err(b"ERR_CALLBACK_OF_FAILED_PROMISE"), - }; - check_promise.sdk_unwrap(); - } - - let mut engine: Engine<_, _> = Engine::new( - predecessor_address(&predecessor_account_id), - current_account_id, - io, - &io, - ) - .sdk_unwrap(); - Engine::call_with_args(&mut engine, args, &mut Runtime) - .map(|res| res.try_to_vec().sdk_expect(errors::ERR_SERIALIZE)) - .sdk_process(); - // TODO: charge for storage + let env = Runtime; + let mut handler = Runtime; + contract_methods::evm_transactions::call(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Process signed Ethereum transaction. @@ -385,28 +313,11 @@ mod contract { #[no_mangle] pub extern "C" fn submit() { let io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - let tx_data = io.read_input().to_vec(); - let current_account_id = io.current_account_id(); - let relayer_address = predecessor_address(&io.predecessor_account_id()); - let args = SubmitArgs { - tx_data, - ..Default::default() - }; - let result = engine::submit( - io, - &io, - &args, - state, - current_account_id, - relayer_address, - &mut Runtime, - ); - - result - .map(|res| res.try_to_vec().sdk_expect(errors::ERR_SERIALIZE)) - .sdk_process(); + let env = Runtime; + let mut handler = Runtime; + contract_methods::evm_transactions::submit(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Analog of the `submit` function, but waits for the `SubmitArgs` structure rather than @@ -414,45 +325,20 @@ mod contract { #[no_mangle] pub extern "C" fn submit_with_args() { let io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - let args: SubmitArgs = io.read_input_borsh().sdk_unwrap(); - let current_account_id = io.current_account_id(); - let relayer_address = predecessor_address(&io.predecessor_account_id()); - let result = engine::submit( - io, - &io, - &args, - state, - current_account_id, - relayer_address, - &mut Runtime, - ); - - result - .map(|res| res.try_to_vec().sdk_expect(errors::ERR_SERIALIZE)) - .sdk_process(); + let env = Runtime; + let mut handler = Runtime; + contract_methods::evm_transactions::submit_with_args(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn register_relayer() { let io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - let relayer_address = io.read_input_arr20().sdk_unwrap(); - - let current_account_id = io.current_account_id(); - let predecessor_account_id = io.predecessor_account_id(); - let mut engine: Engine<_, _> = Engine::new( - predecessor_address(&predecessor_account_id), - current_account_id, - io, - &io, - ) - .sdk_unwrap(); - engine.register_relayer( - predecessor_account_id.as_bytes(), - Address::from_array(relayer_address), - ); + let env = Runtime; + contract_methods::admin::register_relayer(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Updates the bytecode for user's router contracts created by the engine. @@ -460,52 +346,43 @@ mod contract { /// will be sent from. #[no_mangle] pub extern "C" fn factory_update() { - let mut io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - require_owner_only(&state, &io.predecessor_account_id()); - let bytes = io.read_input().to_vec(); - let router_bytecode = crate::xcc::RouterCode::new(bytes); - crate::xcc::update_router_code(&mut io, &router_bytecode); + let io = Runtime; + let env = Runtime; + contract_methods::xcc::factory_update(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Updates the bytecode version for the given account. This is only called as a callback /// when a new version of the router contract is deployed to an account. #[no_mangle] pub extern "C" fn factory_update_address_version() { - let mut io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - // The function is only set to be private, otherwise callback error will happen. - io.assert_private_call().sdk_unwrap(); - let check_deploy: Result<(), &[u8]> = match io.promise_result_check() { - Some(true) => Ok(()), - Some(false) => Err(b"ERR_ROUTER_DEPLOY_FAILED"), - None => Err(b"ERR_ROUTER_UPDATE_NOT_CALLBACK"), - }; - check_deploy.sdk_unwrap(); - let args: crate::xcc::AddressVersionUpdateArgs = io.read_input_borsh().sdk_unwrap(); - crate::xcc::set_code_version_of_address(&mut io, &args.address, args.version); + let io = Runtime; + let env = Runtime; + let handler = Runtime; + contract_methods::xcc::factory_update_address_version(io, &env, &handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Sets the address for the `wNEAR` ERC-20 contract. This contract will be used by the /// cross-contract calls feature to have users pay for their NEAR transactions. #[no_mangle] pub extern "C" fn factory_set_wnear_address() { - let mut io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - 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)); + let io = Runtime; + let env = Runtime; + contract_methods::xcc::factory_set_wnear_address(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Returns the address for the `wNEAR` ERC-20 contract in borsh format. #[no_mangle] pub extern "C" fn factory_get_wnear_address() { - let mut io = Runtime; - let address = aurora_engine_precompiles::xcc::state::get_wnear_address(&io); - let bytes = address.try_to_vec().sdk_expect(errors::ERR_SERIALIZE); - io.return_output(&bytes); + let io = Runtime; + contract_methods::xcc::factory_get_wnear_address(io) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Create and/or fund an XCC sub-account directly (as opposed to having one be automatically @@ -514,15 +391,11 @@ mod contract { #[no_mangle] pub extern "C" fn fund_xcc_sub_account() { let io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - // This method can only be called by the owner because it allows specifying the - // account ID of the wNEAR account. This information must be accurate for the - // sub-account to work properly, therefore this method can only be called by - // a trusted user. - require_owner_only(&state, &io.predecessor_account_id()); - let args: crate::xcc::FundXccArgs = io.read_input_borsh().sdk_unwrap(); - crate::xcc::fund_xcc_sub_account(&io, &mut Runtime, &io, args).sdk_unwrap(); + let env = Runtime; + let mut handler = Runtime; + contract_methods::xcc::fund_xcc_sub_account(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Allow receiving NEP141 tokens to the EVM contract. @@ -533,169 +406,87 @@ mod contract { #[no_mangle] pub extern "C" fn ft_on_transfer() { let io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - let current_account_id = io.current_account_id(); - let predecessor_account_id = io.predecessor_account_id(); - let mut engine: Engine<_, _> = Engine::new( - predecessor_address(&predecessor_account_id), - current_account_id.clone(), - io, - &io, - ) - .sdk_unwrap(); - - let args: NEP141FtOnTransferArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into) + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::ft_on_transfer(io, &env, &mut handler) + .map_err(ContractError::msg) .sdk_unwrap(); - - if predecessor_account_id == current_account_id { - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .ft_on_transfer(&engine, &args) - .sdk_unwrap(); - } else { - engine.receive_erc20_tokens( - &predecessor_account_id, - &args, - ¤t_account_id, - &mut Runtime, - ); - } } /// Deploy ERC20 token mapped to a NEP141 #[no_mangle] pub extern "C" fn deploy_erc20_token() { - let mut io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - // Id of the NEP141 token in Near - let args: DeployErc20TokenArgs = io.read_input_borsh().sdk_unwrap(); - - let address = engine::deploy_erc20_token(args, io, &io, &mut Runtime).sdk_unwrap(); - - io.return_output( - &address - .as_bytes() - .try_to_vec() - .sdk_expect(errors::ERR_SERIALIZE), - ); + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::deploy_erc20_token(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); + } - // TODO: charge for storage + /// Set metadata of ERC-20 contract. + #[no_mangle] + pub extern "C" fn set_erc20_metadata() { + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::set_erc20_metadata(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Callback invoked by exit to NEAR precompile to handle potential - /// errors in the exit call. + /// errors in the exit call or to perform the near tokens transfer. #[no_mangle] - pub extern "C" fn refund_on_error() { + pub extern "C" fn exit_to_near_precompile_callback() { let io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - io.assert_private_call().sdk_unwrap(); - - // This function should only be called as the callback of - // exactly one promise. - if io.promise_results_count() != 1 { - sdk::panic_utf8(errors::ERR_PROMISE_COUNT); - } - - if let Some(PromiseResult::Successful(_)) = io.promise_result(0) { - // Promise succeeded -- nothing to do - } else { - // Exit call failed; need to refund tokens - let args: RefundCallArgs = io.read_input_borsh().sdk_unwrap(); - let refund_result = - engine::refund_on_error(io, &io, state, &args, &mut Runtime).sdk_unwrap(); - - if !refund_result.status.is_ok() { - sdk::panic_utf8(errors::ERR_REFUND_FAILURE); - } - } + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::exit_to_near_precompile_callback(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Sets relayer key manager. #[no_mangle] pub extern "C" fn set_key_manager() { - let mut io = Runtime; - let mut state = state::get_state(&io).sdk_unwrap(); - - require_running(&state); - require_owner_only(&state, &io.predecessor_account_id()); - - let key_manager = - serde_json::from_slice::(&io.read_input().to_vec()) - .map(|args| args.key_manager) - .sdk_expect(errors::ERR_JSON_DESERIALIZE); - - if state.key_manager == key_manager { - sdk::panic_utf8(errors::ERR_SAME_KEY_MANAGER) - } else { - state.key_manager = key_manager; - state::set_state(&mut io, &state).sdk_unwrap(); - } + let io = Runtime; + let env = Runtime; + contract_methods::admin::set_key_manager(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Adds a relayer function call key. #[no_mangle] pub extern "C" fn add_relayer_key() { - let mut io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - - require_running(&state); - require_key_manager_only(&state, &io.predecessor_account_id()); - - let public_key = serde_json::from_slice::(&io.read_input().to_vec()) - .map(|args| args.public_key) - .sdk_expect(errors::ERR_JSON_DESERIALIZE); - let allowance = Yocto::new(io.attached_deposit()); - sdk::log!("attached key allowance: {allowance}"); - - if allowance.as_u128() < 100 { - // TODO: Clarify the minimum amount if check is needed then change error type - sdk::panic_utf8(errors::ERR_NOT_ALLOWED); - } - - engine::add_function_call_key(&mut io, &public_key); - - let action = PromiseAction::AddFunctionCallKey { - public_key, - allowance, - nonce: 0, // not actually used - depends on block height - receiver_id: io.current_account_id(), - function_names: "call,submit,submit_with_args".to_string(), - }; - let promise = PromiseBatchAction { - target_account_id: io.current_account_id(), - actions: vec![action], - }; - - let promise_id = unsafe { io.promise_create_batch(&promise) }; - io.promise_return(promise_id); + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::admin::add_relayer_key(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// Removes a relayer function call key. #[no_mangle] pub extern "C" fn remove_relayer_key() { - let mut io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - - require_running(&state); - require_key_manager_only(&state, &io.predecessor_account_id()); - - let args: RelayerKeyArgs = serde_json::from_slice(&io.read_input().to_vec()) - .sdk_expect(errors::ERR_JSON_DESERIALIZE); - - engine::remove_function_call_key(&mut io, &args.public_key).sdk_unwrap(); - - let action = PromiseAction::DeleteKey { - public_key: args.public_key, - }; - let promise = PromiseBatchAction { - target_account_id: io.current_account_id(), - actions: vec![action], - }; + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::admin::remove_relayer_key(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); + } - let promise_id = unsafe { io.promise_create_batch(&promise) }; - io.promise_return(promise_id); + /// Initialize the hashchain. + #[no_mangle] + pub extern "C" fn start_hashchain() { + let io = Runtime; + let env = Runtime; + contract_methods::admin::start_hashchain(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } /// @@ -760,6 +551,24 @@ mod contract { io.return_output(&value.0); } + #[no_mangle] + pub extern "C" fn get_latest_hashchain() { + let mut io = Runtime; + contract_methods::admin::get_latest_hashchain(&mut io) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + + /// Return metadata of the ERC-20 contract. + #[no_mangle] + pub extern "C" fn get_erc20_metadata() { + let io = Runtime; + let env = ViewEnv; + contract_methods::connector::get_erc20_metadata(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + /// /// BENCHMARKING METHODS /// @@ -769,7 +578,9 @@ mod contract { use crate::prelude::U256; let mut io = Runtime; let mut state = state::get_state(&io).sdk_unwrap(); - require_owner_only(&state, &io.predecessor_account_id()); + if state.owner_id != io.predecessor_account_id() { + sdk::panic_utf8(errors::ERR_NOT_ALLOWED); + } let args: BeginChainArgs = io.read_input_borsh().sdk_unwrap(); state.chain_id = args.chain_id; state::set_state(&mut io, &state).sdk_unwrap(); @@ -790,7 +601,9 @@ mod contract { pub extern "C" fn begin_block() { let io = Runtime; let state = state::get_state(&io).sdk_unwrap(); - require_owner_only(&state, &io.predecessor_account_id()); + if state.owner_id != io.predecessor_account_id() { + sdk::panic_utf8(errors::ERR_NOT_ALLOWED); + } let _args: BeginBlockArgs = io.read_input_borsh().sdk_unwrap(); // TODO: https://github.com/aurora-is-near/aurora-engine/issues/2 } @@ -798,48 +611,28 @@ mod contract { #[no_mangle] pub extern "C" fn new_eth_connector() { let io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - // Only the owner can initialize the EthConnector - let is_private = io.assert_private_call(); - if is_private.is_err() { - require_owner_only(&state, &io.predecessor_account_id()); - } - - let args: InitCallArgs = io.read_input_borsh().sdk_unwrap(); - let owner_id = io.current_account_id(); - - EthConnectorContract::create_contract(io, &owner_id, args).sdk_unwrap(); + let env = Runtime; + contract_methods::connector::new_eth_connector(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn set_eth_connector_contract_data() { - let mut io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - // Only the owner can set the EthConnector contract data - let is_private = io.assert_private_call(); - if is_private.is_err() { - require_owner_only(&state, &io.predecessor_account_id()); - } - - let args: SetContractDataCallArgs = io.read_input_borsh().sdk_unwrap(); - connector::set_contract_data(&mut io, args).sdk_unwrap(); + let io = Runtime; + let env = Runtime; + contract_methods::connector::set_eth_connector_contract_data(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn withdraw() { let io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - io.assert_one_yocto().sdk_unwrap(); - let args = io.read_input_borsh().sdk_unwrap(); - let current_account_id = io.current_account_id(); - let predecessor_account_id = io.predecessor_account_id(); - let result = EthConnectorContract::init_instance(io) - .sdk_unwrap() - .withdraw_eth_from_near(¤t_account_id, &predecessor_account_id, &args) + let env = Runtime; + let result_bytes = contract_methods::connector::withdraw(io, &env) + .map_err(ContractError::msg) .sdk_unwrap(); - let result_bytes = result.try_to_vec().sdk_expect(errors::ERR_SERIALIZE); // We intentionally do not go through the `io` struct here because we must bypass // the check that prevents output that is accepted by the eth_custodian #[allow(clippy::as_conversions)] @@ -853,62 +646,22 @@ mod contract { #[no_mangle] pub extern "C" fn deposit() { - let mut io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - let raw_proof = io.read_input().to_vec(); - let current_account_id = io.current_account_id(); - let predecessor_account_id = io.predecessor_account_id(); - let promise_args = EthConnectorContract::init_instance(io) - .sdk_unwrap() - .deposit(raw_proof, current_account_id, predecessor_account_id) + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + let _ = contract_methods::connector::deposit(io, &env, &mut handler) + .map_err(ContractError::msg) .sdk_unwrap(); - // Safety: this call is safe because it comes from the eth-connector, not users. - // The call is to verify the user-supplied proof for the deposit, with `finish_deposit` - // as a callback. - let promise_id = unsafe { io.promise_create_with_callback(&promise_args) }; - io.promise_return(promise_id); } #[no_mangle] pub extern "C" fn finish_deposit() { - let mut io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - io.assert_private_call().sdk_unwrap(); - - // Check result from proof verification call - if io.promise_results_count() != 1 { - sdk::panic_utf8(errors::ERR_PROMISE_COUNT); - } - let promise_result = match io.promise_result(0) { - Some(PromiseResult::Successful(bytes)) => { - bool::try_from_slice(&bytes).sdk_expect(errors::ERR_PROMISE_ENCODING) - } - _ => sdk::panic_utf8(errors::ERR_PROMISE_FAILED), - }; - if !promise_result { - sdk::panic_utf8(errors::ERR_VERIFY_PROOF); - } - - let data = io.read_input_borsh().sdk_unwrap(); - let current_account_id = io.current_account_id(); - let predecessor_account_id = io.predecessor_account_id(); - let maybe_promise_args = EthConnectorContract::init_instance(io) - .sdk_unwrap() - .finish_deposit( - predecessor_account_id, - current_account_id, - data, - io.prepaid_gas(), - ) + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::finish_deposit(io, &env, &mut handler) + .map_err(ContractError::msg) .sdk_unwrap(); - - if let Some(promise_args) = maybe_promise_args { - // Safety: this call is safe because it comes from the eth-connector, not users. - // The call will be to the Engine's ft_transfer_call`, which is needed as part - // of the bridge flow (if depositing ETH to an Aurora address). - let promise_id = unsafe { io.promise_create_with_callback(&promise_args) }; - io.promise_return(promise_id); - } } #[no_mangle] @@ -971,114 +724,58 @@ mod contract { #[no_mangle] pub extern "C" fn ft_transfer() { let io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - io.assert_one_yocto().sdk_unwrap(); - let predecessor_account_id = io.predecessor_account_id(); - let args: parameters::TransferCallArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into) - .sdk_unwrap(); - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .ft_transfer(&predecessor_account_id, &args) + let env = Runtime; + contract_methods::connector::ft_transfer(io, &env) + .map_err(ContractError::msg) .sdk_unwrap(); } #[no_mangle] pub extern "C" fn ft_resolve_transfer() { let io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - - io.assert_private_call().sdk_unwrap(); - if io.promise_results_count() != 1 { - sdk::panic_utf8(errors::ERR_PROMISE_COUNT); - } - - let args: ResolveTransferCallArgs = io.read_input().to_value().sdk_unwrap(); - let promise_result = io.promise_result(0).sdk_unwrap(); - - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .ft_resolve_transfer(&args, promise_result); + let env = Runtime; + let handler = Runtime; + contract_methods::connector::ft_resolve_transfer(io, &env, &handler) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] pub extern "C" fn ft_transfer_call() { - let mut io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - // Check is payable - io.assert_one_yocto().sdk_unwrap(); - - let args: TransferCallCallArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into) - .sdk_unwrap(); - let current_account_id = io.current_account_id(); - let predecessor_account_id = io.predecessor_account_id(); - let promise_args = EthConnectorContract::init_instance(io) - .sdk_unwrap() - .ft_transfer_call( - predecessor_account_id, - current_account_id, - args, - io.prepaid_gas(), - ) + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + let _ = contract_methods::connector::ft_transfer_call(io, &env, &mut handler) + .map_err(ContractError::msg) .sdk_unwrap(); - // Safety: this call is safe. It is required by the NEP-141 spec that `ft_transfer_call` - // creates a call to another contract's `ft_on_transfer` method. - let promise_id = unsafe { io.promise_create_with_callback(&promise_args) }; - io.promise_return(promise_id); } #[no_mangle] pub extern "C" fn storage_deposit() { - let mut io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - let args: StorageDepositCallArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into) - .sdk_unwrap(); - let predecessor_account_id = io.predecessor_account_id(); - let amount = Yocto::new(io.attached_deposit()); - let maybe_promise = EthConnectorContract::init_instance(io) - .sdk_unwrap() - .storage_deposit(predecessor_account_id, amount, args) + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::storage_deposit(io, &env, &mut handler) + .map_err(ContractError::msg) .sdk_unwrap(); - if let Some(promise) = maybe_promise { - // Safety: This call is safe. It is only a transfer back to the user in the case - // that they over paid for their deposit. - unsafe { io.promise_create_batch(&promise) }; - } } #[no_mangle] pub extern "C" fn storage_unregister() { - let mut io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - io.assert_one_yocto().sdk_unwrap(); - let predecessor_account_id = io.predecessor_account_id(); - let force = serde_json::from_slice::(&io.read_input().to_vec()) - .ok() - .and_then(|args| args["force"].as_bool()); - let maybe_promise = EthConnectorContract::init_instance(io) - .sdk_unwrap() - .storage_unregister(predecessor_account_id, force) + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::connector::storage_unregister(io, &env, &mut handler) + .map_err(ContractError::msg) .sdk_unwrap(); - if let Some(promise) = maybe_promise { - // Safety: This call is safe. It is only a transfer back to the user for their deposit. - unsafe { io.promise_create_batch(&promise) }; - } } #[no_mangle] pub extern "C" fn storage_withdraw() { let io = Runtime; - require_running(&state::get_state(&io).sdk_unwrap()); - io.assert_one_yocto().sdk_unwrap(); - let args: StorageWithdrawCallArgs = serde_json::from_slice(&io.read_input().to_vec()) - .map_err(Into::::into) - .sdk_unwrap(); - let predecessor_account_id = io.predecessor_account_id(); - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .storage_withdraw(&predecessor_account_id, &args) + let env = Runtime; + contract_methods::connector::storage_withdraw(io, &env) + .map_err(ContractError::msg) .sdk_unwrap(); } @@ -1107,16 +804,10 @@ mod contract { #[no_mangle] pub extern "C" fn set_paused_flags() { let io = Runtime; - let state = state::get_state(&io).sdk_unwrap(); - require_running(&state); - let is_private = io.assert_private_call(); - if is_private.is_err() { - require_owner_only(&state, &io.predecessor_account_id()); - } - let args: PauseEthConnectorCallArgs = io.read_input_borsh().sdk_unwrap(); - EthConnectorContract::init_instance(io) - .sdk_unwrap() - .set_paused_flags(&args); + let env = Runtime; + contract_methods::connector::set_paused_flags(io, &env) + .map_err(ContractError::msg) + .sdk_unwrap(); } #[no_mangle] @@ -1225,6 +916,7 @@ mod contract { }; // Safety: this call is safe because it is only used in integration tests. unsafe { + use aurora_engine_sdk::promise::PromiseHandler; io.promise_create_with_callback( &aurora_engine_types::parameters::PromiseWithCallbackArgs { base: verify_call, @@ -1234,6 +926,7 @@ mod contract { }; } + // TODO: rust-2023-08-24#[allow(clippy::empty_line_after_doc_comments)] /// /// Utility methods. /// @@ -1249,32 +942,12 @@ mod contract { } } - fn require_owner_only(state: &state::EngineState, predecessor_account_id: &AccountId) { - if &state.owner_id != predecessor_account_id { - sdk::panic_utf8(errors::ERR_NOT_ALLOWED); - } - } - fn require_running(state: &state::EngineState) { if state.is_paused { sdk::panic_utf8(errors::ERR_PAUSED); } } - fn require_key_manager_only(state: &state::EngineState, predecessor_account_id: &AccountId) { - let key_manager = state - .key_manager - .as_ref() - .expect_utf8(errors::ERR_KEY_MANAGER_IS_NOT_SET); - if key_manager != predecessor_account_id { - sdk::panic_utf8(errors::ERR_NOT_ALLOWED); - } - } - - fn predecessor_address(predecessor_account_id: &AccountId) -> Address { - near_account_to_evm_address(predecessor_account_id.as_bytes()) - } - mod exports { extern "C" { pub(crate) fn value_return(value_len: u64, value_ptr: u64); diff --git a/engine/src/xcc.rs b/engine/src/xcc.rs index 552c53589..6ca60f68f 100644 --- a/engine/src/xcc.rs +++ b/engine/src/xcc.rs @@ -21,7 +21,7 @@ pub const CODE_KEY: &[u8] = b"router_code"; pub const VERSION_UPDATE_GAS: NearGas = NearGas::new(5_000_000_000_000); pub const INITIALIZE_GAS: NearGas = NearGas::new(15_000_000_000_000); pub const UNWRAP_AND_REFUND_GAS: NearGas = NearGas::new(25_000_000_000_000); -pub const WITHDRAW_GAS: NearGas = NearGas::new(30_000_000_000_000); +pub const WITHDRAW_GAS: NearGas = NearGas::new(40_000_000_000_000); /// Solidity selector for the `withdrawToNear` function /// `https://www.4byte.directory/signatures/?bytes4_signature=0x6b351848` pub const WITHDRAW_TO_NEAR_SELECTOR: [u8; 4] = [0x6b, 0x35, 0x18, 0x48];