Skip to content

Commit e988893

Browse files
feat(forge): add new cheatcode attachBlob to send EIP-4844 transaction (#10336)
* feat: add new cheatcode attachBlob * fix: lint * fix: build fail due to missing feature flag --------- Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com>
1 parent 1af5f85 commit e988893

File tree

7 files changed

+129
-6
lines changed

7 files changed

+129
-6
lines changed

crates/cheatcodes/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ alloy-signer-local = { workspace = true, features = [
3737
"keystore",
3838
] }
3939
parking_lot.workspace = true
40-
alloy-consensus = { workspace = true, features = ["k256"] }
40+
alloy-consensus = { workspace = true, features = ["k256", "kzg"] }
4141
alloy-network.workspace = true
4242
alloy-rlp.workspace = true
4343
alloy-chains.workspace = true

crates/cheatcodes/assets/cheatcodes.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatcodes/spec/src/vm.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,10 @@ interface Vm {
22362236
#[cheatcode(group = Scripting)]
22372237
function signAndAttachDelegation(address implementation, uint256 privateKey, uint64 nonce) external returns (SignedDelegation memory signedDelegation);
22382238

2239+
/// Attach an EIP-4844 blob to the next call
2240+
#[cheatcode(group = Scripting)]
2241+
function attachBlob(bytes calldata blob) external;
2242+
22392243
/// Returns addresses of available unlocked wallets in the script environment.
22402244
#[cheatcode(group = Scripting)]
22412245
function getWallets() external returns (address[] memory wallets);

crates/cheatcodes/src/inspector.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ use crate::{
2121
CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result,
2222
Vm::{self, AccountAccess},
2323
};
24+
use alloy_consensus::BlobTransactionSidecar;
25+
use alloy_network::TransactionBuilder4844;
2426
use alloy_primitives::{
2527
hex,
2628
map::{AddressHashMap, HashMap, HashSet},
@@ -393,6 +395,9 @@ pub struct Cheatcodes {
393395
/// transaction construction.
394396
pub active_delegation: Option<SignedAuthorization>,
395397

398+
/// The active EIP-4844 blob that will be attached to the next call.
399+
pub active_blob_sidecar: Option<BlobTransactionSidecar>,
400+
396401
/// The gas price.
397402
///
398403
/// Used in the cheatcode handler to overwrite the gas price separately from the gas price
@@ -526,6 +531,7 @@ impl Cheatcodes {
526531
config,
527532
block: Default::default(),
528533
active_delegation: Default::default(),
534+
active_blob_sidecar: Default::default(),
529535
gas_price: Default::default(),
530536
pranks: Default::default(),
531537
expected_revert: Default::default(),
@@ -1127,10 +1133,30 @@ where {
11271133
..Default::default()
11281134
};
11291135

1130-
if let Some(auth_list) = self.active_delegation.take() {
1131-
tx_req.authorization_list = Some(vec![auth_list]);
1132-
} else {
1133-
tx_req.authorization_list = None;
1136+
match (self.active_delegation.take(), self.active_blob_sidecar.take()) {
1137+
(Some(_), Some(_)) => {
1138+
let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible";
1139+
return Some(CallOutcome {
1140+
result: InterpreterResult {
1141+
result: InstructionResult::Revert,
1142+
output: Error::encode(msg),
1143+
gas,
1144+
},
1145+
memory_offset: call.return_memory_offset.clone(),
1146+
});
1147+
}
1148+
(Some(auth_list), None) => {
1149+
tx_req.authorization_list = Some(vec![auth_list]);
1150+
tx_req.sidecar = None;
1151+
}
1152+
(None, Some(blob_sidecar)) => {
1153+
tx_req.set_blob_sidecar(blob_sidecar);
1154+
tx_req.authorization_list = None;
1155+
}
1156+
(None, None) => {
1157+
tx_req.sidecar = None;
1158+
tx_req.authorization_list = None;
1159+
}
11341160
}
11351161

11361162
self.broadcastable_transactions.push_back(BroadcastableTransaction {

crates/cheatcodes/src/script.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
//! Implementations of [`Scripting`](spec::Group::Scripting) cheatcodes.
22
33
use crate::{Cheatcode, CheatsCtxt, Result, Vm::*};
4+
use alloy_consensus::{SidecarBuilder, SimpleCoder};
45
use alloy_primitives::{Address, Uint, B256, U256};
56
use alloy_rpc_types::Authorization;
67
use alloy_signer::SignerSync;
78
use alloy_signer_local::PrivateKeySigner;
89
use alloy_sol_types::SolValue;
910
use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner};
1011
use parking_lot::Mutex;
11-
use revm::primitives::{Bytecode, SignedAuthorization};
12+
use revm::primitives::{Bytecode, SignedAuthorization, SpecId};
1213
use std::sync::Arc;
1314

1415
impl Cheatcode for broadcast_0Call {
@@ -133,6 +134,21 @@ fn write_delegation(ccx: &mut CheatsCtxt, auth: SignedAuthorization) -> Result<(
133134
Ok(())
134135
}
135136

137+
impl Cheatcode for attachBlobCall {
138+
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
139+
let Self { blob } = self;
140+
ensure!(
141+
ccx.ecx.spec_id() >= SpecId::CANCUN,
142+
"`attachBlob` is not supported before the Cancun hard fork; \
143+
see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
144+
);
145+
let sidecar: SidecarBuilder<SimpleCoder> = SidecarBuilder::from_slice(blob);
146+
let sidecar = sidecar.build().map_err(|e| format!("{e}"))?;
147+
ccx.state.active_blob_sidecar = Some(sidecar);
148+
Ok(Default::default())
149+
}
150+
}
151+
136152
impl Cheatcode for startBroadcast_0Call {
137153
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
138154
let Self {} = self;

testdata/cheats/Vm.sol

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
pragma solidity ^0.8.25;
3+
4+
import "ds-test/test.sol";
5+
import "cheats/Vm.sol";
6+
7+
contract Counter {
8+
uint256 public counter;
9+
10+
function increment() public {
11+
counter++;
12+
}
13+
}
14+
15+
contract AttachBlobTest is DSTest {
16+
Vm constant vm = Vm(HEVM_ADDRESS);
17+
uint256 bobPk = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d;
18+
address bob = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;
19+
20+
Counter public counter;
21+
22+
function setUp() public {
23+
counter = new Counter();
24+
}
25+
26+
function testAttachBlob() public {
27+
bytes memory blob = abi.encode("Blob the Builder");
28+
vm.attachBlob(blob);
29+
30+
vm.broadcast(bobPk);
31+
counter.increment();
32+
}
33+
34+
function testAttachBlobWithCreateTx() public {
35+
bytes memory blob = abi.encode("Blob the Builder");
36+
vm.attachBlob(blob);
37+
38+
vm.broadcast(bobPk);
39+
new Counter();
40+
41+
// blob is attached with this tx instead
42+
vm.broadcast(bobPk);
43+
counter.increment();
44+
}
45+
46+
/// forge-config: default.allow_internal_expect_revert = true
47+
function testRevertAttachBlobWithDelegation() public {
48+
bytes memory blob = abi.encode("Blob the Builder");
49+
vm.attachBlob(blob);
50+
vm.signAndAttachDelegation(address(0), bobPk);
51+
52+
vm.broadcast(bobPk);
53+
vm.expectRevert("both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible");
54+
counter.increment();
55+
}
56+
}

0 commit comments

Comments
 (0)