Skip to content

Commit

Permalink
feat: add setGasToken precompile
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuajbouw committed Jan 23, 2023
1 parent cc15e61 commit c69fb52
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 55 deletions.
17 changes: 9 additions & 8 deletions engine-precompiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -111,8 +112,8 @@ impl HardFork for Istanbul {}
impl HardFork for Berlin {}

pub struct Precompiles<'a, I, E, H> {
pub all_precompiles: prelude::BTreeMap<Address, AllPrecompiles<'a, I, E, H>>,
pub paused_precompiles: prelude::BTreeSet<Address>,
pub all_precompiles: BTreeMap<Address, AllPrecompiles<'a, I, E, H>>,
pub paused_precompiles: BTreeSet<Address>,
}

impl<'a, I, E, H> Precompiles<'a, I, E, H> {
Expand Down Expand Up @@ -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))
}
}
Expand Down Expand Up @@ -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<Box<dyn Precompile>> = vec![
let fun: Vec<Box<dyn Precompile>> = vec![
Box::new(ECRecover),
Box::new(SHA256),
Box::new(RIPEMD160),
Expand Down Expand Up @@ -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],
Expand All @@ -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],
Expand Down
157 changes: 157 additions & 0 deletions engine-precompiles/src/set_gas_token.rs
Original file line number Diff line number Diff line change
@@ -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<EthGas, ExitError> {
Ok(costs::SET_GAS_TOKEN_GAS)
}

fn run(
&self,
input: &[u8],
target_gas: Option<EthGas>,
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);
}
}
45 changes: 33 additions & 12 deletions engine-types/src/types/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand All @@ -26,10 +29,10 @@ impl Address {
}

pub fn decode(address: &str) -> Result<Address, error::AddressError> {
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)))
Expand All @@ -40,18 +43,25 @@ impl Address {
}

pub fn try_from_slice(raw_addr: &[u8]) -> Result<Self, error::AddressError> {
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]))
}
}

Expand All @@ -71,15 +81,15 @@ impl BorshSerialize for Address {

impl BorshDeserialize for Address {
fn deserialize(buf: &mut &[u8]) -> io::Result<Self> {
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)
}
}
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions engine-types/src/types/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit c69fb52

Please sign in to comment.