Skip to content

Add support for EIP-7702 #323

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

Open
wants to merge 25 commits into
base: v0.x
Choose a base branch
from

Conversation

manuelmauro
Copy link

Description

Adds EIP-7702 support.

@manuelmauro manuelmauro marked this pull request as ready for review June 18, 2025 15:36
Comment on lines +36 to +41
/// Get code of address, following EIP-7702 delegations if enabled.
fn delegated_code(&self, address: H160) -> Option<Vec<u8>> {
let code = self.code(address);
evm_core::extract_delegation_address(&code)
.map(|delegated_address| self.code(delegated_address))
}

Choose a reason for hiding this comment

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

It seems that your implementation provide no protection against delegation chains/loops.

EIP-7702 specifies that clients must retrieve only the first code and stop following delegation chains to prevent infinite loops.

You might need to implement delegation chain tracking to prevent infinite loops.

Copy link
Author

Choose a reason for hiding this comment

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

The delegated code is resolved once within call_inner, then executed with no further check.

// EIP-7702: Get execution code, following delegations if enabled
		let code = if self.config.has_eip_7702 {
			self.delegated_code(code_address)
				.unwrap_or_else(|| self.code(code_address))
		} else {
			self.code(code_address)
		};

Under the assumption that call_inner is not invoked within a loop, this should be enough to prevent chain following in my mind.

I added a test to check the behavior of the current implementation: 2564062

The result:

cargo test test_eip7702_delegation_chain_violation -- --nocapture
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.04s
     Running unittests src/lib.rs (target/debug/deps/evm-e7728aa468c7f7c2)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/eip7702_tests.rs (target/debug/deps/eip7702_tests-ff89983bca9ad4d2)

running 1 test
Exit reason: Error(InvalidCode(Opcode(239)))
Return data: []
✓ CORRECT: Delegation chain properly stopped, invalid code executed
test test_eip7702_delegation_chain_violation ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 17 filtered out; finished in 0.00s

Maybe there are other execution branches where this could be violated?
Are there some conservative hardening measures that we could adopt?

Copy link
Member

Choose a reason for hiding this comment

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

It looks sufficient. From my understanding of the specification, we need to prevent loops per call. So each call attempts to fetch delegation exactly once, which seems to be what this PR is doing.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe there are other execution branches where this could be violated?

From my look at the code there's only a single use of delegated_code so it seems fine.

Copy link

@librelois librelois left a comment

Choose a reason for hiding this comment

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

It seems that there is several special cases that might not be handled correctly in this implementation:

  1. Delegation Chain Protection: EIP-7702 specifies that clients must retrieve only the first code and stop following delegation chains to prevent infinite loops.
  2. Delegation to a Precompile: EIP-7702 specifies that when a precompile address is the target of delegation, the retrieved code should be considered empty.
  3. Opcodes CODESIZE and CODECOPY should follows delegation, can you add tests for that?

@sorpaas
Copy link
Member

sorpaas commented Jun 23, 2025

Delegation Chain Protection: EIP-7702 specifies that clients must retrieve only the first code and stop following delegation chains to prevent infinite loops.

See discussions above.

Delegation to a Precompile: EIP-7702 specifies that when a precompile address is the target of delegation, the retrieved code should be considered empty.

This seems to be correctly implemented. In case of delegating to precompile:

  • The initial precompile check will not be triggered because it checks against the authority address.
  • Then it would fetch delegated_code, and in case of precompile, it's empty.

So this seems fine.

Opcodes CODESIZE and CODECOPY should follows delegation, can you add tests for that?

Right now from my review it looks correct. CODESIZE and CODECOPY returns in all cases the runtime code (state.code).

But nevertheless as @librelois has said, it'll always be better to have some more test cases!

@manuelmauro
Copy link
Author

If address is 0x0000000000000000000000000000000000000000, do not write the delegation indicator. Clear the account’s code by resetting the account’s code hash to the empty code hash 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470.

Addressed in 3134553

@manuelmauro
Copy link
Author

Added a comprehensive test suite in f5cc851 and currently investigating the failing tests.

@manuelmauro
Copy link
Author

EIP-7702 states: "When multiple tuples from the same authority are present, set the code using the address in the last valid occurrence."

It is my understanding that, when multiple authorizations from the same authorities are present in a list, for them to be valid they must have increasing nonces.

I updated the corresponding test to reflect this in cc32e59


// Should either succeed (infinite loop prevented) or fail gracefully
assert!(
matches!(call_exit_reason, ExitReason::Succeed(_))

Choose a reason for hiding this comment

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

It should fail as the EOA can't have valid executable bytecode

@librelois
Copy link

You tested EXTCODECOPY and EXTCODESIZE, but I didn’t find tests for the corresponding behavior of CODECOPY and CODESIZE. It’s very important to test all affected opcodes.

@librelois
Copy link

librelois commented Jun 24, 2025

It is my understanding that, when multiple authorizations from the same authorities are present in a list, for them to be valid they must have increasing nonces.

I did some research and it seems that geth increment the nonce for every valid tuple. So, if there is two authorizations for the same authority, the nonce must increase twice.

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.

3 participants