Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support EIP-7702 Delegations in Forge #9236

Merged
merged 57 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
2467a3f
add EIP-7702 cheatcodes: createDelegation, signDelegation, attachDele…
evchip Oct 30, 2024
b16edd8
add cheatcode implementations for EIP-7702: createDelegationCall, sig…
evchip Oct 30, 2024
a286c7d
add delegations hashmap to Cheatcodes struct
evchip Oct 30, 2024
a6de137
add revm crate
evchip Oct 30, 2024
619aa1e
create AttachDelegationTest for EIP-7702 transactions
evchip Oct 30, 2024
d5ae337
regen cheatcodes.json
evchip Oct 30, 2024
05cdad8
cargo fmt
evchip Oct 31, 2024
1d8f42e
move broadcast under attachDelegation
evchip Oct 31, 2024
c819383
Merge branch 'master' into feature/forge-eip-7702
evchip Oct 31, 2024
da62cc8
combine createDelegationCall logic with signDelegationCall in order t…
evchip Nov 3, 2024
bad6230
remove revm import from workspace
evchip Nov 3, 2024
b3666aa
combine createDelegation logic inton signDelegation for simplicity
evchip Nov 3, 2024
376a092
remove revm from forge script deps
evchip Nov 3, 2024
93963ee
combine createDelegation with signDelegation
evchip Nov 3, 2024
df33332
WIP - refactor test to use SimpleDelegateContract and ERC20 - test cu…
evchip Nov 3, 2024
912ce6d
add logic to include authorization_list for EIP 7702 in TransactionRe…
evchip Nov 3, 2024
e97e185
add address authority param to attachDelegation; remove nonce param f…
evchip Nov 4, 2024
40fd5f1
remove 7702 tx request construction logic - now handled in attachDele…
evchip Nov 4, 2024
bdef83c
refactor attachDelegation cheatcode implementation to handle verifyin…
evchip Nov 4, 2024
e4082b1
remove nonce param from attachDelegation cheatcode in favor of loadin…
evchip Nov 4, 2024
f18a0e8
refactor test to check for code on alice account and call execute on …
evchip Nov 4, 2024
d8ea3ec
revert refactor on TransactionRequest
evchip Nov 4, 2024
0bd6f42
format
evchip Nov 4, 2024
8f480fe
cargo fmt
evchip Nov 5, 2024
25aeea7
fix clippy errors
evchip Nov 5, 2024
4310c2d
remove faulty logic comparing nonce to itself - nonce still checked b…
evchip Nov 6, 2024
bdc0b78
add more tests to cover revert cases on attachDelegation and multiple…
evchip Nov 6, 2024
c10be3e
cargo fmt
evchip Nov 6, 2024
7090999
restore logic to check if there's an active delegation when building …
evchip Nov 7, 2024
af7e39c
remove obsolete comment
evchip Nov 7, 2024
4394065
add comments explaining delegations and active_delegation
evchip Nov 7, 2024
aa0ece7
cargo fmt
evchip Nov 7, 2024
a833dd6
add logic to increase gas limit by PER_EMPTY_ACCOUNT_COST(25k) if tx …
evchip Nov 8, 2024
f432430
revert logic to add PER_EMPTY_ACCOUNT_COST for EIP 7702 txs - handled…
evchip Nov 10, 2024
6d46417
remove manually setting transaction type to 4 if auth list is present…
evchip Nov 10, 2024
d79f376
add method set_delegation to Executor for setting EIP-7702 authorizat…
evchip Nov 10, 2024
edbaf4c
Merge branch 'master' into feature/forge-eip-7702
evchip Nov 10, 2024
c050393
remove redundancy with TransactionMaybeSigned var tx
evchip Nov 10, 2024
cdfe503
cargo fmt
evchip Nov 10, 2024
475216d
refactor: use authorization_list() helper to return authorization_lis…
evchip Nov 11, 2024
d0ad8bd
refactor: change Cheatcodes::active_delegation to Option<SignedAuthor…
evchip Nov 11, 2024
113854c
replace verbose logic to set bytecode on EOA with journaled_state.set…
evchip Nov 12, 2024
0a93989
cargo fmt
evchip Nov 12, 2024
74302de
increment nonce of authority account
evchip Nov 12, 2024
de9dea4
add logic to set authorization_list to None if active_delegation is None
evchip Nov 12, 2024
d560464
add test testSwitchDelegation to assert that attaching an additional …
evchip Nov 12, 2024
2a30b42
remove set_delegation logic in favor of adding call_raw_with_authoriz…
evchip Nov 14, 2024
713bd64
refactor signDelegation to return struct SignedDelegation and for att…
evchip Nov 18, 2024
89cd62d
update delegation tests to reflect change in cheatcode interface for …
evchip Nov 18, 2024
7df7dcb
add cheatcode signAndAttachDelegation
evchip Nov 18, 2024
5151459
add signAndAttachDelegationCall cheatcode logic; refactor helper meth…
evchip Nov 18, 2024
1c6e7d0
add test testCallSingleSignAndAttachDelegation for new cheatcode sign…
evchip Nov 18, 2024
ff2801c
add comments to SignedDelegation struct and cargo fmt
evchip Nov 18, 2024
002221a
cargo fmt
evchip Nov 19, 2024
e829b6e
fix ci
klkvr Nov 19, 2024
d2d2376
fix spec
klkvr Nov 19, 2024
8fa8e64
Merge branch 'master' into feature/forge-eip-7702
grandizzy Nov 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions crates/cheatcodes/assets/cheatcodes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions crates/cheatcodes/spec/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,22 @@ interface Vm {
bool success;
}

