Skip to content

Commit

Permalink
[ETH]: Add support for EIP-2930 access lists (#4022)
Browse files Browse the repository at this point in the history
* feat(eth): Add support for EIP-2930 access list

* feat(eth): Fix formatting

* feat(eth): Fix Rust tests

* feat(eth): Fix Rust CI
  • Loading branch information
satoshiotomakan authored Sep 13, 2024
1 parent 992b248 commit d66b0b9
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 82 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/linux-ci-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,15 @@ jobs:
./tools/release-size measure-rust > release-report.json
- name: Upload release report
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: release_report
path: release-report.json

# Download previous release report, compare the release binary sizes, and post/update a comment at the Pull Request.
- name: Download previous release report
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
uses: dawidd6/action-download-artifact@v3
uses: dawidd6/action-download-artifact@v6
with:
commit: ${{github.event.pull_request.base.sha}}
path: previous
Expand Down
10 changes: 5 additions & 5 deletions rust/tw_evm/src/modules/rlp_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ pub struct RlpEncoder<Context: EvmContext> {
}

impl<Context: EvmContext> RlpEncoder<Context> {
pub fn encode<T>(val: T) -> Data
pub fn encode<T>(val: &T) -> Data
where
T: RlpEncode,
T: RlpEncode + ?Sized,
{
let mut buf = RlpBuffer::new();
val.rlp_append(&mut buf);
Expand Down Expand Up @@ -64,16 +64,16 @@ impl<Context: EvmContext> RlpEncoder<Context> {

let encoded_item = match rlp_item.item {
Item::string_item(str) => RlpEncoder::<Context>::encode(str.as_ref()),
Item::number_u64(num) => RlpEncoder::<Context>::encode(U256::from(num)),
Item::number_u64(num) => RlpEncoder::<Context>::encode(&U256::from(num)),
Item::number_u256(num_be) => {
let num = U256::from_big_endian_slice(num_be.as_ref())
.into_tw()
.context("Invalid U256 number")?;
RlpEncoder::<Context>::encode(num)
RlpEncoder::<Context>::encode(&num)
},
Item::address(addr_s) => {
let addr = Context::Address::from_str(addr_s.as_ref())?;
RlpEncoder::<Context>::encode(addr.into())
RlpEncoder::<Context>::encode(&addr.into())
},
Item::data(data) => RlpEncoder::<Context>::encode(data.as_ref()),
Item::list(proto_nested_list) => {
Expand Down
30 changes: 28 additions & 2 deletions rust/tw_evm/src/modules/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ use crate::abi::prebuild::erc4337::{Erc4337SimpleAccount, ExecuteArgs};
use crate::abi::prebuild::erc721::Erc721;
use crate::address::{Address, EvmAddress};
use crate::evm_context::EvmContext;
use crate::transaction::access_list::{Access, AccessList};
use crate::transaction::transaction_eip1559::TransactionEip1559;
use crate::transaction::transaction_non_typed::TransactionNonTyped;
use crate::transaction::user_operation::UserOperation;
use crate::transaction::UnsignedTransactionBox;
use std::marker::PhantomData;
use std::str::FromStr;
use tw_coin_entry::error::prelude::*;
use tw_hash::H256;
use tw_memory::Data;
use tw_number::U256;
use tw_proto::Common::Proto::SigningError as CommonError;
Expand Down Expand Up @@ -249,6 +251,9 @@ impl<Context: EvmContext> TxBuilder<Context> {
.into_tw()
.context("Invalid max fee per gas")?;

let access_list =
Self::parse_access_list(&input.access_list).context("Invalid access list")?;

Ok(TransactionEip1559 {
nonce,
max_inclusion_fee_per_gas,
Expand All @@ -257,6 +262,7 @@ impl<Context: EvmContext> TxBuilder<Context> {
to: to_address,
amount: eth_amount,
payload,
access_list,
})
}

Expand Down Expand Up @@ -315,19 +321,39 @@ impl<Context: EvmContext> TxBuilder<Context> {
})
}

#[inline]
fn parse_address(addr: &str) -> SigningResult<Address> {
Context::Address::from_str(addr)
.map(Context::Address::into)
.map_err(SigningError::from)
}

#[inline]
fn parse_address_optional(addr: &str) -> SigningResult<Option<Address>> {
match Context::Address::from_str_optional(addr) {
Ok(Some(addr)) => Ok(Some(addr.into())),
Ok(None) => Ok(None),
Err(e) => Err(SigningError::from(e)),
}
}

fn parse_access_list(list_proto: &[Proto::Access]) -> SigningResult<AccessList> {
let mut access_list = AccessList::default();
for access_proto in list_proto.iter() {
access_list.add_access(Self::parse_access(access_proto)?);
}
Ok(access_list)
}

fn parse_access(access_proto: &Proto::Access) -> SigningResult<Access> {
let addr =
Self::parse_address(access_proto.address.as_ref()).context("Invalid access address")?;

let mut access = Access::new(addr);
for key_proto in access_proto.stored_keys.iter() {
let storage_key = H256::try_from(key_proto.as_ref())
.tw_err(|_| SigningErrorType::Error_invalid_params)
.context("Invalid storage key")?;
access.add_storage_key(storage_key);
}
Ok(access)
}
}
11 changes: 9 additions & 2 deletions rust/tw_evm/src/rlp/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use crate::address::Address;
use crate::rlp::buffer::RlpBuffer;
use crate::rlp::RlpEncode;
use tw_hash::H256;
use tw_number::U256;

impl RlpEncode for U256 {
Expand All @@ -13,6 +14,12 @@ impl RlpEncode for U256 {
}
}

impl RlpEncode for H256 {
fn rlp_append(&self, buf: &mut RlpBuffer) {
buf.append_data(self.as_slice())
}
}

impl RlpEncode for Address {
fn rlp_append(&self, buf: &mut RlpBuffer) {
buf.append_data(self.as_slice())
Expand All @@ -28,13 +35,13 @@ impl RlpEncode for Option<Address> {
}
}

impl<'a> RlpEncode for &'a [u8] {
impl RlpEncode for [u8] {
fn rlp_append(&self, buf: &mut RlpBuffer) {
buf.append_data(self)
}
}

impl<'a> RlpEncode for &'a str {
impl RlpEncode for str {
fn rlp_append(&self, buf: &mut RlpBuffer) {
buf.append_data(self.as_bytes())
}
Expand Down
4 changes: 2 additions & 2 deletions rust/tw_evm/src/rlp/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ impl RlpList {
}

/// Appends an item.
pub fn append<T>(&mut self, item: T) -> &mut Self
pub fn append<T>(&mut self, item: &T) -> &mut Self
where
T: RlpEncode,
T: RlpEncode + ?Sized,
{
item.rlp_append(&mut self.buf);
self
Expand Down
72 changes: 72 additions & 0 deletions rust/tw_evm/src/transaction/access_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use crate::address::Address;
use crate::rlp::buffer::RlpBuffer;
use crate::rlp::RlpEncode;
use tw_hash::H256;

/// A list of addresses and storage keys that the transaction plans to access.
pub struct Access {
pub address: Address,
pub storage_keys: Vec<H256>,
}

impl Access {
#[inline]
pub fn new(address: Address) -> Self {
Access {
address,
storage_keys: Vec::default(),
}
}

#[inline]
pub fn add_storage_key(&mut self, key: H256) -> &mut Self {
self.storage_keys.push(key);
self
}
}

impl RlpEncode for Access {
fn rlp_append(&self, buf: &mut RlpBuffer) {
buf.begin_list();
self.address.rlp_append(buf);

// append the list of keys
{
buf.begin_list();
for storage_key in self.storage_keys.iter() {
storage_key.rlp_append(buf);
}
buf.finalize_list();
}

buf.finalize_list();
}
}

/// [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access list.
#[derive(Default)]
pub struct AccessList(Vec<Access>);

impl AccessList {
#[inline]
pub fn add_access(&mut self, access: Access) -> &mut Self {
self.0.push(access);
self
}
}

impl RlpEncode for AccessList {
fn rlp_append(&self, buf: &mut RlpBuffer) {
buf.begin_list();

for access in self.0.iter() {
access.rlp_append(buf);
}

buf.finalize_list();
}
}
1 change: 1 addition & 0 deletions rust/tw_evm/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use tw_keypair::ecdsa::secp256k1;
use tw_memory::Data;
use tw_number::U256;

pub mod access_list;
pub mod signature;
pub mod transaction_eip1559;
pub mod transaction_non_typed;
Expand Down
70 changes: 54 additions & 16 deletions rust/tw_evm/src/transaction/transaction_eip1559.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use crate::address::Address;
use crate::rlp::list::RlpList;
use crate::transaction::access_list::AccessList;
use crate::transaction::signature::{EthSignature, Signature};
use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction};
use tw_coin_entry::error::prelude::*;
Expand All @@ -22,6 +23,7 @@ pub struct TransactionEip1559 {
pub to: Option<Address>,
pub amount: U256,
pub payload: Data,
pub access_list: AccessList,
}

impl TransactionCommon for TransactionEip1559 {
Expand Down Expand Up @@ -86,21 +88,20 @@ fn encode_transaction(
signature: Option<&Signature>,
) -> Data {
let mut list = RlpList::new();
list.append(chain_id)
.append(tx.nonce)
.append(tx.max_inclusion_fee_per_gas)
.append(tx.max_fee_per_gas)
.append(tx.gas_limit)
.append(tx.to)
.append(tx.amount)
list.append(&chain_id)
.append(&tx.nonce)
.append(&tx.max_inclusion_fee_per_gas)
.append(&tx.max_fee_per_gas)
.append(&tx.gas_limit)
.append(&tx.to)
.append(&tx.amount)
.append(tx.payload.as_slice())
// empty `access_list`.
.append_empty_list();
.append(&tx.access_list);

if let Some(signature) = signature {
list.append(signature.v());
list.append(signature.r());
list.append(signature.s());
list.append(&signature.v());
list.append(&signature.r());
list.append(&signature.s());
}

let tx_encoded = list.finish();
Expand All @@ -114,23 +115,60 @@ fn encode_transaction(
#[cfg(test)]
mod tests {
use super::*;
use crate::transaction::access_list::Access;
use tw_encoding::hex;
use tw_hash::H256;

#[test]
fn test_encode_transaction_eip1559() {
let tx = TransactionEip1559 {
nonce: U256::from(6u64),
max_inclusion_fee_per_gas: U256::from(2_000_000_000u64),
max_fee_per_gas: U256::from(3_000_000_000u64),
gas_limit: U256::from(21100u32),
nonce: U256::from(6_u64),
max_inclusion_fee_per_gas: U256::from(2_000_000_000_u64),
max_fee_per_gas: U256::from(3_000_000_000_u64),
gas_limit: U256::from(21100_u64),
to: Some(Address::from("0x6b175474e89094c44da98b954eedeac495271d0f")),
amount: U256::zero(),
payload: hex::decode("a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1").unwrap(),
access_list: AccessList::default(),
};
let chain_id = U256::from(10u64);
let actual = tx.encode(chain_id);

let expected = "02f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0";
assert_eq!(hex::encode(actual, false), expected);
}

#[test]
fn test_encode_transaction_eip1559_with_access_list() {
let mut access = Access::new(Address::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"));

#[rustfmt::skip]
access
.add_storage_key(H256::from("0x76c8f33bcdf467e4f1313522c10a40512a867cdcd34f2b898232ad4669200764"))
.add_storage_key(H256::from("0x000000000000000000000000000000000000000000000000000000000000000a"))
.add_storage_key(H256::from("0x0000000000000000000000000000000000000000000000000000000000000003"))
.add_storage_key(H256::from("0x0000000000000000000000000000000000000000000000000000000000000004"))
.add_storage_key(H256::from("0xb12459e057d0da4389f95b7ff0ce45a52ad71b02913a5466ffaab252e7ce918a"))
.add_storage_key(H256::from("0x1bba044274699cc8c429fbe84bdad5d5a49519e29430f25309cbbab31dc63043"))
.add_storage_key(H256::from("0x0000000000000000000000000000000000000000000000000000000000000000"));

let mut access_list = AccessList::default();
access_list.add_access(access);

let tx = TransactionEip1559 {
nonce: U256::from(1u64),
max_inclusion_fee_per_gas: U256::from(2_000_000_000_u64),
max_fee_per_gas: U256::from(3_000_000_000_u64),
gas_limit: U256::from(100_000_u64),
to: Some(Address::from("0xdAC17F958D2ee523a2206206994597C13D831ec7")),
amount: U256::zero(),
payload: hex::decode("a9059cbb000000000000000000000000b2fb4372e663b2e53da97d98100433d1fd06ca5500000000000000000000000000000000000000000000000000000000000f4240").unwrap(),
access_list,
};
let chain_id = U256::from(1_u64);
let actual = tx.encode(chain_id);

let expected = "02f9016f0101847735940084b2d05e00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000b2fb4372e663b2e53da97d98100433d1fd06ca5500000000000000000000000000000000000000000000000000000000000f4240f90100f8fe94dac17f958d2ee523a2206206994597c13d831ec7f8e7a076c8f33bcdf467e4f1313522c10a40512a867cdcd34f2b898232ad4669200764a0000000000000000000000000000000000000000000000000000000000000000aa00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000004a0b12459e057d0da4389f95b7ff0ce45a52ad71b02913a5466ffaab252e7ce918aa01bba044274699cc8c429fbe84bdad5d5a49519e29430f25309cbbab31dc63043a00000000000000000000000000000000000000000000000000000000000000000";
assert_eq!(hex::encode(actual, false), expected);
}
}
12 changes: 6 additions & 6 deletions rust/tw_evm/src/transaction/transaction_non_typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,18 @@ fn encode_transaction(
signature: Option<&SignatureEip155>,
) -> Data {
let mut list = RlpList::new();
list.append(tx.nonce)
.append(tx.gas_price)
.append(tx.gas_limit)
.append(tx.to)
.append(tx.amount)
list.append(&tx.nonce)
.append(&tx.gas_price)
.append(&tx.gas_limit)
.append(&tx.to)
.append(&tx.amount)
.append(tx.payload.as_slice());

let (v, r, s) = match signature {
Some(sign) => (sign.v(), sign.r(), sign.s()),
None => (chain_id, U256::zero(), U256::zero()),
};
list.append(v).append(r).append(s);
list.append(&v).append(&r).append(&s);
list.finish()
}

Expand Down
Loading

0 comments on commit d66b0b9

Please sign in to comment.