diff --git a/.github/workflows/ci_levm.yaml b/.github/workflows/ci_levm.yaml index 7c305b2682..493237d2b0 100644 --- a/.github/workflows/ci_levm.yaml +++ b/.github/workflows/ci_levm.yaml @@ -75,7 +75,7 @@ jobs: hive-test: if: ${{ github.event_name != 'merge_group' }} name: Hive Tests Check - needs: hive-report-creation + needs: [hive-report-creation, hive-report-creation-main] runs-on: ubuntu-latest steps: - name: Checkout sources diff --git a/.github/workflows/daily_reports.yaml b/.github/workflows/daily_reports.yaml index 7cc3874cf3..ba2cf43ace 100644 --- a/.github/workflows/daily_reports.yaml +++ b/.github/workflows/daily_reports.yaml @@ -108,19 +108,20 @@ jobs: - name: Run tests run: | cd crates/vm/levm - make generate-evm-ef-tests-report + make generate-evm-ef-tests-report | tee test_result.txt - name: Post results in summary run: | + cd crates/vm/levm echo "# Daily LEVM EF Tests Run Report" >> $GITHUB_STEP_SUMMARY - cat cmd/ef_tests/levm/levm_ef_tests_summary_github.txt >> $GITHUB_STEP_SUMMARY + cat test_result.txt >> $GITHUB_STEP_SUMMARY - name: Check EF-TESTS status is 100% id: check_tests continue-on-error: true run: | cd crates/vm/levm - if [ "$(awk '/**Summary**:/ {print $(NF)}' cmd/ef_tests/levm/levm_ef_tests_summary_github.txt)" != "(100.00%)" ]; then + if [ "$(awk '/**Summary**:/ {print $(NF)}' test_result.txt)" != "(100.00%)" ]; then echo "Percentage is not 100%." exit 1 fi diff --git a/Cargo.lock b/Cargo.lock index b41d414f07..e4a25b3c0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,52 +102,66 @@ checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "alloy-consensus" -version = "0.4.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705687d5bfd019fee57cf9e206b27b30a9a9617535d5590a02b171e813208f8e" +checksum = "e88e1edea70787c33e11197d3f32ae380f3db19e6e061e539a5bcf8184a6b326" dependencies = [ "alloy-eips", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "alloy-rlp", "alloy-serde", + "alloy-trie", "auto_impl", "c-kzg", "derive_more 1.0.0", "serde", ] +[[package]] +name = "alloy-consensus-any" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b1bb53f40c0273cd1975573cd457b39213e68584e36d1401d25fd0398a1d65" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives 0.8.15", + "alloy-rlp", +] + [[package]] name = "alloy-eip2930" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "alloy-rlp", "serde", ] [[package]] name = "alloy-eip7702" -version = "0.1.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea59dc42102bc9a1905dc57901edc6dd48b9f38115df86c7d252acba70d71d04" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "alloy-rlp", + "derive_more 1.0.0", "k256", "serde", ] [[package]] name = "alloy-eips" -version = "0.4.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffb906284a1e1f63c4607da2068c8197458a352d0b3e9796e67353d72a9be85" +checksum = "5f9fadfe089e9ccc0650473f2d4ef0a28bc015bbca5631d9f0f09e49b557fdb3" dependencies = [ "alloy-eip2930", "alloy-eip7702", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "alloy-rlp", "alloy-serde", "c-kzg", @@ -159,11 +173,11 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac4b22b3e51cac09fd2adfcc73b55f447b4df669f983c13f7894ec82b607c63f" +checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "alloy-sol-type-parser", "serde", "serde_json", @@ -171,13 +185,13 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "0.4.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801492711d4392b2ccf5fc0bc69e299fa1aab15167d74dcaa9aab96a54f684bd" +checksum = "9081c099e798b8a2bba2145eb82a9a146f01fc7a35e9ab6e7b43305051f97550" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "alloy-serde", "serde", ] @@ -206,9 +220,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" dependencies = [ "alloy-rlp", "bytes", @@ -256,17 +270,18 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.4.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413f4aa3ccf2c3e4234a047c5fa4727916d7daf25a89f9b765df0ba09784fd87" +checksum = "8737d7a6e37ca7bba9c23e9495c6534caec6760eb24abc9d5ffbaaba147818e1" dependencies = [ "alloy-consensus", + "alloy-consensus-any", "alloy-eips", "alloy-network-primitives", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "alloy-rlp", "alloy-serde", - "alloy-sol-types 0.8.14", + "alloy-sol-types 0.8.15", "derive_more 1.0.0", "itertools 0.13.0", "serde", @@ -275,25 +290,25 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.4.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017cad3e5793c5613588c1f9732bcbad77e820ba7d0feaba3527749f856fdbc5" +checksum = "db14a83665cd28ffd01939f04c2adf0e0fd9bb648b73ca651dcaa0869dae027f" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "alloy-rpc-types-eth", "alloy-serde", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.9", ] [[package]] name = "alloy-serde" -version = "0.4.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dff0ab1cdd43ca001e324dc27ee0e8606bd2161d6623c63e0e0b8c4dfc13600" +checksum = "5851bf8d5ad33014bd0c45153c603303e730acc8a209450a7ae6b4a12c2789e2" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "serde", "serde_json", ] @@ -314,12 +329,12 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bfd7853b65a2b4f49629ec975fee274faf6dff15ab8894c620943398ef283c0" +checksum = "8d039d267aa5cbb7732fa6ce1fd9b5e9e29368f580f80ba9d7a8450c794de4b2" dependencies = [ - "alloy-sol-macro-expander 0.8.14", - "alloy-sol-macro-input 0.8.14", + "alloy-sol-macro-expander 0.8.19", + "alloy-sol-macro-input 0.8.19", "proc-macro-error2", "proc-macro2", "quote", @@ -346,11 +361,11 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ec42f342d9a9261699f8078e57a7a4fda8aaa73c1a212ed3987080e6a9cd13" +checksum = "620ae5eee30ee7216a38027dec34e0585c55099f827f92f50d11e3d2d3a4a954" dependencies = [ - "alloy-sol-macro-input 0.8.14", + "alloy-sol-macro-input 0.8.19", "const-hex", "heck 0.5.0", "indexmap 2.6.0", @@ -358,7 +373,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.89", - "syn-solidity 0.8.14", + "syn-solidity 0.8.19", "tiny-keccak", ] @@ -379,9 +394,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2c50e6a62ee2b4f7ab3c6d0366e5770a21cad426e109c2f40335a1b3aff3df" +checksum = "ad9f7d057e00f8c5994e4ff4492b76532c51ead39353aa2ed63f8c50c0f4d52e" dependencies = [ "const-hex", "dunce", @@ -389,14 +404,14 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.89", - "syn-solidity 0.8.14", + "syn-solidity 0.8.19", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac17c6e89a50fb4a758012e4b409d9a0ba575228e69b539fe37d7a1bd507ca4a" +checksum = "74e60b084fe1aef8acecda2743ff2d93c18ff3eb67a2d3b12f62582a1e66ef5e" dependencies = [ "serde", "winnow 0.6.20", @@ -416,17 +431,33 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9dc0fffe397aa17628160e16b89f704098bf3c9d74d5d369ebc239575936de5" +checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.8.14", - "alloy-sol-macro 0.8.14", + "alloy-primitives 0.8.15", + "alloy-sol-macro 0.8.19", "const-hex", "serde", ] +[[package]] +name = "alloy-trie" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6917c79e837aa7b77b7a6dae9f89cbe15313ac161c4d3cfaf8909ef21f3d22d8" +dependencies = [ + "alloy-primitives 0.8.15", + "alloy-rlp", + "arrayvec", + "derive_more 1.0.0", + "nybbles", + "serde", + "smallvec", + "tracing", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -769,6 +800,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "async-trait" @@ -2289,7 +2323,7 @@ dependencies = [ "hex", "itertools 0.13.0", "keccak-hash", - "revm 14.0.3", + "revm 18.0.0", "serde", "serde_json", "spinoff", @@ -2902,7 +2936,7 @@ dependencies = [ "lambdaworks-math", "libsecp256k1", "num-bigint 0.4.6", - "revm-primitives 10.0.0", + "revm-primitives 14.0.0", "ripemd", "serde", "serde_json", @@ -3109,9 +3143,9 @@ dependencies = [ "ethrex-trie", "hex", "lazy_static", - "revm 14.0.3", + "revm 18.0.0", "revm-inspectors", - "revm-primitives 10.0.0", + "revm-primitives 14.0.0", "serde", "thiserror 2.0.9", "tracing", @@ -5272,6 +5306,17 @@ dependencies = [ "cc", ] +[[package]] +name = "nybbles" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +dependencies = [ + "const-hex", + "serde", + "smallvec", +] + [[package]] name = "objc" version = "0.2.7" @@ -6608,34 +6653,34 @@ dependencies = [ [[package]] name = "revm" -version = "14.0.3" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "641702b12847f9ed418d552f4fcabe536d867a2c980e96b6e7e25d7b992f929f" +checksum = "15689a3c6a8d14b647b4666f2e236ef47b5a5133cdfd423f545947986fff7013" dependencies = [ "auto_impl", "cfg-if", "dyn-clone", - "revm-interpreter 10.0.3", - "revm-precompile 11.0.3", + "revm-interpreter 14.0.0", + "revm-precompile 15.0.0", "serde", "serde_json", ] [[package]] name = "revm-inspectors" -version = "0.8.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c44af0bf801f48d25f7baf25cf72aff4c02d610f83b428175228162fef0246" +checksum = "8d056aaa21f36038ab35fe8ce940ee332903a0b4b992b8ca805fb60c85eb2086" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "alloy-rpc-types-eth", "alloy-rpc-types-trace", - "alloy-sol-types 0.8.14", + "alloy-sol-types 0.8.15", "anstyle", "colorchoice", - "revm 14.0.3", + "revm 18.0.0", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.9", ] [[package]] @@ -6650,11 +6695,11 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "10.0.3" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5e14002afae20b5bf1566f22316122f42f57517000e559c55b25bf7a49cba2" +checksum = "74e3f11d0fed049a4a10f79820c59113a79b38aed4ebec786a79d5c667bfeb51" dependencies = [ - "revm-primitives 10.0.0", + "revm-primitives 14.0.0", "serde", ] @@ -6678,9 +6723,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "11.0.3" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3198c06247e8d4ad0d1312591edf049b0de4ddffa9fecb625c318fd67db8639b" +checksum = "e381060af24b750069a2b2d2c54bba273d84e8f5f9e8026fc9262298e26cc336" dependencies = [ "aurora-engine-modexp", "blst", @@ -6688,7 +6733,7 @@ dependencies = [ "cfg-if", "k256", "once_cell", - "revm-primitives 10.0.0", + "revm-primitives 14.0.0", "ripemd", "secp256k1", "sha2 0.10.8", @@ -6718,13 +6763,13 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "10.0.0" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f1525851a03aff9a9d6a1d018b414d76252d6802ab54695b27093ecd7e7a101" +checksum = "3702f132bb484f4f0d0ca4f6fbde3c82cfd745041abbedd6eda67730e1868ef0" dependencies = [ "alloy-eip2930", "alloy-eip7702", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.15", "auto_impl", "bitflags 2.6.0", "bitvec", @@ -7751,6 +7796,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "snap" @@ -8379,9 +8427,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0523f59468a2696391f2a772edc089342aacd53c3caa2ac3264e598edf119b" +checksum = "b84e4d83a0a6704561302b917a932484e1cae2d8c6354c64be8b7bac1c1fe057" dependencies = [ "paste", "proc-macro2", diff --git a/cmd/ef_tests/levm/Cargo.toml b/cmd/ef_tests/levm/Cargo.toml index d3f548aa88..cc4fa55d4d 100644 --- a/cmd/ef_tests/levm/Cargo.toml +++ b/cmd/ef_tests/levm/Cargo.toml @@ -21,7 +21,7 @@ thiserror.workspace = true clap = { version = "4.3", features = ["derive"] } clap_complete = "4.5.17" itertools = "0.13.0" -revm = { version = "14.0.3", features = [ +revm = { version = "18.0.0", features = [ "serde", "std", "serde-json", diff --git a/cmd/ef_tests/levm/runner/levm_runner.rs b/cmd/ef_tests/levm/runner/levm_runner.rs index 27fa7fba22..bcae0968f4 100644 --- a/cmd/ef_tests/levm/runner/levm_runner.rs +++ b/cmd/ef_tests/levm/runner/levm_runner.rs @@ -5,13 +5,13 @@ use crate::{ utils::{self, effective_gas_price}, }; use ethrex_core::{ - types::{code_hash, AccountInfo}, + types::{code_hash, tx_fields::*, AccountInfo}, H256, U256, }; use ethrex_levm::{ db::CacheDB, errors::{ExecutionReport, TxValidationError, VMError}, - vm::{AuthorizationTuple, EVMConfig, VM}, + vm::{EVMConfig, VM}, Environment, }; use ethrex_storage::AccountUpdate; @@ -95,7 +95,7 @@ pub fn prepare_vm_for_tx(vector: &TestVector, test: &EFTest) -> Result( }) .collect(); - let authorization_list = None; - - // WARNING: Do not delete the following. - // The latest version of revm(19.3.0) is needed. + // The latest version of revm(19.3.0) is needed to run the ef-tests with the latest changes. // Update it in every Cargo.toml. // revm-inspectors and revm-primitives have to be bumped too. // NOTE: // - rust 1.82.X is needed // - rust-toolchain 1.82.X is needed (this can be found in ethrex/crates/vm/levm/rust-toolchain.toml) - /* let authorization_list = tx.authorization_list.clone().map(|list| { list.iter() .map(|auth_t| { SignedAuthorization::new_unchecked( Authorization { - chain_id: RevmU256::from_le_bytes(auth_t.chain_id.to_little_endian()), + // The latest spec defined chain_id as a U256 + //chain_id: RevmU256::from_le_bytes(auth_t.chain_id.to_little_endian()), + chain_id: auth_t.chain_id.as_u64(), address: RevmAddress(auth_t.address.0.into()), nonce: auth_t.nonce, }, @@ -163,7 +162,7 @@ pub fn prepare_revm_for_tx<'state>( .collect::>() .into() }); - */ + let tx_env = RevmTxEnv { caller: tx.sender.0.into(), gas_limit: tx.gas_limit, diff --git a/crates/blockchain/metrics/metrics_transactions.rs b/crates/blockchain/metrics/metrics_transactions.rs index 143028cd82..a69582caba 100644 --- a/crates/blockchain/metrics/metrics_transactions.rs +++ b/crates/blockchain/metrics/metrics_transactions.rs @@ -128,6 +128,7 @@ impl MetricsTxType { ethrex_core::types::TxType::EIP2930 => "EIP2930", ethrex_core::types::TxType::EIP1559 => "EIP1559", ethrex_core::types::TxType::EIP4844 => "EIP4844", + ethrex_core::types::TxType::EIP7702 => "EIP7702", ethrex_core::types::TxType::Privileged => "Privileged", } } diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index d5508333d2..00b218ff09 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -9,7 +9,7 @@ use ethrex_core::{ calculate_base_fee_per_blob_gas, calculate_base_fee_per_gas, compute_receipts_root, compute_transactions_root, compute_withdrawals_root, BlobsBundle, Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Fork, MempoolTransaction, Receipt, - Transaction, Withdrawal, DEFAULT_OMMERS_HASH, EMPTY_KECCACK_HASH, + Transaction, Withdrawal, DEFAULT_OMMERS_HASH, DEFAULT_REQUESTS_HASH, }, Address, Bloom, Bytes, H256, U256, }; @@ -137,7 +137,7 @@ pub fn create_payload(args: &BuildPayloadArgs, storage: &Store) -> Result for &str { @@ -182,7 +182,7 @@ impl From for &str { Fork::Shanghai => "Shanghai", Fork::Cancun => "Cancun", Fork::Prague => "Prague", - Fork::PragueEof => "Prague EOF", + Fork::Osaka => "Osaka", } } } @@ -334,7 +334,7 @@ impl Genesis { requests_hash: self .config .is_prague_activated(self.timestamp) - .then_some(self.requests_hash.unwrap_or(*EMPTY_KECCACK_HASH)), + .then_some(self.requests_hash.unwrap_or(*DEFAULT_REQUESTS_HASH)), } } diff --git a/crates/common/types/mod.rs b/crates/common/types/mod.rs index 736de7a16a..141e2cc3c9 100644 --- a/crates/common/types/mod.rs +++ b/crates/common/types/mod.rs @@ -6,6 +6,7 @@ mod fork_id; mod genesis; mod receipt; pub mod transaction; +pub mod tx_fields; pub use account::*; pub use blobs_bundle::*; @@ -15,3 +16,4 @@ pub use fork_id::*; pub use genesis::*; pub use receipt::*; pub use transaction::*; +pub use tx_fields::*; diff --git a/crates/common/types/transaction.rs b/crates/common/types/transaction.rs index 44a0ddbeb3..aa871f0cef 100644 --- a/crates/common/types/transaction.rs +++ b/crates/common/types/transaction.rs @@ -17,7 +17,7 @@ use ethrex_rlp::{ structs::{Decoder, Encoder}, }; -use super::BlobsBundle; +use crate::types::{AccessList, AuthorizationList, BlobsBundle}; // The `#[serde(untagged)]` attribute allows the `Transaction` enum to be serialized without // a tag indicating the variant type. This means that Serde will serialize the enum's variants @@ -34,6 +34,7 @@ pub enum Transaction { EIP2930Transaction(EIP2930Transaction), EIP1559Transaction(EIP1559Transaction), EIP4844Transaction(EIP4844Transaction), + EIP7702Transaction(EIP7702Transaction), PrivilegedL2Transaction(PrivilegedL2Transaction), } @@ -45,6 +46,7 @@ pub enum P2PTransaction { EIP2930Transaction(EIP2930Transaction), EIP1559Transaction(EIP1559Transaction), EIP4844TransactionWithBlobs(WrappedEIP4844Transaction), + EIP7702Transaction(EIP7702Transaction), PrivilegedL2Transaction(PrivilegedL2Transaction), } @@ -56,6 +58,7 @@ impl TryInto for P2PTransaction { P2PTransaction::LegacyTransaction(itx) => Ok(Transaction::LegacyTransaction(itx)), P2PTransaction::EIP2930Transaction(itx) => Ok(Transaction::EIP2930Transaction(itx)), P2PTransaction::EIP1559Transaction(itx) => Ok(Transaction::EIP1559Transaction(itx)), + P2PTransaction::EIP7702Transaction(itx) => Ok(Transaction::EIP7702Transaction(itx)), P2PTransaction::PrivilegedL2Transaction(itx) => { Ok(Transaction::PrivilegedL2Transaction(itx)) } @@ -94,7 +97,9 @@ impl RLPDecode for P2PTransaction { // EIP4844 0x3 => WrappedEIP4844Transaction::decode_unfinished(tx_encoding) .map(|(tx, rem)| (P2PTransaction::EIP4844TransactionWithBlobs(tx), rem)), - + // EIP7702 + 0x4 => EIP7702Transaction::decode_unfinished(tx_encoding) + .map(|(tx, rem)| (P2PTransaction::EIP7702Transaction(tx), rem)), // PriviligedL2 0x7e => PrivilegedL2Transaction::decode_unfinished(tx_encoding) .map(|(tx, rem)| (P2PTransaction::PrivilegedL2Transaction(tx), rem)), @@ -172,7 +177,7 @@ pub struct EIP2930Transaction { pub to: TxKind, pub value: U256, pub data: Bytes, - pub access_list: Vec<(Address, Vec)>, + pub access_list: AccessList, pub signature_y_parity: bool, pub signature_r: U256, pub signature_s: U256, @@ -188,7 +193,7 @@ pub struct EIP1559Transaction { pub to: TxKind, pub value: U256, pub data: Bytes, - pub access_list: Vec<(Address, Vec)>, + pub access_list: AccessList, pub signature_y_parity: bool, pub signature_r: U256, pub signature_s: U256, @@ -204,7 +209,7 @@ pub struct EIP4844Transaction { pub to: Address, pub value: U256, pub data: Bytes, - pub access_list: Vec<(Address, Vec)>, + pub access_list: AccessList, pub max_fee_per_blob_gas: U256, pub blob_versioned_hashes: Vec, pub signature_y_parity: bool, @@ -212,6 +217,23 @@ pub struct EIP4844Transaction { pub signature_s: U256, } +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct EIP7702Transaction { + pub chain_id: u64, + pub nonce: u64, + pub max_priority_fee_per_gas: u64, + pub max_fee_per_gas: u64, + pub gas_limit: u64, + pub to: Address, + pub value: U256, + pub data: Bytes, + pub access_list: AccessList, + pub authorization_list: AuthorizationList, + pub signature_y_parity: bool, + pub signature_r: U256, + pub signature_s: U256, +} + #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct PrivilegedL2Transaction { pub chain_id: u64, @@ -222,7 +244,7 @@ pub struct PrivilegedL2Transaction { pub to: TxKind, pub value: U256, pub data: Bytes, - pub access_list: Vec<(Address, Vec)>, + pub access_list: AccessList, pub tx_type: PrivilegedTxType, pub signature_y_parity: bool, pub signature_r: U256, @@ -243,6 +265,7 @@ pub enum TxType { EIP2930 = 0x01, EIP1559 = 0x02, EIP4844 = 0x03, + EIP7702 = 0x04, // We take the same approach as Optimism to define the privileged tx prefix // https://github.com/ethereum-optimism/specs/blob/c6903a3b2cad575653e1f5ef472debb573d83805/specs/protocol/deposits.md#the-deposited-transaction-type Privileged = 0x7e, @@ -255,6 +278,7 @@ impl From for u8 { TxType::EIP2930 => 0x01, TxType::EIP1559 => 0x02, TxType::EIP4844 => 0x03, + TxType::EIP7702 => 0x04, TxType::Privileged => 0x7e, } } @@ -280,28 +304,26 @@ impl Transaction { Transaction::EIP2930Transaction(_) => TxType::EIP2930, Transaction::EIP1559Transaction(_) => TxType::EIP1559, Transaction::EIP4844Transaction(_) => TxType::EIP4844, + Transaction::EIP7702Transaction(_) => TxType::EIP7702, Transaction::PrivilegedL2Transaction(_) => TxType::Privileged, } } + fn calc_effective_gas_price(&self, base_fee_per_gas: Option) -> Option { + let priority_fee_per_gas = min( + self.max_priority_fee()?, + self.max_fee_per_gas()? - base_fee_per_gas?, + ); + Some(priority_fee_per_gas + base_fee_per_gas?) + } + pub fn effective_gas_price(&self, base_fee_per_gas: Option) -> Option { match self.tx_type() { TxType::Legacy => Some(self.gas_price()), TxType::EIP2930 => Some(self.gas_price()), - TxType::EIP1559 => { - let priority_fee_per_gas = min( - self.max_priority_fee()?, - self.max_fee_per_gas()? - base_fee_per_gas?, - ); - Some(priority_fee_per_gas + base_fee_per_gas?) - } - TxType::EIP4844 => { - let priority_fee_per_gas = min( - self.max_priority_fee()?, - self.max_fee_per_gas()?.saturating_sub(base_fee_per_gas?), - ); - Some(priority_fee_per_gas + base_fee_per_gas?) - } + TxType::EIP1559 => self.calc_effective_gas_price(base_fee_per_gas), + TxType::EIP4844 => self.calc_effective_gas_price(base_fee_per_gas), + TxType::EIP7702 => self.calc_effective_gas_price(base_fee_per_gas), TxType::Privileged => Some(self.gas_price()), } } @@ -312,6 +334,7 @@ impl Transaction { TxType::EIP2930 => self.gas_price(), TxType::EIP1559 => self.max_fee_per_gas()?, TxType::EIP4844 => self.max_fee_per_gas()?, + TxType::EIP7702 => self.max_fee_per_gas()?, TxType::Privileged => self.gas_price(), }; @@ -360,6 +383,9 @@ impl RLPDecode for Transaction { // EIP4844 0x3 => EIP4844Transaction::decode_unfinished(tx_encoding) .map(|(tx, rem)| (Transaction::EIP4844Transaction(tx), rem)), + // EIP7702 + 0x4 => EIP7702Transaction::decode_unfinished(tx_encoding) + .map(|(tx, rem)| (Transaction::EIP7702Transaction(tx), rem)), // PriviligedL2 0x7e => PrivilegedL2Transaction::decode_unfinished(tx_encoding) .map(|(tx, rem)| (Transaction::PrivilegedL2Transaction(tx), rem)), @@ -494,6 +520,26 @@ impl RLPEncode for EIP4844Transaction { } } +impl RLPEncode for EIP7702Transaction { + fn encode(&self, buf: &mut dyn bytes::BufMut) { + Encoder::new(buf) + .encode_field(&self.chain_id) + .encode_field(&self.nonce) + .encode_field(&self.max_priority_fee_per_gas) + .encode_field(&self.max_fee_per_gas) + .encode_field(&self.gas_limit) + .encode_field(&self.to) + .encode_field(&self.value) + .encode_field(&self.data) + .encode_field(&self.access_list) + .encode_field(&self.authorization_list) + .encode_field(&self.signature_y_parity) + .encode_field(&self.signature_r) + .encode_field(&self.signature_s) + .finish() + } +} + impl RLPEncode for PrivilegedL2Transaction { fn encode(&self, buf: &mut dyn bytes::BufMut) { Encoder::new(buf) @@ -521,6 +567,7 @@ impl PayloadRLPEncode for Transaction { Transaction::EIP1559Transaction(tx) => tx.encode_payload(buf), Transaction::EIP2930Transaction(tx) => tx.encode_payload(buf), Transaction::EIP4844Transaction(tx) => tx.encode_payload(buf), + Transaction::EIP7702Transaction(tx) => tx.encode_payload(buf), Transaction::PrivilegedL2Transaction(tx) => tx.encode_payload(buf), } } @@ -588,6 +635,23 @@ impl PayloadRLPEncode for EIP4844Transaction { } } +impl PayloadRLPEncode for EIP7702Transaction { + fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { + Encoder::new(buf) + .encode_field(&self.chain_id) + .encode_field(&self.nonce) + .encode_field(&self.max_priority_fee_per_gas) + .encode_field(&self.max_fee_per_gas) + .encode_field(&self.gas_limit) + .encode_field(&self.to) + .encode_field(&self.value) + .encode_field(&self.data) + .encode_field(&self.access_list) + .encode_field(&self.authorization_list) + .finish(); + } +} + impl PayloadRLPEncode for PrivilegedL2Transaction { fn encode_payload(&self, buf: &mut dyn bytes::BufMut) { Encoder::new(buf) @@ -739,6 +803,43 @@ impl RLPDecode for EIP4844Transaction { } } +impl RLPDecode for EIP7702Transaction { + fn decode_unfinished(rlp: &[u8]) -> Result<(EIP7702Transaction, &[u8]), RLPDecodeError> { + let decoder = Decoder::new(rlp)?; + let (chain_id, decoder) = decoder.decode_field("chain_id")?; + let (nonce, decoder) = decoder.decode_field("nonce")?; + let (max_priority_fee_per_gas, decoder) = + decoder.decode_field("max_priority_fee_per_gas")?; + let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?; + let (gas_limit, decoder) = decoder.decode_field("gas_limit")?; + let (to, decoder) = decoder.decode_field("to")?; + let (value, decoder) = decoder.decode_field("value")?; + let (data, decoder) = decoder.decode_field("data")?; + let (access_list, decoder) = decoder.decode_field("access_list")?; + let (authorization_list, decoder) = decoder.decode_field("authorization_list")?; + let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?; + let (signature_r, decoder) = decoder.decode_field("signature_r")?; + let (signature_s, decoder) = decoder.decode_field("signature_s")?; + + let tx = EIP7702Transaction { + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + to, + value, + data, + access_list, + authorization_list, + signature_y_parity, + signature_r, + signature_s, + }; + Ok((tx, decoder.finish()?)) + } +} + impl RLPDecode for PrivilegedL2Transaction { fn decode_unfinished(rlp: &[u8]) -> Result<(PrivilegedL2Transaction, &[u8]), RLPDecodeError> { let decoder = Decoder::new(rlp)?; @@ -783,6 +884,7 @@ impl Signable for Transaction { Transaction::EIP2930Transaction(tx) => tx.sign_inplace(private_key), Transaction::EIP1559Transaction(tx) => tx.sign_inplace(private_key), Transaction::EIP4844Transaction(tx) => tx.sign_inplace(private_key), + Transaction::EIP7702Transaction(tx) => tx.sign_inplace(private_key), Transaction::PrivilegedL2Transaction(tx) => tx.sign_inplace(private_key), } } @@ -811,21 +913,13 @@ impl Signable for EIP1559Transaction { fn sign_inplace(&mut self, private_key: &SecretKey) { let mut payload = vec![TxType::EIP1559 as u8]; payload.append(self.encode_payload_to_vec().as_mut()); - let data = Message::from_digest_slice(&keccak(payload).0).unwrap(); - - let (recovery_id, signature) = secp256k1::SECP256K1 - .sign_ecdsa_recoverable(&data, private_key) - .serialize_compact(); - - let mut r = [0u8; 32]; - let mut s = [0u8; 32]; - r.copy_from_slice(&signature[..32]); - s.copy_from_slice(&signature[32..]); - let parity = recovery_id.to_i32() != 0; - - self.signature_r = U256::from_big_endian(&r); - self.signature_s = U256::from_big_endian(&s); - self.signature_y_parity = parity; + sing_inplace( + &payload, + private_key, + &mut self.signature_r, + &mut self.signature_s, + &mut self.signature_y_parity, + ); } } @@ -833,21 +927,13 @@ impl Signable for EIP2930Transaction { fn sign_inplace(&mut self, private_key: &SecretKey) { let mut payload = vec![TxType::EIP2930 as u8]; payload.append(self.encode_payload_to_vec().as_mut()); - let data = Message::from_digest_slice(&keccak(payload).0).unwrap(); - - let (recovery_id, signature) = secp256k1::SECP256K1 - .sign_ecdsa_recoverable(&data, private_key) - .serialize_compact(); - - let mut r = [0u8; 32]; - let mut s = [0u8; 32]; - r.copy_from_slice(&signature[..32]); - s.copy_from_slice(&signature[32..]); - let parity = recovery_id.to_i32() != 0; - - self.signature_r = U256::from_big_endian(&r); - self.signature_s = U256::from_big_endian(&s); - self.signature_y_parity = parity; + sing_inplace( + &payload, + private_key, + &mut self.signature_r, + &mut self.signature_s, + &mut self.signature_y_parity, + ); } } @@ -855,21 +941,27 @@ impl Signable for EIP4844Transaction { fn sign_inplace(&mut self, private_key: &SecretKey) { let mut payload = vec![TxType::EIP4844 as u8]; payload.append(self.encode_payload_to_vec().as_mut()); - let data = Message::from_digest_slice(&keccak(payload).0).unwrap(); - - let (recovery_id, signature) = secp256k1::SECP256K1 - .sign_ecdsa_recoverable(&data, private_key) - .serialize_compact(); - - let mut r = [0u8; 32]; - let mut s = [0u8; 32]; - r.copy_from_slice(&signature[..32]); - s.copy_from_slice(&signature[32..]); - let parity = recovery_id.to_i32() != 0; + sing_inplace( + &payload, + private_key, + &mut self.signature_r, + &mut self.signature_s, + &mut self.signature_y_parity, + ); + } +} - self.signature_r = U256::from_big_endian(&r); - self.signature_s = U256::from_big_endian(&s); - self.signature_y_parity = parity; +impl Signable for EIP7702Transaction { + fn sign_inplace(&mut self, private_key: &SecretKey) { + let mut payload = vec![TxType::EIP7702 as u8]; + payload.append(self.encode_payload_to_vec().as_mut()); + sing_inplace( + &payload, + private_key, + &mut self.signature_r, + &mut self.signature_s, + &mut self.signature_y_parity, + ); } } @@ -877,24 +969,41 @@ impl Signable for PrivilegedL2Transaction { fn sign_inplace(&mut self, private_key: &SecretKey) { let mut payload = vec![TxType::Privileged as u8]; payload.append(self.encode_payload_to_vec().as_mut()); - let data = Message::from_digest_slice(&keccak(payload).0).unwrap(); - - let (recovery_id, signature) = secp256k1::SECP256K1 - .sign_ecdsa_recoverable(&data, private_key) - .serialize_compact(); - - let mut r = [0u8; 32]; - let mut s = [0u8; 32]; - r.copy_from_slice(&signature[..32]); - s.copy_from_slice(&signature[32..]); - let parity = recovery_id.to_i32() != 0; - - self.signature_r = U256::from_big_endian(&r); - self.signature_s = U256::from_big_endian(&s); - self.signature_y_parity = parity; + sing_inplace( + &payload, + private_key, + &mut self.signature_r, + &mut self.signature_s, + &mut self.signature_y_parity, + ); } } +fn sing_inplace( + payload: &Vec, + private_key: &SecretKey, + signature_r: &mut U256, + signature_s: &mut U256, + signature_y_parity: &mut bool, +) { + let data = Message::from_digest_slice(&keccak(payload).0).unwrap(); + + let (recovery_id, signature) = secp256k1::SECP256K1 + .sign_ecdsa_recoverable(&data, private_key) + .serialize_compact(); + + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + + let parity = recovery_id.to_i32() != 0; + + *signature_r = U256::from_big_endian(&r); + *signature_s = U256::from_big_endian(&s); + *signature_y_parity = parity; +} + impl Transaction { pub fn sender(&self) -> Address { match self { @@ -988,6 +1097,27 @@ impl Transaction { &Bytes::from(buf), ) } + Transaction::EIP7702Transaction(tx) => { + let mut buf = vec![self.tx_type() as u8]; + Encoder::new(&mut buf) + .encode_field(&tx.chain_id) + .encode_field(&tx.nonce) + .encode_field(&tx.max_priority_fee_per_gas) + .encode_field(&tx.max_fee_per_gas) + .encode_field(&tx.gas_limit) + .encode_field(&tx.to) + .encode_field(&tx.value) + .encode_field(&tx.data) + .encode_field(&tx.access_list) + .encode_field(&tx.authorization_list) + .finish(); + recover_address( + &tx.signature_r, + &tx.signature_s, + tx.signature_y_parity, + &Bytes::from(buf), + ) + } Transaction::PrivilegedL2Transaction(tx) => { let mut buf = vec![self.tx_type() as u8]; Encoder::new(&mut buf) @@ -1017,6 +1147,7 @@ impl Transaction { Transaction::LegacyTransaction(tx) => tx.gas, Transaction::EIP2930Transaction(tx) => tx.gas_limit, Transaction::EIP1559Transaction(tx) => tx.gas_limit, + Transaction::EIP7702Transaction(tx) => tx.gas_limit, Transaction::EIP4844Transaction(tx) => tx.gas, Transaction::PrivilegedL2Transaction(tx) => tx.gas_limit, } @@ -1027,6 +1158,7 @@ impl Transaction { Transaction::LegacyTransaction(tx) => tx.gas_price, Transaction::EIP2930Transaction(tx) => tx.gas_price, Transaction::EIP1559Transaction(tx) => tx.max_fee_per_gas, + Transaction::EIP7702Transaction(tx) => tx.max_fee_per_gas, Transaction::EIP4844Transaction(tx) => tx.max_fee_per_gas, Transaction::PrivilegedL2Transaction(tx) => tx.max_fee_per_gas, } @@ -1038,6 +1170,7 @@ impl Transaction { Transaction::EIP2930Transaction(tx) => tx.to.clone(), Transaction::EIP1559Transaction(tx) => tx.to.clone(), Transaction::EIP4844Transaction(tx) => TxKind::Call(tx.to), + Transaction::EIP7702Transaction(tx) => TxKind::Call(tx.to), Transaction::PrivilegedL2Transaction(tx) => tx.to.clone(), } } @@ -1048,6 +1181,7 @@ impl Transaction { Transaction::EIP2930Transaction(tx) => tx.value, Transaction::EIP1559Transaction(tx) => tx.value, Transaction::EIP4844Transaction(tx) => tx.value, + Transaction::EIP7702Transaction(tx) => tx.value, Transaction::PrivilegedL2Transaction(tx) => tx.value, } } @@ -1058,6 +1192,7 @@ impl Transaction { Transaction::EIP2930Transaction(_tx) => None, Transaction::EIP1559Transaction(tx) => Some(tx.max_priority_fee_per_gas), Transaction::EIP4844Transaction(tx) => Some(tx.max_priority_fee_per_gas), + Transaction::EIP7702Transaction(tx) => Some(tx.max_priority_fee_per_gas), Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_priority_fee_per_gas), } } @@ -1068,26 +1203,40 @@ impl Transaction { Transaction::EIP2930Transaction(tx) => Some(tx.chain_id), Transaction::EIP1559Transaction(tx) => Some(tx.chain_id), Transaction::EIP4844Transaction(tx) => Some(tx.chain_id), + Transaction::EIP7702Transaction(tx) => Some(tx.chain_id), Transaction::PrivilegedL2Transaction(tx) => Some(tx.chain_id), } } - pub fn access_list(&self) -> Vec<(Address, Vec)> { + pub fn access_list(&self) -> AccessList { match self { Transaction::LegacyTransaction(_tx) => Vec::new(), Transaction::EIP2930Transaction(tx) => tx.access_list.clone(), Transaction::EIP1559Transaction(tx) => tx.access_list.clone(), Transaction::EIP4844Transaction(tx) => tx.access_list.clone(), + Transaction::EIP7702Transaction(tx) => tx.access_list.clone(), Transaction::PrivilegedL2Transaction(tx) => tx.access_list.clone(), } } + pub fn authorization_list(&self) -> Option { + match self { + Transaction::LegacyTransaction(_) => None, + Transaction::EIP2930Transaction(_) => None, + Transaction::EIP1559Transaction(_) => None, + Transaction::EIP4844Transaction(_) => None, + Transaction::EIP7702Transaction(tx) => Some(tx.authorization_list.clone()), + Transaction::PrivilegedL2Transaction(_) => None, + } + } + pub fn nonce(&self) -> u64 { match self { Transaction::LegacyTransaction(tx) => tx.nonce, Transaction::EIP2930Transaction(tx) => tx.nonce, Transaction::EIP1559Transaction(tx) => tx.nonce, Transaction::EIP4844Transaction(tx) => tx.nonce, + Transaction::EIP7702Transaction(tx) => tx.nonce, Transaction::PrivilegedL2Transaction(tx) => tx.nonce, } } @@ -1098,27 +1247,30 @@ impl Transaction { Transaction::EIP2930Transaction(tx) => &tx.data, Transaction::EIP1559Transaction(tx) => &tx.data, Transaction::EIP4844Transaction(tx) => &tx.data, + Transaction::EIP7702Transaction(tx) => &tx.data, Transaction::PrivilegedL2Transaction(tx) => &tx.data, } } pub fn blob_versioned_hashes(&self) -> Vec { match self { - Transaction::LegacyTransaction(_tx) => Vec::new(), - Transaction::EIP2930Transaction(_tx) => Vec::new(), - Transaction::EIP1559Transaction(_tx) => Vec::new(), + Transaction::LegacyTransaction(_) => Vec::new(), + Transaction::EIP2930Transaction(_) => Vec::new(), + Transaction::EIP1559Transaction(_) => Vec::new(), Transaction::EIP4844Transaction(tx) => tx.blob_versioned_hashes.clone(), - Transaction::PrivilegedL2Transaction(_tx) => Vec::new(), + Transaction::EIP7702Transaction(_) => Vec::new(), + Transaction::PrivilegedL2Transaction(_) => Vec::new(), } } pub fn max_fee_per_blob_gas(&self) -> Option { match self { - Transaction::LegacyTransaction(_tx) => None, - Transaction::EIP2930Transaction(_tx) => None, - Transaction::EIP1559Transaction(_tx) => None, + Transaction::LegacyTransaction(_) => None, + Transaction::EIP2930Transaction(_) => None, + Transaction::EIP1559Transaction(_) => None, Transaction::EIP4844Transaction(tx) => Some(tx.max_fee_per_blob_gas), - Transaction::PrivilegedL2Transaction(_tx) => None, + Transaction::EIP7702Transaction(_) => None, + Transaction::PrivilegedL2Transaction(_) => None, } } @@ -1128,6 +1280,7 @@ impl Transaction { Transaction::EIP2930Transaction(t) => matches!(t.to, TxKind::Create), Transaction::EIP1559Transaction(t) => matches!(t.to, TxKind::Create), Transaction::EIP4844Transaction(_) => false, + Transaction::EIP7702Transaction(_) => false, Transaction::PrivilegedL2Transaction(t) => matches!(t.to, TxKind::Create), } } @@ -1138,6 +1291,7 @@ impl Transaction { Transaction::EIP2930Transaction(_tx) => None, Transaction::EIP1559Transaction(tx) => Some(tx.max_fee_per_gas), Transaction::EIP4844Transaction(tx) => Some(tx.max_fee_per_gas), + Transaction::EIP7702Transaction(tx) => Some(tx.max_fee_per_gas), Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_fee_per_gas), } } @@ -1218,6 +1372,7 @@ impl TxType { 0x01 => Some(Self::EIP2930), 0x02 => Some(Self::EIP1559), 0x03 => Some(Self::EIP4844), + 0x04 => Some(Self::EIP7702), 0x7e => Some(Self::Privileged), _ => None, } @@ -1321,6 +1476,9 @@ mod canonic_encoding { // EIP4844 0x3 => EIP4844Transaction::decode(tx_bytes) .map(Transaction::EIP4844Transaction), + // EIP7702 + 0x4 => EIP7702Transaction::decode(tx_bytes) + .map(Transaction::EIP7702Transaction), 0x7e => PrivilegedL2Transaction::decode(tx_bytes) .map(Transaction::PrivilegedL2Transaction), ty => Err(RLPDecodeError::Custom(format!( @@ -1349,6 +1507,7 @@ mod canonic_encoding { Transaction::EIP2930Transaction(t) => t.encode(buf), Transaction::EIP1559Transaction(t) => t.encode(buf), Transaction::EIP4844Transaction(t) => t.encode(buf), + Transaction::EIP7702Transaction(t) => t.encode(buf), Transaction::PrivilegedL2Transaction(t) => t.encode(buf), }; } @@ -1372,6 +1531,7 @@ mod canonic_encoding { P2PTransaction::EIP2930Transaction(_) => TxType::EIP2930, P2PTransaction::EIP1559Transaction(_) => TxType::EIP1559, P2PTransaction::EIP4844TransactionWithBlobs(_) => TxType::EIP4844, + P2PTransaction::EIP7702Transaction(_) => TxType::EIP7702, P2PTransaction::PrivilegedL2Transaction(_) => TxType::Privileged, } } @@ -1387,6 +1547,7 @@ mod canonic_encoding { P2PTransaction::EIP2930Transaction(t) => t.encode(buf), P2PTransaction::EIP1559Transaction(t) => t.encode(buf), P2PTransaction::EIP4844TransactionWithBlobs(t) => t.encode(buf), + P2PTransaction::EIP7702Transaction(t) => t.encode(buf), P2PTransaction::PrivilegedL2Transaction(t) => t.encode(buf), }; } @@ -1403,10 +1564,13 @@ mod canonic_encoding { // This is used for RPC messaging and passing data into a RISC-V zkVM mod serde_impl { + use serde::de::Error; use serde::Deserialize; use serde_json::Value; use std::{collections::HashMap, str::FromStr}; + use crate::types::{AccessListItem, AuthorizationTuple}; + use super::*; impl Serialize for TxKind { @@ -1469,8 +1633,8 @@ mod serde_impl { pub storage_keys: Vec, } - impl From<&(Address, Vec)> for AccessListEntry { - fn from(value: &(Address, Vec)) -> AccessListEntry { + impl From<&AccessListItem> for AccessListEntry { + fn from(value: &AccessListItem) -> AccessListEntry { AccessListEntry { address: value.0, storage_keys: value.1.clone(), @@ -1478,6 +1642,45 @@ mod serde_impl { } } + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] + #[serde(rename_all = "camelCase")] + pub struct AuthorizationTupleEntry { + pub chain_id: U256, + pub address: Address, + pub nonce: u64, + pub v: U256, + pub y_parity: U256, + pub r: U256, + pub s: U256, + } + + impl From<&AuthorizationTuple> for AuthorizationTupleEntry { + fn from(value: &AuthorizationTuple) -> AuthorizationTupleEntry { + AuthorizationTupleEntry { + chain_id: value.chain_id, + address: value.address, + nonce: value.nonce, + v: value.y_parity, + y_parity: value.y_parity, + r: value.r_signature, + s: value.s_signature, + } + } + } + + impl From for AuthorizationTuple { + fn from(entry: AuthorizationTupleEntry) -> AuthorizationTuple { + AuthorizationTuple { + chain_id: entry.chain_id, + address: entry.address, + nonce: entry.nonce, + y_parity: entry.y_parity, + r_signature: entry.r, + s_signature: entry.s, + } + } + } + impl Serialize for LegacyTransaction { fn serialize(&self, serializer: S) -> Result where @@ -1618,6 +1821,53 @@ mod serde_impl { } } + impl Serialize for EIP7702Transaction { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut struct_serializer = serializer.serialize_struct("Eip7702Transaction", 15)?; + struct_serializer.serialize_field("type", &TxType::EIP7702)?; + struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?; + struct_serializer.serialize_field("to", &self.to)?; + struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?; + struct_serializer.serialize_field("value", &self.value)?; + struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?; + struct_serializer.serialize_field( + "maxPriorityFeePerGas", + &format!("{:#x}", self.max_priority_fee_per_gas), + )?; + struct_serializer + .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?; + struct_serializer + .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?; + struct_serializer.serialize_field( + "accessList", + &self + .access_list + .iter() + .map(AccessListEntry::from) + .collect::>(), + )?; + struct_serializer.serialize_field( + "authorizationList", + &self + .authorization_list + .iter() + .map(AuthorizationTupleEntry::from) + .collect::>(), + )?; + struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?; + struct_serializer + .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?; + struct_serializer + .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; // added to match Hive tests + struct_serializer.serialize_field("r", &self.signature_r)?; + struct_serializer.serialize_field("s", &self.signature_s)?; + struct_serializer.end() + } + } + impl Serialize for PrivilegedL2Transaction { fn serialize(&self, serializer: S) -> Result where @@ -1703,6 +1953,13 @@ mod serde_impl { serde::de::Error::custom(format!("Couldn't Deserialize EIP4844 {e}")) }) } + TxType::EIP7702 => { + EIP7702Transaction::deserialize(serde::de::value::MapDeserializer::new(iter)) + .map(Transaction::EIP7702Transaction) + .map_err(|e| { + serde::de::Error::custom(format!("Couldn't Deserialize EIP7702 {e}")) + }) + } TxType::Privileged => PrivilegedL2Transaction::deserialize( serde::de::value::MapDeserializer::new(iter), ) @@ -1736,64 +1993,38 @@ mod serde_impl { } } + fn deserialize_field<'de, T, D>( + map: &mut HashMap, + key: &str, + ) -> Result + where + D: serde::Deserializer<'de>, + T: serde::de::DeserializeOwned, + { + map.remove(key) + .ok_or_else(|| D::Error::custom(format!("Missing field: {}", key))) + .and_then(|value| { + serde_json::from_value(value).map_err(|err| D::Error::custom(err.to_string())) + }) + } + impl<'de> Deserialize<'de> for LegacyTransaction { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let mut map = >::deserialize(deserializer)?; - let nonce = serde_json::from_value::( - map.remove("nonce") - .ok_or_else(|| serde::de::Error::missing_field("nonce"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(); - let to = serde_json::from_value( - map.remove("to") - .ok_or_else(|| serde::de::Error::missing_field("to"))?, - ) - .map_err(serde::de::Error::custom)?; - let value = serde_json::from_value( - map.remove("value") - .ok_or_else(|| serde::de::Error::missing_field("value"))?, - ) - .map_err(serde::de::Error::custom)?; - let data = deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?; - let r = serde_json::from_value( - map.remove("r") - .ok_or_else(|| serde::de::Error::missing_field("r"))?, - ) - .map_err(serde::de::Error::custom)?; - let s = serde_json::from_value( - map.remove("s") - .ok_or_else(|| serde::de::Error::missing_field("s"))?, - ) - .map_err(serde::de::Error::custom)?; Ok(LegacyTransaction { - nonce, - gas_price: serde_json::from_value::( - map.remove("gasPrice") - .ok_or_else(|| serde::de::Error::missing_field("gasPrice"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - gas: serde_json::from_value::( - map.remove("gas") - .ok_or_else(|| serde::de::Error::missing_field("gas"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - to, - value, - data, - v: serde_json::from_value( - map.remove("v") - .ok_or_else(|| serde::de::Error::missing_field("v"))?, - ) - .map_err(serde::de::Error::custom)?, - r, - s, + nonce: deserialize_field::(&mut map, "nonce")?.as_u64(), + gas_price: deserialize_field::(&mut map, "gasPrice")?.as_u64(), + gas: deserialize_field::(&mut map, "gas")?.as_u64(), + to: deserialize_field::(&mut map, "to")?, + value: deserialize_field::(&mut map, "value")?, + data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?, + v: deserialize_field::(&mut map, "v")?, + r: deserialize_field::(&mut map, "r")?, + s: deserialize_field::(&mut map, "s")?, }) } } @@ -1804,75 +2035,27 @@ mod serde_impl { D: serde::Deserializer<'de>, { let mut map = >::deserialize(deserializer)?; - let nonce = serde_json::from_value::( - map.remove("nonce") - .ok_or_else(|| serde::de::Error::missing_field("nonce"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(); - let to = serde_json::from_value( - map.remove("to") - .ok_or_else(|| serde::de::Error::missing_field("to"))?, - ) - .map_err(serde::de::Error::custom)?; - let value = serde_json::from_value( - map.remove("value") - .ok_or_else(|| serde::de::Error::missing_field("value"))?, - ) - .map_err(serde::de::Error::custom)?; - let data = deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?; - let r = serde_json::from_value( - map.remove("r") - .ok_or_else(|| serde::de::Error::missing_field("r"))?, - ) - .map_err(serde::de::Error::custom)?; - let s = serde_json::from_value( - map.remove("s") - .ok_or_else(|| serde::de::Error::missing_field("s"))?, - ) - .map_err(serde::de::Error::custom)?; Ok(EIP2930Transaction { - chain_id: serde_json::from_value::( - map.remove("chainId") - .ok_or_else(|| serde::de::Error::missing_field("chainId"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - nonce, - gas_price: serde_json::from_value::( - map.remove("gasPrice") - .ok_or_else(|| serde::de::Error::missing_field("gasPrice"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - gas_limit: serde_json::from_value::( - map.remove("gas") - .ok_or_else(|| serde::de::Error::missing_field("gas"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - to, - value, - data, - access_list: serde_json::from_value( - map.remove("accessList") - .ok_or_else(|| serde::de::Error::missing_field("accessList"))?, - ) - .map_err(serde::de::Error::custom)?, + chain_id: deserialize_field::(&mut map, "chainId")?.as_u64(), + nonce: deserialize_field::(&mut map, "nonce")?.as_u64(), + gas_price: deserialize_field::(&mut map, "gasPrice")?.as_u64(), + gas_limit: deserialize_field::(&mut map, "gas")?.as_u64(), + to: deserialize_field::(&mut map, "to")?, + value: deserialize_field::(&mut map, "value")?, + data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?, + access_list: deserialize_field::, D>(&mut map, "accessList")? + .into_iter() + .map(|v| (v.address, v.storage_keys)) + .collect::>(), signature_y_parity: u8::from_str_radix( - serde_json::from_value::( - map.remove("yParity") - .ok_or_else(|| serde::de::Error::missing_field("yParity"))?, - ) - .map_err(serde::de::Error::custom)? - .trim_start_matches("0x"), + deserialize_field::(&mut map, "yParity")?.trim_start_matches("0x"), 16, ) .map_err(serde::de::Error::custom)? != 0, - signature_r: r, - signature_s: s, + signature_r: deserialize_field::(&mut map, "r")?, + signature_s: deserialize_field::(&mut map, "s")?, }) } } @@ -1883,187 +2066,116 @@ mod serde_impl { D: serde::Deserializer<'de>, { let mut map = >::deserialize(deserializer)?; - let nonce = serde_json::from_value::( - map.remove("nonce") - .ok_or_else(|| serde::de::Error::missing_field("nonce"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(); - let to = serde_json::from_value( - map.remove("to") - .ok_or_else(|| serde::de::Error::missing_field("to"))?, - ) - .map_err(serde::de::Error::custom)?; - let value = serde_json::from_value( - map.remove("value") - .ok_or_else(|| serde::de::Error::missing_field("value"))?, - ) - .map_err(serde::de::Error::custom)?; - let data = deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?; - let r = serde_json::from_value( - map.remove("r") - .ok_or_else(|| serde::de::Error::missing_field("r"))?, - ) - .map_err(serde::de::Error::custom)?; - let s = serde_json::from_value( - map.remove("s") - .ok_or_else(|| serde::de::Error::missing_field("s"))?, - ) - .map_err(serde::de::Error::custom)?; Ok(EIP1559Transaction { - chain_id: serde_json::from_value::( - map.remove("chainId") - .ok_or_else(|| serde::de::Error::missing_field("chainId"))?, - ) - .map_err(serde::de::Error::custom)? + chain_id: deserialize_field::(&mut map, "chainId")?.as_u64(), + nonce: deserialize_field::(&mut map, "nonce")?.as_u64(), + max_priority_fee_per_gas: deserialize_field::( + &mut map, + "maxPriorityFeePerGas", + )? .as_u64(), - nonce, - max_priority_fee_per_gas: serde_json::from_value::( - map.remove("maxPriorityFeePerGas") - .ok_or_else(|| serde::de::Error::missing_field("maxPriorityFeePerGas"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - max_fee_per_gas: serde_json::from_value::( - map.remove("maxFeePerGas") - .ok_or_else(|| serde::de::Error::missing_field("maxFeePerGas"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - gas_limit: serde_json::from_value::( - map.remove("gas") - .ok_or_else(|| serde::de::Error::missing_field("gas"))?, + max_fee_per_gas: deserialize_field::(&mut map, "maxFeePerGas")?.as_u64(), + gas_limit: deserialize_field::(&mut map, "gas")?.as_u64(), + to: deserialize_field::(&mut map, "to")?, + value: deserialize_field::(&mut map, "value")?, + data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?, + access_list: deserialize_field::, D>(&mut map, "accessList")? + .into_iter() + .map(|v| (v.address, v.storage_keys)) + .collect::>(), + signature_y_parity: u8::from_str_radix( + deserialize_field::(&mut map, "yParity")?.trim_start_matches("0x"), + 16, ) .map_err(serde::de::Error::custom)? + != 0, + signature_r: deserialize_field::(&mut map, "r")?, + signature_s: deserialize_field::(&mut map, "s")?, + }) + } + } + + impl<'de> Deserialize<'de> for EIP4844Transaction { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let mut map = >::deserialize(deserializer)?; + + Ok(EIP4844Transaction { + chain_id: deserialize_field::(&mut map, "chainId")?.as_u64(), + nonce: deserialize_field::(&mut map, "nonce")?.as_u64(), + max_priority_fee_per_gas: deserialize_field::( + &mut map, + "maxPriorityFeePerGas", + )? .as_u64(), - to, - value, - data, - access_list: serde_json::from_value( - map.remove("accessList") - .ok_or_else(|| serde::de::Error::missing_field("accessList"))?, - ) - .map_err(serde::de::Error::custom)?, + max_fee_per_gas: deserialize_field::(&mut map, "maxFeePerGas")?.as_u64(), + gas: deserialize_field::(&mut map, "gas")?.as_u64(), + to: deserialize_field::(&mut map, "to")?, + value: deserialize_field::(&mut map, "value")?, + data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?, + access_list: deserialize_field::, D>(&mut map, "accessList")? + .into_iter() + .map(|v| (v.address, v.storage_keys)) + .collect::>(), + max_fee_per_blob_gas: deserialize_field::(&mut map, "maxFeePerBlobGas")?, + blob_versioned_hashes: deserialize_field::, D>( + &mut map, + "blobVersionedHashes", + )?, signature_y_parity: u8::from_str_radix( - serde_json::from_value::( - map.remove("yParity") - .ok_or_else(|| serde::de::Error::missing_field("yParity"))?, - ) - .map_err(serde::de::Error::custom)? - .trim_start_matches("0x"), + deserialize_field::(&mut map, "yParity")?.trim_start_matches("0x"), 16, ) .map_err(serde::de::Error::custom)? != 0, - signature_r: r, - signature_s: s, + signature_r: deserialize_field::(&mut map, "r")?, + signature_s: deserialize_field::(&mut map, "s")?, }) } } - impl<'de> Deserialize<'de> for EIP4844Transaction { + impl<'de> Deserialize<'de> for EIP7702Transaction { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let mut map = >::deserialize(deserializer)?; - let chain_id = serde_json::from_value::( - map.remove("chainId") - .ok_or_else(|| serde::de::Error::missing_field("chainId"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(); - let nonce = serde_json::from_value::( - map.remove("nonce") - .ok_or_else(|| serde::de::Error::missing_field("nonce"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(); - let max_priority_fee_per_gas = serde_json::from_value::( - map.remove("maxPriorityFeePerGas") - .ok_or_else(|| serde::de::Error::missing_field("maxPriorityFeePerGas"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(); - let max_fee_per_gas = serde_json::from_value::( - map.remove("maxFeePerGas") - .ok_or_else(|| serde::de::Error::missing_field("maxFeePerGas"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(); - let gas = serde_json::from_value::( - map.remove("gas") - .ok_or_else(|| serde::de::Error::missing_field("gas"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(); - let to = serde_json::from_value( - map.remove("to") - .ok_or_else(|| serde::de::Error::missing_field("to"))?, - ) - .map_err(serde::de::Error::custom)?; - let value = serde_json::from_value( - map.remove("value") - .ok_or_else(|| serde::de::Error::missing_field("value"))?, - ) - .map_err(serde::de::Error::custom)?; - let data = deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?; - let access_list = serde_json::from_value::>( - map.remove("accessList") - .ok_or_else(|| serde::de::Error::missing_field("accessList"))?, - ) - .map_err(serde::de::Error::custom)? - .into_iter() - .map(|v| (v.address, v.storage_keys)) - .collect::>(); - let max_fee_per_blob_gas = serde_json::from_value::( - map.remove("maxFeePerBlobGas") - .ok_or_else(|| serde::de::Error::missing_field("maxFeePerBlobGas"))?, - ) - .map_err(serde::de::Error::custom)?; - let blob_versioned_hashes = serde_json::from_value( - map.remove("blobVersionedHashes") - .ok_or_else(|| serde::de::Error::missing_field("blobVersionedHashes"))?, - ) - .map_err(serde::de::Error::custom)?; - let signature_y_parity = u8::from_str_radix( - serde_json::from_value::( - map.remove("yParity") - .ok_or_else(|| serde::de::Error::missing_field("yParity"))?, + + Ok(EIP7702Transaction { + chain_id: deserialize_field::(&mut map, "chainId")?.as_u64(), + nonce: deserialize_field::(&mut map, "nonce")?.as_u64(), + max_priority_fee_per_gas: deserialize_field::( + &mut map, + "maxPriorityFeePerGas", + )? + .as_u64(), + max_fee_per_gas: deserialize_field::(&mut map, "maxFeePerGas")?.as_u64(), + gas_limit: deserialize_field::(&mut map, "gas")?.as_u64(), + to: deserialize_field::(&mut map, "to")?, + value: deserialize_field::(&mut map, "value")?, + data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?, + access_list: deserialize_field::, D>(&mut map, "accessList")? + .into_iter() + .map(|v| (v.address, v.storage_keys)) + .collect::>(), + authorization_list: deserialize_field::, D>( + &mut map, + "authorizationList", + )? + .into_iter() + .map(AuthorizationTuple::from) + .collect::>(), + signature_y_parity: u8::from_str_radix( + deserialize_field::(&mut map, "yParity")?.trim_start_matches("0x"), + 16, ) .map_err(serde::de::Error::custom)? - .trim_start_matches("0x"), - 16, - ) - .map_err(serde::de::Error::custom)? - != 0; - let signature_r = serde_json::from_value( - map.remove("r") - .ok_or_else(|| serde::de::Error::missing_field("r"))?, - ) - .map_err(serde::de::Error::custom)?; - let signature_s = serde_json::from_value( - map.remove("s") - .ok_or_else(|| serde::de::Error::missing_field("s"))?, - ) - .map_err(serde::de::Error::custom)?; - - Ok(EIP4844Transaction { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, - gas, - to, - value, - data, - access_list, - max_fee_per_blob_gas, - blob_versioned_hashes, - signature_y_parity, - signature_r, - signature_s, + != 0, + signature_r: deserialize_field::(&mut map, "r")?, + signature_s: deserialize_field::(&mut map, "s")?, }) } } @@ -2074,86 +2186,33 @@ mod serde_impl { D: serde::Deserializer<'de>, { let mut map = >::deserialize(deserializer)?; - let nonce = serde_json::from_value::( - map.remove("nonce") - .ok_or_else(|| serde::de::Error::missing_field("nonce"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(); - let to = serde_json::from_value( - map.remove("to") - .ok_or_else(|| serde::de::Error::missing_field("to"))?, - ) - .map_err(serde::de::Error::custom)?; - let value = serde_json::from_value( - map.remove("value") - .ok_or_else(|| serde::de::Error::missing_field("value"))?, - ) - .map_err(serde::de::Error::custom)?; - let data = deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?; - let r = serde_json::from_value( - map.remove("r") - .ok_or_else(|| serde::de::Error::missing_field("r"))?, - ) - .map_err(serde::de::Error::custom)?; - let s = serde_json::from_value( - map.remove("s") - .ok_or_else(|| serde::de::Error::missing_field("s"))?, - ) - .map_err(serde::de::Error::custom)?; Ok(PrivilegedL2Transaction { - chain_id: serde_json::from_value::( - map.remove("chainId") - .ok_or_else(|| serde::de::Error::missing_field("chainId"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - nonce, - max_priority_fee_per_gas: serde_json::from_value::( - map.remove("maxPriorityFeePerGas") - .ok_or_else(|| serde::de::Error::missing_field("maxPriorityFeePerGas"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - max_fee_per_gas: serde_json::from_value::( - map.remove("maxFeePerGas") - .ok_or_else(|| serde::de::Error::missing_field("maxFeePerGas"))?, - ) - .map_err(serde::de::Error::custom)? + chain_id: deserialize_field::(&mut map, "chainId")?.as_u64(), + nonce: deserialize_field::(&mut map, "nonce")?.as_u64(), + max_priority_fee_per_gas: deserialize_field::( + &mut map, + "maxPriorityFeePerGas", + )? .as_u64(), - gas_limit: serde_json::from_value::( - map.remove("gas") - .ok_or_else(|| serde::de::Error::missing_field("gas"))?, - ) - .map_err(serde::de::Error::custom)? - .as_u64(), - to, - value, - data, - access_list: serde_json::from_value( - map.remove("accessList") - .ok_or_else(|| serde::de::Error::missing_field("accessList"))?, - ) - .map_err(serde::de::Error::custom)?, + max_fee_per_gas: deserialize_field::(&mut map, "maxFeePerGas")?.as_u64(), + gas_limit: deserialize_field::(&mut map, "gas")?.as_u64(), + to: deserialize_field::(&mut map, "to")?, + value: deserialize_field::(&mut map, "value")?, + data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?, + access_list: deserialize_field::, D>(&mut map, "accessList")? + .into_iter() + .map(|v| (v.address, v.storage_keys)) + .collect::>(), signature_y_parity: u8::from_str_radix( - serde_json::from_value::( - map.remove("yParity") - .ok_or_else(|| serde::de::Error::missing_field("yParity"))?, - ) - .map_err(serde::de::Error::custom)? - .trim_start_matches("0x"), + deserialize_field::(&mut map, "yParity")?.trim_start_matches("0x"), 16, ) .map_err(serde::de::Error::custom)? != 0, - signature_r: r, - signature_s: s, - tx_type: serde_json::from_value( - map.remove("tx_type") - .ok_or_else(|| serde::de::Error::missing_field("tx_type"))?, - ) - .map_err(serde::de::Error::custom)?, + signature_r: deserialize_field::(&mut map, "r")?, + signature_s: deserialize_field::(&mut map, "s")?, + tx_type: deserialize_field::(&mut map, "tx_type")?, }) } } @@ -2186,6 +2245,8 @@ mod serde_impl { #[serde(default)] pub access_list: Vec, #[serde(default)] + pub authorization_list: Option>, + #[serde(default)] pub blob_versioned_hashes: Vec, #[serde(default, with = "crate::serde_utils::bytes::vec")] pub blobs: Vec, @@ -2211,6 +2272,7 @@ mod serde_impl { .iter() .map(AccessListEntry::from) .collect(), + authorization_list: None, blob_versioned_hashes: vec![], blobs: vec![], chain_id: Some(value.chain_id), @@ -2237,6 +2299,7 @@ mod serde_impl { .iter() .map(AccessListEntry::from) .collect(), + authorization_list: None, blob_versioned_hashes: value.blob_versioned_hashes, blobs: vec![], chain_id: Some(value.chain_id), @@ -2245,6 +2308,39 @@ mod serde_impl { } } + impl From for GenericTransaction { + fn from(value: EIP7702Transaction) -> Self { + Self { + r#type: TxType::EIP7702, + nonce: Some(value.nonce), + to: TxKind::Call(value.to), + gas: Some(value.gas_limit), + value: value.value, + input: value.data, + gas_price: value.max_fee_per_gas, + max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas), + max_fee_per_gas: Some(value.max_fee_per_gas), + max_fee_per_blob_gas: None, + access_list: value + .access_list + .iter() + .map(AccessListEntry::from) + .collect(), + authorization_list: Some( + value + .authorization_list + .iter() + .map(AuthorizationTupleEntry::from) + .collect(), + ), + blob_versioned_hashes: vec![], + blobs: vec![], + chain_id: Some(value.chain_id), + from: Address::default(), + } + } + } + impl From for GenericTransaction { fn from(value: PrivilegedL2Transaction) -> Self { Self { @@ -2263,6 +2359,7 @@ mod serde_impl { .iter() .map(AccessListEntry::from) .collect(), + authorization_list: None, blob_versioned_hashes: vec![], blobs: vec![], chain_id: Some(value.chain_id), @@ -2368,7 +2465,9 @@ mod mempool { mod tests { use super::*; - use crate::types::{compute_receipts_root, compute_transactions_root, BlockBody, Receipt}; + use crate::types::{ + compute_receipts_root, compute_transactions_root, AuthorizationTuple, BlockBody, Receipt, + }; use ethereum_types::H160; use hex_literal::hex; use serde_impl::{AccessListEntry, GenericTransaction}; @@ -2590,6 +2689,7 @@ mod tests { blob_versioned_hashes: Default::default(), blobs: Default::default(), chain_id: Default::default(), + authorization_list: None, }; assert_eq!( deserialized_generic_transaction, @@ -2668,7 +2768,10 @@ mod tests { to: TxKind::Call(H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B0").unwrap()), value: U256::from(100000), data: Bytes::from_static(b"03"), - access_list: vec![], + access_list: vec![( + H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B3").unwrap(), + vec![H256::zero()], + )], signature_y_parity: true, signature_r: U256::one(), signature_s: U256::zero(), @@ -2676,8 +2779,6 @@ mod tests { let tx_to_serialize = Transaction::EIP1559Transaction(eip1559.clone()); let serialized = serde_json::to_string(&tx_to_serialize).expect("Failed to serialize"); - println!("{serialized:?}"); - let deserialized_tx: Transaction = serde_json::from_str(&serialized).expect("Failed to deserialize"); @@ -2687,4 +2788,41 @@ mod tests { assert_eq!(tx, eip1559); } } + + #[test] + fn serialize_deserialize_eip7702transaction() { + let eip7702 = EIP7702Transaction { + chain_id: 1729, + nonce: 1, + max_priority_fee_per_gas: 1000, + max_fee_per_gas: 2000, + gas_limit: 21000, + to: Address::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B0").unwrap(), + value: U256::from(100000), + data: Bytes::from_static(b"03"), + access_list: vec![], + signature_y_parity: true, + signature_r: U256::one(), + signature_s: U256::zero(), + authorization_list: vec![AuthorizationTuple { + chain_id: U256::from(1729), + address: H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B1").unwrap(), + nonce: 2, + y_parity: U256::one(), + r_signature: U256::from(22), + s_signature: U256::from(37), + }], + }; + let tx_to_serialize = Transaction::EIP7702Transaction(eip7702.clone()); + let serialized = serde_json::to_string(&tx_to_serialize).expect("Failed to serialize"); + + let deserialized_tx: Transaction = + serde_json::from_str(&serialized).expect("Failed to deserialize"); + + assert!(deserialized_tx.tx_type() == TxType::EIP7702); + + if let Transaction::EIP7702Transaction(tx) = deserialized_tx { + assert_eq!(tx, eip7702); + } + } } diff --git a/crates/common/types/tx_fields.rs b/crates/common/types/tx_fields.rs new file mode 100644 index 0000000000..42a41b0360 --- /dev/null +++ b/crates/common/types/tx_fields.rs @@ -0,0 +1,59 @@ +use crate::{Address, H256, U256}; +use ethrex_rlp::{ + decode::RLPDecode, + encode::RLPEncode, + error::RLPDecodeError, + structs::{Decoder, Encoder}, +}; +use serde::{Deserialize, Serialize}; + +pub type AccessList = Vec; +pub type AccessListItem = (Address, Vec); + +pub type AuthorizationList = Vec; +#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct AuthorizationTuple { + pub chain_id: U256, + pub address: Address, + pub nonce: u64, + pub y_parity: U256, + pub r_signature: U256, + pub s_signature: U256, +} + +impl RLPEncode for AuthorizationTuple { + fn encode(&self, buf: &mut dyn bytes::BufMut) { + Encoder::new(buf) + .encode_field(&self.chain_id) + .encode_field(&self.address) + .encode_field(&self.nonce) + .encode_field(&self.y_parity) + .encode_field(&self.r_signature) + .encode_field(&self.s_signature) + .finish(); + } +} + +impl RLPDecode for AuthorizationTuple { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> { + let decoder = Decoder::new(rlp)?; + let (chain_id, decoder) = decoder.decode_field("chain_id")?; + let (address, decoder) = decoder.decode_field("address")?; + let (nonce, decoder) = decoder.decode_field("nonce")?; + let (y_parity, decoder) = decoder.decode_field("y_parity")?; + let (r_signature, decoder) = decoder.decode_field("r_signature")?; + let (s_signature, decoder) = decoder.decode_field("s_signature")?; + let rest = decoder.finish()?; + Ok(( + AuthorizationTuple { + chain_id, + address, + nonce, + y_parity, + r_signature, + s_signature, + }, + rest, + )) + } +} diff --git a/crates/networking/p2p/rlpx/eth/transactions.rs b/crates/networking/p2p/rlpx/eth/transactions.rs index b3b1002bdd..56ff26c409 100644 --- a/crates/networking/p2p/rlpx/eth/transactions.rs +++ b/crates/networking/p2p/rlpx/eth/transactions.rs @@ -201,6 +201,7 @@ impl GetPooledTransactions { blobs_bundle: bundle, }) } + Transaction::EIP7702Transaction(itx) => P2PTransaction::EIP7702Transaction(itx), Transaction::PrivilegedL2Transaction(itx) => { P2PTransaction::PrivilegedL2Transaction(itx) } diff --git a/crates/networking/rpc/eth/fee_market.rs b/crates/networking/rpc/eth/fee_market.rs index 1c621d91a6..48bc1cd9f8 100644 --- a/crates/networking/rpc/eth/fee_market.rs +++ b/crates/networking/rpc/eth/fee_market.rs @@ -217,6 +217,9 @@ impl FeeHistoryRequest { Transaction::EIP4844Transaction(t) => t .max_priority_fee_per_gas .min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)), + Transaction::EIP7702Transaction(t) => t + .max_priority_fee_per_gas + .min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)), Transaction::PrivilegedL2Transaction(t) => t .max_priority_fee_per_gas .min(t.max_fee_per_gas.saturating_sub(base_fee_per_gas)), diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index b183689baf..1ed8913cda 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -9,7 +9,7 @@ ethrex-storage = { path = "../storage/store", default-features = false } ethrex-levm = { path = "./levm", optional = true } ethrex-trie = { path = "../storage/trie", default-features = false } ethrex-rlp = { path = "../common/rlp", default-features = false } -revm = { version = "14.0.3", features = [ +revm = { version = "18.0.0", features = [ "serde", "std", "serde-json", @@ -18,8 +18,8 @@ revm = { version = "14.0.3", features = [ ], default-features = false } # These dependencies must be kept up to date with the corresponding revm version, otherwise errors may pop up because of trait implementation mismatches -revm-inspectors = { version = "0.8.1" } -revm-primitives = { version = "10.0.0", features = [ +revm-inspectors = { version = "=0.13.0" } +revm-primitives = { version = "14.0.0", features = [ "std", ], default-features = false } bytes.workspace = true diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index 5b9c1b4eb3..c732556cdb 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true [dependencies] ethrex-core.workspace = true ethrex-rlp.workspace = true -revm-primitives = { version = "10.0.0", features = [ +revm-primitives = { version = "14.0.0", features = [ "std", ], default-features = false } diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index f91b5616fb..7137a816ef 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -12,11 +12,14 @@ use crate::{ BLOB_GAS_PER_BLOB, COLD_ADDRESS_ACCESS_COST, CREATE_BASE_COST, WARM_ADDRESS_ACCESS_COST, }, opcodes::Opcode, - vm::{AccessList, AuthorizationList, AuthorizationTuple, EVMConfig, Substate}, + vm::{EVMConfig, Substate}, AccountInfo, }; use bytes::Bytes; -use ethrex_core::{types::Fork, Address, H256, U256}; +use ethrex_core::{ + types::{tx_fields::*, Fork}, + Address, H256, U256, +}; use ethrex_rlp; use ethrex_rlp::encode::RLPEncode; use keccak_hash::keccak; @@ -456,7 +459,7 @@ pub fn eip7702_recover_address( if auth_tuple.r_signature > *SECP256K1_ORDER || U256::zero() >= auth_tuple.r_signature { return Ok(None); } - if auth_tuple.v != U256::one() && auth_tuple.v != U256::zero() { + if auth_tuple.y_parity != U256::one() && auth_tuple.y_parity != U256::zero() { return Ok(None); } @@ -483,7 +486,7 @@ pub fn eip7702_recover_address( let Ok(recovery_id) = RecoveryId::parse( auth_tuple - .v + .y_parity .as_u32() .try_into() .map_err(|_| VMError::Internal(InternalError::ConversionError))?, diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 11f8fa0f62..46a3dc0bd4 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -18,7 +18,10 @@ use crate::{ }; use bytes::Bytes; use ethrex_core::{ - types::{Fork, ForkBlobSchedule, TxKind}, + types::{ + tx_fields::{AccessList, AuthorizationList}, + Fork, ForkBlobSchedule, TxKind, + }, Address, H256, U256, }; use std::{ @@ -112,7 +115,7 @@ impl EVMConfig { const fn max_blobs_per_block(fork: Fork) -> u64 { match fork { Fork::Prague => MAX_BLOB_COUNT_ELECTRA, - Fork::PragueEof => MAX_BLOB_COUNT_ELECTRA, + Fork::Osaka => MAX_BLOB_COUNT_ELECTRA, _ => MAX_BLOB_COUNT, } } @@ -125,7 +128,7 @@ impl EVMConfig { /// blocks)." const fn get_blob_base_fee_update_fraction_value(fork: Fork) -> u64 { match fork { - Fork::Prague | Fork::PragueEof => BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, + Fork::Prague | Fork::Osaka => BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, _ => BLOB_BASE_FEE_UPDATE_FRACTION, } } @@ -133,7 +136,7 @@ impl EVMConfig { /// According to [EIP-7691](https://eips.ethereum.org/EIPS/eip-7691#specification): const fn get_target_blob_gas_per_block_(fork: Fork) -> u64 { match fork { - Fork::Prague | Fork::PragueEof => TARGET_BLOB_GAS_PER_BLOCK_PECTRA, + Fork::Prague | Fork::Osaka => TARGET_BLOB_GAS_PER_BLOCK_PECTRA, _ => TARGET_BLOB_GAS_PER_BLOCK, } } @@ -165,20 +168,6 @@ pub struct VM { pub authorization_list: Option, } -pub type AccessList = Vec<(Address, Vec)>; - -pub type AuthorizationList = Vec; -// TODO: We have to implement this in ethrex_core -#[derive(Debug, Clone, Default, Copy)] -pub struct AuthorizationTuple { - pub chain_id: U256, - pub address: Address, - pub nonce: u64, - pub v: U256, - pub r_signature: U256, - pub s_signature: U256, -} - impl VM { // TODO: Refactor this. #[allow(clippy::too_many_arguments)] diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index 440566406f..94fa62ab3d 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -29,8 +29,8 @@ use revm::{ use revm_inspectors::access_list::AccessListInspector; // Rename imported types for clarity use revm_primitives::{ - ruint::Uint, AccessList as RevmAccessList, AccessListItem, Bytes, FixedBytes, - TxKind as RevmTxKind, + ruint::Uint, AccessList as RevmAccessList, AccessListItem, Authorization as RevmAuthorization, + Bytes, FixedBytes, SignedAuthorization, TxKind as RevmTxKind, }; // Export needed types pub use errors::EvmError; @@ -352,9 +352,7 @@ cfg_if::cfg_if! { db, block_cache, tx.access_list(), - // TODO: Here we should pass the tx.authorization_list - // We have to implement the EIP7702 tx in ethrex_core - None + tx.authorization_list(), )?; vm.execute() @@ -918,9 +916,29 @@ pub fn tx_env(tx: &Transaction) -> TxEnv { .map(|hash| B256::from(hash.0)) .collect(), max_fee_per_blob_gas, - // TODO revise + // EIP7702 // https://eips.ethereum.org/EIPS/eip-7702 - authorization_list: None, + // The latest version of revm(19.3.0) is needed to run with the latest changes. + // NOTE: + // - rust 1.82.X is needed + // - rust-toolchain 1.82.X is needed (this can be found in ethrex/crates/vm/levm/rust-toolchain.toml) + authorization_list: tx.authorization_list().map(|list| { + list.into_iter() + .map(|auth_t| { + SignedAuthorization::new_unchecked( + RevmAuthorization { + chain_id: auth_t.chain_id.as_u64(), + address: RevmAddress(auth_t.address.0.into()), + nonce: auth_t.nonce, + }, + auth_t.y_parity.as_u32() as u8, + RevmU256::from_le_bytes(auth_t.r_signature.to_little_endian()), + RevmU256::from_le_bytes(auth_t.s_signature.to_little_endian()), + ) + }) + .collect::>() + .into() + }), } } @@ -963,9 +981,30 @@ fn tx_env_from_generic(tx: &GenericTransaction, basefee: u64) -> TxEnv { .map(|hash| B256::from(hash.0)) .collect(), max_fee_per_blob_gas: tx.max_fee_per_blob_gas.map(|x| RevmU256::from_limbs(x.0)), - // TODO revise + // EIP7702 // https://eips.ethereum.org/EIPS/eip-7702 - authorization_list: None, + // The latest version of revm(19.3.0) is needed to run with the latest changes. + // NOTE: + // - rust 1.82.X is needed + // - rust-toolchain 1.82.X is needed (this can be found in ethrex/crates/vm/levm/rust-toolchain.toml) + authorization_list: tx.authorization_list.clone().map(|list| { + list.into_iter() + .map(|auth_t| { + SignedAuthorization::new_unchecked( + RevmAuthorization { + //chain_id: RevmU256::from_le_bytes(auth_t.chain_id.to_little_endian()), + chain_id: auth_t.chain_id.as_u64(), + address: RevmAddress(auth_t.address.0.into()), + nonce: auth_t.nonce, + }, + auth_t.y_parity.as_u32() as u8, + RevmU256::from_le_bytes(auth_t.r.to_little_endian()), + RevmU256::from_le_bytes(auth_t.s.to_little_endian()), + ) + }) + .collect::>() + .into() + }), } } @@ -1029,7 +1068,7 @@ pub fn fork_to_spec_id(fork: Fork) -> SpecId { Fork::Shanghai => SpecId::SHANGHAI, Fork::Cancun => SpecId::CANCUN, Fork::Prague => SpecId::PRAGUE, - Fork::PragueEof => SpecId::PRAGUE_EOF, + Fork::Osaka => SpecId::OSAKA, } }