/// Holds a signed EIP-7702 authorization for an authority account to delegate to an implementation.
struct SignedDelegation {
/// The y-parity of the recovered secp256k1 signature (0 or 1).
uint8 v;
/// First 32 bytes of the signature.
bytes32 r;
/// Second 32 bytes of the signature.
bytes32 s;
/// The current nonce of the authority account at signing time.
/// Used to ensure signature can't be replayed after account nonce changes.
uint64 nonce;
/// Address of the contract implementation that will be delegated to.
/// Gets encoded into delegation code: 0xef0100 || implementation.
address implementation;
}

// ======== EVM ========

/// Gets the address for a given private key.
Expand Down Expand Up @@ -2002,6 +2018,18 @@ interface Vm {
#[cheatcode(group = Scripting)]
function broadcastRawTransaction(bytes calldata data) external;

/// Sign an EIP-7702 authorization for delegation
#[cheatcode(group = Scripting)]
function signDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation);

/// Designate the next call as an EIP-7702 transaction
#[cheatcode(group = Scripting)]
function attachDelegation(SignedDelegation memory signedDelegation) external;

/// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction
#[cheatcode(group = Scripting)]
function signAndAttachDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation);

/// Returns addresses of available unlocked wallets in the script environment.
#[cheatcode(group = Scripting)]
function getWallets() external returns (address[] memory wallets);
Expand Down
39 changes: 28 additions & 11 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ use revm::{
EOFCreateInputs, EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterAction,
InterpreterResult,
},
primitives::{BlockEnv, CreateScheme, EVMError, EvmStorageSlot, SpecId, EOF_MAGIC_BYTES},
primitives::{
BlockEnv, CreateScheme, EVMError, EvmStorageSlot, SignedAuthorization, SpecId,
EOF_MAGIC_BYTES,
},
EvmContext, InnerEvmContext, Inspector,
};
use serde_json::Value;
Expand Down Expand Up @@ -373,6 +376,11 @@ pub struct Cheatcodes {
/// execution block environment.
pub block: Option<BlockEnv>,

/// Currently active EIP-7702 delegation that will be consumed when building the next
/// transaction. Set by `vm.attachDelegation()` and consumed via `.take()` during
/// transaction construction.
pub active_delegation: Option<SignedAuthorization>,

/// The gas price.
///
/// Used in the cheatcode handler to overwrite the gas price separately from the gas price
Expand Down Expand Up @@ -497,6 +505,7 @@ impl Cheatcodes {
labels: config.labels.clone(),
config,
block: Default::default(),
active_delegation: Default::default(),
gas_price: Default::default(),
prank: Default::default(),
expected_revert: Default::default(),
Expand Down Expand Up @@ -1001,18 +1010,26 @@ where {
let account =
ecx.journaled_state.state().get_mut(&broadcast.new_origin).unwrap();

let mut tx_req = TransactionRequest {
from: Some(broadcast.new_origin),
to: Some(TxKind::from(Some(call.target_address))),
value: call.transfer_value(),
input: TransactionInput::new(call.input.clone()),
nonce: Some(account.info.nonce),
chain_id: Some(ecx.env.cfg.chain_id),
gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None },
..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;
}

self.broadcastable_transactions.push_back(BroadcastableTransaction {
rpc: ecx.db.active_fork_url(),
transaction: TransactionRequest {
from: Some(broadcast.new_origin),
to: Some(TxKind::from(Some(call.target_address))),
value: call.transfer_value(),
input: TransactionInput::new(call.input.clone()),
nonce: Some(account.info.nonce),
gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None },
..Default::default()
}
.into(),
transaction: tx_req.into(),
});
debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call");

Expand Down
89 changes: 89 additions & 0 deletions crates/cheatcodes/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

use crate::{Cheatcode, CheatsCtxt, Result, Vm::*};
use alloy_primitives::{Address, B256, U256};
use alloy_rpc_types::Authorization;
use alloy_signer::{Signature, SignerSync};
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 std::sync::Arc;

impl Cheatcode for broadcast_0Call {
Expand All @@ -29,6 +32,92 @@ impl Cheatcode for broadcast_2Call {
}
}

impl Cheatcode for attachDelegationCall {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { signedDelegation } = self;
let SignedDelegation { v, r, s, nonce, implementation } = signedDelegation;

let auth = Authorization {
address: *implementation,
nonce: *nonce,
chain_id: ccx.ecx.env.cfg.chain_id,
};
let signed_auth = SignedAuthorization::new_unchecked(
auth,
*v,
U256::from_be_bytes(r.0),
U256::from_be_bytes(s.0),
);
write_delegation(ccx, signed_auth.clone())?;
ccx.state.active_delegation = Some(signed_auth);
Ok(Default::default())
}
}

