From 0d0be23b8d142759a635f40248fed29dabb11d80 Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Tue, 19 Oct 2021 21:05:31 +0200 Subject: [PATCH] Feat(engine): Make engine parametric in storage access (#314) --- engine-precompiles/src/native.rs | 7 +- engine-sdk/src/error.rs | 47 ++++ engine-sdk/src/io.rs | 138 +++++++++++ engine-sdk/src/lib.rs | 349 +-------------------------- engine-sdk/src/near_runtime.rs | 289 +++++++++++++++++++++++ engine-sdk/src/types.rs | 5 +- engine-tests/src/test_utils/mod.rs | 2 +- engine-tests/src/tests/sanity.rs | 3 +- engine/src/connector.rs | 150 ++++++------ engine/src/engine.rs | 350 ++++++++++++++++------------ engine/src/fungible_token.rs | 47 ++-- engine/src/lib.rs | 288 ++++++++++++++--------- engine/src/map.rs | 60 +++-- etc/state-migration-test/src/lib.rs | 17 +- 14 files changed, 1017 insertions(+), 735 deletions(-) create mode 100644 engine-sdk/src/error.rs create mode 100644 engine-sdk/src/io.rs create mode 100644 engine-sdk/src/near_runtime.rs diff --git a/engine-precompiles/src/native.rs b/engine-precompiles/src/native.rs index e8bde29c5..32c518360 100644 --- a/engine-precompiles/src/native.rs +++ b/engine-precompiles/src/native.rs @@ -201,9 +201,12 @@ impl ExitToNear { #[cfg(feature = "contract")] fn get_nep141_from_erc20(erc20_token: &[u8]) -> AccountId { + use sdk::io::{StorageIntermediate, IO}; AccountId::try_from( - &sdk::read_storage(bytes_to_key(KeyPrefix::Erc20Nep141Map, erc20_token).as_slice()) - .expect(ERR_TARGET_TOKEN_NOT_FOUND)[..], + sdk::near_runtime::Runtime + .read_storage(bytes_to_key(KeyPrefix::Erc20Nep141Map, erc20_token).as_slice()) + .map(|s| s.to_vec()) + .expect(ERR_TARGET_TOKEN_NOT_FOUND), ) .unwrap() } diff --git a/engine-sdk/src/error.rs b/engine-sdk/src/error.rs new file mode 100644 index 000000000..afa10702c --- /dev/null +++ b/engine-sdk/src/error.rs @@ -0,0 +1,47 @@ +#[derive(Debug)] +pub struct BorshDeserializeError; + +impl AsRef<[u8]> for BorshDeserializeError { + fn as_ref(&self) -> &[u8] { + b"ERR_ARG_PARSE" + } +} + +#[derive(Debug)] +pub struct IncorrectInputLength; + +impl AsRef<[u8]> for IncorrectInputLength { + fn as_ref(&self) -> &[u8] { + b"ERR_INCORRECT_INPUT_LENGTH" + } +} + +#[derive(Debug)] +pub enum ReadU64Error { + InvalidU64, + MissingValue, +} + +impl AsRef<[u8]> for ReadU64Error { + fn as_ref(&self) -> &[u8] { + match self { + Self::InvalidU64 => b"ERR_NOT_U64", + Self::MissingValue => b"ERR_U64_NOT_FOUND", + } + } +} + +#[derive(Debug)] +pub enum ReadU256Error { + InvalidU256, + MissingValue, +} + +impl AsRef<[u8]> for ReadU256Error { + fn as_ref(&self) -> &[u8] { + match self { + Self::InvalidU256 => b"ERR_NOT_U256", + Self::MissingValue => b"ERR_U256_NOT_FOUND", + } + } +} diff --git a/engine-sdk/src/io.rs b/engine-sdk/src/io.rs new file mode 100644 index 000000000..05642d45f --- /dev/null +++ b/engine-sdk/src/io.rs @@ -0,0 +1,138 @@ +use crate::error; +use crate::prelude::{vec, Vec}; +use aurora_engine_types::U256; +use borsh::{BorshDeserialize, BorshSerialize}; + +/// The purpose of this trait is to represent a reference to a value that +/// could be obtained by IO, but without eagerly loading it into memory. +/// For example, the NEAR runtime registers API allows querying the length +/// of some bytes read from input or storage without loading them into the +/// wasm memory. +pub trait StorageIntermediate: Sized { + fn len(&self) -> usize; + fn is_empty(&self) -> bool; + fn copy_to_slice(&self, buffer: &mut [u8]); + + fn to_vec(&self) -> Vec { + let size = self.len(); + let mut buf = vec![0u8; size]; + self.copy_to_slice(&mut buf); + buf + } + + fn to_value(&self) -> Result { + let bytes = self.to_vec(); + T::try_from_slice(&bytes).map_err(|_| error::BorshDeserializeError) + } +} + +/// Trait for reading/writing values from storage and a generalized `stdin`/`stdout`. +pub trait IO { + /// A type giving a reference to a value obtained by IO without loading it + /// into memory. For example, in the case of a wasm contract on NEAR this + /// will correspond to a register index. + type StorageValue: StorageIntermediate; + + /// Read bytes that were passed as input to the process. This can be thought of as a + /// generalization of `stdin` or command-line arguments. In the case of wasm contracts + /// on NEAR these would be the arguments to the method. + fn read_input(&self) -> Self::StorageValue; + + /// Return a value to an external process. In the case of wasm contracts on NEAR + /// this corresponds to the return value from the contract method. + fn return_output(&mut self, value: &[u8]); + + /// Read the value in storage at the given key, if any. + fn read_storage(&self, key: &[u8]) -> Option; + + /// Check if there is a value in storage at the given key, but do not read the value. + /// Equivalent to `self.read_storage(key).is_some()` but more efficient. + fn storage_has_key(&self, key: &[u8]) -> bool; + + /// Write the given value to storage under the given key. Returns a reference to the old + /// value stored at that key (if any). + fn write_storage(&mut self, key: &[u8], value: &[u8]) -> Option; + + /// Write a `StorageIntermediate` to storage directly under the given key + /// (without ever needing to load the value into memory).Returns a reference + /// to the old value stored at that key (if any). + fn write_storage_direct( + &mut self, + key: &[u8], + value: Self::StorageValue, + ) -> Option; + + /// Remove entry from storage and capture the value present at the given key (if any) + fn remove_storage(&mut self, key: &[u8]) -> Option; + + /// Read the length of the bytes stored at the given key. + fn read_storage_len(&self, key: &[u8]) -> Option { + self.read_storage(key).map(|s| s.len()) + } + + /// Convenience function to read the input and deserialize the bytes using borsh. + fn read_input_borsh(&self) -> Result { + self.read_input().to_value() + } + + /// Convenience function to read the input into a 20-byte array. + fn read_input_arr20(&self) -> Result<[u8; 20], error::IncorrectInputLength> { + let value = self.read_input(); + + if value.len() != 20 { + return Err(error::IncorrectInputLength); + } + + let mut buf = [0u8; 20]; + value.copy_to_slice(&mut buf); + Ok(buf) + } + + /// Convenience function to store the input directly in storage under the + /// given key (without ever loading it into memory). + fn read_input_and_store(&mut self, key: &[u8]) { + let value = self.read_input(); + self.write_storage_direct(key, value); + } + + /// Convenience function to read a 64-bit unsigned integer from storage + /// (assumes little-endian encoding). + fn read_u64(&self, key: &[u8]) -> Result { + let value = self + .read_storage(key) + .ok_or(error::ReadU64Error::MissingValue)?; + + if value.len() != 8 { + return Err(error::ReadU64Error::InvalidU64); + } + + let mut result = [0u8; 8]; + value.copy_to_slice(&mut result); + Ok(u64::from_le_bytes(result)) + } + + /// Convenience function to read a 256-bit unsigned integer from storage + /// (assumes big-endian encoding). + fn read_u256(&self, key: &[u8]) -> Result { + let value = self + .read_storage(key) + .ok_or(error::ReadU256Error::MissingValue)?; + + if value.len() != 32 { + return Err(error::ReadU256Error::InvalidU256); + } + + let mut result = [0u8; 32]; + value.copy_to_slice(&mut result); + Ok(U256::from_big_endian(&result)) + } + + fn write_borsh( + &mut self, + key: &[u8], + value: &T, + ) -> Option { + let bytes = value.try_to_vec().ok()?; + self.write_storage(key, &bytes) + } +} diff --git a/engine-sdk/src/lib.rs b/engine-sdk/src/lib.rs index 57857c3a7..0d9d3acac 100644 --- a/engine-sdk/src/lib.rs +++ b/engine-sdk/src/lib.rs @@ -3,321 +3,23 @@ #![cfg_attr(not(feature = "std"), feature(alloc_error_handler))] #![cfg_attr(feature = "log", feature(panic_info_message))] -use crate::prelude::{ - vec, Address, BorshDeserialize, BorshSerialize, PromiseResult, Vec, H256, - STORAGE_PRICE_PER_BYTE, -}; +use crate::prelude::{vec, Address, PromiseResult, Vec, H256, STORAGE_PRICE_PER_BYTE}; pub use types::keccak; +pub mod error; +pub mod io; +pub mod near_runtime; mod prelude; pub mod types; -const READ_STORAGE_REGISTER_ID: u64 = 0; -const INPUT_REGISTER_ID: u64 = 0; +use near_runtime::exports; + const ECRECOVER_MESSAGE_SIZE: u64 = 32; const ECRECOVER_SIGNATURE_LENGTH: u64 = 64; const ECRECOVER_MALLEABILITY_FLAG: u64 = 1; -/// Register used to record evicted values from the storage. -const EVICTED_REGISTER: u64 = 0; - const GAS_FOR_STATE_MIGRATION: u64 = 100_000_000_000_000; -mod exports { - #[allow(unused)] - extern "C" { - // ############# - // # Registers # - // ############# - pub(crate) fn read_register(register_id: u64, ptr: u64); - pub(crate) fn register_len(register_id: u64) -> u64; - // ############### - // # Context API # - // ############### - pub(crate) fn current_account_id(register_id: u64); - pub(crate) fn signer_account_id(register_id: u64); - pub(crate) fn signer_account_pk(register_id: u64); - pub(crate) fn predecessor_account_id(register_id: u64); - pub(crate) fn input(register_id: u64); - // TODO #1903 fn block_height() -> u64; - pub(crate) fn block_index() -> u64; - pub(crate) fn block_timestamp() -> u64; - fn epoch_height() -> u64; - pub(crate) fn storage_usage() -> u64; - // ################# - // # Economics API # - // ################# - fn account_balance(balance_ptr: u64); - pub(crate) fn attached_deposit(balance_ptr: u64); - pub(crate) fn prepaid_gas() -> u64; - fn used_gas() -> u64; - // ############ - // # Math API # - // ############ - fn random_seed(register_id: u64); - pub(crate) fn sha256(value_len: u64, value_ptr: u64, register_id: u64); - pub(crate) fn keccak256(value_len: u64, value_ptr: u64, register_id: u64); - pub(crate) fn ripemd160(value_len: u64, value_ptr: u64, register_id: u64); - pub(crate) fn ecrecover( - hash_len: u64, - hash_ptr: u64, - sig_len: u64, - sig_ptr: u64, - v: u64, - malleability_flag: u64, - register_id: u64, - ) -> u64; - // ##################### - // # Miscellaneous API # - // ##################### - pub(crate) fn value_return(value_len: u64, value_ptr: u64); - pub(crate) fn panic(); - pub(crate) fn panic_utf8(len: u64, ptr: u64); - pub(crate) fn log_utf8(len: u64, ptr: u64); - fn log_utf16(len: u64, ptr: u64); - fn abort(msg_ptr: u32, filename_ptr: u32, line: u32, col: u32); - // ################ - // # Promises API # - // ################ - pub(crate) fn promise_create( - account_id_len: u64, - account_id_ptr: u64, - method_name_len: u64, - method_name_ptr: u64, - arguments_len: u64, - arguments_ptr: u64, - amount_ptr: u64, - gas: u64, - ) -> u64; - pub(crate) fn promise_then( - promise_index: u64, - account_id_len: u64, - account_id_ptr: u64, - method_name_len: u64, - method_name_ptr: u64, - arguments_len: u64, - arguments_ptr: u64, - amount_ptr: u64, - gas: u64, - ) -> u64; - fn promise_and(promise_idx_ptr: u64, promise_idx_count: u64) -> u64; - pub(crate) fn promise_batch_create(account_id_len: u64, account_id_ptr: u64) -> u64; - fn promise_batch_then(promise_index: u64, account_id_len: u64, account_id_ptr: u64) -> u64; - // ####################### - // # Promise API actions # - // ####################### - fn promise_batch_action_create_account(promise_index: u64); - pub(crate) fn promise_batch_action_deploy_contract( - promise_index: u64, - code_len: u64, - code_ptr: u64, - ); - pub(crate) fn promise_batch_action_function_call( - promise_index: u64, - method_name_len: u64, - method_name_ptr: u64, - arguments_len: u64, - arguments_ptr: u64, - amount_ptr: u64, - gas: u64, - ); - pub(crate) fn promise_batch_action_transfer(promise_index: u64, amount_ptr: u64); - fn promise_batch_action_stake( - promise_index: u64, - amount_ptr: u64, - public_key_len: u64, - public_key_ptr: u64, - ); - fn promise_batch_action_add_key_with_full_access( - promise_index: u64, - public_key_len: u64, - public_key_ptr: u64, - nonce: u64, - ); - fn promise_batch_action_add_key_with_function_call( - promise_index: u64, - public_key_len: u64, - public_key_ptr: u64, - nonce: u64, - allowance_ptr: u64, - receiver_id_len: u64, - receiver_id_ptr: u64, - method_names_len: u64, - method_names_ptr: u64, - ); - fn promise_batch_action_delete_key( - promise_index: u64, - public_key_len: u64, - public_key_ptr: u64, - ); - fn promise_batch_action_delete_account( - promise_index: u64, - beneficiary_id_len: u64, - beneficiary_id_ptr: u64, - ); - // ####################### - // # Promise API results # - // ####################### - pub(crate) fn promise_results_count() -> u64; - pub(crate) fn promise_result(result_idx: u64, register_id: u64) -> u64; - pub(crate) fn promise_return(promise_id: u64); - // ############### - // # Storage API # - // ############### - pub(crate) fn storage_write( - key_len: u64, - key_ptr: u64, - value_len: u64, - value_ptr: u64, - register_id: u64, - ) -> u64; - pub(crate) fn storage_read(key_len: u64, key_ptr: u64, register_id: u64) -> u64; - pub(crate) fn storage_remove(key_len: u64, key_ptr: u64, register_id: u64) -> u64; - pub(crate) fn storage_has_key(key_len: u64, key_ptr: u64) -> u64; - fn storage_iter_prefix(prefix_len: u64, prefix_ptr: u64) -> u64; - fn storage_iter_range(start_len: u64, start_ptr: u64, end_len: u64, end_ptr: u64) -> u64; - fn storage_iter_next(iterator_id: u64, key_register_id: u64, value_register_id: u64) - -> u64; - // ############### - // # Validator API # - // ############### - fn validator_stake(account_id_len: u64, account_id_ptr: u64, stake_ptr: u64); - fn validator_total_stake(stake_ptr: u64); - } -} - -pub fn read_input() -> Vec { - unsafe { - exports::input(INPUT_REGISTER_ID); - let bytes: Vec = vec![0; exports::register_len(INPUT_REGISTER_ID) as usize]; - exports::read_register(INPUT_REGISTER_ID, bytes.as_ptr() as *const u64 as u64); - bytes - } -} - -#[cfg_attr(not(feature = "contract"), allow(dead_code))] -pub fn read_input_borsh() -> Result { - let bytes = read_input(); - T::try_from_slice(&bytes).map_err(|_| ArgParseErr) -} - -#[cfg_attr(not(feature = "contract"), allow(dead_code))] -pub fn read_input_arr20() -> Result<[u8; 20], IncorrectInputLength> { - unsafe { - exports::input(INPUT_REGISTER_ID); - if exports::register_len(INPUT_REGISTER_ID) == 20 { - let bytes = [0u8; 20]; - exports::read_register(INPUT_REGISTER_ID, bytes.as_ptr() as *const u64 as u64); - Ok(bytes) - } else { - Err(IncorrectInputLength) - } - } -} - -/// Reads current input and stores in the given key keeping data in the runtime. -pub fn read_input_and_store(key: &[u8]) { - unsafe { - exports::input(0); - // Store register 0 into key, store the previous value in register 1. - exports::storage_write(key.len() as _, key.as_ptr() as _, u64::MAX, 0, 1); - } -} - -pub fn return_output(value: &[u8]) { - unsafe { - exports::value_return(value.len() as u64, value.as_ptr() as u64); - } -} - -#[allow(dead_code)] -pub fn read_storage(key: &[u8]) -> Option> { - read_storage_len(key).map(|value_size| unsafe { - let bytes = vec![0u8; value_size]; - exports::read_register( - READ_STORAGE_REGISTER_ID, - bytes.as_ptr() as *const u64 as u64, - ); - bytes - }) -} - -pub fn read_storage_len(key: &[u8]) -> Option { - unsafe { - if exports::storage_read( - key.len() as u64, - key.as_ptr() as u64, - READ_STORAGE_REGISTER_ID, - ) == 1 - { - Some(exports::register_len(READ_STORAGE_REGISTER_ID) as usize) - } else { - None - } - } -} - -/// Read u64 from storage at given key. -pub fn read_u64(key: &[u8]) -> Result { - read_storage_len(key) - .ok_or(ReadU64Error::MissingValue) - .and_then(|value_size| unsafe { - if value_size == 8 { - let result = [0u8; 8]; - exports::read_register(READ_STORAGE_REGISTER_ID, result.as_ptr() as _); - Ok(u64::from_le_bytes(result)) - } else { - Err(ReadU64Error::InvalidU64) - } - }) -} - -pub fn write_storage(key: &[u8], value: &[u8]) { - unsafe { - exports::storage_write( - key.len() as u64, - key.as_ptr() as u64, - value.len() as u64, - value.as_ptr() as u64, - 0, - ); - } -} - -pub fn remove_storage(key: &[u8]) { - unsafe { - exports::storage_remove(key.len() as u64, key.as_ptr() as u64, EVICTED_REGISTER); - } -} - -/// Returns the size of the blob stored in the given register. -/// * If register is used, then returns the size, which can potentially be zero; -/// * If register is not used, returns `u64::MAX` -pub fn register_len(register_id: u64) -> Option { - let len = unsafe { exports::register_len(register_id) }; - - if len == u64::MAX { - None - } else { - Some(len) - } -} - -/// Reads the most recent value that was evicted with `storage_write` or `storage_remove` command. -fn storage_get_evicted() -> Option> { - let len = register_len(EVICTED_REGISTER)?; - let bytes: Vec = vec![0u8; len as usize]; - unsafe { - exports::read_register(EVICTED_REGISTER, bytes.as_ptr() as *const u64 as u64); - }; - Some(bytes) -} - -pub fn remove_storage_with_result(key: &[u8]) -> Option> { - remove_storage(key); - storage_get_evicted() -} - #[allow(dead_code)] pub fn block_timestamp() -> u64 { // NEAR timestamp is in nanoseconds @@ -458,10 +160,6 @@ pub fn self_deploy(code_key: &[u8]) { } } -pub fn save_contract(key: &[u8], data: &T) { - write_storage(key, &data.try_to_vec().unwrap()[..]); -} - #[allow(dead_code)] pub fn log(data: &str) { log_utf8(data.as_bytes()) @@ -603,41 +301,6 @@ pub fn promise_batch_action_function_call( } } -#[allow(dead_code)] -pub fn storage_has_key(key: &[u8]) -> bool { - unsafe { exports::storage_has_key(key.len() as _, key.as_ptr() as _) == 1 } -} - -pub struct IncorrectInputLength; - -impl AsRef<[u8]> for IncorrectInputLength { - fn as_ref(&self) -> &[u8] { - b"ERR_INCORRECT_INPUT_LENGTH" - } -} - -pub struct ArgParseErr; - -impl AsRef<[u8]> for ArgParseErr { - fn as_ref(&self) -> &[u8] { - b"ERR_ARG_PARSE" - } -} - -pub enum ReadU64Error { - InvalidU64, - MissingValue, -} - -impl AsRef<[u8]> for ReadU64Error { - fn as_ref(&self) -> &[u8] { - match self { - Self::InvalidU64 => b"ERR_NOT_U64", - Self::MissingValue => b"ERR_U64_NOT_FOUND", - } - } -} - pub struct ECRecoverErr; impl ECRecoverErr { diff --git a/engine-sdk/src/near_runtime.rs b/engine-sdk/src/near_runtime.rs new file mode 100644 index 000000000..272c84c1f --- /dev/null +++ b/engine-sdk/src/near_runtime.rs @@ -0,0 +1,289 @@ +/// Wrapper type for indices in NEAR's register API. +pub struct RegisterIndex(u64); + +/// Singleton type used to implement the IO traits in the case of using NEAR's +/// runtime (i.e. for wasm contracts). +#[derive(Copy, Clone, Default)] +pub struct Runtime; + +impl Runtime { + const READ_STORAGE_REGISTER_ID: RegisterIndex = RegisterIndex(0); + const INPUT_REGISTER_ID: RegisterIndex = RegisterIndex(1); + const WRITE_REGISTER_ID: RegisterIndex = RegisterIndex(2); + const EVICT_REGISTER_ID: RegisterIndex = RegisterIndex(3); +} + +impl crate::io::StorageIntermediate for RegisterIndex { + fn len(&self) -> usize { + unsafe { + let result = exports::register_len(self.0); + // By convention, an unused register will return a length of U64::MAX + // (see https://nomicon.io/RuntimeSpec/Components/BindingsSpec/RegistersAPI.html). + if result < u64::MAX { + result as usize + } else { + 0 + } + } + } + + fn is_empty(&self) -> bool { + self.len() == 0 + } + + fn copy_to_slice(&self, buffer: &mut [u8]) { + unsafe { exports::read_register(self.0, buffer.as_ptr() as u64) } + } +} + +impl crate::io::IO for Runtime { + type StorageValue = RegisterIndex; + + fn read_input(&self) -> Self::StorageValue { + unsafe { + exports::input(Runtime::INPUT_REGISTER_ID.0); + } + Runtime::INPUT_REGISTER_ID + } + + fn return_output(&mut self, value: &[u8]) { + unsafe { + exports::value_return(value.len() as u64, value.as_ptr() as u64); + } + } + + fn read_storage(&self, key: &[u8]) -> Option { + unsafe { + if exports::storage_read( + key.len() as u64, + key.as_ptr() as u64, + Runtime::READ_STORAGE_REGISTER_ID.0, + ) == 1 + { + Some(Runtime::READ_STORAGE_REGISTER_ID) + } else { + None + } + } + } + + fn storage_has_key(&self, key: &[u8]) -> bool { + unsafe { exports::storage_has_key(key.len() as _, key.as_ptr() as _) == 1 } + } + + fn write_storage(&mut self, key: &[u8], value: &[u8]) -> Option { + unsafe { + if exports::storage_write( + key.len() as u64, + key.as_ptr() as u64, + value.len() as u64, + value.as_ptr() as u64, + Runtime::WRITE_REGISTER_ID.0, + ) == 1 + { + Some(Runtime::WRITE_REGISTER_ID) + } else { + None + } + } + } + + fn write_storage_direct( + &mut self, + key: &[u8], + value: Self::StorageValue, + ) -> Option { + unsafe { + if exports::storage_write( + key.len() as _, + key.as_ptr() as _, + u64::MAX, + value.0, + Runtime::WRITE_REGISTER_ID.0, + ) == 1 + { + Some(Runtime::WRITE_REGISTER_ID) + } else { + None + } + } + } + + fn remove_storage(&mut self, key: &[u8]) -> Option { + unsafe { + if exports::storage_remove( + key.len() as _, + key.as_ptr() as _, + Runtime::EVICT_REGISTER_ID.0, + ) == 1 + { + Some(Runtime::EVICT_REGISTER_ID) + } else { + None + } + } + } +} + +pub(crate) mod exports { + #[allow(unused)] + extern "C" { + // ############# + // # Registers # + // ############# + pub(crate) fn read_register(register_id: u64, ptr: u64); + pub(crate) fn register_len(register_id: u64) -> u64; + // ############### + // # Context API # + // ############### + pub(crate) fn current_account_id(register_id: u64); + pub(crate) fn signer_account_id(register_id: u64); + pub(crate) fn signer_account_pk(register_id: u64); + pub(crate) fn predecessor_account_id(register_id: u64); + pub(crate) fn input(register_id: u64); + // TODO #1903 fn block_height() -> u64; + pub(crate) fn block_index() -> u64; + pub(crate) fn block_timestamp() -> u64; + fn epoch_height() -> u64; + pub(crate) fn storage_usage() -> u64; + // ################# + // # Economics API # + // ################# + fn account_balance(balance_ptr: u64); + pub(crate) fn attached_deposit(balance_ptr: u64); + pub(crate) fn prepaid_gas() -> u64; + fn used_gas() -> u64; + // ############ + // # Math API # + // ############ + fn random_seed(register_id: u64); + pub(crate) fn sha256(value_len: u64, value_ptr: u64, register_id: u64); + pub(crate) fn keccak256(value_len: u64, value_ptr: u64, register_id: u64); + pub(crate) fn ripemd160(value_len: u64, value_ptr: u64, register_id: u64); + pub(crate) fn ecrecover( + hash_len: u64, + hash_ptr: u64, + sig_len: u64, + sig_ptr: u64, + v: u64, + malleability_flag: u64, + register_id: u64, + ) -> u64; + // ##################### + // # Miscellaneous API # + // ##################### + pub(crate) fn value_return(value_len: u64, value_ptr: u64); + pub(crate) fn panic(); + pub(crate) fn panic_utf8(len: u64, ptr: u64); + pub(crate) fn log_utf8(len: u64, ptr: u64); + fn log_utf16(len: u64, ptr: u64); + fn abort(msg_ptr: u32, filename_ptr: u32, line: u32, col: u32); + // ################ + // # Promises API # + // ################ + pub(crate) fn promise_create( + account_id_len: u64, + account_id_ptr: u64, + method_name_len: u64, + method_name_ptr: u64, + arguments_len: u64, + arguments_ptr: u64, + amount_ptr: u64, + gas: u64, + ) -> u64; + pub(crate) fn promise_then( + promise_index: u64, + account_id_len: u64, + account_id_ptr: u64, + method_name_len: u64, + method_name_ptr: u64, + arguments_len: u64, + arguments_ptr: u64, + amount_ptr: u64, + gas: u64, + ) -> u64; + fn promise_and(promise_idx_ptr: u64, promise_idx_count: u64) -> u64; + pub(crate) fn promise_batch_create(account_id_len: u64, account_id_ptr: u64) -> u64; + fn promise_batch_then(promise_index: u64, account_id_len: u64, account_id_ptr: u64) -> u64; + // ####################### + // # Promise API actions # + // ####################### + fn promise_batch_action_create_account(promise_index: u64); + pub(crate) fn promise_batch_action_deploy_contract( + promise_index: u64, + code_len: u64, + code_ptr: u64, + ); + pub(crate) fn promise_batch_action_function_call( + promise_index: u64, + method_name_len: u64, + method_name_ptr: u64, + arguments_len: u64, + arguments_ptr: u64, + amount_ptr: u64, + gas: u64, + ); + pub(crate) fn promise_batch_action_transfer(promise_index: u64, amount_ptr: u64); + fn promise_batch_action_stake( + promise_index: u64, + amount_ptr: u64, + public_key_len: u64, + public_key_ptr: u64, + ); + fn promise_batch_action_add_key_with_full_access( + promise_index: u64, + public_key_len: u64, + public_key_ptr: u64, + nonce: u64, + ); + fn promise_batch_action_add_key_with_function_call( + promise_index: u64, + public_key_len: u64, + public_key_ptr: u64, + nonce: u64, + allowance_ptr: u64, + receiver_id_len: u64, + receiver_id_ptr: u64, + method_names_len: u64, + method_names_ptr: u64, + ); + fn promise_batch_action_delete_key( + promise_index: u64, + public_key_len: u64, + public_key_ptr: u64, + ); + fn promise_batch_action_delete_account( + promise_index: u64, + beneficiary_id_len: u64, + beneficiary_id_ptr: u64, + ); + // ####################### + // # Promise API results # + // ####################### + pub(crate) fn promise_results_count() -> u64; + pub(crate) fn promise_result(result_idx: u64, register_id: u64) -> u64; + pub(crate) fn promise_return(promise_id: u64); + // ############### + // # Storage API # + // ############### + pub(crate) fn storage_write( + key_len: u64, + key_ptr: u64, + value_len: u64, + value_ptr: u64, + register_id: u64, + ) -> u64; + pub(crate) fn storage_read(key_len: u64, key_ptr: u64, register_id: u64) -> u64; + pub(crate) fn storage_remove(key_len: u64, key_ptr: u64, register_id: u64) -> u64; + pub(crate) fn storage_has_key(key_len: u64, key_ptr: u64) -> u64; + fn storage_iter_prefix(prefix_len: u64, prefix_ptr: u64) -> u64; + fn storage_iter_range(start_len: u64, start_ptr: u64, end_len: u64, end_ptr: u64) -> u64; + fn storage_iter_next(iterator_id: u64, key_register_id: u64, value_register_id: u64) + -> u64; + // ############### + // # Validator API # + // ############### + fn validator_stake(account_id_len: u64, account_id_ptr: u64, stake_ptr: u64); + fn validator_total_stake(stake_ptr: u64); + } +} diff --git a/engine-sdk/src/types.rs b/engine-sdk/src/types.rs index a6292789f..90388db9c 100644 --- a/engine-sdk/src/types.rs +++ b/engine-sdk/src/types.rs @@ -1,5 +1,6 @@ +use crate::io::IO; +use crate::panic_utf8; use crate::prelude::{Address, H256}; -use crate::{panic_utf8, return_output}; #[cfg(not(feature = "contract"))] use sha3::{Digest, Keccak256}; @@ -99,7 +100,7 @@ pub trait SdkProcess { impl, E: AsRef<[u8]>> SdkProcess for Result { fn sdk_process(self) { match self { - Ok(r) => return_output(r.as_ref()), + Ok(r) => crate::near_runtime::Runtime.return_output(r.as_ref()), Err(e) => panic_utf8(e.as_ref()), } } diff --git a/engine-tests/src/test_utils/mod.rs b/engine-tests/src/test_utils/mod.rs index b4492c815..dac0405ea 100644 --- a/engine-tests/src/test_utils/mod.rs +++ b/engine-tests/src/test_utils/mod.rs @@ -246,7 +246,7 @@ impl AuroraRunner { &[crate::prelude::storage::EthConnectorStorageId::FungibleToken as u8], ); let ft_value = { - let mut current_ft: FungibleToken = trie + let mut current_ft: FungibleToken = trie .get(&ft_key) .map(|bytes| FungibleToken::try_from_slice(&bytes).unwrap()) .unwrap_or_default(); diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs index 5fc3da378..cb2923f1a 100644 --- a/engine-tests/src/tests/sanity.rs +++ b/engine-tests/src/tests/sanity.rs @@ -505,8 +505,7 @@ fn test_block_hash() { crate::prelude::u256_to_arr(&number) }; let account_id = runner.aurora_account_id; - let block_hash = - aurora_engine::engine::Engine::compute_block_hash(chain_id, 10, account_id.as_bytes()); + let block_hash = aurora_engine::engine::compute_block_hash(chain_id, 10, account_id.as_bytes()); assert_eq!( hex::encode(block_hash.0).as_str(), diff --git a/engine/src/connector.rs b/engine/src/connector.rs index d4c8892cd..b79efdc31 100644 --- a/engine/src/connector.rs +++ b/engine/src/connector.rs @@ -17,6 +17,7 @@ use crate::prelude::{ U256, }; use crate::proof::Proof; +use aurora_engine_sdk::io::{StorageIntermediate, IO}; pub const ERR_NOT_ENOUGH_BALANCE_FOR_FEE: &str = "ERR_NOT_ENOUGH_BALANCE_FOR_FEE"; pub const NO_DEPOSIT: Balance = 0; @@ -29,10 +30,11 @@ pub const PAUSE_DEPOSIT: PausedMask = 1 << 0; pub const PAUSE_WITHDRAW: PausedMask = 1 << 1; #[derive(BorshSerialize, BorshDeserialize)] -pub struct EthConnectorContract { +pub struct EthConnectorContract { contract: EthConnector, - ft: FungibleToken, + ft: FungibleToken, paused_mask: PausedMask, + io: I, } /// eth-connector specific data @@ -60,12 +62,13 @@ pub struct OnTransferMessageData { pub fee: U256, } -impl EthConnectorContract { - pub fn get_instance() -> Self { +impl EthConnectorContract { + pub fn get_instance(io: I) -> Self { Self { - contract: Self::get_contract_data(&EthConnectorStorageId::Contract), - ft: Self::get_contract_data(&EthConnectorStorageId::FungibleToken), - paused_mask: Self::get_contract_data(&EthConnectorStorageId::PausedMask), + contract: Self::get_contract_data(&io, &EthConnectorStorageId::Contract), + ft: Self::get_contract_data(&io, &EthConnectorStorageId::FungibleToken), + paused_mask: Self::get_contract_data(&io, &EthConnectorStorageId::PausedMask), + io, } } @@ -73,25 +76,30 @@ impl EthConnectorContract { crate::prelude::bytes_to_key(KeyPrefix::EthConnector, &[*suffix as u8]) } - fn get_contract_data(suffix: &EthConnectorStorageId) -> T { - let data = sdk::read_storage(&Self::get_contract_key(suffix)).expect("Failed read storage"); - T::try_from_slice(&data[..]).unwrap() + fn get_contract_data(io: &I, suffix: &EthConnectorStorageId) -> T { + io.read_storage(&Self::get_contract_key(suffix)) + .expect("Failed read storage") + .to_value() + .unwrap() } /// Init eth-connector contract specific data - pub fn init_contract(args: InitCallArgs) { + pub fn init_contract(mut io: I, args: InitCallArgs) { // Check is it already initialized assert!( - !sdk::storage_has_key(&Self::get_contract_key(&EthConnectorStorageId::Contract)), + !io.storage_has_key(&Self::get_contract_key(&EthConnectorStorageId::Contract)), "ERR_CONTRACT_INITIALIZED" ); sdk::log!("[init contract]"); - let contract_data = Self::set_contract_data(SetContractDataCallArgs { - prover_account: args.prover_account, - eth_custodian_address: args.eth_custodian_address, - metadata: args.metadata, - }); + let contract_data = Self::set_contract_data( + &mut io, + SetContractDataCallArgs { + prover_account: args.prover_account, + eth_custodian_address: args.eth_custodian_address, + metadata: args.metadata, + }, + ); let current_account_id = sdk::current_account_id(); let owner_id = AccountId::try_from(current_account_id).unwrap(); @@ -100,7 +108,7 @@ impl EthConnectorContract { ft.internal_register_account(&owner_id); let paused_mask = UNPAUSE_ALL; - sdk::save_contract( + io.write_borsh( &Self::get_contract_key(&EthConnectorStorageId::PausedMask), &paused_mask, ); @@ -109,24 +117,25 @@ impl EthConnectorContract { contract: contract_data, ft, paused_mask, + io, } .save_ft_contract(); } /// Sets the contract data and returns it back - pub fn set_contract_data(args: SetContractDataCallArgs) -> EthConnector { + pub fn set_contract_data(io: &mut I, args: SetContractDataCallArgs) -> EthConnector { // Get initial contract arguments let contract_data = EthConnector { prover_account: args.prover_account, eth_custodian_address: validate_eth_address(args.eth_custodian_address).sdk_unwrap(), }; // Save eth-connector specific data - sdk::save_contract( + io.write_borsh( &Self::get_contract_key(&EthConnectorStorageId::Contract), &contract_data, ); - sdk::save_contract( + io.write_borsh( &Self::get_contract_key(&EthConnectorStorageId::FungibleTokenMetadata), &args.metadata, ); @@ -190,13 +199,12 @@ impl EthConnectorContract { } /// Deposit all types of tokens - pub fn deposit(&self) { + pub fn deposit(&self, raw_proof: Vec) { self.assert_not_paused(PAUSE_DEPOSIT); sdk::log!("[Deposit tokens]"); // Get incoming deposit arguments - let raw_proof = sdk::read_input(); let proof: Proof = Proof::try_from_slice(&raw_proof).expect(ERR_FAILED_PARSE); // Fetch event data from Proof let event = DepositedEvent::from_log_entry_data(&proof.log_entry_data); @@ -306,10 +314,7 @@ impl EthConnectorContract { /// is that in this case we only calculate the amount to be credited but /// do not save it, however, if an error occurs during the calculation, /// this will happen before `record_proof`. After that contract will save. - pub fn finish_deposit(&mut self) { - sdk::assert_private_call(); - let data: FinishDepositCallArgs = - FinishDepositCallArgs::try_from_slice(&sdk::read_input()).unwrap(); + pub fn finish_deposit(&mut self, data: FinishDepositCallArgs) { sdk::log!(&format!("Finish deposit with the amount: {}", data.amount)); assert_eq!(sdk::promise_results_count(), 1); @@ -391,45 +396,44 @@ impl EthConnectorContract { /// Withdraw nETH from NEAR accounts /// NOTE: it should be without any log data - pub fn withdraw_eth_from_near(&mut self) { + pub fn withdraw_eth_from_near(&mut self, args: WithdrawCallArgs) -> WithdrawResult { self.assert_not_paused(PAUSE_WITHDRAW); sdk::assert_one_yocto(); - let args = WithdrawCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); - let res = WithdrawResult { - recipient_id: args.recipient_address, - amount: args.amount, - eth_custodian_address: self.contract.eth_custodian_address, - } - .try_to_vec() - .unwrap(); // Burn tokens to recipient let predecessor_account_id = AccountId::try_from(sdk::predecessor_account_id()).unwrap(); self.ft .internal_withdraw_eth_from_near(&predecessor_account_id, args.amount); // Save new contract data self.save_ft_contract(); - sdk::return_output(&res[..]); + + WithdrawResult { + recipient_id: args.recipient_address, + amount: args.amount, + eth_custodian_address: self.contract.eth_custodian_address, + } } /// Returns total ETH supply on NEAR (nETH as NEP-141 token) - pub fn ft_total_eth_supply_on_near(&self) { + pub fn ft_total_eth_supply_on_near(&mut self) { let total_supply = self.ft.ft_total_eth_supply_on_near(); sdk::log!(&format!("Total ETH supply on NEAR: {}", total_supply)); - sdk::return_output(format!("\"{}\"", total_supply.to_string()).as_bytes()); + self.io + .return_output(format!("\"{}\"", total_supply.to_string()).as_bytes()); } /// Returns total ETH supply on Aurora (ETH in Aurora EVM) - pub fn ft_total_eth_supply_on_aurora(&self) { + pub fn ft_total_eth_supply_on_aurora(&mut self) { let total_supply = self.ft.ft_total_eth_supply_on_aurora(); sdk::log!(&format!("Total ETH supply on Aurora: {}", total_supply)); - sdk::return_output(format!("\"{}\"", total_supply.to_string()).as_bytes()); + self.io + .return_output(format!("\"{}\"", total_supply.to_string()).as_bytes()); } /// Return balance of nETH (ETH on Near) - pub fn ft_balance_of(&self) { + pub fn ft_balance_of(&mut self) { let args = BalanceOfCallArgs::from( - parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + parse_json(&self.io.read_input().to_vec()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), ); let balance = self.ft.ft_balance_of(&args.account_id); @@ -438,13 +442,13 @@ impl EthConnectorContract { args.account_id, balance )); - sdk::return_output(format!("\"{}\"", balance.to_string()).as_bytes()); + self.io + .return_output(format!("\"{}\"", balance.to_string()).as_bytes()); } /// Return balance of ETH (ETH in Aurora EVM) - pub fn ft_balance_of_eth_on_aurora(&self) { - let args = - BalanceOfEthCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + pub fn ft_balance_of_eth_on_aurora(&mut self) { + let args: BalanceOfEthCallArgs = self.io.read_input().to_value().expect(ERR_FAILED_PARSE); let balance = self .ft .internal_unwrap_balance_of_eth_on_aurora(args.address); @@ -453,14 +457,15 @@ impl EthConnectorContract { hex::encode(args.address), balance )); - sdk::return_output(format!("\"{}\"", balance.to_string()).as_bytes()); + self.io + .return_output(format!("\"{}\"", balance.to_string()).as_bytes()); } /// Transfer between NEAR accounts pub fn ft_transfer(&mut self) { sdk::assert_one_yocto(); let args = TransferCallArgs::from( - parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + parse_json(&self.io.read_input().to_vec()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), ); self.ft .ft_transfer(&args.receiver_id, args.amount, &args.memo); @@ -477,7 +482,7 @@ impl EthConnectorContract { // Check if previous promise succeeded assert_eq!(sdk::promise_results_count(), 1); - let args = ResolveTransferCallArgs::try_from_slice(&sdk::read_input()).unwrap(); + let args: ResolveTransferCallArgs = self.io.read_input().to_value().unwrap(); let amount = self .ft .ft_resolve_transfer(&args.sender_id, &args.receiver_id, args.amount); @@ -487,7 +492,8 @@ impl EthConnectorContract { )); // `ft_resolve_transfer` can change `total_supply` so we should save the contract self.save_ft_contract(); - sdk::return_output(format!("\"{}\"", amount.to_string()).as_bytes()); + self.io + .return_output(format!("\"{}\"", amount.to_string()).as_bytes()); } /// FT transfer call from sender account (invoker account) to receiver @@ -531,37 +537,38 @@ impl EthConnectorContract { /// FT storage deposit logic pub fn storage_deposit(&mut self) { let args = StorageDepositCallArgs::from( - parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + parse_json(&self.io.read_input().to_vec()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), ); let res = self .ft .storage_deposit(args.account_id.as_ref(), args.registration_only); self.save_ft_contract(); - sdk::return_output(&res.to_json_bytes()); + self.io.return_output(&res.to_json_bytes()); } /// FT storage withdraw pub fn storage_withdraw(&mut self) { sdk::assert_one_yocto(); let args = StorageWithdrawCallArgs::from( - parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + parse_json(&self.io.read_input().to_vec()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), ); let res = self.ft.storage_withdraw(args.amount); self.save_ft_contract(); - sdk::return_output(&res.to_json_bytes()); + self.io.return_output(&res.to_json_bytes()); } /// Get balance of storage - pub fn storage_balance_of(&self) { + pub fn storage_balance_of(&mut self) { let args = StorageBalanceOfCallArgs::from( - parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + parse_json(&self.io.read_input().to_vec()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), ); - sdk::return_output(&self.ft.storage_balance_of(&args.account_id).to_json_bytes()); + self.io + .return_output(&self.ft.storage_balance_of(&args.account_id).to_json_bytes()); } /// ft_on_transfer callback function - pub fn ft_on_transfer(&mut self, engine: &Engine, args: &NEP141FtOnTransferArgs) { + pub fn ft_on_transfer(&mut self, engine: &Engine, args: &NEP141FtOnTransferArgs) { sdk::log!("Call ft_on_transfer"); // Parse message with specific rules let message_data = self.parse_on_transfer_message(&args.msg); @@ -578,18 +585,19 @@ impl EthConnectorContract { _ => self.mint_eth_on_aurora(message_data.recipient, args.amount), } self.save_ft_contract(); - sdk::return_output("\"0\"".as_bytes()); + self.io.return_output("\"0\"".as_bytes()); } /// Get accounts counter for statistics. /// It represents total unique accounts (all-time, including accounts which now have zero balance). - pub fn get_accounts_counter(&self) { - sdk::return_output(&self.ft.get_accounts_counter().to_le_bytes()); + pub fn get_accounts_counter(&mut self) { + self.io + .return_output(&self.ft.get_accounts_counter().to_le_bytes()); } /// Save eth-connector contract data fn save_ft_contract(&mut self) { - sdk::save_contract( + self.io.write_borsh( &Self::get_contract_key(&EthConnectorStorageId::FungibleToken), &self.ft, ); @@ -603,13 +611,13 @@ impl EthConnectorContract { } /// Save already used event proof as hash key - fn save_used_event(&self, key: &str) { - sdk::save_contract(&self.used_event_key(key), &0u8); + fn save_used_event(&mut self, key: &str) { + self.io.write_borsh(&self.used_event_key(key), &0u8); } /// Check is event of proof already used fn check_used_event(&self, key: &str) -> bool { - sdk::storage_has_key(&self.used_event_key(key)) + self.io.storage_has_key(&self.used_event_key(key)) } /// Checks whether the provided proof was already used @@ -628,22 +636,22 @@ impl EthConnectorContract { } /// Return metdata - pub fn get_metadata() -> Option { - sdk::read_storage(&Self::get_contract_key( + pub fn get_metadata(io: &I) -> Option { + io.read_storage(&Self::get_contract_key( &EthConnectorStorageId::FungibleTokenMetadata, )) - .and_then(|data| FungibleTokenMetadata::try_from_slice(&data).ok()) + .and_then(|data| data.to_value().ok()) } } -impl AdminControlled for EthConnectorContract { +impl AdminControlled for EthConnectorContract { fn get_paused(&self) -> PausedMask { self.paused_mask } fn set_paused(&mut self, paused_mask: PausedMask) { self.paused_mask = paused_mask; - sdk::save_contract( + self.io.write_borsh( &Self::get_contract_key(&EthConnectorStorageId::PausedMask), &self.paused_mask, ); diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 2efdc3fc7..172c395f8 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -10,6 +10,8 @@ use crate::connector::EthConnectorContract; #[cfg(feature = "contract")] use crate::contract::current_address; use crate::map::{BijectionMap, LookupMap}; +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_sdk::near_runtime::Runtime; use crate::parameters::{NewCallArgs, TransactionStatus}; use crate::prelude::precompiles::native::{ExitToEthereum, ExitToNear}; @@ -34,13 +36,13 @@ pub fn current_address() -> Address { } macro_rules! unwrap_res_or_finish { - ($e:expr, $output:expr) => { + ($e:expr, $output:expr, $io:expr) => { match $e { Ok(v) => v, Err(_e) => { #[cfg(feature = "log")] sdk::log(crate::prelude::format!("{:?}", _e).as_str()); - sdk::return_output($output); + $io.return_output($output); return; } } @@ -48,9 +50,9 @@ macro_rules! unwrap_res_or_finish { } macro_rules! assert_or_finish { - ($e:expr, $output:expr) => { + ($e:expr, $output:expr, $io:expr) => { if !$e { - sdk::return_output($output); + $io.return_output($output); return; } }; @@ -271,10 +273,11 @@ impl StackExecutorParams { } } - fn make_executor<'a>( + fn make_executor<'a, I: IO + Default + Copy>( &'a self, - engine: &'a Engine, - ) -> executor::StackExecutor<'static, 'a, executor::MemoryStackState, Precompiles> { + engine: &'a Engine, + ) -> executor::StackExecutor<'static, 'a, executor::MemoryStackState>, Precompiles> + { let metadata = executor::StackSubstateMetadata::new(self.gas_limit, CONFIG); let state = executor::MemoryStackState::new(metadata, engine); executor::StackExecutor::new_with_precompiles(state, CONFIG, &self.precompiles) @@ -303,7 +306,8 @@ pub struct EngineState { /// How many blocks after staging upgrade can deploy it. pub upgrade_delay_blocks: u64, /// Mapping between relayer account id and relayer evm address - pub relayers_evm_addresses: LookupMap<{ KeyPrefix::RelayerEvmAddressMap as KeyPrefixU8 }>, + pub relayers_evm_addresses: + LookupMap, } impl From for EngineState { @@ -313,15 +317,16 @@ impl From for EngineState { owner_id: args.owner_id, bridge_prover_id: args.bridge_prover_id, upgrade_delay_blocks: args.upgrade_delay_blocks, - relayers_evm_addresses: LookupMap::new(), + relayers_evm_addresses: LookupMap::default(), } } } -pub struct Engine { +pub struct Engine { state: EngineState, origin: Address, gas_price: U256, + io: I, } // TODO: upgrade to Berlin HF @@ -330,62 +335,28 @@ pub(crate) const CONFIG: &Config = &Config::london(); /// Key for storing the state of the engine. const STATE_KEY: &[u8; 5] = b"STATE"; -impl Engine { - pub fn new(origin: Address) -> Result { - Engine::get_state().map(|state| Self::new_with_state(state, origin)) +impl Engine { + pub fn new(origin: Address, io: I) -> Result { + Engine::get_state(&io).map(|state| Self::new_with_state(state, origin, io)) } - pub fn new_with_state(state: EngineState, origin: Address) -> Self { + pub fn new_with_state(state: EngineState, origin: Address, io: I) -> Self { Self { state, origin, gas_price: U256::zero(), + io, } } /// Saves state into the storage. - pub fn set_state(state: EngineState) { - sdk::write_storage( + pub fn set_state(io: &mut I, state: EngineState) { + io.write_storage( &bytes_to_key(KeyPrefix::Config, STATE_KEY), &state.try_to_vec().expect("ERR_SER"), ); } - /// There is one Aurora block per NEAR block height (note: when heights in NEAR are skipped - /// they are interpreted as empty blocks on Aurora). The blockhash is derived from the height - /// according to - /// ```text - /// block_hash = sha256(concat( - /// BLOCK_HASH_PREFIX, - /// block_height as u64, - /// chain_id, - /// engine_account_id, - /// )) - /// ``` - pub fn compute_block_hash(chain_id: [u8; 32], block_height: u64, account_id: &[u8]) -> H256 { - debug_assert_eq!(BLOCK_HASH_PREFIX_SIZE, mem::size_of_val(&BLOCK_HASH_PREFIX)); - debug_assert_eq!(BLOCK_HEIGHT_SIZE, mem::size_of_val(&block_height)); - debug_assert_eq!(CHAIN_ID_SIZE, mem::size_of_val(&chain_id)); - let mut data = Vec::with_capacity( - BLOCK_HASH_PREFIX_SIZE + BLOCK_HEIGHT_SIZE + CHAIN_ID_SIZE + account_id.len(), - ); - data.push(BLOCK_HASH_PREFIX); - data.extend_from_slice(&chain_id); - data.extend_from_slice(account_id); - data.extend_from_slice(&block_height.to_be_bytes()); - - #[cfg(not(feature = "contract"))] - { - use sha2::Digest; - - let output = sha2::Sha256::digest(&data); - H256(output.into()) - } - - #[cfg(feature = "contract")] - sdk::sha256(&data) - } - pub fn charge_gas( &mut self, sender: &Address, @@ -405,11 +376,11 @@ impl Engine { .map(Wei::new) .ok_or(GasPaymentError::EthAmountOverflow)?; - let new_balance = Self::get_balance(sender) + let new_balance = Self::get_balance(&self.io, sender) .checked_sub(prepaid_amount) .ok_or(GasPaymentError::OutOfFund)?; - Self::set_balance(sender, &new_balance); + Self::set_balance(&mut self.io, sender, &new_balance); self.gas_price = effective_gas_price; @@ -421,6 +392,7 @@ impl Engine { } pub fn refund_unused_gas( + io: &mut I, sender: &Address, gas_used: u64, gas_result: GasPaymentResult, @@ -445,53 +417,60 @@ impl Engine { .checked_sub(spent_amount) .ok_or(GasPaymentError::EthAmountOverflow)?; - Self::add_balance(sender, refund)?; - Self::add_balance(relayer, reward_amount)?; + Self::add_balance(io, sender, refund)?; + Self::add_balance(io, relayer, reward_amount)?; Ok(()) } /// Fails if state is not found. - pub fn get_state() -> Result { - match sdk::read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) { + pub fn get_state(io: &I) -> Result { + match io.read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) { None => Err(EngineStateError::NotFound), - Some(bytes) => EngineState::try_from_slice(&bytes) + Some(bytes) => EngineState::try_from_slice(&bytes.to_vec()) .map_err(|_| EngineStateError::DeserializationFailed), } } - pub fn set_code(address: &Address, code: &[u8]) { - sdk::write_storage(&address_to_key(KeyPrefix::Code, address), code); + pub fn set_code(io: &mut I, address: &Address, code: &[u8]) { + io.write_storage(&address_to_key(KeyPrefix::Code, address), code); } - pub fn remove_code(address: &Address) { - sdk::remove_storage(&address_to_key(KeyPrefix::Code, address)) + pub fn remove_code(io: &mut I, address: &Address) { + io.remove_storage(&address_to_key(KeyPrefix::Code, address)); } - pub fn get_code(address: &Address) -> Vec { - sdk::read_storage(&address_to_key(KeyPrefix::Code, address)).unwrap_or_else(Vec::new) + pub fn get_code(io: &I, address: &Address) -> Vec { + io.read_storage(&address_to_key(KeyPrefix::Code, address)) + .map(|s| s.to_vec()) + .unwrap_or_else(Vec::new) } - pub fn get_code_size(address: &Address) -> usize { - sdk::read_storage_len(&address_to_key(KeyPrefix::Code, address)).unwrap_or(0) + pub fn get_code_size(io: &I, address: &Address) -> usize { + io.read_storage_len(&address_to_key(KeyPrefix::Code, address)) + .unwrap_or(0) } - pub fn set_nonce(address: &Address, nonce: &U256) { - sdk::write_storage( + pub fn set_nonce(io: &mut I, address: &Address, nonce: &U256) { + io.write_storage( &address_to_key(KeyPrefix::Nonce, address), &u256_to_arr(nonce), ); } - pub fn remove_nonce(address: &Address) { - sdk::remove_storage(&address_to_key(KeyPrefix::Nonce, address)) + pub fn remove_nonce(io: &mut I, address: &Address) { + io.remove_storage(&address_to_key(KeyPrefix::Nonce, address)); } /// Checks the nonce to ensure that the address matches the transaction /// nonce. #[inline] - pub fn check_nonce(address: &Address, transaction_nonce: &U256) -> Result<(), EngineErrorKind> { - let account_nonce = Self::get_nonce(address); + pub fn check_nonce( + io: &I, + address: &Address, + transaction_nonce: &U256, + ) -> Result<(), EngineErrorKind> { + let account_nonce = Self::get_nonce(io, address); if transaction_nonce != &account_nonce { return Err(EngineErrorKind::IncorrectNonce); @@ -500,81 +479,88 @@ impl Engine { Ok(()) } - pub fn get_nonce(address: &Address) -> U256 { - sdk::read_storage(&address_to_key(KeyPrefix::Nonce, address)) - .map(|value| U256::from_big_endian(&value)) - .unwrap_or_else(U256::zero) + pub fn get_nonce(io: &I, address: &Address) -> U256 { + io.read_u256(&address_to_key(KeyPrefix::Nonce, address)) + .unwrap_or_else(|_| U256::zero()) } - pub fn add_balance(address: &Address, amount: Wei) -> Result<(), BalanceOverflow> { - let current_balance = Self::get_balance(address); + pub fn add_balance(io: &mut I, address: &Address, amount: Wei) -> Result<(), BalanceOverflow> { + let current_balance = Self::get_balance(io, address); let new_balance = current_balance.checked_add(amount).ok_or(BalanceOverflow)?; - Self::set_balance(address, &new_balance); + Self::set_balance(io, address, &new_balance); Ok(()) } - pub fn set_balance(address: &Address, balance: &Wei) { - sdk::write_storage( + pub fn set_balance(io: &mut I, address: &Address, balance: &Wei) { + io.write_storage( &address_to_key(KeyPrefix::Balance, address), &balance.to_bytes(), ); } - pub fn remove_balance(address: &Address) { - let balance = Self::get_balance(address); + pub fn remove_balance(io: &mut I, address: &Address) { + let balance = Self::get_balance(io, address); // Apply changes for eth-conenctor - EthConnectorContract::get_instance().internal_remove_eth(address, &balance.raw()); - sdk::remove_storage(&address_to_key(KeyPrefix::Balance, address)) + EthConnectorContract::get_instance(*io).internal_remove_eth(address, &balance.raw()); + io.remove_storage(&address_to_key(KeyPrefix::Balance, address)); } - pub fn get_balance(address: &Address) -> Wei { - let raw = sdk::read_storage(&address_to_key(KeyPrefix::Balance, address)) - .map(|value| U256::from_big_endian(&value)) - .unwrap_or_else(U256::zero); + pub fn get_balance(io: &I, address: &Address) -> Wei { + let raw = io + .read_u256(&address_to_key(KeyPrefix::Balance, address)) + .unwrap_or_else(|_| U256::zero()); Wei::new(raw) } - pub fn remove_storage(address: &Address, key: &H256, generation: u32) { - sdk::remove_storage(storage_to_key(address, key, generation).as_ref()); + pub fn remove_storage(io: &mut I, address: &Address, key: &H256, generation: u32) { + io.remove_storage(storage_to_key(address, key, generation).as_ref()); } - pub fn set_storage(address: &Address, key: &H256, value: &H256, generation: u32) { - sdk::write_storage(storage_to_key(address, key, generation).as_ref(), &value.0); + pub fn set_storage(io: &mut I, address: &Address, key: &H256, value: &H256, generation: u32) { + io.write_storage(storage_to_key(address, key, generation).as_ref(), &value.0); } - pub fn get_storage(address: &Address, key: &H256, generation: u32) -> H256 { - sdk::read_storage(storage_to_key(address, key, generation).as_ref()) - .map(|value| H256::from_slice(&value)) + pub fn get_storage(io: &I, address: &Address, key: &H256, generation: u32) -> H256 { + io.read_storage(storage_to_key(address, key, generation).as_ref()) + .and_then(|value| { + if value.len() == 32 { + let mut buf = [0u8; 32]; + value.copy_to_slice(&mut buf); + Some(H256(buf)) + } else { + None + } + }) .unwrap_or_else(H256::default) } - pub fn is_account_empty(address: &Address) -> bool { - let balance = Self::get_balance(address); - let nonce = Self::get_nonce(address); - let code_len = Self::get_code_size(address); + pub fn is_account_empty(io: &I, address: &Address) -> bool { + let balance = Self::get_balance(io, address); + let nonce = Self::get_nonce(io, address); + let code_len = Self::get_code_size(io, address); balance.is_zero() && nonce.is_zero() && code_len == 0 } /// Increments storage generation for a given address. - pub fn set_generation(address: &Address, generation: u32) { - sdk::write_storage( + pub fn set_generation(io: &mut I, address: &Address, generation: u32) { + io.write_storage( &address_to_key(KeyPrefix::Generation, address), &generation.to_be_bytes(), ); } - pub fn get_generation(address: &Address) -> u32 { - sdk::read_storage(&address_to_key(KeyPrefix::Generation, address)) + pub fn get_generation(io: &I, address: &Address) -> u32 { + io.read_storage(&address_to_key(KeyPrefix::Generation, address)) .map(|value| { let mut bytes = [0u8; 4]; - bytes[0..4].copy_from_slice(&value[0..4]); + value.copy_to_slice(&mut bytes); u32::from_be_bytes(bytes) }) .unwrap_or(0) } /// Removes all storage for the given address. - fn remove_all_storage(address: &Address, generation: u32) { + fn remove_all_storage(io: &mut I, address: &Address, generation: u32) { // FIXME: there is presently no way to prefix delete trie state. // NOTE: There is not going to be a method on runtime for this. // You may need to store all keys in a list if you want to do this in a contract. @@ -583,15 +569,15 @@ impl Engine { // Either way you may have to store the nonce per storage address root. When the account // has to be deleted the storage nonce needs to be increased, and the old nonce keys // can be deleted over time. That's how TurboGeth does storage. - Self::set_generation(address, generation + 1); + Self::set_generation(io, address, generation + 1); } /// Removes an account. - fn remove_account(address: &Address, generation: u32) { - Self::remove_nonce(address); - Self::remove_balance(address); - Self::remove_code(address); - Self::remove_all_storage(address, generation); + fn remove_account(io: &mut I, address: &Address, generation: u32) { + Self::remove_nonce(io, address); + Self::remove_balance(io, address); + Self::remove_code(io, address); + Self::remove_all_storage(io, address, generation); } pub fn deploy_code_with_input(&mut self, input: Vec) -> EngineResult { @@ -620,7 +606,7 @@ impl Engine { let status = match exit_reason.into_result(result.0.to_vec()) { Ok(status) => status, Err(e) => { - Engine::increment_nonce(&origin); + Engine::increment_nonce(&mut self.io, &origin); return Err(e.with_gas_used(used_gas)); } }; @@ -658,7 +644,7 @@ impl Engine { let status = match exit_reason.into_result(result) { Ok(status) => status, Err(e) => { - Engine::increment_nonce(&origin); + Engine::increment_nonce(&mut self.io, &origin); return Err(e.with_gas_used(used_gas)); } }; @@ -673,10 +659,10 @@ impl Engine { Ok(SubmitResult::new(status, used_gas, logs)) } - pub fn increment_nonce(address: &Address) { - let account_nonce = Self::get_nonce(address); + pub fn increment_nonce(io: &mut I, address: &Address) { + let account_nonce = Self::get_nonce(io, address); let new_nonce = account_nonce.saturating_add(U256::one()); - Self::set_nonce(address, &new_nonce); + Self::set_nonce(io, address, &new_nonce); } pub fn view_with_args(&self, args: ViewCallArgs) -> Result { @@ -720,7 +706,7 @@ impl Engine { erc20_token: &[u8], nep141_token: &[u8], ) -> Result<(), RegisterTokenError> { - match Self::get_erc20_from_nep141(nep141_token) { + match Self::get_erc20_from_nep141(self.io, nep141_token) { Err(GetErc20FromNep141Error::Nep141NotFound) => (), Err(GetErc20FromNep141Error::InvalidNep141AccountId) => { return Err(RegisterTokenError::InvalidNep141AccountId); @@ -728,17 +714,18 @@ impl Engine { Ok(_) => return Err(RegisterTokenError::TokenAlreadyRegistered), } - Self::nep141_erc20_map().insert(nep141_token, erc20_token); + Self::nep141_erc20_map(self.io).insert(nep141_token, erc20_token); Ok(()) } pub fn get_erc20_from_nep141( + io: I, nep141_account_id: &[u8], ) -> Result, GetErc20FromNep141Error> { AccountId::try_from(nep141_account_id) .map_err(|_| GetErc20FromNep141Error::InvalidNep141AccountId)?; - Self::nep141_erc20_map() + Self::nep141_erc20_map(io) .lookup_left(nep141_account_id) .ok_or(GetErc20FromNep141Error::Nep141NotFound) } @@ -778,20 +765,21 @@ impl Engine { // Recipient of the transaction - 40 characters (Address in hex) // Fee to be paid in ETH (Optional) - 64 characters (Encoded in big endian / hex) let mut message = args.msg.as_bytes(); - assert_or_finish!(message.len() >= 40, output_on_fail); + assert_or_finish!(message.len() >= 40, output_on_fail, self.io); let recipient = Address(unwrap_res_or_finish!( hex::decode(&message[..40]).unwrap().as_slice().try_into(), - output_on_fail + output_on_fail, + self.io )); message = &message[40..]; let fee = if message.is_empty() { U256::from(0) } else { - assert_or_finish!(message.len() == 64, output_on_fail); + assert_or_finish!(message.len() == 64, output_on_fail, self.io); U256::from_big_endian( - unwrap_res_or_finish!(hex::decode(message), output_on_fail).as_slice(), + unwrap_res_or_finish!(hex::decode(message), output_on_fail, self.io).as_slice(), ) }; @@ -800,17 +788,23 @@ impl Engine { let token = sdk::predecessor_account_id(); let erc20_token = Address(unwrap_res_or_finish!( - unwrap_res_or_finish!(Self::get_erc20_from_nep141(&token), output_on_fail) - .as_slice() - .try_into(), - output_on_fail + unwrap_res_or_finish!( + Self::get_erc20_from_nep141(self.io, &token), + output_on_fail, + self.io + ) + .as_slice() + .try_into(), + output_on_fail, + self.io )); if fee != U256::from(0) { let relayer_account_id = sdk::signer_account_id(); let relayer_address = unwrap_res_or_finish!( self.get_relayer(relayer_account_id.as_slice()).ok_or(()), - output_on_fail + output_on_fail, + self.io ); unwrap_res_or_finish!( @@ -820,7 +814,8 @@ impl Engine { Wei::new_u64(fee.as_u64()), u64::MAX, ), - output_on_fail + output_on_fail, + self.io ); } @@ -873,19 +868,23 @@ impl Engine { }), } }), - output_on_fail + output_on_fail, + self.io ); // TODO(marX) // Everything succeed so return "0" - sdk::return_output(b"\"0\""); + self.io.return_output(b"\"0\""); } - pub fn nep141_erc20_map() -> BijectionMap< + pub fn nep141_erc20_map( + io: I, + ) -> BijectionMap< + I, { KeyPrefix::Nep141Erc20Map as KeyPrefixU8 }, { KeyPrefix::Erc20Nep141Map as KeyPrefixU8 }, > { - Default::default() + BijectionMap { io } } fn filter_promises_from_logs>(logs: T) -> Vec { @@ -949,7 +948,42 @@ impl Engine { } } -impl evm::backend::Backend for Engine { +/// There is one Aurora block per NEAR block height (note: when heights in NEAR are skipped +/// they are interpreted as empty blocks on Aurora). The blockhash is derived from the height +/// according to +/// ```text +/// block_hash = sha256(concat( +/// BLOCK_HASH_PREFIX, +/// block_height as u64, +/// chain_id, +/// engine_account_id, +/// )) +/// ``` +pub fn compute_block_hash(chain_id: [u8; 32], block_height: u64, account_id: &[u8]) -> H256 { + debug_assert_eq!(BLOCK_HASH_PREFIX_SIZE, mem::size_of_val(&BLOCK_HASH_PREFIX)); + debug_assert_eq!(BLOCK_HEIGHT_SIZE, mem::size_of_val(&block_height)); + debug_assert_eq!(CHAIN_ID_SIZE, mem::size_of_val(&chain_id)); + let mut data = Vec::with_capacity( + BLOCK_HASH_PREFIX_SIZE + BLOCK_HEIGHT_SIZE + CHAIN_ID_SIZE + account_id.len(), + ); + data.push(BLOCK_HASH_PREFIX); + data.extend_from_slice(&chain_id); + data.extend_from_slice(account_id); + data.extend_from_slice(&block_height.to_be_bytes()); + + #[cfg(not(feature = "contract"))] + { + use sha2::Digest; + + let output = sha2::Sha256::digest(&data); + H256(output.into()) + } + + #[cfg(feature = "contract")] + sdk::sha256(&data) +} + +impl evm::backend::Backend for Engine { /// Returns the "effective" gas price (as defined by EIP-1559) fn gas_price(&self) -> U256 { self.gas_price @@ -984,11 +1018,11 @@ impl evm::backend::Backend for Engine { #[cfg(feature = "contract")] { let account_id = sdk::current_account_id(); - Self::compute_block_hash(self.state.chain_id, number.low_u64(), &account_id) + compute_block_hash(self.state.chain_id, number.low_u64(), &account_id) } #[cfg(not(feature = "contract"))] - Self::compute_block_hash(self.state.chain_id, number.low_u64(), b"aurora") + compute_block_hash(self.state.chain_id, number.low_u64(), b"aurora") } else { H256::zero() } @@ -1050,26 +1084,26 @@ impl evm::backend::Backend for Engine { /// Checks if an address exists. fn exists(&self, address: Address) -> bool { - !Engine::is_account_empty(&address) + !Engine::is_account_empty(&self.io, &address) } /// Returns basic account information. fn basic(&self, address: Address) -> Basic { Basic { - nonce: Engine::get_nonce(&address), - balance: Engine::get_balance(&address).raw(), + nonce: Engine::get_nonce(&self.io, &address), + balance: Engine::get_balance(&self.io, &address).raw(), } } /// Returns the code of the contract from an address. fn code(&self, address: Address) -> Vec { - Engine::get_code(&address) + Engine::get_code(&self.io, &address) } /// Get storage value of address at index. fn storage(&self, address: Address, index: H256) -> H256 { - let generation = Self::get_generation(&address); - Engine::get_storage(&address, &index, generation) + let generation = Self::get_generation(&self.io, &address); + Engine::get_storage(&self.io, &address, &index, generation) } /// Get original storage value of address at index, if available. @@ -1080,7 +1114,7 @@ impl evm::backend::Backend for Engine { } } -impl ApplyBackend for Engine { +impl ApplyBackend for Engine { fn apply(&mut self, values: A, _logs: L, delete_empty: bool) where A: IntoIterator>, @@ -1098,13 +1132,13 @@ impl ApplyBackend for Engine { storage, reset_storage, } => { - let generation = Self::get_generation(&address); - Engine::set_nonce(&address, &basic.nonce); - Engine::set_balance(&address, &Wei::new(basic.balance)); + let generation = Self::get_generation(&self.io, &address); + Engine::set_nonce(&mut self.io, &address, &basic.nonce); + Engine::set_balance(&mut self.io, &address, &Wei::new(basic.balance)); writes_counter += 2; // 1 for nonce, 1 for balance if let Some(code) = code { - Engine::set_code(&address, &code); + Engine::set_code(&mut self.io, &address, &code); code_bytes_written = code.len(); sdk::log!(crate::prelude::format!( "code_write_at_address {:?} {}", @@ -1115,7 +1149,7 @@ impl ApplyBackend for Engine { } let next_generation = if reset_storage { - Engine::remove_all_storage(&address, generation); + Engine::remove_all_storage(&mut self.io, &address, generation); generation + 1 } else { generation @@ -1123,9 +1157,15 @@ impl ApplyBackend for Engine { for (index, value) in storage { if value == H256::default() { - Engine::remove_storage(&address, &index, next_generation) + Engine::remove_storage(&mut self.io, &address, &index, next_generation) } else { - Engine::set_storage(&address, &index, &value, next_generation) + Engine::set_storage( + &mut self.io, + &address, + &index, + &value, + next_generation, + ) } writes_counter += 1; } @@ -1136,16 +1176,16 @@ impl ApplyBackend for Engine { // 3. we didn't already clear out the storage (because if we did then there is // nothing to do) if delete_empty - && Engine::is_account_empty(&address) + && Engine::is_account_empty(&self.io, &address) && generation == next_generation { - Engine::remove_account(&address, generation); + Engine::remove_account(&mut self.io, &address, generation); writes_counter += 1; } } Apply::Delete { address } => { - let generation = Self::get_generation(&address); - Engine::remove_account(&address, generation); + let generation = Self::get_generation(&self.io, &address); + Engine::remove_account(&mut self.io, &address, generation); writes_counter += 1; } } diff --git a/engine/src/fungible_token.rs b/engine/src/fungible_token.rs index a8738e944..88f26dbf6 100644 --- a/engine/src/fungible_token.rs +++ b/engine/src/fungible_token.rs @@ -8,12 +8,13 @@ use crate::prelude::{ PromiseResult, StorageBalanceBounds, StorageUsage, String, ToString, TryFrom, TryInto, Vec, Wei, U256, }; +use aurora_engine_sdk::io::{StorageIntermediate, IO}; const GAS_FOR_RESOLVE_TRANSFER: Gas = 5_000_000_000_000; const GAS_FOR_FT_ON_TRANSFER: Gas = 10_000_000_000_000; #[derive(Debug, Default, BorshDeserialize, BorshSerialize)] -pub struct FungibleToken { +pub struct FungibleToken { /// Total ETH supply on Near (nETH as NEP-141 token) pub total_eth_supply_on_near: Balance, @@ -22,6 +23,9 @@ pub struct FungibleToken { /// The storage size in bytes for one account. pub account_storage_usage: StorageUsage, + + #[borsh_skip] + io: I, } #[derive(BorshDeserialize, BorshSerialize, Clone)] @@ -85,7 +89,7 @@ impl From for JsonValue { } } -impl FungibleToken { +impl FungibleToken { pub fn new() -> Self { Self::default() } @@ -100,7 +104,9 @@ impl FungibleToken { /// Balance of ETH (ETH on Aurora) pub fn internal_unwrap_balance_of_eth_on_aurora(&self, address: EthAddress) -> Balance { - Engine::get_balance(&Address(address)).raw().as_u128() + Engine::get_balance(&self.io, &Address(address)) + .raw() + .as_u128() } /// Internal ETH deposit to NEAR - nETH (NEP-141) @@ -121,7 +127,11 @@ impl FungibleToken { pub fn internal_deposit_eth_to_aurora(&mut self, address: EthAddress, amount: Balance) { let balance = self.internal_unwrap_balance_of_eth_on_aurora(address); if let Some(new_balance) = balance.checked_add(amount) { - Engine::set_balance(&Address(address), &Wei::new(U256::from(new_balance))); + Engine::set_balance( + &mut self.io, + &Address(address), + &Wei::new(U256::from(new_balance)), + ); self.total_eth_supply_on_aurora = self .total_eth_supply_on_aurora .checked_add(amount) @@ -149,7 +159,11 @@ impl FungibleToken { pub fn internal_withdraw_eth_from_aurora(&mut self, address: EthAddress, amount: Balance) { let balance = self.internal_unwrap_balance_of_eth_on_aurora(address); if let Some(new_balance) = balance.checked_sub(amount) { - Engine::set_balance(&Address(address), &Wei::new(U256::from(new_balance))); + Engine::set_balance( + &mut self.io, + &Address(address), + &Wei::new(U256::from(new_balance)), + ); self.total_eth_supply_on_aurora = self .total_eth_supply_on_aurora .checked_sub(amount) @@ -448,34 +462,39 @@ impl FungibleToken { /// Insert account. /// Calculate total unique accounts - pub fn accounts_insert(&self, account_id: &AccountId, amount: Balance) { + pub fn accounts_insert(&mut self, account_id: &AccountId, amount: Balance) { if !self.accounts_contains_key(account_id) { let key = Self::get_statistic_key(); - let accounts_counter = sdk::read_u64(&key) + let accounts_counter = self + .io + .read_u64(&key) .unwrap_or(0) .checked_add(1) .expect("ERR_ACCOUNTS_COUNTER_OVERFLOW"); - sdk::write_storage(&key, &accounts_counter.to_le_bytes()); + self.io.write_storage(&key, &accounts_counter.to_le_bytes()); } - sdk::save_contract(&Self::account_to_key(account_id), &amount); + self.io + .write_borsh(&Self::account_to_key(account_id), &amount); } /// Get accounts counter for statistics /// It represents total unique accounts. pub fn get_accounts_counter(&self) -> u64 { - sdk::read_u64(&Self::get_statistic_key()).unwrap_or(0) + self.io.read_u64(&Self::get_statistic_key()).unwrap_or(0) } fn accounts_contains_key(&self, account_id: &AccountId) -> bool { - sdk::storage_has_key(&Self::account_to_key(account_id)) + self.io.storage_has_key(&Self::account_to_key(account_id)) } - fn accounts_remove(&self, account_id: &AccountId) { - sdk::remove_storage(&Self::account_to_key(account_id)) + fn accounts_remove(&mut self, account_id: &AccountId) { + self.io.remove_storage(&Self::account_to_key(account_id)); } pub fn accounts_get(&self, account_id: &AccountId) -> Option> { - sdk::read_storage(&Self::account_to_key(account_id)) + self.io + .read_storage(&Self::account_to_key(account_id)) + .map(|s| s.to_vec()) } /// Fungible token key diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 4fc31c4e6..ce84f5993 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -64,7 +64,7 @@ pub unsafe fn on_alloc_error(_: core::alloc::Layout) -> ! { #[cfg(feature = "contract")] mod contract { - use borsh::{BorshDeserialize, BorshSerialize}; + use borsh::BorshSerialize; use crate::connector::EthConnectorContract; use crate::engine::{Engine, EngineState, GasPaymentError}; @@ -77,6 +77,8 @@ mod contract { PauseEthConnectorCallArgs, SetContractDataCallArgs, SubmitResult, TransactionStatus, TransferCallCallArgs, ViewCallArgs, }; + use aurora_engine_sdk::io::{StorageIntermediate, IO}; + use aurora_engine_sdk::near_runtime::Runtime; use crate::json::parse_json; use crate::prelude::parameters::RefundCallArgs; @@ -103,58 +105,65 @@ mod contract { /// Should be called on deployment. #[no_mangle] pub extern "C" fn new() { - if let Ok(state) = Engine::get_state() { + let mut io = Runtime; + if let Ok(state) = Engine::get_state(&io) { require_owner_only(&state); } - let args: NewCallArgs = sdk::read_input_borsh().sdk_unwrap(); - Engine::set_state(args.into()); + let args: NewCallArgs = io.read_input_borsh().sdk_unwrap(); + Engine::set_state(&mut io, args.into()); } /// Get version of the contract. #[no_mangle] pub extern "C" fn get_version() { + let mut io = Runtime; let version = match option_env!("NEAR_EVM_VERSION") { Some(v) => v.as_bytes(), None => include_bytes!("../../VERSION"), }; - sdk::return_output(version) + io.return_output(version) } /// Get owner account id for this contract. #[no_mangle] pub extern "C" fn get_owner() { - let state = Engine::get_state().sdk_unwrap(); - sdk::return_output(state.owner_id.as_bytes()); + let mut io = Runtime; + let state = Engine::get_state(&io).sdk_unwrap(); + io.return_output(state.owner_id.as_bytes()); } /// Get bridge prover id for this contract. #[no_mangle] pub extern "C" fn get_bridge_prover() { - let state = Engine::get_state().sdk_unwrap(); - sdk::return_output(state.bridge_prover_id.as_bytes()); + let mut io = Runtime; + let state = Engine::get_state(&io).sdk_unwrap(); + io.return_output(state.bridge_prover_id.as_bytes()); } /// Get chain id for this contract. #[no_mangle] pub extern "C" fn get_chain_id() { - sdk::return_output(&Engine::get_state().sdk_unwrap().chain_id) + let mut io = Runtime; + io.return_output(&Engine::get_state(&io).sdk_unwrap().chain_id) } #[no_mangle] pub extern "C" fn get_upgrade_index() { - let state = Engine::get_state().sdk_unwrap(); + let mut io = Runtime; + let state = Engine::get_state(&io).sdk_unwrap(); let index = internal_get_upgrade_index(); - sdk::return_output(&(index + state.upgrade_delay_blocks).to_le_bytes()) + io.return_output(&(index + state.upgrade_delay_blocks).to_le_bytes()) } /// Stage new code for deployment. #[no_mangle] pub extern "C" fn stage_upgrade() { - let state = Engine::get_state().sdk_unwrap(); + let mut io = Runtime; + let state = Engine::get_state(&io).sdk_unwrap(); require_owner_only(&state); - sdk::read_input_and_store(&bytes_to_key(KeyPrefix::Config, CODE_KEY)); - sdk::write_storage( + io.read_input_and_store(&bytes_to_key(KeyPrefix::Config, CODE_KEY)); + io.write_storage( &bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY), &sdk::block_index().to_le_bytes(), ); @@ -163,7 +172,8 @@ mod contract { /// Deploy staged upgrade. #[no_mangle] pub extern "C" fn deploy_upgrade() { - let state = Engine::get_state().sdk_unwrap(); + let io = Runtime; + let state = Engine::get_state(&io).sdk_unwrap(); let index = internal_get_upgrade_index(); if sdk::block_index() <= index + state.upgrade_delay_blocks { sdk::panic_utf8(b"ERR_NOT_ALLOWED:TOO_EARLY"); @@ -186,8 +196,9 @@ mod contract { /// Deploy code into the EVM. #[no_mangle] pub extern "C" fn deploy_code() { - let input = sdk::read_input(); - let mut engine = Engine::new(predecessor_address()).sdk_unwrap(); + let io = Runtime; + let input = io.read_input().to_vec(); + let mut engine = Engine::new(predecessor_address(), io).sdk_unwrap(); Engine::deploy_code_with_input(&mut engine, input) .map(|res| res.try_to_vec().sdk_expect("ERR_SERIALIZE")) .sdk_process(); @@ -197,8 +208,9 @@ mod contract { /// Call method on the EVM contract. #[no_mangle] pub extern "C" fn call() { - let args: FunctionCallArgs = sdk::read_input_borsh().sdk_unwrap(); - let mut engine = Engine::new(predecessor_address()).sdk_unwrap(); + let io = Runtime; + let args: FunctionCallArgs = io.read_input_borsh().sdk_unwrap(); + let mut engine = Engine::new(predecessor_address(), io).sdk_unwrap(); Engine::call_with_args(&mut engine, args) .map(|res| res.try_to_vec().sdk_expect("ERR_SERIALIZE")) .sdk_process(); @@ -209,13 +221,14 @@ mod contract { /// Must match CHAIN_ID to make sure it's signed for given chain vs replayed from another chain. #[no_mangle] pub extern "C" fn submit() { - let input = sdk::read_input(); + let mut io = Runtime; + let input = io.read_input().to_vec(); let transaction: NormalizedEthTransaction = EthTransactionKind::try_from(input.as_slice()) .sdk_unwrap() .into(); - let state = Engine::get_state().sdk_unwrap(); + let state = Engine::get_state(&io).sdk_unwrap(); // Validate the chain ID, if provided inside the signature: if let Some(chain_id) = transaction.chain_id { @@ -231,7 +244,7 @@ mod contract { sdk::log!(crate::prelude::format!("signer_address {:?}", sender).as_str()); - Engine::check_nonce(&sender, &transaction.nonce).sdk_unwrap(); + Engine::check_nonce(&io, &sender, &transaction.nonce).sdk_unwrap(); // Check intrinsic gas is covered by transaction gas limit match transaction.intrinsic_gas(crate::engine::CONFIG) { @@ -248,13 +261,13 @@ mod contract { } // Figure out what kind of a transaction this is, and execute it: - let mut engine = Engine::new_with_state(state, sender); + let mut engine = Engine::new_with_state(state, sender, io); let prepaid_amount = match engine.charge_gas(&sender, &transaction) { Ok(gas_result) => gas_result, Err(GasPaymentError::OutOfFund) => { - Engine::increment_nonce(&sender); + Engine::increment_nonce(&mut io, &sender); let result = SubmitResult::new(TransactionStatus::OutOfFund, 0, vec![]); - sdk::return_output(&result.try_to_vec().unwrap()); + io.return_output(&result.try_to_vec().unwrap()); return; } Err(err) => sdk::panic_utf8(err.as_ref()), @@ -295,7 +308,8 @@ mod contract { Ok(submit_result) => submit_result.gas_used, Err(engine_err) => engine_err.gas_used, }; - Engine::refund_unused_gas(&sender, gas_used, prepaid_amount, &relayer).sdk_unwrap(); + Engine::refund_unused_gas(&mut io, &sender, gas_used, prepaid_amount, &relayer) + .sdk_unwrap(); // return result to user result @@ -306,8 +320,9 @@ mod contract { #[cfg(feature = "meta-call")] #[no_mangle] pub extern "C" fn meta_call() { - let input = sdk::read_input(); - let state = Engine::get_state().sdk_unwrap(); + let io = Runtime; + let input = io.read_input().to_vec(); + let state = Engine::get_state(&io).sdk_unwrap(); let domain_separator = crate::meta_parsing::near_erc712_domain(U256::from(state.chain_id)); let meta_call_args = crate::meta_parsing::parse_meta_call( &domain_separator, @@ -316,9 +331,9 @@ mod contract { ) .sdk_expect("ERR_META_TX_PARSE"); - Engine::check_nonce(&meta_call_args.sender, &meta_call_args.nonce).sdk_unwrap(); + Engine::check_nonce(&io, &meta_call_args.sender, &meta_call_args.nonce).sdk_unwrap(); - let mut engine = Engine::new_with_state(state, meta_call_args.sender); + let mut engine = Engine::new_with_state(state, meta_call_args.sender, io); let result = engine.call( meta_call_args.sender, meta_call_args.contract_address, @@ -334,9 +349,10 @@ mod contract { #[no_mangle] pub extern "C" fn register_relayer() { - let relayer_address = sdk::read_input_arr20().sdk_unwrap(); + let io = Runtime; + let relayer_address = io.read_input_arr20().sdk_unwrap(); - let mut engine = Engine::new(predecessor_address()).sdk_unwrap(); + let mut engine = Engine::new(predecessor_address(), io).sdk_unwrap(); engine.register_relayer( sdk::predecessor_account_id().as_slice(), Address(relayer_address), @@ -350,16 +366,17 @@ mod contract { /// error, or no token is returned if tx was successful. #[no_mangle] pub extern "C" fn ft_on_transfer() { - let mut engine = Engine::new(predecessor_address()).sdk_unwrap(); + let io = Runtime; + let mut engine = Engine::new(predecessor_address(), io).sdk_unwrap(); - let args: NEP141FtOnTransferArgs = parse_json(sdk::read_input().as_slice()) + let args: NEP141FtOnTransferArgs = parse_json(io.read_input().to_vec().as_slice()) .sdk_unwrap() .try_into() .sdk_unwrap(); if sdk::predecessor_account_id() == sdk::current_account_id() { - let engine = Engine::new(predecessor_address()).sdk_unwrap(); - EthConnectorContract::get_instance().ft_on_transfer(&engine, &args); + let engine = Engine::new(predecessor_address(), io).sdk_unwrap(); + EthConnectorContract::get_instance(io).ft_on_transfer(&engine, &args); } else { engine.receive_erc20_tokens(&args); } @@ -368,11 +385,11 @@ mod contract { /// Deploy ERC20 token mapped to a NEP141 #[no_mangle] pub extern "C" fn deploy_erc20_token() { + let mut io = Runtime; // Id of the NEP141 token in Near - let args: DeployErc20TokenArgs = - DeployErc20TokenArgs::try_from_slice(&sdk::read_input()).sdk_expect("ERR_ARG_PARSE"); + let args: DeployErc20TokenArgs = io.read_input_borsh().sdk_unwrap(); - let mut engine = Engine::new(predecessor_address()).sdk_unwrap(); + let mut engine = Engine::new(predecessor_address(), io).sdk_unwrap(); let erc20_admin_address = current_address(); #[cfg(feature = "error_refund")] @@ -402,7 +419,7 @@ mod contract { engine .register_token(address.as_bytes(), args.nep141.as_bytes()) .sdk_unwrap(); - sdk::return_output(&address.as_bytes().try_to_vec().sdk_expect("ERR_SERIALIZE")); + io.return_output(&address.as_bytes().try_to_vec().sdk_expect("ERR_SERIALIZE")); // TODO: charge for storage } @@ -412,6 +429,7 @@ mod contract { #[no_mangle] pub extern "C" fn refund_on_error() { sdk::assert_private_call(); + let io = Runtime; // This function should only be called as the callback of // exactly one promise. @@ -421,12 +439,12 @@ mod contract { // Exit call failed; need to refund tokens if let PromiseResult::Failed = sdk::promise_result(0) { - let args: RefundCallArgs = sdk::read_input_borsh().sdk_unwrap(); + let args: RefundCallArgs = io.read_input_borsh().sdk_unwrap(); let refund_result = match args.erc20_address { // ERC-20 exit; re-mint burned tokens Some(erc20_address) => { let erc20_admin_address = current_address(); - let mut engine = Engine::new(erc20_admin_address).sdk_unwrap(); + let mut engine = Engine::new(erc20_admin_address, io).sdk_unwrap(); let erc20_address = Address(erc20_address); let refund_address = Address(args.recipient_address); let amount = U256::from_big_endian(&args.amount); @@ -451,7 +469,7 @@ mod contract { // ETH exit; transfer ETH back from precompile address None => { let exit_address = aurora_engine_precompiles::native::ExitToNear::ADDRESS; - let mut engine = Engine::new(exit_address).sdk_unwrap(); + let mut engine = Engine::new(exit_address, io).sdk_unwrap(); let refund_address = Address(args.recipient_address); let amount = Wei::new(U256::from_big_endian(&args.amount)); engine @@ -477,49 +495,57 @@ mod contract { /// #[no_mangle] pub extern "C" fn view() { - let args: ViewCallArgs = sdk::read_input_borsh().sdk_unwrap(); - let engine = Engine::new(Address::from_slice(&args.sender)).sdk_unwrap(); + let mut io = Runtime; + let args: ViewCallArgs = io.read_input_borsh().sdk_unwrap(); + let engine = Engine::new(Address::from_slice(&args.sender), io).sdk_unwrap(); let result = Engine::view_with_args(&engine, args).sdk_unwrap(); - sdk::return_output(&result.try_to_vec().sdk_expect("ERR_SERIALIZE")); + io.return_output(&result.try_to_vec().sdk_expect("ERR_SERIALIZE")); } #[no_mangle] pub extern "C" fn get_block_hash() { - let block_height = sdk::read_input_borsh().sdk_unwrap(); + let mut io = Runtime; + let block_height = io.read_input_borsh().sdk_unwrap(); let account_id = sdk::current_account_id(); - let chain_id = Engine::get_state().map(|state| state.chain_id).sdk_unwrap(); - let block_hash = Engine::compute_block_hash(chain_id, block_height, &account_id); - sdk::return_output(block_hash.as_bytes()) + let chain_id = Engine::get_state(&io) + .map(|state| state.chain_id) + .sdk_unwrap(); + let block_hash = crate::engine::compute_block_hash(chain_id, block_height, &account_id); + io.return_output(block_hash.as_bytes()) } #[no_mangle] pub extern "C" fn get_code() { - let address = sdk::read_input_arr20().sdk_unwrap(); - let code = Engine::get_code(&Address(address)); - sdk::return_output(&code) + let mut io = Runtime; + let address = io.read_input_arr20().sdk_unwrap(); + let code = Engine::get_code(&io, &Address(address)); + io.return_output(&code) } #[no_mangle] pub extern "C" fn get_balance() { - let address = sdk::read_input_arr20().sdk_unwrap(); - let balance = Engine::get_balance(&Address(address)); - sdk::return_output(&balance.to_bytes()) + let mut io = Runtime; + let address = io.read_input_arr20().sdk_unwrap(); + let balance = Engine::get_balance(&io, &Address(address)); + io.return_output(&balance.to_bytes()) } #[no_mangle] pub extern "C" fn get_nonce() { - let address = sdk::read_input_arr20().sdk_unwrap(); - let nonce = Engine::get_nonce(&Address(address)); - sdk::return_output(&u256_to_arr(&nonce)) + let mut io = Runtime; + let address = io.read_input_arr20().sdk_unwrap(); + let nonce = Engine::get_nonce(&io, &Address(address)); + io.return_output(&u256_to_arr(&nonce)) } #[no_mangle] pub extern "C" fn get_storage_at() { - let args: GetStorageAtArgs = sdk::read_input_borsh().sdk_unwrap(); + let mut io = Runtime; + let args: GetStorageAtArgs = io.read_input_borsh().sdk_unwrap(); let address = Address(args.address); - let generation = Engine::get_generation(&address); - let value = Engine::get_storage(&Address(args.address), &H256(args.key), generation); - sdk::return_output(&value.0) + let generation = Engine::get_generation(&io, &address); + let value = Engine::get_storage(&io, &Address(args.address), &H256(args.key), generation); + io.return_output(&value.0) } /// @@ -528,28 +554,31 @@ mod contract { #[cfg(feature = "evm_bully")] #[no_mangle] pub extern "C" fn begin_chain() { - let mut state = Engine::get_state().sdk_unwrap(); + let mut io = Runtime; + let mut state = Engine::get_state(&io).sdk_unwrap(); require_owner_only(&state); - let args: BeginChainArgs = sdk::read_input_borsh().sdk_unwrap(); + let args: BeginChainArgs = io.read_input_borsh().sdk_unwrap(); state.chain_id = args.chain_id; - Engine::set_state(state); + Engine::set_state(&mut io, state); // set genesis block balances for account_balance in args.genesis_alloc { Engine::set_balance( + &mut io, &Address(account_balance.address), &crate::prelude::types::Wei::new(U256::from(account_balance.balance)), ) } // return new chain ID - sdk::return_output(&Engine::get_state().sdk_unwrap().chain_id) + io.return_output(&Engine::get_state(&io).sdk_unwrap().chain_id) } #[cfg(feature = "evm_bully")] #[no_mangle] pub extern "C" fn begin_block() { - let state = Engine::get_state().sdk_unwrap(); + let io = Runtime; + let state = Engine::get_state(&io).sdk_unwrap(); require_owner_only(&state); - let _args: BeginBlockArgs = sdk::read_input_borsh().sdk_unwrap(); + let _args: BeginBlockArgs = io.read_input_borsh().sdk_unwrap(); // TODO: https://github.com/aurora-is-near/aurora-engine/issues/2 } @@ -558,9 +587,10 @@ mod contract { // Only the owner can initialize the EthConnector sdk::assert_private_call(); - let args = InitCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let io = Runtime; + let args: InitCallArgs = io.read_input_borsh().sdk_unwrap(); - EthConnectorContract::init_contract(args); + EthConnectorContract::init_contract(io, args); } #[no_mangle] @@ -568,69 +598,86 @@ mod contract { // Only the owner can set the EthConnector contract data sdk::assert_private_call(); - let args = - SetContractDataCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let mut io = Runtime; + let args: SetContractDataCallArgs = io.read_input_borsh().sdk_unwrap(); - EthConnectorContract::set_contract_data(args); + EthConnectorContract::set_contract_data(&mut io, args); } #[no_mangle] pub extern "C" fn withdraw() { - EthConnectorContract::get_instance().withdraw_eth_from_near() + let mut io = Runtime; + let args = io.read_input_borsh().sdk_unwrap(); + let result = EthConnectorContract::get_instance(io).withdraw_eth_from_near(args); + let result_bytes = result.try_to_vec().sdk_expect("ERR_SERIALIZE"); + io.return_output(&result_bytes); } #[no_mangle] pub extern "C" fn deposit() { - EthConnectorContract::get_instance().deposit() + let io = Runtime; + let raw_proof = io.read_input().to_vec(); + EthConnectorContract::get_instance(io).deposit(raw_proof) } #[no_mangle] pub extern "C" fn finish_deposit() { - EthConnectorContract::get_instance().finish_deposit(); + sdk::assert_private_call(); + let io = Runtime; + let data = io.read_input_borsh().sdk_unwrap(); + EthConnectorContract::get_instance(io).finish_deposit(data); } #[no_mangle] pub extern "C" fn is_used_proof() { - let args = IsUsedProofCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); + let mut io = Runtime; + let args: IsUsedProofCallArgs = io.read_input_borsh().sdk_unwrap(); - let is_used_proof = EthConnectorContract::get_instance().is_used_proof(args.proof); + let is_used_proof = EthConnectorContract::get_instance(io).is_used_proof(args.proof); let res = is_used_proof.try_to_vec().unwrap(); - sdk::return_output(&res[..]); + io.return_output(&res[..]); } #[no_mangle] pub extern "C" fn ft_total_supply() { - EthConnectorContract::get_instance().ft_total_eth_supply_on_near(); + let io = Runtime; + EthConnectorContract::get_instance(io).ft_total_eth_supply_on_near(); } #[no_mangle] pub extern "C" fn ft_total_eth_supply_on_near() { - EthConnectorContract::get_instance().ft_total_eth_supply_on_near(); + let io = Runtime; + EthConnectorContract::get_instance(io).ft_total_eth_supply_on_near(); } #[no_mangle] pub extern "C" fn ft_total_eth_supply_on_aurora() { - EthConnectorContract::get_instance().ft_total_eth_supply_on_aurora(); + let io = Runtime; + EthConnectorContract::get_instance(io).ft_total_eth_supply_on_aurora(); } #[no_mangle] pub extern "C" fn ft_balance_of() { - EthConnectorContract::get_instance().ft_balance_of(); + let io = Runtime; + EthConnectorContract::get_instance(io).ft_balance_of(); } #[no_mangle] pub extern "C" fn ft_balance_of_eth() { - EthConnectorContract::get_instance().ft_balance_of_eth_on_aurora(); + let io = Runtime; + EthConnectorContract::get_instance(io).ft_balance_of_eth_on_aurora(); } #[no_mangle] pub extern "C" fn ft_transfer() { - EthConnectorContract::get_instance().ft_transfer(); + let io = Runtime; + EthConnectorContract::get_instance(io).ft_transfer(); } #[no_mangle] pub extern "C" fn ft_resolve_transfer() { - EthConnectorContract::get_instance().ft_resolve_transfer(); + let io = Runtime; + EthConnectorContract::get_instance(io).ft_resolve_transfer(); } #[no_mangle] @@ -639,56 +686,62 @@ mod contract { // Check is payable sdk::assert_one_yocto(); + let io = Runtime; let args = TransferCallCallArgs::from( - parse_json(&sdk::read_input()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), + parse_json(&io.read_input().to_vec()).expect_utf8(ERR_FAILED_PARSE.as_bytes()), ); - EthConnectorContract::get_instance().ft_transfer_call(args); + EthConnectorContract::get_instance(io).ft_transfer_call(args); } #[no_mangle] pub extern "C" fn storage_deposit() { - EthConnectorContract::get_instance().storage_deposit() + let io = Runtime; + EthConnectorContract::get_instance(io).storage_deposit() } #[no_mangle] pub extern "C" fn storage_withdraw() { - EthConnectorContract::get_instance().storage_withdraw() + let io = Runtime; + EthConnectorContract::get_instance(io).storage_withdraw() } #[no_mangle] pub extern "C" fn storage_balance_of() { - EthConnectorContract::get_instance().storage_balance_of() + let io = Runtime; + EthConnectorContract::get_instance(io).storage_balance_of() } #[no_mangle] pub extern "C" fn get_paused_flags() { - let paused_flags = EthConnectorContract::get_instance().get_paused_flags(); + let mut io = Runtime; + let paused_flags = EthConnectorContract::get_instance(io).get_paused_flags(); let data = paused_flags.try_to_vec().expect(ERR_FAILED_PARSE); - sdk::return_output(&data[..]); + io.return_output(&data[..]); } #[no_mangle] pub extern "C" fn set_paused_flags() { sdk::assert_private_call(); - let args = - PauseEthConnectorCallArgs::try_from_slice(&sdk::read_input()).expect(ERR_FAILED_PARSE); - EthConnectorContract::get_instance().set_paused_flags(args); + let io = Runtime; + let args: PauseEthConnectorCallArgs = io.read_input_borsh().sdk_unwrap(); + + EthConnectorContract::get_instance(io).set_paused_flags(args); } #[no_mangle] pub extern "C" fn get_accounts_counter() { - EthConnectorContract::get_instance().get_accounts_counter() + let io = Runtime; + EthConnectorContract::get_instance(io).get_accounts_counter(); } #[no_mangle] pub extern "C" fn get_erc20_from_nep141() { - let args: GetErc20FromNep141CallArgs = - GetErc20FromNep141CallArgs::try_from_slice(&sdk::read_input()) - .sdk_expect("ERR_ARG_PARSE"); + let mut io = Runtime; + let args: GetErc20FromNep141CallArgs = io.read_input_borsh().sdk_unwrap(); - sdk::return_output( - Engine::get_erc20_from_nep141(args.nep141.as_bytes()) + io.return_output( + Engine::get_erc20_from_nep141(io, args.nep141.as_bytes()) .sdk_unwrap() .as_slice(), ); @@ -696,9 +749,10 @@ mod contract { #[no_mangle] pub extern "C" fn get_nep141_from_erc20() { - sdk::return_output( - Engine::nep141_erc20_map() - .lookup_right(sdk::read_input().as_slice()) + let mut io = Runtime; + io.return_output( + Engine::nep141_erc20_map(io) + .lookup_right(io.read_input().to_vec().as_slice()) .sdk_expect("ERC20_NOT_FOUND") .as_slice(), ); @@ -706,18 +760,20 @@ mod contract { #[no_mangle] pub extern "C" fn ft_metadata() { + let mut io = Runtime; let metadata: FungibleTokenMetadata = - EthConnectorContract::get_metadata().unwrap_or_default(); + EthConnectorContract::get_metadata(&io).unwrap_or_default(); let json_data = crate::json::JsonValue::from(metadata); - sdk::return_output(json_data.to_string().as_bytes()) + io.return_output(json_data.to_string().as_bytes()) } #[cfg(feature = "integration-test")] #[no_mangle] pub extern "C" fn verify_log_entry() { sdk::log!("Call from verify_log_entry"); + let mut io = Runtime; let data = true.try_to_vec().unwrap(); - sdk::return_output(&data[..]); + io.return_output(&data[..]); } /// Function used to create accounts for tests @@ -728,11 +784,12 @@ mod contract { const GAS_FOR_VERIFY: u64 = 20_000_000_000_000; const GAS_FOR_FINISH: u64 = 50_000_000_000_000; - let args: ([u8; 20], u64, u64) = sdk::read_input_borsh().sdk_expect("ERR_ARGS"); + let io = Runtime; + let args: ([u8; 20], u64, u64) = io.read_input_borsh().sdk_expect("ERR_ARGS"); let address = Address(args.0); let nonce = U256::from(args.1); let balance = U256::from(args.2); - let mut engine = Engine::new(address).sdk_unwrap(); + let mut engine = Engine::new(address, io).sdk_unwrap(); let state_change = evm::backend::Apply::Modify { address, basic: evm::backend::Basic { balance, nonce }, @@ -773,10 +830,11 @@ mod contract { /// fn internal_get_upgrade_index() -> u64 { - match sdk::read_u64(&bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY)) { + let io = Runtime; + match io.read_u64(&bytes_to_key(KeyPrefix::Config, CODE_STAGE_KEY)) { Ok(index) => index, - Err(sdk::ReadU64Error::InvalidU64) => sdk::panic_utf8(b"ERR_INVALID_UPGRADE"), - Err(sdk::ReadU64Error::MissingValue) => sdk::panic_utf8(b"ERR_NO_UPGRADE"), + Err(sdk::error::ReadU64Error::InvalidU64) => sdk::panic_utf8(b"ERR_INVALID_UPGRADE"), + Err(sdk::error::ReadU64Error::MissingValue) => sdk::panic_utf8(b"ERR_NO_UPGRADE"), } } diff --git a/engine/src/map.rs b/engine/src/map.rs index c08ce18be..28de64c14 100644 --- a/engine/src/map.rs +++ b/engine/src/map.rs @@ -1,14 +1,25 @@ pub use crate::prelude::{bytes_to_key, BorshDeserialize, BorshSerialize, KeyPrefixU8, Vec}; +use aurora_engine_sdk::io::{StorageIntermediate, IO}; +use aurora_engine_sdk::near_runtime::Runtime; /// An non-iterable implementation of a map that stores its content directly on the trie. /// Use `key_prefix` as a unique prefix for keys. -#[derive(BorshSerialize, BorshDeserialize, Default)] -pub struct LookupMap {} +#[derive(BorshSerialize, BorshDeserialize)] +pub struct LookupMap { + #[borsh_skip] + pub io: I, +} + +impl Default for LookupMap { + fn default() -> Self { + Self { io: Runtime } + } +} -impl LookupMap { +impl LookupMap { /// Create a new map. - pub fn new() -> Self { - Self {} + pub fn new(io: I) -> Self { + Self { io } } /// Build key for this map scope @@ -20,20 +31,20 @@ impl LookupMap { #[allow(dead_code)] pub fn contains_key_raw(&self, key_raw: &[u8]) -> bool { let storage_key = self.raw_key_to_storage_key(key_raw); - crate::prelude::sdk::storage_has_key(&storage_key) + self.io.storage_has_key(&storage_key) } /// Returns the serialized value corresponding to the serialized key. #[allow(dead_code)] pub fn get_raw(&self, key_raw: &[u8]) -> Option> { let storage_key = self.raw_key_to_storage_key(key_raw); - crate::prelude::sdk::read_storage(&storage_key) + self.io.read_storage(&storage_key).map(|s| s.to_vec()) } /// Inserts a serialized key-value pair into the map. pub fn insert_raw(&mut self, key_raw: &[u8], value_raw: &[u8]) { let storage_key = self.raw_key_to_storage_key(key_raw); - crate::prelude::sdk::write_storage(&storage_key, value_raw); + self.io.write_storage(&storage_key, value_raw); } /// Removes a serialized key from the map, returning the serialized value at the key if the key @@ -41,47 +52,50 @@ impl LookupMap { #[allow(dead_code)] pub fn remove_raw(&mut self, key_raw: &[u8]) -> Option> { let storage_key = self.raw_key_to_storage_key(key_raw); - crate::prelude::sdk::remove_storage_with_result(&storage_key) + self.io.remove_storage(&storage_key).map(|s| s.to_vec()) } } #[derive(BorshSerialize, BorshDeserialize, Default)] -pub struct BijectionMap {} +pub struct BijectionMap { + #[borsh_skip] + pub io: I, +} -impl BijectionMap { - fn left_to_right() -> LookupMap { - Default::default() +impl BijectionMap { + fn left_to_right(&self) -> LookupMap { + LookupMap { io: self.io } } - fn right_to_left() -> LookupMap { - Default::default() + fn right_to_left(&self) -> LookupMap { + LookupMap { io: self.io } } pub fn insert(&self, value_left: &[u8], value_right: &[u8]) { - Self::left_to_right().insert_raw(value_left, value_right); - Self::right_to_left().insert_raw(value_right, value_left); + self.left_to_right().insert_raw(value_left, value_right); + self.right_to_left().insert_raw(value_right, value_left); } pub fn lookup_left(&self, value_left: &[u8]) -> Option> { - Self::left_to_right().get_raw(value_left) + self.left_to_right().get_raw(value_left) } #[allow(dead_code)] pub fn lookup_right(&self, value_right: &[u8]) -> Option> { - Self::right_to_left().get_raw(value_right) + self.right_to_left().get_raw(value_right) } #[allow(dead_code)] pub fn remove_left(&self, value_left: &[u8]) { - if let Some(value_right) = Self::left_to_right().remove_raw(value_left) { - Self::right_to_left().remove_raw(value_right.as_slice()); + if let Some(value_right) = self.left_to_right().remove_raw(value_left) { + self.right_to_left().remove_raw(value_right.as_slice()); } } #[allow(dead_code)] pub fn remove_right(&self, value_right: &[u8]) { - if let Some(value_left) = Self::right_to_left().remove_raw(value_right) { - Self::left_to_right().remove_raw(value_left.as_slice()); + if let Some(value_left) = self.right_to_left().remove_raw(value_right) { + self.left_to_right().remove_raw(value_left.as_slice()); } } } diff --git a/etc/state-migration-test/src/lib.rs b/etc/state-migration-test/src/lib.rs index 8f949be4b..ef87ae348 100644 --- a/etc/state-migration-test/src/lib.rs +++ b/etc/state-migration-test/src/lib.rs @@ -4,7 +4,8 @@ extern crate alloc; use alloc::vec::Vec; use aurora_engine::engine::{Engine, EngineState}; -use aurora_engine_sdk as sdk; +use aurora_engine_sdk::near_runtime::Runtime; +use aurora_engine_sdk::io::{IO, StorageIntermediate}; use aurora_engine_types::storage; use borsh::{BorshDeserialize, BorshSerialize}; @@ -16,9 +17,10 @@ struct NewFancyState { #[no_mangle] pub extern "C" fn state_migration() { - let old_state = match Engine::get_state() { + let mut io = Runtime; + let old_state = match Engine::get_state(&io) { Ok(state) => state, - Err(e) => sdk::panic_utf8(e.as_ref()), + Err(e) => aurora_engine_sdk::panic_utf8(e.as_ref()), }; let new_state = NewFancyState { @@ -26,16 +28,17 @@ pub extern "C" fn state_migration() { some_other_numbers: [3, 1, 4, 1, 5, 9, 2], }; - sdk::write_storage(&state_key(), &new_state.try_to_vec().expect("ERR_SER")); + io.write_storage(&state_key(), &new_state.try_to_vec().expect("ERR_SER")); } #[no_mangle] pub extern "C" fn some_new_fancy_function() { - let state = sdk::read_storage(&state_key()) - .and_then(|bytes| NewFancyState::try_from_slice(&bytes).ok()) + let mut io = Runtime; + let state = io.read_storage(&state_key()) + .and_then(|bytes| NewFancyState::try_from_slice(&bytes.to_vec()).ok()) .unwrap(); - sdk::return_output(&state.some_other_numbers.try_to_vec().unwrap()); + io.return_output(&state.some_other_numbers.try_to_vec().unwrap()); } fn state_key() -> Vec {