diff --git a/engine-precompiles/src/lib.rs b/engine-precompiles/src/lib.rs
index fc6d9cc4e..772cd3a72 100644
--- a/engine-precompiles/src/lib.rs
+++ b/engine-precompiles/src/lib.rs
@@ -15,6 +15,7 @@ pub mod prepaid_gas;
pub mod promise_result;
pub mod random;
pub mod secp256k1;
+pub mod set_gas_token;
mod utils;
pub mod xcc;
@@ -111,8 +112,8 @@ impl HardFork for Istanbul {}
impl HardFork for Berlin {}
pub struct Precompiles<'a, I, E, H> {
- pub all_precompiles: prelude::BTreeMap
>,
- pub paused_precompiles: prelude::BTreeSet,
+ pub all_precompiles: BTreeMap>,
+ pub paused_precompiles: BTreeSet,
}
impl<'a, I, E, H> Precompiles<'a, I, E, H> {
@@ -149,7 +150,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> executor::stack::Preco
Some(result.and_then(|output| post_process(output, handle)))
}
- fn is_precompile(&self, address: prelude::H160) -> bool {
+ fn is_precompile(&self, address: H160) -> bool {
self.all_precompiles.contains_key(&Address::new(address))
}
}
@@ -270,7 +271,7 @@ impl<'a, I: IO + Copy, E: Env, H: ReadOnlyPromiseHandler> Precompiles<'a, I, E,
RandomSeed::ADDRESS,
CurrentAccount::ADDRESS,
];
- let fun: prelude::Vec> = vec![
+ let fun: Vec> = vec![
Box::new(ECRecover),
Box::new(SHA256),
Box::new(RIPEMD160),
@@ -386,10 +387,10 @@ pub enum AllPrecompiles<'a, I, E, H> {
/// fn for making an address by concatenating the bytes from two given numbers,
/// Note that 32 + 128 = 160 = 20 bytes (the length of an address). This function is used
/// as a convenience for specifying the addresses of the various precompiles.
-pub const fn make_address(x: u32, y: u128) -> prelude::types::Address {
+pub const fn make_address(x: u32, y: u128) -> Address {
let x_bytes = x.to_be_bytes();
let y_bytes = y.to_be_bytes();
- prelude::types::Address::new(H160([
+ Address::new(H160([
x_bytes[0],
x_bytes[1],
x_bytes[2],
@@ -413,10 +414,10 @@ pub const fn make_address(x: u32, y: u128) -> prelude::types::Address {
]))
}
-const fn make_h256(x: u128, y: u128) -> prelude::H256 {
+const fn make_h256(x: u128, y: u128) -> H256 {
let x_bytes = x.to_be_bytes();
let y_bytes = y.to_be_bytes();
- prelude::H256([
+ H256([
x_bytes[0],
x_bytes[1],
x_bytes[2],
diff --git a/engine-precompiles/src/set_gas_token.rs b/engine-precompiles/src/set_gas_token.rs
new file mode 100644
index 000000000..3dbcd0ae3
--- /dev/null
+++ b/engine-precompiles/src/set_gas_token.rs
@@ -0,0 +1,157 @@
+use crate::set_gas_token::events::SetGasTokenLog;
+use crate::{EvmPrecompileResult, Precompile, PrecompileOutput};
+use aurora_engine_types::types::{Address, EthGas};
+use evm::backend::Log;
+use evm::{Context, ExitError};
+use std::borrow::Cow;
+
+pub use consts::SET_GAS_TOKEN_ADDRESS;
+
+mod costs {
+ use crate::prelude::types::EthGas;
+
+ // TODO: gas costs, could be calculated returning logs of NEAR gas used prior and after.
+ // Should check if the gas check adds gas itself as well..?
+ pub(super) const SET_GAS_TOKEN_GAS: EthGas = EthGas::new(0);
+}
+
+pub mod consts {
+ use aurora_engine_types::types::Address;
+
+ /// Change gas token precompile address.
+ ///
+ /// Address: `0x076dae45c8e16a92258252fe04dedd97f1ea93d6`
+ ///
+ /// This address is computed as: `keccak("setGasToken")[12..]`
+ pub const SET_GAS_TOKEN_ADDRESS: Address =
+ crate::make_address(0x076dae45, 0xc8e16a92258252fe04dedd97f1ea93d6);
+}
+
+pub mod events {
+ use crate::set_gas_token::consts;
+ use aurora_engine_types::types::Address;
+ use aurora_engine_types::H256;
+ use evm::backend::Log;
+
+ // TODO
+ pub(crate) const SET_GAS_TOKEN_SIGNATURE: H256 = crate::make_h256(
+ 0x29d0b6eaa171d0d1607729f506329510,
+ 0x7bc9766ba17d250f129cb5bd06503d13,
+ );
+
+ pub(crate) struct SetGasTokenLog {
+ pub sender: Address,
+ pub gas_token: Address,
+ }
+
+ impl SetGasTokenLog {
+ pub(crate) fn encode(self) -> Log {
+ let data = ethabi::encode(&[ethabi::Token::Address(self.gas_token.raw())]);
+ let sender_address = {
+ let mut buf = [0u8; 32];
+ buf[12..].copy_from_slice(self.sender.as_bytes());
+ H256(buf)
+ };
+ let topics = vec![SET_GAS_TOKEN_SIGNATURE, sender_address];
+
+ let raw_log = ethabi::RawLog { topics, data };
+
+ Log {
+ address: consts::SET_GAS_TOKEN_ADDRESS.raw(),
+ topics: raw_log.topics,
+ data: raw_log.data,
+ }
+ }
+ }
+
+ #[cfg(test)]
+ pub fn set_gas_token_schema() -> ethabi::Event {
+ ethabi::Event {
+ name: "SetGasToken".into(),
+ inputs: vec![
+ ethabi::EventParam {
+ name: "sender".into(),
+ kind: ethabi::ParamType::Address,
+ indexed: true,
+ },
+ ethabi::EventParam {
+ name: "gas_token".into(),
+ kind: ethabi::ParamType::Address,
+ indexed: true,
+ },
+ ],
+ anonymous: false,
+ }
+ }
+}
+
+/// A precompile contract used to set the gas token.
+///
+/// Takes an input which must be an approved ERC-20 contract, or ETH itself at
+/// the address `0x0`.
+pub struct SetGasToken;
+
+impl SetGasToken {
+ pub const ADDRESS: Address = SET_GAS_TOKEN_ADDRESS;
+}
+
+impl Precompile for SetGasToken {
+ fn required_gas(_input: &[u8]) -> Result {
+ Ok(costs::SET_GAS_TOKEN_GAS)
+ }
+
+ fn run(
+ &self,
+ input: &[u8],
+ target_gas: Option,
+ context: &Context,
+ is_static: bool,
+ ) -> EvmPrecompileResult {
+ let required_gas = Self::required_gas(input)?;
+ if let Some(target_gas) = target_gas {
+ if required_gas > target_gas {
+ return Err(ExitError::OutOfGas);
+ }
+ }
+
+ // It's not allowed to call exit precompiles in static mode
+ if is_static {
+ return Err(ExitError::Other(Cow::from("ERR_INVALID_IN_STATIC")));
+ } else if context.address != Self::ADDRESS.raw() {
+ return Err(ExitError::Other(Cow::from("ERR_INVALID_IN_DELEGATE")));
+ }
+
+ let set_gas_token_log: Log = {
+ let sender = Address::new(context.caller);
+ let gas_token = Address::try_from_slice(input)
+ .map_err(|_e| ExitError::Other(Cow::from("ERR_INVALID_ETH_ADDRESS")))?;
+ SetGasTokenLog { sender, gas_token }.encode()
+ };
+
+ Ok(PrecompileOutput {
+ cost: required_gas,
+ logs: vec![set_gas_token_log],
+ ..Default::default()
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use aurora_engine_sdk::types::near_account_to_evm_address;
+
+ #[test]
+ fn test_precompile_id() {
+ assert_eq!(
+ SET_GAS_TOKEN_ADDRESS,
+ near_account_to_evm_address("setGasToken".as_bytes())
+ );
+ }
+
+ #[test]
+ fn test_signature() {
+ let schema = events::set_gas_token_schema();
+ assert_eq!(schema.signature(), events::SET_GAS_TOKEN_SIGNATURE);
+ }
+}
diff --git a/engine-types/src/types/address.rs b/engine-types/src/types/address.rs
index 13be5223d..6d3b6135d 100755
--- a/engine-types/src/types/address.rs
+++ b/engine-types/src/types/address.rs
@@ -4,6 +4,9 @@ use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
+const ADDRESS_HEX_LENGTH: usize = 40;
+const ADDRESS_BYTE_LENGTH: usize = 20;
+
/// Base Eth Address type
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -26,10 +29,10 @@ impl Address {
}
pub fn decode(address: &str) -> Result {
- if address.len() != 40 {
+ if address.len() != ADDRESS_HEX_LENGTH {
return Err(error::AddressError::IncorrectLength);
}
- let mut result = [0u8; 20];
+ let mut result = [0u8; ADDRESS_BYTE_LENGTH];
hex::decode_to_slice(address, &mut result)
.map_err(|_| error::AddressError::FailedDecodeHex)?;
Ok(Address::new(H160(result)))
@@ -40,18 +43,25 @@ impl Address {
}
pub fn try_from_slice(raw_addr: &[u8]) -> Result {
- if raw_addr.len() != 20 {
- return Err(error::AddressError::IncorrectLength);
+ use core::cmp::Ordering;
+ match raw_addr.len().cmp(&ADDRESS_BYTE_LENGTH) {
+ Ordering::Greater => Err(error::AddressError::IncorrectLength),
+ Ordering::Less => {
+ let mut buf = [0u8; ADDRESS_BYTE_LENGTH];
+ let pos = ADDRESS_BYTE_LENGTH - raw_addr.len();
+ buf[pos..].copy_from_slice(raw_addr);
+ Ok(Self::new(H160::from_slice(&buf)))
+ }
+ Ordering::Equal => Ok(Self::new(H160::from_slice(raw_addr))),
}
- Ok(Self::new(H160::from_slice(raw_addr)))
}
- pub const fn from_array(array: [u8; 20]) -> Self {
+ pub const fn from_array(array: [u8; ADDRESS_BYTE_LENGTH]) -> Self {
Self(H160(array))
}
pub const fn zero() -> Self {
- Address::new(H160([0u8; 20]))
+ Address::new(H160([0u8; ADDRESS_BYTE_LENGTH]))
}
}
@@ -71,15 +81,15 @@ impl BorshSerialize for Address {
impl BorshDeserialize for Address {
fn deserialize(buf: &mut &[u8]) -> io::Result {
- if buf.len() < 20 {
+ if buf.len() < ADDRESS_BYTE_LENGTH {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("{}", error::AddressError::IncorrectLength),
));
}
// Guaranty no panics. The length checked early
- let address = Self(H160::from_slice(&buf[..20]));
- *buf = &buf[20..];
+ let address = Self(H160::from_slice(&buf[..ADDRESS_BYTE_LENGTH]));
+ *buf = &buf[ADDRESS_BYTE_LENGTH..];
Ok(address)
}
}
@@ -139,8 +149,19 @@ mod tests {
}
#[test]
- fn test_wrong_address_19() {
- let serialized_addr = [0u8; 19];
+ fn test_address_less_than_20_byte_length() {
+ let serialized_addr = [0x1u8; 1];
+ let addr = Address::try_from_slice(&serialized_addr).unwrap();
+ let expected = Address::try_from_slice(
+ &hex::decode("0000000000000000000000000000000000000001").unwrap(),
+ )
+ .unwrap();
+ assert_eq!(addr, expected);
+ }
+
+ #[test]
+ fn test_address_greater_than_20_byte_length() {
+ let serialized_addr = [0x11u8; 21];
let addr = Address::try_from_slice(&serialized_addr);
let err = addr.unwrap_err();
matches!(err, error::AddressError::IncorrectLength);
diff --git a/engine-types/src/types/gas.rs b/engine-types/src/types/gas.rs
index 0232b776d..b715faf70 100644
--- a/engine-types/src/types/gas.rs
+++ b/engine-types/src/types/gas.rs
@@ -56,6 +56,8 @@ impl Display for EthGas {
}
impl EthGas {
+ pub const MAX: EthGas = EthGas(u64::MAX);
+
/// Constructs a new `EthGas` with a given u64 value.
pub const fn new(gas: u64) -> EthGas {
Self(gas)
diff --git a/engine/src/engine.rs b/engine/src/engine.rs
index d1ac3aa47..7a363afbc 100644
--- a/engine/src/engine.rs
+++ b/engine/src/engine.rs
@@ -1,7 +1,7 @@
use crate::parameters::{CallArgs, NEP141FtOnTransferArgs, ResultLog, SubmitResult, ViewCallArgs};
use core::mem;
use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log};
-use evm::executor;
+use evm::{executor, Context};
use evm::{Config, CreateScheme, ExitError, ExitFatal, ExitReason};
use crate::connector::EthConnectorContract;
@@ -20,14 +20,16 @@ use crate::pausables::{
use crate::prelude::parameters::RefundCallArgs;
use crate::prelude::precompiles::native::{exit_to_ethereum, exit_to_near};
use crate::prelude::precompiles::xcc::cross_contract_call;
-use crate::prelude::precompiles::Precompiles;
+use crate::prelude::precompiles::{self, Precompiles};
use crate::prelude::transactions::{EthTransactionKind, NormalizedEthTransaction};
use crate::prelude::{
address_to_key, bytes_to_key, sdk, storage_to_key, u256_to_arr, vec, AccountId, Address,
BTreeMap, BorshDeserialize, BorshSerialize, KeyPrefix, PromiseArgs, PromiseCreateArgs,
ToString, Vec, Wei, Yocto, ERC20_MINT_SELECTOR, H160, H256, U256,
};
-use aurora_engine_precompiles::PrecompileConstructorContext;
+use aurora_engine_precompiles::set_gas_token::SetGasToken;
+use aurora_engine_precompiles::{Precompile, PrecompileConstructorContext};
+use aurora_engine_types::types::EthGas;
use core::cell::RefCell;
use core::iter::once;
@@ -422,7 +424,6 @@ pub enum GasToken {
ERC20(Address),
}
-
pub struct Engine<'env, I: IO, E: Env> {
state: EngineState,
origin: Address,
@@ -510,6 +511,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
}
GasToken::ERC20(addr) => {
// TODO: Needs SputnikVM balance check
+ todo!()
}
}
@@ -529,7 +531,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
) -> EngineResult {
let origin = Address::new(self.origin());
let value = Wei::zero();
- self.deploy_code(origin, value, input, u64::MAX, Vec::new(), handler)
+ self.deploy_code(origin, value, input, EthGas::MAX, Vec::new(), handler)
}
pub fn deploy_code(
@@ -537,20 +539,25 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
origin: Address,
value: Wei,
input: Vec,
- gas_limit: u64,
+ gas_limit: EthGas,
access_list: Vec<(H160, Vec)>, // See EIP-2930
handler: &mut P,
) -> EngineResult {
let pause_flags = EnginePrecompilesPauser::from_io(self.io).paused();
let precompiles = self.create_precompiles(pause_flags, handler);
- let executor_params = StackExecutorParams::new(gas_limit, precompiles);
+ let executor_params = StackExecutorParams::new(gas_limit.as_u64(), precompiles);
let mut executor = executor_params.make_executor(self);
let address = executor.create_address(CreateScheme::Legacy {
caller: origin.raw(),
});
- let (exit_reason, return_value) =
- executor.transact_create(origin.raw(), value.raw(), input, gas_limit, access_list);
+ let (exit_reason, return_value) = executor.transact_create(
+ origin.raw(),
+ value.raw(),
+ input,
+ gas_limit.as_u64(),
+ access_list,
+ );
let result = if exit_reason.is_succeed() {
address.0.to_vec()
} else {
@@ -591,7 +598,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
&contract,
value,
input,
- u64::MAX,
+ EthGas::MAX,
Vec::new(),
handler,
)
@@ -605,7 +612,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
&contract,
value,
input,
- u64::MAX,
+ EthGas::MAX,
Vec::new(),
handler,
)
@@ -620,21 +627,21 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
contract: &Address,
value: Wei,
input: Vec,
- gas_limit: u64,
+ gas_limit: EthGas,
access_list: Vec<(H160, Vec)>, // See EIP-2930
handler: &mut P,
) -> EngineResult {
let pause_flags = EnginePrecompilesPauser::from_io(self.io).paused();
let precompiles = self.create_precompiles(pause_flags, handler);
- let executor_params = StackExecutorParams::new(gas_limit, precompiles);
+ let executor_params = StackExecutorParams::new(gas_limit.as_u64(), precompiles);
let mut executor = executor_params.make_executor(self);
let (exit_reason, result) = executor.transact_call(
origin.raw(),
contract.raw(),
value.raw(),
input,
- gas_limit,
+ gas_limit.as_u64(),
access_list,
);
@@ -740,7 +747,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
sender: Address,
receiver: Address,
value: Wei,
- gas_limit: u64,
+ gas_limit: EthGas,
handler: &mut P,
) -> EngineResult {
self.call(
@@ -873,8 +880,7 @@ impl<'env, I: IO + Copy, E: Env> Engine<'env, I, E> {
promise_handler: ro_promise_handler,
});
// Cross contract calls are not enabled on mainnet yet.
- tmp.all_precompiles
- .remove(&aurora_engine_precompiles::xcc::cross_contract_call::ADDRESS);
+ tmp.all_precompiles.remove(&cross_contract_call::ADDRESS);
tmp
} else {
Precompiles::new_london(PrecompileConstructorContext {
@@ -953,7 +959,7 @@ pub fn submit(
check_nonce(&io, &sender, &transaction.nonce)?;
// Check intrinsic gas is covered by transaction gas limit
- match transaction.intrinsic_gas(crate::engine::CONFIG) {
+ match transaction.intrinsic_gas(CONFIG) {
Err(_e) => {
return Err(EngineErrorKind::GasOverflow.into());
}
@@ -969,7 +975,8 @@ pub fn submit(
}
let mut engine = Engine::new_with_state(state, sender, current_account_id, io, env);
- let prepaid_amount = match engine.charge_gas(&sender, &transaction) {
+ // TODO: Have GasToken derived from storage.
+ let prepaid_amount = match engine.charge_gas(&sender, &transaction, GasToken::ETH) {
Ok(gas_result) => gas_result,
Err(GasPaymentError::OutOfFund) => {
let result = SubmitResult::new(TransactionStatus::OutOfFund, 0, vec![]);
@@ -979,7 +986,7 @@ pub fn submit(
return Err(EngineErrorKind::GasPayment(err).into());
}
};
- let gas_limit: u64 = transaction
+ let gas_limit: EthGas = transaction
.gas_limit
.try_into()
.map_err(|_| EngineErrorKind::GasOverflow)?;
@@ -989,16 +996,41 @@ pub fn submit(
.map(|a| (a.address, a.storage_keys))
.collect();
let result = if let Some(receiver) = transaction.to {
- engine.call(
- &sender,
- &receiver,
- transaction.value,
- transaction.data,
- gas_limit,
- access_list,
- handler,
- )
- // TODO: charge for storage
+ // We are making this precompile here special for now as we want to
+ // ensure that it can only be directly called. This may change in the
+ // future after careful research and consideration. For example, we are
+ // not sure if there are consequences in changing the gas during the
+ // execution of the transaction.
+ if receiver == precompiles::set_gas_token::SET_GAS_TOKEN_ADDRESS {
+ let set_gas_token = SetGasToken;
+ let context = Context {
+ address: receiver.raw(),
+ caller: transaction.address.raw(),
+ apparent_value: Default::default(),
+ };
+ match set_gas_token.run(&transaction.data, Some(gas_limit), &context, false) {
+ Ok(v) => {
+ let tx_status = TransactionStatus::Succeed(vec![]);
+ let mut result_logs = Vec::new();
+ for log in v.logs {
+ result_logs.push(log.into());
+ }
+ Ok(SubmitResult::new(tx_status, v.cost.as_u64(), result_logs))
+ }
+ Err(e) => Err(EngineError::from(EngineErrorKind::EvmError(e))),
+ }
+ } else {
+ engine.call(
+ &sender,
+ &receiver,
+ transaction.value,
+ transaction.data,
+ gas_limit,
+ access_list,
+ handler,
+ )
+ // TODO: charge for storage
+ }
} else {
// Execute a contract deployment:
engine.deploy_code(
@@ -1062,14 +1094,14 @@ pub fn refund_on_error(
&erc20_address,
Wei::zero(),
input,
- u64::MAX,
+ EthGas::MAX,
Vec::new(),
handler,
)
}
// ETH exit; transfer ETH back from precompile address
None => {
- let exit_address = aurora_engine_precompiles::native::exit_to_near::ADDRESS;
+ let exit_address = exit_to_near::ADDRESS;
let mut engine =
Engine::new_with_state(state, exit_address, current_account_id, io, env);
let refund_address = args.recipient_address;
@@ -1079,7 +1111,7 @@ pub fn refund_on_error(
&refund_address,
amount,
Vec::new(),
- u64::MAX,
+ EthGas::MAX,
vec![
(exit_address.raw(), Vec::new()),
(refund_address.raw(), Vec::new()),
@@ -1957,7 +1989,7 @@ mod tests {
let mut engine =
Engine::new_with_state(EngineState::default(), origin, current_account_id, io, &env);
- let gas_limit = u64::MAX;
+ let gas_limit = EthGas::MAX;
let mut handler = Noop;
let receiver = make_address(1, 1);
let value = Wei::new_u64(1000);
diff --git a/engine/src/lib.rs b/engine/src/lib.rs
index 67e52b23d..ff133643a 100644
--- a/engine/src/lib.rs
+++ b/engine/src/lib.rs
@@ -30,7 +30,6 @@ pub mod log_entry;
pub mod pausables;
mod prelude;
pub mod xcc;
-mod erc20;
#[cfg(target_arch = "wasm32")]
#[global_allocator]