impl Cheatcode for signDelegationCall {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { implementation, privateKey } = self;
let signer = PrivateKeySigner::from_bytes(&B256::from(*privateKey))?;
let authority = signer.address();
let (auth, nonce) = create_auth(ccx, *implementation, authority)?;
let sig = signer.sign_hash_sync(&auth.signature_hash())?;
Ok(sig_to_delegation(sig, nonce, *implementation).abi_encode())
}
}

impl Cheatcode for signAndAttachDelegationCall {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { implementation, privateKey } = self;
let signer = PrivateKeySigner::from_bytes(&B256::from(*privateKey))?;
let authority = signer.address();
let (auth, nonce) = create_auth(ccx, *implementation, authority)?;
let sig = signer.sign_hash_sync(&auth.signature_hash())?;
let signed_auth = sig_to_auth(sig, auth);
write_delegation(ccx, signed_auth.clone())?;
ccx.state.active_delegation = Some(signed_auth);
Ok(sig_to_delegation(sig, nonce, *implementation).abi_encode())
}
}

fn create_auth(
ccx: &mut CheatsCtxt,
implementation: Address,
authority: Address,
) -> Result<(Authorization, u64)> {
let authority_acc = ccx.ecx.journaled_state.load_account(authority, &mut ccx.ecx.db)?;
let nonce = authority_acc.data.info.nonce;
Ok((
Authorization { address: implementation, nonce, chain_id: ccx.ecx.env.cfg.chain_id },
nonce,
))
}

