From d66b0b929157be299538234b85da725445927d52 Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:26:23 +0700 Subject: [PATCH] [ETH]: Add support for EIP-2930 access lists (#4022) * feat(eth): Add support for EIP-2930 access list * feat(eth): Fix formatting * feat(eth): Fix Rust tests * feat(eth): Fix Rust CI --- .github/workflows/linux-ci-rust.yml | 4 +- rust/tw_evm/src/modules/rlp_encoder.rs | 10 +- rust/tw_evm/src/modules/tx_builder.rs | 30 +++- rust/tw_evm/src/rlp/impls.rs | 11 +- rust/tw_evm/src/rlp/list.rs | 4 +- rust/tw_evm/src/transaction/access_list.rs | 72 +++++++++ rust/tw_evm/src/transaction/mod.rs | 1 + .../src/transaction/transaction_eip1559.rs | 70 ++++++-- .../src/transaction/transaction_non_typed.rs | 12 +- rust/tw_evm/tests/barz.rs | 6 +- rust/tw_evm/tests/signer.rs | 149 ++++++++++++------ src/proto/Ethereum.proto | 12 ++ 12 files changed, 299 insertions(+), 82 deletions(-) create mode 100644 rust/tw_evm/src/transaction/access_list.rs diff --git a/.github/workflows/linux-ci-rust.yml b/.github/workflows/linux-ci-rust.yml index 768af167d88..36d3e57931a 100644 --- a/.github/workflows/linux-ci-rust.yml +++ b/.github/workflows/linux-ci-rust.yml @@ -126,7 +126,7 @@ 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 @@ -134,7 +134,7 @@ jobs: # 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 diff --git a/rust/tw_evm/src/modules/rlp_encoder.rs b/rust/tw_evm/src/modules/rlp_encoder.rs index 8f22880d811..a935a719102 100644 --- a/rust/tw_evm/src/modules/rlp_encoder.rs +++ b/rust/tw_evm/src/modules/rlp_encoder.rs @@ -23,9 +23,9 @@ pub struct RlpEncoder { } impl RlpEncoder { - pub fn encode(val: T) -> Data + pub fn encode(val: &T) -> Data where - T: RlpEncode, + T: RlpEncode + ?Sized, { let mut buf = RlpBuffer::new(); val.rlp_append(&mut buf); @@ -64,16 +64,16 @@ impl RlpEncoder { let encoded_item = match rlp_item.item { Item::string_item(str) => RlpEncoder::::encode(str.as_ref()), - Item::number_u64(num) => RlpEncoder::::encode(U256::from(num)), + Item::number_u64(num) => RlpEncoder::::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::::encode(num) + RlpEncoder::::encode(&num) }, Item::address(addr_s) => { let addr = Context::Address::from_str(addr_s.as_ref())?; - RlpEncoder::::encode(addr.into()) + RlpEncoder::::encode(&addr.into()) }, Item::data(data) => RlpEncoder::::encode(data.as_ref()), Item::list(proto_nested_list) => { diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index 394fc6715c1..830189a08f1 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -9,6 +9,7 @@ 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; @@ -16,6 +17,7 @@ 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; @@ -249,6 +251,9 @@ impl TxBuilder { .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, @@ -257,6 +262,7 @@ impl TxBuilder { to: to_address, amount: eth_amount, payload, + access_list, }) } @@ -315,14 +321,12 @@ impl TxBuilder { }) } - #[inline] fn parse_address(addr: &str) -> SigningResult
{ Context::Address::from_str(addr) .map(Context::Address::into) .map_err(SigningError::from) } - #[inline] fn parse_address_optional(addr: &str) -> SigningResult> { match Context::Address::from_str_optional(addr) { Ok(Some(addr)) => Ok(Some(addr.into())), @@ -330,4 +334,26 @@ impl TxBuilder { Err(e) => Err(SigningError::from(e)), } } + + fn parse_access_list(list_proto: &[Proto::Access]) -> SigningResult { + 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 { + 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) + } } diff --git a/rust/tw_evm/src/rlp/impls.rs b/rust/tw_evm/src/rlp/impls.rs index 21e8532da2d..371a3a7b47e 100644 --- a/rust/tw_evm/src/rlp/impls.rs +++ b/rust/tw_evm/src/rlp/impls.rs @@ -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 { @@ -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()) @@ -28,13 +35,13 @@ impl RlpEncode for Option
{ } } -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()) } diff --git a/rust/tw_evm/src/rlp/list.rs b/rust/tw_evm/src/rlp/list.rs index ae7b0924aef..65a568e35c5 100644 --- a/rust/tw_evm/src/rlp/list.rs +++ b/rust/tw_evm/src/rlp/list.rs @@ -26,9 +26,9 @@ impl RlpList { } /// Appends an item. - pub fn append(&mut self, item: T) -> &mut Self + pub fn append(&mut self, item: &T) -> &mut Self where - T: RlpEncode, + T: RlpEncode + ?Sized, { item.rlp_append(&mut self.buf); self diff --git a/rust/tw_evm/src/transaction/access_list.rs b/rust/tw_evm/src/transaction/access_list.rs new file mode 100644 index 00000000000..191eee1ab75 --- /dev/null +++ b/rust/tw_evm/src/transaction/access_list.rs @@ -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, +} + +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); + +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(); + } +} diff --git a/rust/tw_evm/src/transaction/mod.rs b/rust/tw_evm/src/transaction/mod.rs index 949153e7946..9d4ba81b32b 100644 --- a/rust/tw_evm/src/transaction/mod.rs +++ b/rust/tw_evm/src/transaction/mod.rs @@ -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; diff --git a/rust/tw_evm/src/transaction/transaction_eip1559.rs b/rust/tw_evm/src/transaction/transaction_eip1559.rs index 4d2c5130970..66b59b0f16b 100644 --- a/rust/tw_evm/src/transaction/transaction_eip1559.rs +++ b/rust/tw_evm/src/transaction/transaction_eip1559.rs @@ -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::*; @@ -22,6 +23,7 @@ pub struct TransactionEip1559 { pub to: Option
, pub amount: U256, pub payload: Data, + pub access_list: AccessList, } impl TransactionCommon for TransactionEip1559 { @@ -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(); @@ -114,18 +115,21 @@ 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); @@ -133,4 +137,38 @@ mod tests { 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); + } } diff --git a/rust/tw_evm/src/transaction/transaction_non_typed.rs b/rust/tw_evm/src/transaction/transaction_non_typed.rs index 9efd792020f..437a27fc158 100644 --- a/rust/tw_evm/src/transaction/transaction_non_typed.rs +++ b/rust/tw_evm/src/transaction/transaction_non_typed.rs @@ -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() } diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index e41a12aa244..2b4a250fd26 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -35,7 +35,6 @@ fn test_barz_transfer_account_deployed() { chain_id: U256::encode_be_compact(97), nonce: U256::encode_be_compact(2), tx_mode: Proto::TransactionMode::UserOp, - gas_price: Cow::default(), gas_limit: U256::encode_be_compact(0x186A0), max_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), max_inclusion_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), @@ -45,6 +44,7 @@ fn test_barz_transfer_account_deployed() { transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), }), user_operation: Some(user_op), + ..Proto::SigningInput::default() }; let output = Signer::::sign_proto(input); @@ -88,7 +88,6 @@ fn test_barz_transfer_account_not_deployed() { chain_id: U256::encode_be_compact(97), nonce: U256::encode_be_compact(0), tx_mode: Proto::TransactionMode::UserOp, - gas_price: Cow::default(), gas_limit: U256::encode_be_compact(0x26_25A0), max_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), max_inclusion_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), @@ -98,6 +97,7 @@ fn test_barz_transfer_account_not_deployed() { transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), }), user_operation: Some(user_op), + ..Proto::SigningInput::default() }; let output = Signer::::sign_proto(input); @@ -163,7 +163,6 @@ fn test_barz_batched_account_deployed() { chain_id: U256::encode_be_compact(97), nonce: U256::encode_be_compact(3), tx_mode: Proto::TransactionMode::UserOp, - gas_price: Cow::default(), gas_limit: U256::encode_be_compact(0x01_5A61), max_fee_per_gas: U256::encode_be_compact(0x02_540B_E400), max_inclusion_fee_per_gas: U256::encode_be_compact(0x02_540B_E400), @@ -175,6 +174,7 @@ fn test_barz_batched_account_deployed() { ), }), user_operation: Some(user_op), + ..Proto::SigningInput::default() }; let output = Signer::::sign_proto(input); diff --git a/rust/tw_evm/tests/signer.rs b/rust/tw_evm/tests/signer.rs index eace3005b4c..15fec9df0cd 100644 --- a/rust/tw_evm/tests/signer.rs +++ b/rust/tw_evm/tests/signer.rs @@ -11,10 +11,14 @@ use tw_number::U256; use tw_proto::Ethereum::Proto; use tw_proto::Ethereum::Proto::TransactionMode; +fn parse_hex(key: &str) -> Cow<'_, [u8]> { + hex::decode(key).unwrap().into() +} + #[test] fn test_sign_transaction_non_typed_erc20_transfer() { - let private = - hex::decode("0x4646464646464646464646464646464646464646464646464646464646464646").unwrap(); + let private_key = + parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), @@ -35,7 +39,7 @@ fn test_sign_transaction_non_typed_erc20_transfer() { erc20_transfer, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -54,8 +58,8 @@ fn test_sign_transaction_non_typed_erc20_transfer() { #[test] fn test_sign_transaction_non_typed_native() { - let private = - hex::decode("0x4646464646464646464646464646464646464646464646464646464646464646").unwrap(); + let private_key = + parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); let transfer = Proto::mod_Transaction::Transfer { amount: U256::encode_be_compact(1_000_000_000_000_000_000), @@ -71,7 +75,7 @@ fn test_sign_transaction_non_typed_native() { transaction: Some(Proto::Transaction { transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -100,8 +104,8 @@ fn test_sign_transaction_non_typed_native() { #[test] fn test_sign_transaction_non_typed_erc20_approve() { - let private = - hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + let private_key = + parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); let approve = Proto::mod_Transaction::ERC20Approve { // DAI @@ -122,7 +126,7 @@ fn test_sign_transaction_non_typed_erc20_approve() { approve, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -151,13 +155,13 @@ fn test_sign_transaction_non_typed_erc20_approve() { #[test] fn test_sign_transaction_non_typed_contract_generic() { - let private = - hex::decode("0x4646464646464646464646464646464646464646464646464646464646464646").unwrap(); + let private_key = + parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); let call_data = "60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f540000000000000000000000000000000000000000000000000000000000"; let contract = Proto::mod_Transaction::ContractGeneric { amount: U256::encode_be_compact(0), - data: hex::decode(call_data).unwrap().into(), + data: parse_hex(call_data), }; // `tx_mode` is not set, Legacy is the default. @@ -171,7 +175,7 @@ fn test_sign_transaction_non_typed_contract_generic() { contract, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -193,8 +197,7 @@ fn test_sign_transaction_non_typed_contract_generic() { // https://ropsten.etherscan.io/tx/0x14429509307efebfdaa05227d84c147450d168c68539351fbc01ed87c916ab2e #[test] fn test_sign_transaction_eip1559_native_transfer() { - let private = - hex::decode("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904").unwrap(); + let private_key = parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"); let transfer = Proto::mod_Transaction::Transfer { amount: U256::encode_be_compact(543_210_987_654_321), @@ -212,7 +215,7 @@ fn test_sign_transaction_eip1559_native_transfer() { transaction: Some(Proto::Transaction { transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -241,8 +244,8 @@ fn test_sign_transaction_eip1559_native_transfer() { #[test] fn test_sign_transaction_eip1559_erc20_transfer() { - let private = - hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + let private_key = + parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), @@ -266,7 +269,7 @@ fn test_sign_transaction_eip1559_erc20_transfer() { erc20_transfer, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -283,10 +286,68 @@ fn test_sign_transaction_eip1559_erc20_transfer() { ); } +#[test] +fn test_sign_transaction_eip1559_erc20_transfer_with_access_list() { + // 0xa7e48D95882bd370Eb37aCB2f94a38Fb0c0ad66b + let private_key = + parse_hex("0x40dbed8d2ce4ba7d31b4edde7ecffc5177d5d32fdec3389b09c22ef67c9e7182"); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0xb2Fb4372E663B2e53dA97D98100433d1fd06ca55".into(), + amount: U256::encode_be_compact(1_000_000), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(2), + tx_mode: TransactionMode::Enveloped, + // 0x130B9 + gas_limit: U256::encode_be_compact(100_000), + // 0x77359400 + max_inclusion_fee_per_gas: U256::encode_be_compact(2_000_000_000), + // 0xB2D05E00 + max_fee_per_gas: U256::encode_be_compact(3_000_000_000), + // USDT + to_address: "0xdAC17F958D2ee523a2206206994597C13D831ec7".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_transfer( + erc20_transfer, + ), + }), + private_key, + access_list: vec![Proto::Access { + address: "0xdAC17F958D2ee523a2206206994597C13D831ec7".into(), + stored_keys: vec![ + parse_hex("0x76c8f33bcdf467e4f1313522c10a40512a867cdcd34f2b898232ad4669200764"), + parse_hex("0x000000000000000000000000000000000000000000000000000000000000000a"), + parse_hex("0x0000000000000000000000000000000000000000000000000000000000000003"), + parse_hex("0x0000000000000000000000000000000000000000000000000000000000000004"), + parse_hex("0xb12459e057d0da4389f95b7ff0ce45a52ad71b02913a5466ffaab252e7ce918a"), + parse_hex("0x1bba044274699cc8c429fbe84bdad5d5a49519e29430f25309cbbab31dc63043"), + parse_hex("0x0000000000000000000000000000000000000000000000000000000000000000"), + ], + }], + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + // Successfully broadcasted: https://etherscan.io/tx/0x66ba06527e766f2350eab17fe41a028c4257606b4678e987e47cee4bb4bfcdfc + let expected = "02f901b20102847735940084b2d05e00830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000b2fb4372e663b2e53da97d98100433d1fd06ca5500000000000000000000000000000000000000000000000000000000000f4240f90100f8fe94dac17f958d2ee523a2206206994597c13d831ec7f8e7a076c8f33bcdf467e4f1313522c10a40512a867cdcd34f2b898232ad4669200764a0000000000000000000000000000000000000000000000000000000000000000aa00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000004a0b12459e057d0da4389f95b7ff0ce45a52ad71b02913a5466ffaab252e7ce918aa01bba044274699cc8c429fbe84bdad5d5a49519e29430f25309cbbab31dc63043a0000000000000000000000000000000000000000000000000000000000000000080a069c97777fa1f507e2a9097ec6304cb2b159df4837d3cfe59f514d023d4c33aa3a0574c0cac9d2dc3ad4eebc0e6aa1710f514fb91b1dfb6baec93b67d98e0751497"; + assert_eq!(hex::encode(output.encoded, false), expected); + + assert_eq!( + hex::encode(output.pre_hash, false), + "78f4f271a376c7a6fdf15d137028509a738eae1ea4b20d9a4bd6b796a3119679" + ); +} + #[test] fn test_sign_transaction_eip1559_erc20_approve() { - let private = - hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + let private_key = + parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); let erc20_approve = Proto::mod_Transaction::ERC20Approve { spender: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), @@ -310,7 +371,7 @@ fn test_sign_transaction_eip1559_erc20_approve() { erc20_approve, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -329,8 +390,8 @@ fn test_sign_transaction_eip1559_erc20_approve() { #[test] fn test_sign_transaction_eip1559_erc721_transfer() { - let private = - hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + let private_key = + parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); let erc20_approve = Proto::mod_Transaction::ERC721Transfer { from: "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc".into(), @@ -354,7 +415,7 @@ fn test_sign_transaction_eip1559_erc721_transfer() { erc20_approve, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -373,8 +434,8 @@ fn test_sign_transaction_eip1559_erc721_transfer() { #[test] fn test_sign_transaction_eip1559_erc1155_transfer() { - let private = - hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + let private_key = + parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); let erc1155_approve = Proto::mod_Transaction::ERC1155Transfer { from: "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc".into(), @@ -400,7 +461,7 @@ fn test_sign_transaction_eip1559_erc1155_transfer() { erc1155_approve, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -419,14 +480,14 @@ fn test_sign_transaction_eip1559_erc1155_transfer() { #[test] fn test_sign_transaction_non_typed_erc20_transfer_as_contract_generic() { - let private = - hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + let private_key = + parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); // payload: transfer(0x5322b34c88ed0691971bf52a7047448f0f4efc84, 2000000000000000000) - let contract_data = hex::decode("0xa9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000").unwrap(); + let contract_data = parse_hex("0xa9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000"); let contract = Proto::mod_Transaction::ContractGeneric { amount: U256::encode_be_compact(0), - data: contract_data.into(), + data: contract_data, }; // `tx_mode` is not set, Legacy is the default. @@ -443,7 +504,7 @@ fn test_sign_transaction_non_typed_erc20_transfer_as_contract_generic() { contract, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -462,8 +523,8 @@ fn test_sign_transaction_non_typed_erc20_transfer_as_contract_generic() { #[test] fn test_sign_transaction_non_typed_erc20_transfer_invalid_address() { - let private = - hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + let private_key = + parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), @@ -484,7 +545,7 @@ fn test_sign_transaction_non_typed_erc20_transfer_invalid_address() { erc20_transfer, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -495,8 +556,8 @@ fn test_sign_transaction_non_typed_erc20_transfer_invalid_address() { #[test] fn test_sign_transaction_non_typed_erc721_transfer() { - let private = - hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + let private_key = + parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); let erc721_transfer = Proto::mod_Transaction::ERC721Transfer { from: "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc".into(), @@ -517,7 +578,7 @@ fn test_sign_transaction_non_typed_erc721_transfer() { erc721_transfer, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; @@ -539,15 +600,15 @@ fn test_sign_transaction_non_typed_erc721_transfer() { #[test] fn test_sign_transaction_non_typed_erc1155_transfer() { - let private = - hex::decode("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151").unwrap(); + let private_key = + parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); let erc1155_transfer = Proto::mod_Transaction::ERC1155Transfer { from: "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc".into(), to: "0x5322b34c88ed0691971bf52a7047448f0f4efc84".into(), - token_id: hex::decode("23c47ee5").unwrap().into(), + token_id: parse_hex("23c47ee5"), value: U256::encode_be_compact(2_000_000_000_000_000_000), - data: hex::decode("01020304").unwrap().into(), + data: parse_hex("01020304"), }; let input = Proto::SigningInput { @@ -563,7 +624,7 @@ fn test_sign_transaction_non_typed_erc1155_transfer() { erc1155_transfer, ), }), - private_key: private.into(), + private_key, ..Proto::SigningInput::default() }; diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index d3de8617ba8..cfe413cb54b 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -133,6 +133,14 @@ message UserOperation { bytes paymaster_and_data = 6; } +// An item of the [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access list. +message Access { + // Address to be accessed by the transaction. + string address = 1; + // Storage keys to be accessed by the transaction. + repeated bytes stored_keys = 2; +} + // Input data necessary to create a signed transaction. // Legacy and EIP2718/EIP1559 transactions supported, see TransactionMode. message SigningInput { @@ -172,6 +180,10 @@ message SigningInput { // UserOperation for ERC-4337 wallets UserOperation user_operation = 11; + + // Optional list of addresses and storage keys that the transaction plans to access. + // Used in `TransactionMode::Enveloped` only. + repeated Access access_list = 12; } // Result containing the signed and encoded transaction.