diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs
index fc6d9cc4e..622c123b1 100644
--- a/engine-precompiles/src/lib.rs
+++ b/engine-precompiles/src/lib.rs
@@ -15,6 +15,7 @@ pub mod prepaid_gas;
pub mod promise_result;
pub mod random;
pub mod secp256k1;
+pub mod set_gas_token;
mod utils;
pub mod xcc;
@@ -111,8 +112,8 @@ impl HardFork for Istanbul {}
impl HardFork for Berlin {}
pub struct Precompiles<'a, I, E, H> {
- pub all_precompiles: prelude::BTreeMap
>,
- pub paused_precompiles: prelude::BTreeSet,
+ pub all_precompiles: BTreeMap>,
+ pub paused_precompiles: BTreeSet,
}
impl<'a, I, E, H> Precompiles<'a, I, E, H> {
@@ -149,7 +150,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::Preco
Some(result.and_then(|output| post_process(output, handle)))
}
- fn is_precompile(&self, address: prelude::H160) -> bool {
+ fn is_precompile(&self, address: H160) -> bool {
self.all_precompiles.contains_key(&Address::new(address))
}
}
@@ -206,7 +207,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E,
RandomSeed::ADDRESS,
CurrentAccount::ADDRESS,
];
- let fun: prelude::Vec> = vec![
+ let fun: Vec> = vec![
Box::new(ECRecover),
Box::new(SHA256),
Box::new(RIPEMD160),
@@ -235,7 +236,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E,
RandomSeed::ADDRESS,
CurrentAccount::ADDRESS,
];
- let fun: prelude::Vec> = vec![
+ let fun: Vec> = vec![
Box::new(ECRecover),
Box::new(SHA256),
Box::new(RIPEMD160),
@@ -270,7 +271,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E,
RandomSeed::ADDRESS,
CurrentAccount::ADDRESS,
];
- let fun: prelude::Vec> = vec![
+ let fun: Vec> = vec![
Box::new(ECRecover),
Box::new(SHA256),
Box::new(RIPEMD160),
@@ -306,7 +307,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E,
RandomSeed::ADDRESS,
CurrentAccount::ADDRESS,
];
- let fun: prelude::Vec> = vec![
+ let fun: Vec> = vec![
Box::new(ECRecover),
Box::new(SHA256),
Box::new(RIPEMD160),
@@ -386,10 +387,10 @@ pub enum AllPrecompiles<'a, I, E, H> {
/// fn for making an address by concatenating the bytes from two given numbers,
/// Note that 32 + 128 = 160 = 20 bytes (the length of an address). This function is used
/// as a convenience for specifying the addresses of the various precompiles.
-pub const fn make_address(x: u32, y: u128) -> prelude::types::Address {
+pub const fn make_address(x: u32, y: u128) -> Address {
let x_bytes = x.to_be_bytes();
let y_bytes = y.to_be_bytes();
- prelude::types::Address::new(H160([
+ Address::new(H160([
x_bytes[0],
x_bytes[1],
x_bytes[2],
@@ -413,10 +414,10 @@ pub const fn make_address(x: u32, y: u128) -> prelude::types::Address {
]))
}
-const fn make_h256(x: u128, y: u128) -> prelude::H256 {
+const fn make_h256(x: u128, y: u128) -> H256 {
let x_bytes = x.to_be_bytes();
let y_bytes = y.to_be_bytes();
- prelude::H256([
+ H256([
x_bytes[0],
x_bytes[1],
x_bytes[2],
diff --git a/engine-precompiles/src/set_gas_token.rs b/engine-precompiles/src/set_gas_token.rs
new file mode 100644
index 000000000..13614d7cf
--- /dev/null
+++ b/engine-precompiles/src/set_gas_token.rs
@@ -0,0 +1,175 @@
+use crate::prelude::{vec, Cow};
+use crate::set_gas_token::events::SetGasTokenLog;
+use crate::{EvmPrecompileResult, Precompile, PrecompileOutput};
+use aurora_engine_types::types::{Address, EthGas};
+use evm::backend::Log;
+use evm::{Context, ExitError};
+
+pub use consts::SET_GAS_TOKEN_ADDRESS;
+
+mod costs {
+ use crate::prelude::types::EthGas;
+
+ // TODO: gas costs, could be calculated returning logs of NEAR gas used prior and after.
+ // Should check if the gas check adds gas itself as well..?
+ pub(super) const SET_GAS_TOKEN_GAS: EthGas = EthGas::new(0);
+}
+
+pub mod consts {
+ use aurora_engine_types::types::Address;
+
+ /// Change gas token precompile address.
+ ///
+ /// Address: `0x076dae45c8e16a92258252fe04dedd97f1ea93d6`
+ ///
+ /// This address is computed as: `keccak("setGasToken")[12..]`
+ pub const SET_GAS_TOKEN_ADDRESS: Address =
+ crate::make_address(0x076dae45, 0xc8e16a92258252fe04dedd97f1ea93d6);
+}
+
+pub mod events {
+ use crate::prelude::vec;
+ use crate::set_gas_token::consts;
+ use aurora_engine_types::types::Address;
+ use aurora_engine_types::H256;
+ use evm::backend::Log;
+
+ pub const SET_GAS_TOKEN_SIGNATURE: H256 = crate::make_h256(
+ 0x29d0b6eaa171d0d1607729f506329510,
+ 0x7bc9766ba17d250f129cb5bd06503d13,
+ );
+
+ pub(crate) struct SetGasTokenLog {
+ pub sender: Address,
+ pub gas_token: Address,
+ }
+
+ impl SetGasTokenLog {
+ pub(crate) fn encode(self) -> Log {
+ let gas_token_address = {
+ let mut buf = [0u8; 32];
+ buf[12..].copy_from_slice(self.gas_token.as_bytes());
+ H256(buf)
+ };
+ let data = ethabi::encode(&[ethabi::Token::Address(self.sender.raw())]);
+ let topics = vec![SET_GAS_TOKEN_SIGNATURE, gas_token_address];
+
+ let raw_log = ethabi::RawLog { topics, data };
+
+ Log {
+ address: consts::SET_GAS_TOKEN_ADDRESS.raw(),
+ topics: raw_log.topics,
+ data: raw_log.data,
+ }
+ }
+ }
+
+ #[cfg(test)]
+ pub fn set_gas_token_schema() -> ethabi::Event {
+ ethabi::Event {
+ name: "SetGasToken".into(),
+ inputs: vec![
+ ethabi::EventParam {
+ name: "sender".into(),
+ kind: ethabi::ParamType::Address,
+ indexed: true,
+ },
+ ethabi::EventParam {
+ name: "gas_token".into(),
+ kind: ethabi::ParamType::Address,
+ indexed: true,
+ },
+ ],
+ anonymous: false,
+ }
+ }
+}
+
+/// A precompile contract used to set the gas token.
+///
+/// Takes an input which must be an approved ERC-20 contract, or ETH itself at
+/// the address `0x0`.
+pub struct SetGasToken;
+
+impl SetGasToken {
+ pub const ADDRESS: Address = SET_GAS_TOKEN_ADDRESS;
+}
+
+impl Precompile for SetGasToken {
+ fn required_gas(_input: &[u8]) -> Result {
+ Ok(costs::SET_GAS_TOKEN_GAS)
+ }
+
+ fn run(
+ &self,
+ input: &[u8],
+ target_gas: Option,
+ context: &Context,
+ is_static: bool,
+ ) -> EvmPrecompileResult {
+ let required_gas = Self::required_gas(input)?;
+ if let Some(target_gas) = target_gas {
+ if required_gas > target_gas {
+ return Err(ExitError::OutOfGas);
+ }
+ }
+
+ // It's not allowed to call exit precompiles in static mode
+ if is_static {
+ return Err(ExitError::Other(Cow::from("ERR_INVALID_IN_STATIC")));
+ } else if context.address != Self::ADDRESS.raw() {
+ return Err(ExitError::Other(Cow::from("ERR_INVALID_IN_DELEGATE")));
+ }
+
+ let set_gas_token_log: Log = {
+ let sender = Address::new(context.caller);
+ let gas_token = Address::try_from_slice(input)
+ .map_err(|_e| ExitError::Other(Cow::from("ERR_INVALID_ETH_ADDRESS")))?;
+ SetGasTokenLog { sender, gas_token }.encode()
+ };
+
+ Ok(PrecompileOutput {
+ cost: required_gas,
+ logs: vec![set_gas_token_log],
+ ..Default::default()
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::prelude::H160;
+ use aurora_engine_sdk::types::near_account_to_evm_address;
+
+ #[test]
+ fn test_precompile_id() {
+ assert_eq!(
+ SET_GAS_TOKEN_ADDRESS,
+ near_account_to_evm_address("setGasToken".as_bytes())
+ );
+ }
+
+ #[test]
+ fn test_signature() {
+ let schema = events::set_gas_token_schema();
+ assert_eq!(schema.signature(), events::SET_GAS_TOKEN_SIGNATURE);
+ }
+
+ #[test]
+ fn test_run() {
+ let set_gas_token = SetGasToken;
+ let user = H160([0x11u8; 20]);
+ let target_gas = EthGas::new(10_000);
+ let context = Context {
+ address: SET_GAS_TOKEN_ADDRESS.raw(),
+ caller: user,
+ apparent_value: Default::default(),
+ };
+ let input = hex::decode("8BEc47865aDe3B172A928df8f990Bc7f2A3b9f79").unwrap(); // Aurora mainnet contract address
+ let result = set_gas_token
+ .run(&input, Some(target_gas), &context, false)
+ .unwrap();
+ println!("{result:?}");
+ }
+}
diff --git a/engine-sdk/src/io.rs b/engine-sdk/src/io.rs
index 7aeb7aaa8..6742f9b7e 100644
--- a/engine-sdk/src/io.rs
+++ b/engine-sdk/src/io.rs
@@ -156,7 +156,7 @@ pub trait IO {
Ok(U256::from_big_endian(&result))
}
- fn write_borsh(
+ fn write_storage_borsh(
&mut self,
key: &[u8],
value: &T,
diff --git a/engine-standalone-storage/src/relayer_db/mod.rs b/engine-standalone-storage/src/relayer_db/mod.rs
index a528982ce..9cfb4466a 100644
--- a/engine-standalone-storage/src/relayer_db/mod.rs
+++ b/engine-standalone-storage/src/relayer_db/mod.rs
@@ -198,6 +198,7 @@ pub mod error {
mod test {
use super::FallibleIterator;
use crate::sync::types::{TransactionKind, TransactionMessage};
+ use aurora_engine::state::EngineStateV2;
use aurora_engine::{connector, parameters, state};
use aurora_engine_types::H256;
@@ -210,12 +211,12 @@ mod test {
fn test_fill_db() {
let mut storage = crate::Storage::open("rocks_tmp/").unwrap();
let mut connection = super::connect_without_tls(&Default::default()).unwrap();
- let engine_state = state::EngineState {
+ let engine_state = state::EngineState::V2(EngineStateV2 {
chain_id: aurora_engine_types::types::u256_to_arr(&1313161555.into()),
owner_id: "aurora".parse().unwrap(),
- bridge_prover_id: "prover.bridge.near".parse().unwrap(),
upgrade_delay_blocks: 0,
- };
+ default_gas_token: Default::default(),
+ });
// Initialize engine and connector states in storage.
// Use explicit scope so borrows against `storage` are dropped before processing DB rows.
@@ -234,9 +235,9 @@ mod test {
state::set_state(&mut local_io, engine_state.clone()).unwrap();
connector::EthConnectorContract::create_contract(
io,
- engine_state.owner_id.clone(),
+ engine_state.owner_id(),
parameters::InitCallArgs {
- prover_account: engine_state.bridge_prover_id.clone(),
+ prover_account: "prover.bridge.near".parse().unwrap(),
eth_custodian_address: "6bfad42cfc4efc96f529d786d643ff4a8b89fa52"
.to_string(),
metadata: Default::default(),
diff --git a/engine-standalone-storage/src/sync/mod.rs b/engine-standalone-storage/src/sync/mod.rs
index 006d1aaba..1a83a5ab5 100644
--- a/engine-standalone-storage/src/sync/mod.rs
+++ b/engine-standalone-storage/src/sync/mod.rs
@@ -420,9 +420,9 @@ pub enum ConsumeMessageOutcome {
#[derive(Debug)]
pub struct TransactionIncludedOutcome {
- pub hash: aurora_engine_types::H256,
+ pub hash: H256,
pub info: TransactionMessage,
- pub diff: crate::Diff,
+ pub diff: Diff,
pub maybe_result: Result, error::Error>,
}
diff --git a/engine-tests/src/test_utils/mod.rs b/engine-tests/src/test_utils/mod.rs
index ade752843..1ffe266fa 100644
--- a/engine-tests/src/test_utils/mod.rs
+++ b/engine-tests/src/test_utils/mod.rs
@@ -615,8 +615,8 @@ pub(crate) fn deploy_evm() -> AuroraRunner {
let args = NewCallArgs {
chain_id: crate::prelude::u256_to_arr(&U256::from(runner.chain_id)),
owner_id: str_to_account_id(runner.aurora_account_id.as_str()),
- bridge_prover_id: str_to_account_id("bridge_prover.near"),
upgrade_delay_blocks: 1,
+ default_gas_token: [0u8; 20],
};
let account_id = runner.aurora_account_id.clone();
@@ -821,8 +821,7 @@ pub(crate) fn as_account_id(account_id: &str) -> near_primitives_core::types::Ac
}
pub(crate) fn str_to_account_id(account_id: &str) -> AccountId {
- use aurora_engine_types::str::FromStr;
- AccountId::from_str(account_id).unwrap()
+ AccountId::new(account_id).unwrap()
}
pub fn unwrap_success(result: SubmitResult) -> Vec {
diff --git a/engine-tests/src/test_utils/standalone/mocks/mod.rs b/engine-tests/src/test_utils/standalone/mocks/mod.rs
index cfb5ec29d..0f761c6eb 100644
--- a/engine-tests/src/test_utils/standalone/mocks/mod.rs
+++ b/engine-tests/src/test_utils/standalone/mocks/mod.rs
@@ -17,7 +17,7 @@ pub const ETH_CUSTODIAN_ADDRESS: Address =
aurora_engine_precompiles::make_address(0xd045f7e1, 0x9b2488924b97f9c145b5e51d0d895a65);
pub fn compute_block_hash(block_height: u64) -> H256 {
- aurora_engine::engine::compute_block_hash([0u8; 32], block_height, b"aurora")
+ engine::compute_block_hash([0u8; 32], block_height, b"aurora")
}
pub fn insert_block(storage: &mut Storage, block_height: u64) {
@@ -52,8 +52,8 @@ pub fn init_evm(mut io: I, env: &E, chain_id: u64) {
let new_args = NewCallArgs {
chain_id: aurora_engine_types::types::u256_to_arr(&U256::from(chain_id)),
owner_id: env.current_account_id(),
- bridge_prover_id: test_utils::str_to_account_id("bridge_prover.near"),
upgrade_delay_blocks: 1,
+ default_gas_token: [0u8; 20], // Base coin
};
state::set_state(&mut io, new_args.into()).unwrap();
diff --git a/engine-tests/src/tests/eth_connector.rs b/engine-tests/src/tests/eth_connector.rs
index c0d0274d6..1c2a6adf1 100644
--- a/engine-tests/src/tests/eth_connector.rs
+++ b/engine-tests/src/tests/eth_connector.rs
@@ -13,7 +13,6 @@ use aurora_engine_types::types::{Fee, NEP141Wei};
use borsh::{BorshDeserialize, BorshSerialize};
use byte_slice_cast::AsByteSlice;
use ethabi::ethereum_types::U256;
-use near_sdk::test_utils::accounts;
use near_sdk_sim::transaction::ExecutionStatus;
use near_sdk_sim::{to_yocto, ExecutionResult, UserAccount, DEFAULT_GAS, STORAGE_AMOUNT};
use serde_json::json;
@@ -71,8 +70,8 @@ fn init_contract(
&NewCallArgs {
chain_id: [0u8; 32],
owner_id: str_to_account_id(master_account.account_id.clone().as_str()),
- bridge_prover_id: str_to_account_id(accounts(0).as_str()),
upgrade_delay_blocks: 1,
+ default_gas_token: [0u8; 20],
}
.try_to_vec()
.unwrap(),
diff --git a/engine-tests/src/tests/sanity.rs b/engine-tests/src/tests/sanity.rs
index 5e1e9ecea..cb7766765 100644
--- a/engine-tests/src/tests/sanity.rs
+++ b/engine-tests/src/tests/sanity.rs
@@ -136,15 +136,16 @@ fn test_state_format() {
let args = aurora_engine::parameters::NewCallArgs {
chain_id: aurora_engine_types::types::u256_to_arr(&666.into()),
owner_id: "boss".parse().unwrap(),
- bridge_prover_id: "prover_mcprovy_face".parse().unwrap(),
upgrade_delay_blocks: 3,
+ default_gas_token: [255u8; 20],
};
let state: aurora_engine::state::EngineState = args.into();
let expected_hex: String = [
+ "00", // V2 - "00", V1 - "01",
"000000000000000000000000000000000000000000000000000000000000029a",
"04000000626f7373",
- "1300000070726f7665725f6d6370726f76795f66616365",
"0300000000000000",
+ "01ffffffffffffffffffffffffffffffffffffffff",
]
.concat();
assert_eq!(hex::encode(state.try_to_vec().unwrap()), expected_hex);
diff --git a/engine-tests/src/tests/standalone/sanity.rs b/engine-tests/src/tests/standalone/sanity.rs
index bc9566a83..187d32372 100644
--- a/engine-tests/src/tests/standalone/sanity.rs
+++ b/engine-tests/src/tests/standalone/sanity.rs
@@ -1,8 +1,9 @@
+use aurora_engine::state::EngineStateV2;
use aurora_engine::{engine, state};
use aurora_engine_sdk::env::DEFAULT_PREPAID_GAS;
use aurora_engine_test_doubles::io::{Storage, StoragePointer};
use aurora_engine_test_doubles::promise::PromiseTracker;
-use aurora_engine_types::types::{Address, Wei};
+use aurora_engine_types::types::{Address, EthGas, Wei};
use aurora_engine_types::{account_id::AccountId, H160, H256, U256};
use std::cell::RefCell;
@@ -15,12 +16,12 @@ fn test_deploy_code() {
buf
};
let owner_id: AccountId = "aurora".parse().unwrap();
- let state = state::EngineState {
+ let state = state::EngineState::V2(EngineStateV2 {
chain_id,
owner_id: owner_id.clone(),
- bridge_prover_id: "mr_the_prover".parse().unwrap(),
upgrade_delay_blocks: 0,
- };
+ default_gas_token: Default::default(),
+ });
let origin = Address::new(H160([0u8; 20]));
let storage = RefCell::new(Storage::default());
let io = StoragePointer(&storage);
@@ -41,7 +42,7 @@ fn test_deploy_code() {
origin,
Wei::zero(),
evm_deploy(&code_to_deploy),
- u64::MAX,
+ EthGas::MAX,
Vec::new(),
&mut handler,
);
diff --git a/engine-tests/src/tests/state_migration.rs b/engine-tests/src/tests/state_migration.rs
index e175e0fca..4fe8bc90d 100644
--- a/engine-tests/src/tests/state_migration.rs
+++ b/engine-tests/src/tests/state_migration.rs
@@ -41,8 +41,8 @@ pub fn deploy_evm() -> AuroraAccount {
let new_args = NewCallArgs {
chain_id: crate::prelude::u256_to_arr(&U256::from(aurora_runner.chain_id)),
owner_id: str_to_account_id(main_account.account_id.as_str()),
- bridge_prover_id: prover_account.clone(),
upgrade_delay_blocks: 1,
+ default_gas_token: [0u8; 20],
};
main_account
.call(
diff --git a/engine-types/src/storage.rs b/engine-types/src/storage.rs
index e1b1be281..c37ca498b 100644
--- a/engine-types/src/storage.rs
+++ b/engine-types/src/storage.rs
@@ -30,6 +30,7 @@ pub enum KeyPrefix {
Nep141Erc20Map = 0x8,
Erc20Nep141Map = 0x9,
CrossContractCall = 0xa,
+ GasToken = 0xb,
}
impl From for u8 {
@@ -47,6 +48,7 @@ impl From for u8 {
Nep141Erc20Map => 0x8,
Erc20Nep141Map => 0x9,
CrossContractCall => 0xa,
+ GasToken => 0xb,
}
}
}
diff --git a/engine-types/src/types/address.rs b/engine-types/src/types/address.rs
index 2eaeeb7a7..da9541dd8 100755
--- a/engine-types/src/types/address.rs
+++ b/engine-types/src/types/address.rs
@@ -3,6 +3,9 @@ use borsh::maybestd::io;
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
+const ADDRESS_HEX_LENGTH: usize = 40;
+const ADDRESS_BYTE_LENGTH: usize = 20;
+
/// Base Eth Address type
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Address(H160);
@@ -24,10 +27,10 @@ impl Address {
}
pub fn decode(address: &str) -> Result {
- if address.len() != 40 {
+ if address.len() != ADDRESS_HEX_LENGTH {
return Err(error::AddressError::IncorrectLength);
}
- let mut result = [0u8; 20];
+ let mut result = [0u8; ADDRESS_BYTE_LENGTH];
hex::decode_to_slice(address, &mut result)
.map_err(|_| error::AddressError::FailedDecodeHex)?;
Ok(Address::new(H160(result)))
@@ -38,18 +41,25 @@ impl Address {
}
pub fn try_from_slice(raw_addr: &[u8]) -> Result {
- if raw_addr.len() != 20 {
- return Err(error::AddressError::IncorrectLength);
+ use core::cmp::Ordering;
+ match raw_addr.len().cmp(&ADDRESS_BYTE_LENGTH) {
+ Ordering::Greater => Err(error::AddressError::IncorrectLength),
+ Ordering::Less => {
+ let mut buf = [0u8; ADDRESS_BYTE_LENGTH];
+ let pos = ADDRESS_BYTE_LENGTH - raw_addr.len();
+ buf[pos..].copy_from_slice(raw_addr);
+ Ok(Self::new(H160::from_slice(&buf)))
+ }
+ Ordering::Equal => Ok(Self::new(H160::from_slice(raw_addr))),
}
- Ok(Self::new(H160::from_slice(raw_addr)))
}
- pub const fn from_array(array: [u8; 20]) -> Self {
+ pub const fn from_array(array: [u8; ADDRESS_BYTE_LENGTH]) -> Self {
Self(H160(array))
}
pub const fn zero() -> Self {
- Address::new(H160([0u8; 20]))
+ Address::new(H160([0u8; ADDRESS_BYTE_LENGTH]))
}
}
@@ -69,15 +79,15 @@ impl BorshSerialize for Address {
impl BorshDeserialize for Address {
fn deserialize(buf: &mut &[u8]) -> io::Result {
- if buf.len() < 20 {
+ if buf.len() < ADDRESS_BYTE_LENGTH {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("{}", error::AddressError::IncorrectLength),
));
}
// Guaranty no panics. The length checked early
- let address = Self(H160::from_slice(&buf[..20]));
- *buf = &buf[20..];
+ let address = Self(H160::from_slice(&buf[..ADDRESS_BYTE_LENGTH]));
+ *buf = &buf[ADDRESS_BYTE_LENGTH..];
Ok(address)
}
}
@@ -137,8 +147,19 @@ mod tests {
}
#[test]
- fn test_wrong_address_19() {
- let serialized_addr = [0u8; 19];
+ fn test_address_less_than_20_byte_length() {
+ let serialized_addr = [0x1u8; 1];
+ let addr = Address::try_from_slice(&serialized_addr).unwrap();
+ let expected = Address::try_from_slice(
+ &hex::decode("0000000000000000000000000000000000000001").unwrap(),
+ )
+ .unwrap();
+ assert_eq!(addr, expected);
+ }
+
+ #[test]
+ fn test_address_greater_than_20_byte_length() {
+ let serialized_addr = [0x11u8; 21];
let addr = Address::try_from_slice(&serialized_addr);
let err = addr.unwrap_err();
matches!(err, error::AddressError::IncorrectLength);
diff --git a/engine-types/src/types/gas.rs b/engine-types/src/types/gas.rs
index cd9bc0db1..28b47f5e6 100644
--- a/engine-types/src/types/gas.rs
+++ b/engine-types/src/types/gas.rs
@@ -54,6 +54,8 @@ impl Display for EthGas {
}
impl EthGas {
+ pub const MAX: EthGas = EthGas(u64::MAX);
+
/// Constructs a new `EthGas` with a given u64 value.
pub const fn new(gas: u64) -> EthGas {
Self(gas)
diff --git a/engine-types/src/types/mod.rs b/engine-types/src/types/mod.rs
index 82e666199..2efe0b2b4 100644
--- a/engine-types/src/types/mod.rs
+++ b/engine-types/src/types/mod.rs
@@ -133,10 +133,6 @@ impl Stack {
}
}
-pub fn str_from_slice(inp: &[u8]) -> &str {
- str::from_utf8(inp).unwrap()
-}
-
#[cfg(test)]
mod tests {
use super::*;
diff --git a/engine/src/connector.rs b/engine/src/connector.rs
index 21b359643..08edb30d4 100644
--- a/engine/src/connector.rs
+++ b/engine/src/connector.rs
@@ -104,7 +104,7 @@ impl EthConnectorContract {
ft.internal_register_account(&owner_id);
let paused_mask = UNPAUSE_ALL;
- io.write_borsh(
+ io.write_storage_borsh(
&construct_contract_key(&EthConnectorStorageId::PausedMask),
&paused_mask,
);
@@ -618,7 +618,7 @@ impl EthConnectorContract {
/// Save eth-connector fungible token contract data
fn save_ft_contract(&mut self) {
- self.io.write_borsh(
+ self.io.write_storage_borsh(
&construct_contract_key(&EthConnectorStorageId::FungibleToken),
&self.ft.data(),
);
@@ -633,7 +633,7 @@ impl EthConnectorContract {
/// Save already used event proof as hash key
fn save_used_event(&mut self, key: &str) {
- self.io.write_borsh(&self.used_event_key(key), &0u8);
+ self.io.write_storage_borsh(&self.used_event_key(key), &0u8);
}
/// Check is event of proof already used
@@ -666,7 +666,7 @@ impl AdminControlled for EthConnectorContract {
/// Set admin paused status
fn set_paused(&mut self, paused_mask: PausedMask) {
self.paused_mask = paused_mask;
- self.io.write_borsh(
+ self.io.write_storage_borsh(
&construct_contract_key(&EthConnectorStorageId::PausedMask),
&self.paused_mask,
);
@@ -700,12 +700,12 @@ pub fn set_contract_data(
eth_custodian_address: Address::decode(&args.eth_custodian_address)?,
};
// Save eth-connector specific data
- io.write_borsh(
+ io.write_storage_borsh(
&construct_contract_key(&EthConnectorStorageId::Contract),
&contract_data,
);
- io.write_borsh(
+ io.write_storage_borsh(
&construct_contract_key(&EthConnectorStorageId::FungibleTokenMetadata),
&args.metadata,
);
diff --git a/engine/src/deposit_event.rs b/engine/src/deposit_event.rs
index 496d24a2e..dc34a4fb0 100644
--- a/engine/src/deposit_event.rs
+++ b/engine/src/deposit_event.rs
@@ -527,10 +527,8 @@ mod tests {
let parse_error =
TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee)
.unwrap_err();
- let actual_parse_error = std::str::from_utf8(parse_error.as_ref()).unwrap();
- let expected_parse_error = AddressError::IncorrectLength.to_string();
- assert_eq!(expected_parse_error, actual_parse_error);
+ assert_eq!(parse_error.as_ref(), AddressError::IncorrectLength.as_ref());
}
#[test]
@@ -541,10 +539,8 @@ mod tests {
let parse_error =
TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee)
.unwrap_err();
- let actual_parse_error = std::str::from_utf8(parse_error.as_ref()).unwrap();
- let expected_parse_error = AddressError::FailedDecodeHex.to_string();
- assert_eq!(expected_parse_error, actual_parse_error);
+ assert_eq!(parse_error.as_ref(), AddressError::FailedDecodeHex.as_ref());
}
#[test]
diff --git a/engine/src/engine.rs b/engine/src/engine.rs
index 35d2c64a2..c46cfa208 100644
--- a/engine/src/engine.rs
+++ b/engine/src/engine.rs
@@ -1,26 +1,19 @@
-use crate::parameters::{CallArgs, NEP141FtOnTransferArgs, ResultLog, SubmitResult, ViewCallArgs};
-use core::mem;
-use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log};
-use evm::executor;
-use evm::{Config, CreateScheme, ExitError, ExitFatal, ExitReason};
-
use crate::connector::EthConnectorContract;
use crate::map::BijectionMap;
-use crate::{errors, state};
+use crate::{errors, gas_token, state};
use aurora_engine_sdk::caching::FullCache;
use aurora_engine_sdk::env::Env;
use aurora_engine_sdk::io::{StorageIntermediate, IO};
use aurora_engine_sdk::promise::{PromiseHandler, PromiseId, ReadOnlyPromiseHandler};
use crate::accounting;
-use crate::parameters::{DeployErc20TokenArgs, TransactionStatus};
+use crate::gas_token::GasToken;
+use crate::parameters::{DeployErc20TokenArgs, NEP141FtOnTransferArgs, TransactionStatus};
use crate::pausables::{
EngineAuthorizer, EnginePrecompilesPauser, PausedPrecompilesChecker, PrecompileFlags,
};
use crate::prelude::parameters::RefundCallArgs;
-use crate::prelude::precompiles::native::{exit_to_ethereum, exit_to_near};
-use crate::prelude::precompiles::xcc::cross_contract_call;
-use crate::prelude::precompiles::Precompiles;
+use crate::prelude::precompiles::{self, Precompiles};
use crate::prelude::transactions::{EthTransactionKind, NormalizedEthTransaction};
use crate::prelude::{
address_to_key, bytes_to_key, sdk, storage_to_key, u256_to_arr, vec, AccountId, Address,
@@ -28,9 +21,15 @@ use crate::prelude::{
Yocto, ERC20_MINT_SELECTOR, H160, H256, U256,
};
use crate::state::EngineState;
-use aurora_engine_precompiles::PrecompileConstructorContext;
+use aurora_engine_precompiles::{Precompile, PrecompileConstructorContext};
+use aurora_engine_types::parameters::engine::{CallArgs, ResultLog, SubmitResult, ViewCallArgs};
+use aurora_engine_types::types::EthGas;
use core::cell::RefCell;
use core::iter::once;
+use core::mem;
+use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log};
+use evm::{executor, Context};
+use evm::{Config, CreateScheme, ExitError, ExitFatal, ExitReason};
/// Used as the first byte in the concatenation of data used to compute the blockhash.
/// Could be useful in the future as a version byte, or to distinguish different types of blocks.
@@ -413,10 +412,13 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
}
}
+ // TODO: If we don't actually charge_gas here after changes, this should
+ // be renamed.
pub fn charge_gas(
&mut self,
sender: &Address,
transaction: &NormalizedEthTransaction,
+ gas_token: GasToken,
) -> Result {
if transaction.max_fee_per_gas.is_zero() {
return Ok(GasPaymentResult::default());
@@ -432,11 +434,23 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
.map(Wei::new)
.ok_or(GasPaymentError::EthAmountOverflow)?;
- let new_balance = get_balance(&self.io, sender)
- .checked_sub(prepaid_amount)
- .ok_or(GasPaymentError::OutOfFund)?;
-
- set_balance(&mut self.io, sender, &new_balance);
+ match gas_token {
+ GasToken::Base => {
+ // TODO: Remove the set_balance as we need to just check if they can spend
+ // Use new `can_transfer` function instead.
+ // Also needs to be verified how it is done on geth.
+ let new_balance = get_balance(&self.io, sender)
+ .checked_sub(prepaid_amount)
+ .ok_or(GasPaymentError::OutOfFund)?;
+
+ // This part is questionable.
+ set_balance(&mut self.io, sender, &new_balance);
+ }
+ GasToken::Erc20(_addr) => {
+ // TODO: Needs SputnikVM balance check
+ todo!()
+ }
+ }
self.gas_price = effective_gas_price;
@@ -454,7 +468,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
) -> EngineResult {
let origin = Address::new(self.origin());
let value = Wei::zero();
- self.deploy_code(origin, value, input, u64::MAX, Vec::new(), handler)
+ self.deploy_code(origin, value, input, EthGas::MAX, Vec::new(), handler)
}
pub fn deploy_code(
@@ -462,20 +476,25 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
origin: Address,
value: Wei,
input: Vec,
- gas_limit: u64,
+ gas_limit: EthGas,
access_list: Vec<(H160, Vec)>, // See EIP-2930
handler: &mut P,
) -> EngineResult {
let pause_flags = EnginePrecompilesPauser::from_io(self.io).paused();
let precompiles = self.create_precompiles(pause_flags, handler);
- let executor_params = StackExecutorParams::new(gas_limit, precompiles);
+ let executor_params = StackExecutorParams::new(gas_limit.as_u64(), precompiles);
let mut executor = executor_params.make_executor(self);
let address = executor.create_address(CreateScheme::Legacy {
caller: origin.raw(),
});
- let (exit_reason, return_value) =
- executor.transact_create(origin.raw(), value.raw(), input, gas_limit, access_list);
+ let (exit_reason, return_value) = executor.transact_create(
+ origin.raw(),
+ value.raw(),
+ input,
+ gas_limit.as_u64(),
+ access_list,
+ );
let result = if exit_reason.is_succeed() {
address.0.to_vec()
} else {
@@ -486,7 +505,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
let status = exit_reason.into_result(result)?;
let (values, logs) = executor.into_state().deconstruct();
- let logs = filter_promises_from_logs(&self.io, handler, logs, &self.current_account_id);
+ let logs = apply_actions_from_logs(&mut self.io, handler, logs, &self.current_account_id);
self.apply(values, Vec::::new(), true);
@@ -510,7 +529,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
&contract,
value,
input,
- u64::MAX,
+ EthGas::MAX,
Vec::new(),
handler,
)
@@ -524,7 +543,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
&contract,
value,
input,
- u64::MAX,
+ EthGas::MAX,
Vec::new(),
handler,
)
@@ -539,21 +558,21 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
contract: &Address,
value: Wei,
input: Vec,
- gas_limit: u64,
+ gas_limit: EthGas,
access_list: Vec<(H160, Vec)>, // See EIP-2930
handler: &mut P,
) -> EngineResult {
let pause_flags = EnginePrecompilesPauser::from_io(self.io).paused();
let precompiles = self.create_precompiles(pause_flags, handler);
- let executor_params = StackExecutorParams::new(gas_limit, precompiles);
+ let executor_params = StackExecutorParams::new(gas_limit.as_u64(), precompiles);
let mut executor = executor_params.make_executor(self);
let (exit_reason, result) = executor.transact_call(
origin.raw(),
contract.raw(),
value.raw(),
input,
- gas_limit,
+ gas_limit.as_u64(),
access_list,
);
@@ -561,7 +580,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
let status = exit_reason.into_result(result)?;
let (values, logs) = executor.into_state().deconstruct();
- let logs = filter_promises_from_logs(&self.io, handler, logs, &self.current_account_id);
+ let logs = apply_actions_from_logs(&mut self.io, handler, logs, &self.current_account_id);
// There is no way to return the logs to the NEAR log method as it only
// allows a return of UTF-8 strings.
@@ -653,7 +672,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
sender: Address,
receiver: Address,
value: Wei,
- gas_limit: u64,
+ gas_limit: EthGas,
handler: &mut P,
) -> EngineResult {
self.call(
@@ -720,7 +739,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
&erc20_token,
Wei::zero(),
setup_receive_erc20_tokens_input(args, &recipient),
- u64::MAX,
+ EthGas::MAX,
Vec::new(), // TODO: are there values we should put here?
handler,
)
@@ -787,7 +806,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
});
// Cross contract calls are not enabled on mainnet yet.
tmp.all_precompiles
- .remove(&aurora_engine_precompiles::xcc::cross_contract_call::ADDRESS);
+ .remove(&precompiles::xcc::cross_contract_call::ADDRESS);
tmp
} else {
Precompiles::new_london(PrecompileConstructorContext {
@@ -853,7 +872,7 @@ pub fn submit(
// Validate the chain ID, if provided inside the signature:
if let Some(chain_id) = transaction.chain_id {
- if U256::from(chain_id) != U256::from(state.chain_id) {
+ if U256::from(chain_id) != U256::from(state.chain_id()) {
return Err(EngineErrorKind::InvalidChainId.into());
}
}
@@ -866,7 +885,7 @@ pub fn submit(
check_nonce(&io, &sender, &transaction.nonce)?;
// Check intrinsic gas is covered by transaction gas limit
- match transaction.intrinsic_gas(crate::engine::CONFIG) {
+ match transaction.intrinsic_gas(CONFIG) {
Err(_e) => {
return Err(EngineErrorKind::GasOverflow.into());
}
@@ -882,32 +901,61 @@ pub fn submit(
}
let mut engine = Engine::new_with_state(state, sender, current_account_id, io, env);
- let prepaid_amount = match engine.charge_gas(&sender, &transaction) {
+ // TODO: Have GasToken derived from storage.
+ let prepaid_amount = match engine.charge_gas(&sender, &transaction, GasToken::Base) {
Ok(gas_result) => gas_result,
Err(err) => {
return Err(EngineErrorKind::GasPayment(err).into());
}
};
- let gas_limit: u64 = transaction
- .gas_limit
- .try_into()
- .map_err(|_| EngineErrorKind::GasOverflow)?;
+ let gas_limit: EthGas = {
+ let gas_limit: u64 = transaction
+ .gas_limit
+ .try_into()
+ .map_err(|_| EngineErrorKind::GasOverflow)?;
+ EthGas::new(gas_limit)
+ };
let access_list = transaction
.access_list
.into_iter()
.map(|a| (a.address, a.storage_keys))
.collect();
let result = if let Some(receiver) = transaction.to {
- engine.call(
- &sender,
- &receiver,
- transaction.value,
- transaction.data,
- gas_limit,
- access_list,
- handler,
- )
- // TODO: charge for storage
+ // We are making this precompile here special for now as we want to
+ // ensure that it can only be directly called. This may change in the
+ // future after careful research and consideration. For example, we are
+ // not sure if there are consequences in changing the gas during the
+ // execution of the transaction.
+ if receiver == precompiles::set_gas_token::SET_GAS_TOKEN_ADDRESS {
+ let set_gas_token = precompiles::set_gas_token::SetGasToken;
+ let context = Context {
+ address: receiver.raw(),
+ caller: transaction.address.raw(),
+ apparent_value: Default::default(),
+ };
+ match set_gas_token.run(&transaction.data, Some(gas_limit), &context, false) {
+ Ok(v) => {
+ let tx_status = TransactionStatus::Succeed(vec![]);
+ let mut result_logs = Vec::new();
+ for log in v.logs {
+ result_logs.push(evm_log_to_result_log(log));
+ }
+ Ok(SubmitResult::new(tx_status, v.cost.as_u64(), result_logs))
+ }
+ Err(e) => Err(EngineError::from(EngineErrorKind::EvmError(e))),
+ }
+ } else {
+ engine.call(
+ &sender,
+ &receiver,
+ transaction.value,
+ transaction.data,
+ gas_limit,
+ access_list,
+ handler,
+ )
+ // TODO: charge for storage
+ }
} else {
// Execute a contract deployment:
engine.deploy_code(
@@ -971,14 +1019,14 @@ pub fn refund_on_error(
&erc20_address,
Wei::zero(),
input,
- u64::MAX,
+ EthGas::MAX,
Vec::new(),
handler,
)
}
// ETH exit; transfer ETH back from precompile address
None => {
- let exit_address = aurora_engine_precompiles::native::exit_to_near::ADDRESS;
+ let exit_address = precompiles::native::exit_to_near::ADDRESS;
let mut engine =
Engine::new_with_state(state, exit_address, current_account_id, io, env);
let refund_address = args.recipient_address;
@@ -988,7 +1036,7 @@ pub fn refund_on_error(
&refund_address,
amount,
Vec::new(),
- u64::MAX,
+ EthGas::MAX,
vec![
(exit_address.raw(), Vec::new()),
(refund_address.raw(), Vec::new()),
@@ -1032,6 +1080,7 @@ pub fn get_authorizer() -> EngineAuthorizer {
EngineAuthorizer::from_accounts(once(account))
}
+// TODO: Gas must be charged from here.
pub fn refund_unused_gas(
io: &mut I,
sender: &Address,
@@ -1058,8 +1107,12 @@ pub fn refund_unused_gas(
.checked_sub(spent_amount)
.ok_or(GasPaymentError::EthAmountOverflow)?;
- add_balance(io, sender, refund)?;
- add_balance(io, relayer, reward_amount)?;
+ if refund > Wei::zero() {
+ add_balance(io, sender, refund)?;
+ }
+ if reward_amount > Wei::zero() {
+ add_balance(io, relayer, reward_amount)?;
+ }
Ok(())
}
@@ -1243,6 +1296,14 @@ pub fn get_balance(io: &I, address: &Address) -> Wei {
Wei::new(raw)
}
+/// Checks whether there are enough funds for the given address and amount.
+///
+/// This does not take gas into account.
+pub fn can_transfer(io: &I, address: &Address, amount: Wei) -> bool {
+ let current_balance = get_balance(io, address);
+ amount >= current_balance
+}
+
pub fn remove_storage(io: &mut I, address: &Address, key: &H256, generation: u32) {
io.remove_storage(storage_to_key(address, key, generation).as_ref());
}
@@ -1316,8 +1377,8 @@ fn remove_account(io: &mut I, address: &Address, generation: u32)
remove_all_storage(io, address, generation);
}
-fn filter_promises_from_logs(
- io: &I,
+fn apply_actions_from_logs(
+ io: &mut I,
handler: &mut P,
logs: T,
current_account_id: &AccountId,
@@ -1329,8 +1390,8 @@ where
{
logs.into_iter()
.filter_map(|log| {
- if log.address == exit_to_near::ADDRESS.raw()
- || log.address == exit_to_ethereum::ADDRESS.raw()
+ if log.address == precompiles::native::exit_to_near::ADDRESS.raw()
+ || log.address == precompiles::native::exit_to_ethereum::ADDRESS.raw()
{
if log.topics.is_empty() {
if let Ok(promise) = PromiseArgs::try_from_slice(&log.data) {
@@ -1362,8 +1423,8 @@ where
// `topics` field.
Some(evm_log_to_result_log(log))
}
- } else if log.address == cross_contract_call::ADDRESS.raw() {
- if log.topics[0] == cross_contract_call::AMOUNT_TOPIC {
+ } else if log.address == precompiles::xcc::cross_contract_call::ADDRESS.raw() {
+ if log.topics[0] == precompiles::xcc::cross_contract_call::AMOUNT_TOPIC {
// NEAR balances are 128-bit, so the leading 16 bytes of the 256-bit topic
// value should always be zero.
assert_eq!(&log.topics[1].as_bytes()[0..16], &[0; 16]);
@@ -1381,6 +1442,21 @@ where
}
// do not pass on these "internal logs" to caller
None
+ } else if log.address == precompiles::set_gas_token::SET_GAS_TOKEN_ADDRESS.raw() {
+ if log.topics[0] == precompiles::set_gas_token::events::SET_GAS_TOKEN_SIGNATURE {
+ let sender_address = {
+ let mut buf = [0u8; 20];
+ buf.copy_from_slice(&log.data[12..]);
+ Address::from_array(buf)
+ };
+ let gas_token: GasToken = {
+ let mut buf = [0u8; 20];
+ buf.copy_from_slice(&log.topics[1].as_bytes()[12..]); // No need to `get` here given we know the length
+ GasToken::from_address(Address::from_array(buf))
+ };
+ gas_token::set_gas_token(io, sender_address, gas_token);
+ }
+ None
} else {
Some(evm_log_to_result_log(log))
}
@@ -1426,7 +1502,7 @@ unsafe fn schedule_promise_callback(
handler.promise_attach_callback(base_id, promise)
}
-impl<'env, I: IO + Copy, E: Env> evm::backend::Backend for Engine<'env, I, E> {
+impl<'env, I: IO + Copy, E: Env> Backend for Engine<'env, I, E> {
/// Returns the "effective" gas price (as defined by EIP-1559)
fn gas_price(&self) -> U256 {
self.gas_price
@@ -1459,7 +1535,7 @@ impl<'env, I: IO + Copy, E: Env> evm::backend::Backend for Engine<'env, I, E> {
if idx.saturating_sub(U256::from(256)) <= number && number < idx {
// since `idx` comes from `u64` it is always safe to downcast `number` from `U256`
compute_block_hash(
- self.state.chain_id,
+ self.state.chain_id(),
number.low_u64(),
self.current_account_id.as_bytes(),
)
@@ -1519,7 +1595,7 @@ impl<'env, I: IO + Copy, E: Env> evm::backend::Backend for Engine<'env, I, E> {
/// Returns the states chain ID.
fn chain_id(&self) -> U256 {
- U256::from(self.state.chain_id)
+ U256::from(self.state.chain_id())
}
/// Checks if an address exists.
@@ -1713,10 +1789,12 @@ impl<'env, J: IO + Copy, E: Env> ApplyBackend for Engine<'env, J, E> {
}
#[cfg(test)]
+#[cfg(feature = "std")]
mod tests {
use super::*;
use crate::parameters::{FunctionCallArgsV1, FunctionCallArgsV2};
use aurora_engine_precompiles::make_address;
+ use aurora_engine_precompiles::native::exit_to_near;
use aurora_engine_sdk::env::Fixed;
use aurora_engine_sdk::promise::Noop;
use aurora_engine_test_doubles::io::{Storage, StoragePointer};
@@ -1846,7 +1924,7 @@ mod tests {
let mut engine =
Engine::new_with_state(EngineState::default(), origin, current_account_id, io, &env);
- let gas_limit = u64::MAX;
+ let gas_limit = EthGas::MAX;
let mut handler = Noop;
let receiver = make_address(1, 1);
let value = Wei::new_u64(1000);
@@ -1991,7 +2069,10 @@ mod tests {
data: vec![],
access_list: vec![],
};
- let actual_result = engine.charge_gas(&origin, &transaction).unwrap();
+ // TODO: Add other tests than just ETH as a gas token.
+ let actual_result = engine
+ .charge_gas(&origin, &transaction, GasToken::Base)
+ .unwrap();
let expected_result = GasPaymentResult {
prepaid_amount: Wei::zero(),
@@ -2216,7 +2297,7 @@ mod tests {
#[test]
fn test_filtering_promises_from_logs_with_none_keeps_all() {
let storage = RefCell::new(Storage::default());
- let io = StoragePointer(&storage);
+ let mut io = StoragePointer(&storage);
let current_account_id = AccountId::default();
let mut handler = Noop;
let logs = vec![Log {
@@ -2225,7 +2306,7 @@ mod tests {
data: vec![],
}];
- let actual_logs = filter_promises_from_logs(&io, &mut handler, logs, ¤t_account_id);
+ let actual_logs = apply_actions_from_logs(&mut io, &mut handler, logs, ¤t_account_id);
let expected_logs = vec![ResultLog {
address: Default::default(),
topics: vec![],
diff --git a/engine/src/errors.rs b/engine/src/errors.rs
index c1afdb66a..6e993472d 100644
--- a/engine/src/errors.rs
+++ b/engine/src/errors.rs
@@ -50,8 +50,6 @@ pub const ERR_GAS_OVERFLOW: &[u8; 16] = b"ERR_GAS_OVERFLOW";
pub const ERR_BALANCE_OVERFLOW: &[u8; 20] = b"ERR_BALANCE_OVERFLOW";
pub const ERR_GAS_ETH_AMOUNT_OVERFLOW: &[u8; 27] = b"ERR_GAS_ETH_AMOUNT_OVERFLOW";
pub const ERR_PARSE_ADDRESS: &[u8; 17] = b"ERR_PARSE_ADDRESS";
-pub const ERR_STATE_NOT_FOUND: &[u8; 19] = b"ERR_STATE_NOT_FOUND";
-pub const ERR_STATE_CORRUPTED: &[u8; 19] = b"ERR_STATE_CORRUPTED";
pub const ERR_CONNECTOR_STORAGE_KEY_NOT_FOUND: &[u8; 35] = b"ERR_CONNECTOR_STORAGE_KEY_NOT_FOUND";
pub const ERR_FAILED_DESERIALIZE_CONNECTOR_DATA: &[u8; 37] =
diff --git a/engine/src/fungible_token.rs b/engine/src/fungible_token.rs
index 24f98ecc0..52636b4a3 100644
--- a/engine/src/fungible_token.rs
+++ b/engine/src/fungible_token.rs
@@ -496,7 +496,7 @@ impl FungibleTokenOps {
self.io.write_storage(&key, &accounts_counter.to_le_bytes());
}
self.io
- .write_borsh(&Self::account_to_key(account_id), &amount);
+ .write_storage_borsh(&Self::account_to_key(account_id), &amount);
}
/// Get accounts counter for statistics
diff --git a/engine/src/gas_token.rs b/engine/src/gas_token.rs
new file mode 100644
index 000000000..6b8f70192
--- /dev/null
+++ b/engine/src/gas_token.rs
@@ -0,0 +1,57 @@
+use crate::prelude::{BorshDeserialize, BorshSerialize};
+use aurora_engine_sdk::io::{StorageIntermediate, IO};
+use aurora_engine_types::storage;
+use aurora_engine_types::storage::KeyPrefix;
+use aurora_engine_types::types::Address;
+
+mod errors {
+ pub const ERR_DESERIALIZE_GAS_TOKEN: &str = "ERR_DESERIALIZE_GAS_TOKEN";
+}
+
+#[derive(Default, Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
+/// Used to select which gas token to pay in.
+pub enum GasToken {
+ /// Gas is paid in Ether.
+ #[default]
+ Base,
+ /// Gas is paid in a ERC-20 compatible token.
+ Erc20(Address),
+}
+
+impl GasToken {
+ // TODO: wait for use of this
+ // fn into_address(self) -> Address {
+ // use GasToken::*;
+ // match self {
+ // Base => Address::from_array([0u8; 20]),
+ // Erc20(addr) => addr,
+ // }
+ // }
+
+ pub(crate) fn from_address(address: Address) -> GasToken {
+ use GasToken::*;
+ if address == Address::zero() {
+ Base
+ } else {
+ Erc20(address)
+ }
+ }
+
+ pub(crate) fn from_array(bytes: [u8; 20]) -> GasToken {
+ Self::from_address(Address::from_array(bytes))
+ }
+}
+
+/// Sets the gas token for a given address and returns the old value, if any.
+pub fn set_gas_token(io: &mut I, address: Address, gas_token: GasToken) -> Option {
+ let key = storage::bytes_to_key(KeyPrefix::GasToken, address.as_bytes());
+ io.write_storage_borsh(&key, &gas_token)
+ .map(|v| v.to_value().expect(errors::ERR_DESERIALIZE_GAS_TOKEN))
+}
+
+/// Gets the gas token set for a given address, if any.
+pub fn get_gas_token(io: &I, address: &Address) -> Option {
+ let key = storage::bytes_to_key(KeyPrefix::GasToken, address.as_bytes());
+ io.read_storage(&key)
+ .map(|v| v.to_value().expect(errors::ERR_DESERIALIZE_GAS_TOKEN))
+}
diff --git a/engine/src/lib.rs b/engine/src/lib.rs
index 78ea86349..08fae60b6 100644
--- a/engine/src/lib.rs
+++ b/engine/src/lib.rs
@@ -25,6 +25,7 @@ pub mod deposit_event;
pub mod engine;
pub mod errors;
pub mod fungible_token;
+pub mod gas_token;
pub mod log_entry;
pub mod pausables;
mod prelude;
@@ -93,7 +94,9 @@ mod contract {
near_account_to_evm_address, SdkExpect, SdkProcess, SdkUnwrap,
};
use crate::prelude::storage::{bytes_to_key, KeyPrefix};
- use crate::prelude::{sdk, u256_to_arr, Address, PromiseResult, Yocto, ERR_FAILED_PARSE, H256};
+ use crate::prelude::{
+ sdk, u256_to_arr, Address, PromiseResult, ToString, Yocto, ERR_FAILED_PARSE, H256,
+ };
use crate::{errors, pausables, state};
use aurora_engine_sdk::env::Env;
use aurora_engine_sdk::io::{StorageIntermediate, IO};
@@ -139,7 +142,7 @@ mod contract {
pub extern "C" fn get_owner() {
let mut io = Runtime;
let state = state::get_state(&io).sdk_unwrap();
- io.return_output(state.owner_id.as_bytes());
+ io.return_output(state.owner_id().as_bytes());
}
/// Get bridge prover id for this contract.
@@ -154,7 +157,7 @@ mod contract {
#[no_mangle]
pub extern "C" fn get_chain_id() {
let mut io = Runtime;
- io.return_output(&state::get_state(&io).sdk_unwrap().chain_id)
+ io.return_output(&state::get_state(&io).sdk_unwrap().chain_id())
}
#[no_mangle]
@@ -162,7 +165,7 @@ mod contract {
let mut io = Runtime;
let state = state::get_state(&io).sdk_unwrap();
let index = internal_get_upgrade_index();
- io.return_output(&(index + state.upgrade_delay_blocks).to_le_bytes())
+ io.return_output(&(index + state.upgrade_delay_blocks()).to_le_bytes())
}
/// Stage new code for deployment.
@@ -186,7 +189,7 @@ mod contract {
let state = state::get_state(&io).sdk_unwrap();
require_owner_only(&state, &io.predecessor_account_id());
let index = internal_get_upgrade_index();
- if io.block_height() <= index + state.upgrade_delay_blocks {
+ if io.block_height() <= index + state.upgrade_delay_blocks() {
sdk::panic_utf8(errors::ERR_NOT_ALLOWED_TOO_EARLY);
}
Runtime::self_deploy(&bytes_to_key(KeyPrefix::Config, CODE_KEY));
@@ -196,9 +199,7 @@ mod contract {
/// to make any necessary changes to the state such that it aligns with the newly deployed
/// code.
#[no_mangle]
- pub extern "C" fn state_migration() {
- // TODO: currently we don't have migrations
- }
+ pub extern "C" fn state_migration() {}
/// Resumes previously [`paused`] precompiles.
///
@@ -474,7 +475,7 @@ mod contract {
let block_height = io.read_input_borsh().sdk_unwrap();
let account_id = io.current_account_id();
let chain_id = state::get_state(&io)
- .map(|state| state.chain_id)
+ .map(|state| state.chain_id())
.sdk_unwrap();
let block_hash =
crate::engine::compute_block_hash(chain_id, block_height, account_id.as_bytes());
@@ -526,7 +527,7 @@ mod contract {
let mut state = state::get_state(&io).sdk_unwrap();
require_owner_only(&state, &io.predecessor_account_id());
let args: BeginChainArgs = io.read_input_borsh().sdk_unwrap();
- state.chain_id = args.chain_id;
+ state.set_chain_id(args.chain_id);
state::set_state(&mut io, state).sdk_unwrap();
// set genesis block balances
for account_balance in args.genesis_alloc {
@@ -537,7 +538,7 @@ mod contract {
)
}
// return new chain ID
- io.return_output(&state::get_state(&io).sdk_unwrap().chain_id)
+ io.return_output(&state::get_state(&io).sdk_unwrap().chain_id())
}
#[cfg(feature = "evm_bully")]
@@ -906,7 +907,7 @@ mod contract {
#[no_mangle]
pub extern "C" fn mint_account() {
use crate::connector::ZERO_ATTACHED_BALANCE;
- use crate::prelude::{NEP141Wei, ToString, U256};
+ use crate::prelude::{NEP141Wei, U256};
use evm::backend::ApplyBackend;
const GAS_FOR_VERIFY: NearGas = NearGas::new(20_000_000_000_000);
const GAS_FOR_FINISH: NearGas = NearGas::new(50_000_000_000_000);
@@ -982,7 +983,7 @@ mod contract {
}
fn require_owner_only(state: &state::EngineState, predecessor_account_id: &AccountId) {
- if &state.owner_id != predecessor_account_id {
+ if &state.owner_id() != predecessor_account_id {
sdk::panic_utf8(errors::ERR_NOT_ALLOWED);
}
}
diff --git a/engine/src/parameters.rs b/engine/src/parameters.rs
index d03e0089b..b5f25f377 100644
--- a/engine/src/parameters.rs
+++ b/engine/src/parameters.rs
@@ -19,11 +19,10 @@ pub struct NewCallArgs {
/// Account which can upgrade this contract.
/// Use empty to disable updatability.
pub owner_id: AccountId,
- /// Account of the bridge prover.
- /// Use empty to not use base token as bridged asset.
- pub bridge_prover_id: AccountId,
/// How many blocks after staging upgrade can deploy it.
pub upgrade_delay_blocks: u64,
+ /// The default gas token.
+ pub default_gas_token: [u8; 20],
}
/// Borsh-encoded (genesis) account balance used by the `begin_chain` function.
diff --git a/engine/src/state.rs b/engine/src/state.rs
index 6023f4338..50948d3a1 100644
--- a/engine/src/state.rs
+++ b/engine/src/state.rs
@@ -1,3 +1,4 @@
+use crate::gas_token::GasToken;
use crate::parameters::NewCallArgs;
use aurora_engine_sdk::io::{StorageIntermediate, IO};
use aurora_engine_types::account_id::AccountId;
@@ -10,9 +11,73 @@ pub use error::EngineStateError;
const STATE_KEY: &[u8; 5] = b"STATE";
/// Engine internal state, mostly configuration.
+#[derive(BorshSerialize, BorshDeserialize, Clone, PartialEq, Eq, Debug)]
+pub enum EngineState {
+ V2(EngineStateV2),
+ V1(EngineStateV1),
+}
+
+impl EngineState {
+ pub fn chain_id(&self) -> [u8; 32] {
+ match self {
+ EngineState::V2(state) => state.chain_id,
+ EngineState::V1(state) => state.chain_id,
+ }
+ }
+
+ pub fn set_chain_id(&mut self, chain_id: [u8; 32]) {
+ match self {
+ EngineState::V2(state) => state.chain_id = chain_id,
+ EngineState::V1(state) => state.chain_id = chain_id,
+ }
+ }
+
+ pub fn owner_id(&self) -> AccountId {
+ match self {
+ EngineState::V2(state) => state.owner_id.clone(),
+ EngineState::V1(state) => state.owner_id.clone(),
+ }
+ }
+
+ pub fn upgrade_delay_blocks(&self) -> u64 {
+ match self {
+ EngineState::V2(state) => state.upgrade_delay_blocks,
+ EngineState::V1(state) => state.upgrade_delay_blocks,
+ }
+ }
+
+ pub fn deserialize(bytes: &[u8]) -> Option {
+ Self::try_from_slice(bytes)
+ .or_else(|_| EngineStateV1::try_from_slice(bytes).map(Self::V1))
+ .ok()
+ }
+}
+
+impl Default for EngineState {
+ fn default() -> Self {
+ Self::V2(EngineStateV2::default())
+ }
+}
+
+/// Engine internal state V2, mostly configuration.
/// Should not contain anything large or enumerable.
#[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)]
-pub struct EngineState {
+pub struct EngineStateV2 {
+ /// Chain id, according to the EIP-155 / ethereum-lists spec.
+ pub chain_id: [u8; 32],
+ /// Account which can upgrade this contract.
+ /// Use empty to disable updatability.
+ pub owner_id: AccountId,
+ /// How many blocks after staging upgrade can deploy it.
+ pub upgrade_delay_blocks: u64,
+ /// The default gas token.
+ pub default_gas_token: GasToken,
+}
+
+/// Engine internal state V1, mostly configuration.
+/// Should not contain anything large or enumerable.
+#[derive(BorshSerialize, BorshDeserialize, Default, Clone, PartialEq, Eq, Debug)]
+pub struct EngineStateV1 {
/// Chain id, according to the EIP-155 / ethereum-lists spec.
pub chain_id: [u8; 32],
/// Account which can upgrade this contract.
@@ -27,31 +92,32 @@ pub struct EngineState {
impl From for EngineState {
fn from(args: NewCallArgs) -> Self {
- EngineState {
+ EngineState::V2(EngineStateV2 {
chain_id: args.chain_id,
owner_id: args.owner_id,
- bridge_prover_id: args.bridge_prover_id,
upgrade_delay_blocks: args.upgrade_delay_blocks,
- }
+ default_gas_token: GasToken::from_array(args.default_gas_token),
+ })
}
}
/// Gets the state from storage, if it exists otherwise it will error.
-pub fn get_state(io: &I) -> Result {
+pub fn get_state(io: &I) -> Result {
match io.read_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY)) {
- None => Err(error::EngineStateError::NotFound),
- Some(bytes) => EngineState::try_from_slice(&bytes.to_vec())
- .map_err(|_| error::EngineStateError::DeserializationFailed),
+ None => Err(EngineStateError::NotFound),
+ Some(bytes) => {
+ EngineState::deserialize(&bytes.to_vec()).ok_or(EngineStateError::DeserializationFailed)
+ }
}
}
/// Saves state into the storage. Does not return the previous state.
-pub fn set_state(io: &mut I, state: EngineState) -> Result<(), error::EngineStateError> {
+pub fn set_state(io: &mut I, state: EngineState) -> Result<(), EngineStateError> {
io.write_storage(
&bytes_to_key(KeyPrefix::Config, STATE_KEY),
&state
.try_to_vec()
- .map_err(|_| error::EngineStateError::SerializationFailed)?,
+ .map_err(|_| EngineStateError::SerializationFailed)?,
);
Ok(())
@@ -96,12 +162,9 @@ mod tests {
fn test_missing_engine_state_is_not_found() {
let storage = RefCell::new(Storage::default());
let io = StoragePointer(&storage);
-
let actual_error = get_state(&io).unwrap_err();
- let actual_error = std::str::from_utf8(actual_error.as_ref()).unwrap();
- let expected_error = std::str::from_utf8(error::ERR_STATE_NOT_FOUND).unwrap();
- assert_eq!(expected_error, actual_error);
+ assert_eq!(actual_error.as_ref(), error::ERR_STATE_NOT_FOUND);
}
#[test]
@@ -111,9 +174,7 @@ mod tests {
io.write_storage(&bytes_to_key(KeyPrefix::Config, STATE_KEY), &[]);
let actual_error = get_state(&io).unwrap_err();
- let actual_error = std::str::from_utf8(actual_error.as_ref()).unwrap();
- let expected_error = std::str::from_utf8(error::ERR_STATE_CORRUPTED).unwrap();
- assert_eq!(expected_error, actual_error);
+ assert_eq!(actual_error.as_ref(), error::ERR_STATE_CORRUPTED);
}
}
diff --git a/etc/tests/state-migration-test/src/lib.rs b/etc/tests/state-migration-test/src/lib.rs
index 08b82b09e..e6908b183 100644
--- a/etc/tests/state-migration-test/src/lib.rs
+++ b/etc/tests/state-migration-test/src/lib.rs
@@ -3,7 +3,7 @@
extern crate alloc;
use alloc::vec::Vec;
-use aurora_engine::state;
+use aurora_engine::state::{self, EngineState};
use aurora_engine_sdk::near_runtime::Runtime;
use aurora_engine_sdk::io::{IO, StorageIntermediate};
use aurora_engine_types::storage;