From 43a62da9e207eb39650cee1ff66de4ff1680ef45 Mon Sep 17 00:00:00 2001 From: 0xcomfycat Date: Fri, 18 Apr 2025 23:22:45 +0700 Subject: [PATCH 1/3] feat: add new cheatcode attachBlob --- crates/cheatcodes/assets/cheatcodes.json | 20 ++++++++ crates/cheatcodes/spec/src/vm.rs | 4 ++ crates/cheatcodes/src/inspector.rs | 34 ++++++++++++-- crates/cheatcodes/src/script.rs | 18 +++++++- testdata/cheats/Vm.sol | 1 + testdata/default/cheats/AttachBlob.t.sol | 59 ++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 testdata/default/cheats/AttachBlob.t.sol diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 7cce924b5f60c..59c5bab3cb457 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3230,6 +3230,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "attachBlob", + "description": "Attach an EIP-4844 blob to the next call", + "declaration": "function attachBlob(bytes calldata blob) external;", + "visibility": "external", + "mutability": "", + "signature": "attachBlob(bytes)", + "selector": "0x10cb385c", + "selectorBytes": [ + 16, + 203, + 56, + 92 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "attachDelegation", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index c0481569af296..00f3b8004a96c 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -2236,6 +2236,10 @@ interface Vm { #[cheatcode(group = Scripting)] function signAndAttachDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation); + /// Attach an EIP-4844 blob to the next call + #[cheatcode(group = Scripting)] + function attachBlob(bytes calldata blob) external; + /// Returns addresses of available unlocked wallets in the script environment. #[cheatcode(group = Scripting)] function getWallets() external returns (address[] memory wallets); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index fd60fb1982a7e..251210113c4e6 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -21,6 +21,8 @@ use crate::{ CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, Vm::{self, AccountAccess}, }; +use alloy_consensus::BlobTransactionSidecar; +use alloy_network::TransactionBuilder4844; use alloy_primitives::{ hex, map::{AddressHashMap, HashMap, HashSet}, @@ -393,6 +395,9 @@ pub struct Cheatcodes { /// transaction construction. pub active_delegation: Option, + /// The active EIP-4844 blob that will be attached to the next call. + pub active_blob_sidecar: Option, + /// The gas price. /// /// Used in the cheatcode handler to overwrite the gas price separately from the gas price @@ -526,6 +531,7 @@ impl Cheatcodes { config, block: Default::default(), active_delegation: Default::default(), + active_blob_sidecar: Default::default(), gas_price: Default::default(), pranks: Default::default(), expected_revert: Default::default(), @@ -1127,10 +1133,30 @@ where { ..Default::default() }; - if let Some(auth_list) = self.active_delegation.take() { - tx_req.authorization_list = Some(vec![auth_list]); - } else { - tx_req.authorization_list = None; + match (self.active_delegation.take(), self.active_blob_sidecar.take()) { + (Some(_), Some(_)) => { + let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible"; + return Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(msg), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }); + } + (Some(auth_list), None) => { + tx_req.authorization_list = Some(vec![auth_list]); + tx_req.sidecar = None; + } + (None, Some(blob_sidecar)) => { + tx_req.set_blob_sidecar(blob_sidecar); + tx_req.authorization_list = None; + } + (None, None) => { + tx_req.sidecar = None; + tx_req.authorization_list = None; + } } self.broadcastable_transactions.push_back(BroadcastableTransaction { diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index d940cb438b8af..dc80b7021ab04 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -1,6 +1,7 @@ //! Implementations of [`Scripting`](spec::Group::Scripting) cheatcodes. use crate::{Cheatcode, CheatsCtxt, Result, Vm::*}; +use alloy_consensus::{SidecarBuilder, SimpleCoder}; use alloy_primitives::{Address, Uint, B256, U256}; use alloy_rpc_types::Authorization; use alloy_signer::SignerSync; @@ -8,7 +9,7 @@ use alloy_signer_local::PrivateKeySigner; use alloy_sol_types::SolValue; use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; use parking_lot::Mutex; -use revm::primitives::{Bytecode, SignedAuthorization}; +use revm::primitives::{Bytecode, SignedAuthorization, SpecId}; use std::sync::Arc; impl Cheatcode for broadcast_0Call { @@ -133,6 +134,21 @@ fn write_delegation(ccx: &mut CheatsCtxt, auth: SignedAuthorization) -> Result<( Ok(()) } +impl Cheatcode for attachBlobCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { blob } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`attachBlob` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(blob); + let sidecar = sidecar.build().map_err(|e| format!("{e}"))?; + ccx.state.active_blob_sidecar = Some(sidecar); + Ok(Default::default()) + } +} + impl Cheatcode for startBroadcast_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index b3a447cc5e1dc..adf6733ba9424 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -155,6 +155,7 @@ interface Vm { function assumeNoRevert() external pure; function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure; function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure; + function attachBlob(bytes calldata blob) external; function attachDelegation(SignedDelegation calldata signedDelegation) external; function blobBaseFee(uint256 newBlobBaseFee) external; function blobhashes(bytes32[] calldata hashes) external; diff --git a/testdata/default/cheats/AttachBlob.t.sol b/testdata/default/cheats/AttachBlob.t.sol new file mode 100644 index 0000000000000..5dc680beee8c8 --- /dev/null +++ b/testdata/default/cheats/AttachBlob.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.25; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Counter { + uint256 public counter; + + function increment() public { + counter++; + } +} + +contract AttachBlobTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + uint256 bobPk = + 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + address bob = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; + + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function testAttachBlob() public { + bytes memory blob = abi.encode("Blob the Builder"); + vm.attachBlob(blob); + + vm.broadcast(bobPk); + counter.increment(); + } + + function testAttachBlobWithCreateTx() public { + bytes memory blob = abi.encode("Blob the Builder"); + vm.attachBlob(blob); + + vm.broadcast(bobPk); + new Counter(); + + // blob is attached with this tx instead + vm.broadcast(bobPk); + counter.increment(); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testRevertAttachBlobWithDelegation() public { + bytes memory blob = abi.encode("Blob the Builder"); + vm.attachBlob(blob); + vm.signAndAttachDelegation(address(0), bobPk); + + vm.broadcast(bobPk); + vm.expectRevert( + "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible" + ); + counter.increment(); + } +} From 23c51d4e9c8c3d00dace8e052098bd255503553a Mon Sep 17 00:00:00 2001 From: 0xcomfycat Date: Fri, 18 Apr 2025 23:47:36 +0700 Subject: [PATCH 2/3] fix: lint --- testdata/default/cheats/AttachBlob.t.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/testdata/default/cheats/AttachBlob.t.sol b/testdata/default/cheats/AttachBlob.t.sol index 5dc680beee8c8..db17c1fae5f4c 100644 --- a/testdata/default/cheats/AttachBlob.t.sol +++ b/testdata/default/cheats/AttachBlob.t.sol @@ -14,8 +14,7 @@ contract Counter { contract AttachBlobTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - uint256 bobPk = - 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + uint256 bobPk = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; address bob = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; Counter public counter; @@ -51,9 +50,7 @@ contract AttachBlobTest is DSTest { vm.signAndAttachDelegation(address(0), bobPk); vm.broadcast(bobPk); - vm.expectRevert( - "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible" - ); + vm.expectRevert("both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible"); counter.increment(); } } From 9de6b78dd2e0e3cea124cf1030e7e10b5d176e1f Mon Sep 17 00:00:00 2001 From: 0xcomfycat Date: Sat, 19 Apr 2025 00:14:57 +0700 Subject: [PATCH 3/3] fix: build fail due to missing feature flag --- crates/cheatcodes/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 645426ea8bf29..49611dcca071c 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -37,7 +37,7 @@ alloy-signer-local = { workspace = true, features = [ "keystore", ] } parking_lot.workspace = true -alloy-consensus = { workspace = true, features = ["k256"] } +alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-network.workspace = true alloy-rlp.workspace = true alloy-chains.workspace = true