fn write_delegation(ccx: &mut CheatsCtxt, auth: SignedAuthorization) -> Result<()> {
let authority = auth.recover_authority().map_err(|e| format!("{e}"))?;
let authority_acc = ccx.ecx.journaled_state.load_account(authority, &mut ccx.ecx.db)?;
if authority_acc.data.info.nonce != auth.nonce {
return Err("invalid nonce".into());
}
authority_acc.data.info.nonce += 1;
let bytecode = Bytecode::new_eip7702(*auth.address());
ccx.ecx.journaled_state.set_code(authority, bytecode);
Ok(())
}

fn sig_to_delegation(sig: Signature, nonce: u64, implementation: Address) -> SignedDelegation {
SignedDelegation {
v: sig.v().y_parity() as u8,
r: sig.r().into(),
s: sig.s().into(),
nonce,
implementation,
}
}

fn sig_to_auth(sig: Signature, auth: Authorization) -> SignedAuthorization {
SignedAuthorization::new_unchecked(auth, sig.v().y_parity() as u8, sig.r(), sig.s())
}

impl Cheatcode for startBroadcast_0Call {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self {} = self;
Expand Down
1 change: 1 addition & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ foundry-config.workspace = true

alloy-contract.workspace = true
alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] }
alloy-eips.workspace = true
alloy-json-abi.workspace = true
alloy-json-rpc.workspace = true
alloy-primitives = { workspace = true, features = [
Expand Down
9 changes: 9 additions & 0 deletions crates/common/src/transactions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Wrappers for transactions.

use alloy_consensus::{Transaction, TxEnvelope};
use alloy_eips::eip7702::SignedAuthorization;
use alloy_primitives::{Address, TxKind, U256};
use alloy_provider::{
network::{AnyNetwork, ReceiptResponse, TransactionBuilder},
Expand Down Expand Up @@ -226,6 +227,14 @@ impl TransactionMaybeSigned {
Self::Unsigned(tx) => tx.nonce,
}
}

pub fn authorization_list(&self) -> Option<Vec<SignedAuthorization>> {
match self {
Self::Signed { tx, .. } => tx.authorization_list().map(|auths| auths.to_vec()),
Self::Unsigned(tx) => tx.authorization_list.as_deref().map(|auths| auths.to_vec()),
}
.filter(|auths| !auths.is_empty())
}
}

impl From<TransactionRequest> for TransactionMaybeSigned {
Expand Down
19 changes: 17 additions & 2 deletions crates/evm/evm/src/executors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ use revm::{
db::{DatabaseCommit, DatabaseRef},
interpreter::{return_ok, InstructionResult},
primitives::{
BlockEnv, Bytecode, Env, EnvWithHandlerCfg, ExecutionResult, Output, ResultAndState,
SpecId, TxEnv, TxKind,
AuthorizationList, BlockEnv, Bytecode, Env, EnvWithHandlerCfg, ExecutionResult, Output,
ResultAndState, SignedAuthorization, SpecId, TxEnv, TxKind,
},
};
use std::borrow::Cow;
Expand Down Expand Up @@ -378,6 +378,21 @@ impl Executor {
self.call_with_env(env)
}

/// Performs a raw call to an account on the current state of the VM with an EIP-7702
/// authorization list.
pub fn call_raw_with_authorization(
&mut self,
from: Address,
to: Address,
calldata: Bytes,
value: U256,
authorization_list: Vec<SignedAuthorization>,
) -> eyre::Result<RawCallResult> {
let mut env = self.build_test_env(from, to.into(), calldata, value);
env.tx.authorization_list = Some(AuthorizationList::Signed(authorization_list));
self.call_with_env(env)
}

/// Performs a raw call to an account on the current state of the VM.
pub fn transact_raw(
&mut self,
Expand Down
Loading
Loading