From ace5ef785fef13766143dbad23a6091e7917c536 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Mon, 13 May 2024 15:17:53 +0300 Subject: [PATCH 1/6] feat: eip-6110 --- Cargo.lock | 1 + crates/ethereum/evm/Cargo.toml | 2 ++ crates/ethereum/evm/src/execute.rs | 30 ++++++++++++++++++++++++++---- crates/interfaces/src/executor.rs | 11 ++++++++++- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 487aaf8ed29..9e6f0f2f81a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6993,6 +6993,7 @@ version = "0.2.0-beta.7" dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=dd7a999)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=dd7a999)", + "alloy-rlp", "reth-evm", "reth-interfaces", "reth-primitives", diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index fc754b5027a..d8294dc0eb1 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -25,6 +25,8 @@ revm-precompile = { version = "7.0.0", features = ["std"], default-features = fa # Alloy alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-rlp.workspace = true # misc tracing.workspace = true diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index f9f53313a52..3a4b07de570 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -106,6 +106,24 @@ where } } } +// EIP-6110 +use alloy_eips::eip6110::{DepositRequest, MAINNET_DEPOSIT_CONTRACT_ADDRESS}; +use alloy_rlp::Decodable; + +fn parse_deposits_from_receipts( + receipts: &[Receipt], +) -> Result, BlockValidationError> { + let res = receipts + .iter() + .flat_map(|receipt| receipt.logs.iter()) + .filter(|log| log.address == MAINNET_DEPOSIT_CONTRACT_ADDRESS) + .map(|log| DepositRequest::decode(&mut log.data.data.as_ref())) + .map(|res| res.map(Request::DepositRequest)) + .collect::, _>>() + // ugly + .map_err(|err| BlockValidationError::DepositRequestDecode(err.to_string()))?; + Ok(res) +} /// Helper type for the output of executing a block. #[derive(Debug, Clone)] @@ -168,7 +186,7 @@ where transaction_gas_limit: transaction.gas_limit(), block_available_gas, } - .into()) + .into()); } EvmConfig::fill_tx_env(evm.tx_mut(), transaction, *sender); @@ -209,13 +227,17 @@ where gas: GotExpected { got: cumulative_gas_used, expected: block.gas_used }, gas_spent_by_tx: receipts.gas_spent_by_tx()?, } - .into()) + .into()); } + // Collect all EIP-6110 deposits + let deposits = parse_deposits_from_receipts(&receipts)?; + // Collect all EIP-7685 requests let withdrawal_requests = post_block_withdrawal_requests(&self.chain_spec, block.timestamp, &mut evm)?; - let requests = withdrawal_requests; + // TODO: Does order matter? + let requests = [deposits, withdrawal_requests].concat(); Ok(EthExecuteOutput { receipts, requests, gas_used: cumulative_gas_used }) } @@ -311,7 +333,7 @@ where receipts.iter(), ) { debug!(target: "evm", %error, ?receipts, "receipts verification failed"); - return Err(error) + return Err(error); }; } diff --git a/crates/interfaces/src/executor.rs b/crates/interfaces/src/executor.rs index 84ab6760509..f5783b4b6a8 100644 --- a/crates/interfaces/src/executor.rs +++ b/crates/interfaces/src/executor.rs @@ -88,12 +88,21 @@ pub enum BlockValidationError { /// The error message. message: String, }, - /// EVM error during withdrawal requests contract call + /// EVM error during withdrawal requests contract call [EIP-7002] + /// + /// [EIP-7002]: https://eips.ethereum.org/EIPS/eip-7002 #[error("failed to apply withdrawal requests contract call: {message}")] WithdrawalRequestsContractCall { /// The error message. message: String, }, + /// Error when decoding deposit requests from receipts [EIP-6110] + /// + /// [EIP-6110]: https://eips.ethereum.org/EIPS/eip-6110 + // TODO(gakonst): This is an RLP decoding error but we don't import alloy_rlp here, + // do we want to? + #[error("could not decode deposit request: {0}")] + DepositRequestDecode(String), } /// BlockExecutor Errors From 46aa6a20d2219b040c4682fc7cfa020cf98e6a6e Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Mon, 13 May 2024 16:09:25 +0300 Subject: [PATCH 2/6] fix: decode Deposits correctly instead of RLP and move to module --- Cargo.lock | 1 + crates/ethereum/evm/Cargo.toml | 1 + crates/ethereum/evm/src/eip6110.rs | 75 +++++++++++++++++++ crates/ethereum/evm/src/execute.rs | 20 +---- crates/ethereum/evm/src/lib.rs | 3 + .../bundle_state_with_receipts.rs | 6 +- 6 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 crates/ethereum/evm/src/eip6110.rs diff --git a/Cargo.lock b/Cargo.lock index 9e6f0f2f81a..2f15cbfb36c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6994,6 +6994,7 @@ dependencies = [ "alloy-consensus 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=dd7a999)", "alloy-eips 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=dd7a999)", "alloy-rlp", + "alloy-sol-types", "reth-evm", "reth-interfaces", "reth-primitives", diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index d8294dc0eb1..c2eff78aa6b 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -27,6 +27,7 @@ revm-precompile = { version = "7.0.0", features = ["std"], default-features = fa alloy-consensus.workspace = true alloy-eips.workspace = true alloy-rlp.workspace = true +alloy-sol-types.workspace = true # misc tracing.workspace = true diff --git a/crates/ethereum/evm/src/eip6110.rs b/crates/ethereum/evm/src/eip6110.rs new file mode 100644 index 00000000000..db7cb6f9ae9 --- /dev/null +++ b/crates/ethereum/evm/src/eip6110.rs @@ -0,0 +1,75 @@ +// EIP-6110 parsing +use alloy_consensus::Request; +use alloy_eips::eip6110::{DepositRequest, MAINNET_DEPOSIT_CONTRACT_ADDRESS}; +use alloy_sol_types::{sol, SolEvent}; +use reth_interfaces::executor::BlockValidationError; +use reth_primitives::Receipt; +use revm_primitives::Log; + +pub(crate) fn parse_deposits_from_receipts( + receipts: &[Receipt], +) -> Result, BlockValidationError> { + let res = receipts + .iter() + .flat_map(|receipt| receipt.logs.iter()) + // No need to filter for topic because there's only one event and that's the Deposit event + // in the deposit contract. + .filter(|log| log.address == MAINNET_DEPOSIT_CONTRACT_ADDRESS) + .map(|log| DepositEvent::decode_log(log, false)) + .map(|res| { + let deposit = parse_deposit_from_log(&res?); + Ok(Request::DepositRequest(deposit)) + }) + .collect::, _>>() + // ugly + .map_err(|err: alloy_sol_types::Error| { + BlockValidationError::DepositRequestDecode(err.to_string()) + })?; + Ok(res) +} + +sol! { + #[allow(missing_docs)] + event DepositEvent( + bytes pubkey, + bytes withdrawal_credentials, + bytes amount, + bytes signature, + bytes index + ); +} + +fn parse_deposit_from_log(log: &Log) -> DepositRequest { + // SAFETY: These `expect` https://github.com/ethereum/consensus-specs/blob/5f48840f4d768bf0e0a8156a3ed06ec333589007/solidity_deposit_contract/deposit_contract.sol#L107-L110 + // are safe because the `DepositEvent` is the only event in the deposit contract and the length + // checks are done there. + DepositRequest { + pubkey: log + .pubkey + .as_ref() + .try_into() + .expect("pubkey length should be enforced in deposit contract"), + withdrawal_credentials: log + .withdrawal_credentials + .as_ref() + .try_into() + .expect("pubkey length should be enforced in deposit contract"), + amount: u64::from_le_bytes( + log.amount + .as_ref() + .try_into() + .expect("pubkey length should be enforced in deposit contract"), + ), + signature: log + .signature + .as_ref() + .try_into() + .expect("pubkey length should be enforced in deposit contract"), + index: u64::from_le_bytes( + log.index + .as_ref() + .try_into() + .expect("pubkey length should be enforced in deposit contract"), + ), + } +} diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 3a4b07de570..52fa998f59f 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -106,24 +106,6 @@ where } } } -// EIP-6110 -use alloy_eips::eip6110::{DepositRequest, MAINNET_DEPOSIT_CONTRACT_ADDRESS}; -use alloy_rlp::Decodable; - -fn parse_deposits_from_receipts( - receipts: &[Receipt], -) -> Result, BlockValidationError> { - let res = receipts - .iter() - .flat_map(|receipt| receipt.logs.iter()) - .filter(|log| log.address == MAINNET_DEPOSIT_CONTRACT_ADDRESS) - .map(|log| DepositRequest::decode(&mut log.data.data.as_ref())) - .map(|res| res.map(Request::DepositRequest)) - .collect::, _>>() - // ugly - .map_err(|err| BlockValidationError::DepositRequestDecode(err.to_string()))?; - Ok(res) -} /// Helper type for the output of executing a block. #[derive(Debug, Clone)] @@ -231,7 +213,7 @@ where } // Collect all EIP-6110 deposits - let deposits = parse_deposits_from_receipts(&receipts)?; + let deposits = crate::eip6110::parse_deposits_from_receipts(&receipts)?; // Collect all EIP-7685 requests let withdrawal_requests = diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 4af5da7babe..d209b3ce761 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -22,6 +22,9 @@ pub mod verify; /// Ethereum DAO hardfork state change data. pub mod dao_fork; +/// [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110) handling. +pub mod eip6110; + mod instructions; /// Ethereum-related EVM configuration. diff --git a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs index b7ce4d176c7..9607110e477 100644 --- a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs +++ b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs @@ -158,11 +158,11 @@ impl BundleStateWithReceipts { /// Transform block number to the index of block. fn block_number_to_index(&self, block_number: BlockNumber) -> Option { if self.first_block > block_number { - return None + return None; } let index = block_number - self.first_block; if index >= self.receipts.len() as u64 { - return None + return None; } Some(index as usize) } @@ -269,7 +269,7 @@ impl BundleStateWithReceipts { /// If the target block number is not included in the state block range. pub fn split_at(self, at: BlockNumber) -> (Option, Self) { if at == self.first_block { - return (None, self) + return (None, self); } let (mut lower_state, mut higher_state) = (self.clone(), self); From d9e16ef3724e82c6a7685c1570de02f2a60f5a15 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 13 May 2024 16:40:46 +0300 Subject: [PATCH 3/6] chore: docs --- crates/ethereum/evm/src/eip6110.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ethereum/evm/src/eip6110.rs b/crates/ethereum/evm/src/eip6110.rs index db7cb6f9ae9..d1d16bb3cae 100644 --- a/crates/ethereum/evm/src/eip6110.rs +++ b/crates/ethereum/evm/src/eip6110.rs @@ -1,4 +1,4 @@ -// EIP-6110 parsing +//! EIP-6110 deposit requests parsing use alloy_consensus::Request; use alloy_eips::eip6110::{DepositRequest, MAINNET_DEPOSIT_CONTRACT_ADDRESS}; use alloy_sol_types::{sol, SolEvent}; @@ -21,7 +21,7 @@ pub(crate) fn parse_deposits_from_receipts( Ok(Request::DepositRequest(deposit)) }) .collect::, _>>() - // ugly + // todo: this is ugly, we should clean it up .map_err(|err: alloy_sol_types::Error| { BlockValidationError::DepositRequestDecode(err.to_string()) })?; From 938ae945fb65673d07e1acd938b5b866823d2ff8 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Mon, 13 May 2024 16:42:14 +0300 Subject: [PATCH 4/6] fix: use correct panic string --- crates/ethereum/evm/src/eip6110.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/ethereum/evm/src/eip6110.rs b/crates/ethereum/evm/src/eip6110.rs index d1d16bb3cae..6a42b685a8a 100644 --- a/crates/ethereum/evm/src/eip6110.rs +++ b/crates/ethereum/evm/src/eip6110.rs @@ -15,9 +15,9 @@ pub(crate) fn parse_deposits_from_receipts( // No need to filter for topic because there's only one event and that's the Deposit event // in the deposit contract. .filter(|log| log.address == MAINNET_DEPOSIT_CONTRACT_ADDRESS) - .map(|log| DepositEvent::decode_log(log, false)) - .map(|res| { - let deposit = parse_deposit_from_log(&res?); + .map(|log| { + let decoded_log = DepositEvent::decode_log(log, false)?; + let deposit = parse_deposit_from_log(&decoded_log); Ok(Request::DepositRequest(deposit)) }) .collect::, _>>() @@ -53,23 +53,23 @@ fn parse_deposit_from_log(log: &Log) -> DepositRequest { .withdrawal_credentials .as_ref() .try_into() - .expect("pubkey length should be enforced in deposit contract"), + .expect("withdrawal_credentials length should be enforced in deposit contract"), amount: u64::from_le_bytes( log.amount .as_ref() .try_into() - .expect("pubkey length should be enforced in deposit contract"), + .expect("amount length should be enforced in deposit contract"), ), signature: log .signature .as_ref() .try_into() - .expect("pubkey length should be enforced in deposit contract"), + .expect("signature length should be enforced in deposit contract"), index: u64::from_le_bytes( log.index .as_ref() .try_into() - .expect("pubkey length should be enforced in deposit contract"), + .expect("deposit index length should be enforced in deposit contract"), ), } } From 0da8fef939eeef146eac739aa8dad9ad126a9587 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Mon, 13 May 2024 17:22:35 +0300 Subject: [PATCH 5/6] test: add deposit parsing test --- Cargo.lock | 1 + crates/ethereum/evm/Cargo.toml | 1 + crates/ethereum/evm/src/eip6110.rs | 41 +++++++++++++++++++++++++++++- crates/ethereum/evm/src/execute.rs | 2 +- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f15cbfb36c..cf363081cec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7003,6 +7003,7 @@ dependencies = [ "revm-precompile", "revm-primitives", "secp256k1 0.28.2", + "serde_json", "tracing", ] diff --git a/crates/ethereum/evm/Cargo.toml b/crates/ethereum/evm/Cargo.toml index c2eff78aa6b..0c11b1cdb91 100644 --- a/crates/ethereum/evm/Cargo.toml +++ b/crates/ethereum/evm/Cargo.toml @@ -31,6 +31,7 @@ alloy-sol-types.workspace = true # misc tracing.workspace = true +serde_json.workspace = true [dev-dependencies] reth-revm = { workspace = true, features = ["test-utils"] } diff --git a/crates/ethereum/evm/src/eip6110.rs b/crates/ethereum/evm/src/eip6110.rs index 6a42b685a8a..3143d14cd4f 100644 --- a/crates/ethereum/evm/src/eip6110.rs +++ b/crates/ethereum/evm/src/eip6110.rs @@ -6,7 +6,9 @@ use reth_interfaces::executor::BlockValidationError; use reth_primitives::Receipt; use revm_primitives::Log; -pub(crate) fn parse_deposits_from_receipts( +/// Parse [deposit contract](https://etherscan.io/address/0x00000000219ab540356cbb839cbe05303d7705fa) Deposits from receipts, +/// and returns them as a `Request`. +pub fn parse_deposits_from_receipts( receipts: &[Receipt], ) -> Result, BlockValidationError> { let res = receipts @@ -73,3 +75,40 @@ fn parse_deposit_from_log(log: &Log) -> DepositRequest { ), } } + +#[cfg(test)] +mod tests { + use reth_primitives::TxType; + + use super::*; + + #[test] + fn test_parse_deposit_from_log() { + let receipts = vec![ + // https://etherscan.io/tx/0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9 + Receipt { + // these don't matter + tx_type: TxType::Legacy, + success: true, + cumulative_gas_used: 0, + logs: serde_json::from_str( + r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f3900000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddec0000000000000000000000000000000000000000000000000000000000000008e474160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9","transactionIndex":"0xc4","logIndex":"0x18f","removed":false}]"# + ).unwrap(), + }, + // https://etherscan.io/tx/0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338 + Receipt { + // these don't matter + tx_type: TxType::Legacy, + success: true, + cumulative_gas_used: 0, + logs: serde_json::from_str( + r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f6000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba0390000000000000000000000000000000000000000000000000000000000000008e374160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338","transactionIndex":"0x7c","logIndex":"0xe2","removed":false}]"#, + ).unwrap(), + }, + ]; + let requests = parse_deposits_from_receipts(&receipts).unwrap(); + assert_eq!(requests.len(), 2); + assert_eq!(requests[0].as_deposit_request().unwrap().amount, 32e9 as u64); + assert_eq!(requests[1].as_deposit_request().unwrap().amount, 32e9 as u64); + } +} diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 52fa998f59f..91c5747ee6a 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -218,7 +218,7 @@ where // Collect all EIP-7685 requests let withdrawal_requests = post_block_withdrawal_requests(&self.chain_spec, block.timestamp, &mut evm)?; - // TODO: Does order matter? + // Requests are ordered by Request ID. let requests = [deposits, withdrawal_requests].concat(); Ok(EthExecuteOutput { receipts, requests, gas_used: cumulative_gas_used }) From 2162971405e551163884713eed4ac13043fb84b6 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Mon, 13 May 2024 07:29:20 -0700 Subject: [PATCH 6/6] chore: nits Co-authored-by: Alexey Shekhirin --- crates/ethereum/evm/src/execute.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index 91c5747ee6a..254b6a4f1ab 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -213,13 +213,13 @@ where } // Collect all EIP-6110 deposits - let deposits = crate::eip6110::parse_deposits_from_receipts(&receipts)?; + let deposit_requests = crate::eip6110::parse_deposits_from_receipts(&receipts)?; // Collect all EIP-7685 requests let withdrawal_requests = post_block_withdrawal_requests(&self.chain_spec, block.timestamp, &mut evm)?; - // Requests are ordered by Request ID. - let requests = [deposits, withdrawal_requests].concat(); + // Requests are ordered by Request Type ID. + let requests = [deposit_requests, withdrawal_requests].concat(); Ok(EthExecuteOutput { receipts, requests, gas_used: cumulative_gas_used }) }