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

Conversation

evchip
Copy link
Contributor

@evchip evchip commented Oct 31, 2024

Motivation

Adds initial support for EIP-7702 transaction delegation in Forge scripts.

Addresses #7128

Solution

  • adds EIP-7702 support to forge scripts for transaction batching + delegation.
  • new cheatcodes:

createDelegation: generates auth hash
signDelegation: signs auth with pk
attachDelegation: connects signed delegation to sender

Limitations:

  • clunky sig handling - requires passing v,r,s separately bc passing raw sig bytes breaks tests
  • only supports raw private keys, needs hardware wallet etc support

Questions:

  1. best way to propagate delegation state from cheatcodes -> broadcast?
  2. should we track delegations in ScriptSequence instead of ScriptResult?

looking for feedback on state handling and current api limitations before expanding functionality.

bytes32 delegation = vm.createDelegation(implementation, nonce);
(uint8 v, bytes32 r, bytes32 s) = vm.signDelegation(delegation, pk);

vm.attachDelegation(implementation, nonce, v, r, s);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe delegations would be attached to separate transactions vs broadcast blocks, so it would be something like

vm.startBroadcast();
vm.attachDelegation(implementation, nonce, v, r, s);
// this transactions would be broadcasted along with attached 7702 delegation
token.transfer(....);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the comment! looking at this closer - since 7702 makes the EOA itself the target of the tx, i think we need to route all calls through the EOA first.

right now we can do this with a raw call:

vm.startBroadcast();
vm.attachDelegation(implementation, nonce, v, r, s);
alice.call(abi.encodeCall(implementation.execute, (calls)));

but for better UX, maybe we could either:

  1. change attachDelegation to take authority instead of implementation (since we have the implementation in the call itself):
vm.attachDelegation(alice, nonce, v, r, s);
implementation.execute(calls);
  1. or add a helper like:
vm.delegatedCall(alice, implementation.execute, calls);

what do you think would be more ergonomic for users?

@klkvr
Copy link
Member

klkvr commented Nov 1, 2024

best way to propagate delegation state from cheatcodes -> broadcast?

We should attach them to TransactionRequest here:

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(),
});

should we track delegations in ScriptSequence instead of ScriptResult?

I think we need to track them in both. In ScriptResult as part of BroadcastableTransactions and in ScriptSequence as TransactionWithMetadata

@evchip
Copy link
Contributor Author

evchip commented Nov 1, 2024

@klkvr thanks for the detailed feedback! Will push an update soon that addresses your comments.

@evchip
Copy link
Contributor Author

evchip commented Nov 3, 2024

@klkvr thanks for the pointer on TransactionRequest. i've added the authorization_list field and i'm constructing 7702 txs (type=0x04) when that field is populated. however, the delegated calls don't seem to be executing properly - i suspect we also need to write the delegation designation (0xef0100 || address) to the EOA's code somewhere.

looking at the anvil tests, it seems revm supports 7702, but i'm not clear on where in foundry we should be handling the delegation setup. any guidance? thanks in advance.

@klkvr
Copy link
Member

klkvr commented Nov 3, 2024

@klkvr thanks for the pointer on TransactionRequest. i've added the authorization_list field and i'm constructing 7702 txs (type=0x04) when that field is populated. however, the delegated calls don't seem to be executing properly - i suspect we also need to write the delegation designation (0xef0100 || address) to the EOA's code somewhere.

looking at the anvil tests, it seems revm supports 7702, but i'm not clear on where in foundry we should be handling the delegation setup. any guidance? thanks in advance.

right, we need to execute something similar to this https://github.com/bluealloy/revm/blob/346aa01398aa9b4e4e36776c21f6fe7169ed5d87/crates/revm/src/handler/mainnet/pre_execution.rs#L102 in call hook where we push to broadcastable_transactions

we can probably skip some of the steps as we will anyway execute this later during on-chain simulation, but I think nonce check and increase should be kept because those may affect contract deployment addresses

also we'll need to respect the authorization list here

let result = runner
when configuring TxEnv for simulation, this might require small changes to executor

…rom signDelegation, as it can be constructed in cheatcode.
…g signature and setting bytecode on EOA; refactor signDelegation cheatcode implementation to get nonce from signer
…alice account through SimpleDelegateContract
…ization> and remove delegations hashmap - tx will only use one active delegation at a time, so no need for mapping
crates/cheatcodes/src/script.rs Outdated Show resolved Hide resolved
crates/script/src/simulate.rs Outdated Show resolved Hide resolved
Comment on lines 35 to 37
impl Cheatcode for attachDelegationCall {
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
let Self { implementation, authority, v, r, s } = self;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove authority argument from here. it's redundant as we are always recovering it from signature

also wdyt on adding signAndAttach method? I think common flow would be calling both of the cheatcodes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing how to construct the authorization without the authority's nonce, which we'd need to recover the authority from the signature. Can you clarify the flow you're thinking of?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can get the nonce from state

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but to get the nonce from state we need the authority's address to load their account - am I missing some other way to get their nonce?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in signDelegation we can get authority address from private key, then get nonce, construct and sign delegation

and in attachDelegation we can just recover authority from the signed delegation. right now the passed authority address is anyway expected to be the same as recovered one making it redundant

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see what you mean. didn't notice that attachDelegation doesn't accept nonce

let's make signDelegation return a new struct SignedDelegation which will contain v,r,s,nonce,implementation. And then attachDelegation would just accept this object

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented a new cheatcode signAndAttachDelegation and refactored the existing cheatcodes to use struct SignedDelegation. Let me know what you think. Thanks!

Copy link
Member

@klkvr klkvr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, lgtm!

@klkvr klkvr marked this pull request as ready for review November 18, 2024 14:38
@zerosnacks zerosnacks self-requested a review November 19, 2024 10:41
Copy link
Member

@zerosnacks zerosnacks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @evchip! Implementation looks good to me

Copy link
Member

@yash-atreya yash-atreya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be merged @grandizzy

@grandizzy grandizzy enabled auto-merge (squash) November 20, 2024 11:36
@grandizzy grandizzy merged commit 2a194bd into foundry-rs:master Nov 20, 2024
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants