EIP-8298: SETCODEFROM Code Reuse Instruction

This proposal comes from an idea raised during an ACDE call: combine the goals of EIP-7851 and EIP-8058 into one lower-level code adoption primitive.

EIP-8298 introduces SETCODEFROM, an EVM instruction that lets the current account adopt the code hash of an existing deployed contract. The source is a live account address, rather than a raw code hash, so the adopted code is tied to the current consensus state.

The proposal has two main uses:

  • Contract bytecode reuse and deployment economics: this addresses the deployment-cost problem targeted by EIP-8058. Contracts with identical runtime code can initialize per-instance storage, then adopt shared deployed code without paying code-deposit gas again for bytecode that clients already store. This is especially relevant if contract deployment is repriced more closely to state growth, for example, under EIP-8037.
  • EOA migration with ECDSA authority disabled: this addresses the account-migration problem targeted by EIP-7851. Migration code can initialize wallet-specific state, including PQ wallet state, then adopt regular wallet code. Once the account has regular deployed code, protocol-level ECDSA transaction origination is disabled under EIP-3607, and EIP-7702 redelegation through the old ECDSA key is no longer available.

Update Log

External Reviews

None as of 2026-06-12.

Outstanding Issues

None as of 2026-06-12.

2 Likes

Delegation is great but I don’t think we need an opcode for delegations specifically. We just need to let existing opcodes support delegations; for example, we could allow constructors to return eip-7702 delegations. I do support mutable code but I would prefer something more powerful like the SETCODE opcode (EIP-6913). Restricting mutable code to delegation is redundant with delegatecall proxies and is overly cautious.

I also think we should allow infinite redirects with delegation; they just need to be priced fairly.

Thanks for the feedback.

SETCODEFROM is intentionally narrower than SETCODE. It is not an opcode for delegation specifically, and it does not introduce arbitrary new bytecode or a general mutable-code facility. It only lets an account adopt code that already exists in the live consensus state.

That narrower scope is the point: it preserves deterministic code availability, avoids new code-deposit storage, supports clone deployment economics, especially if contract deployment becomes much more expensive under EIP-8037, and gives migrated EOAs regular deployed code rather than a 7702 delegation indicator.

If the goal is general mutable code, SETCODE is the broader primitive. If the goal is code reuse plus ECDSA-disabled account migration with minimal new surface area, SETCODEFROM is the smaller primitive.

1 Like

I’m reading the draft, but I think it might overly complicate what this opcode tries to do. If we hit SETCODEFROM in an initcode, I think it should directly abort and it should deploy the code what is stored at that address (i.e. set codeHash to the hash of that code). If we continue running teh code, this raises questions (and will increase the test vectors by a lot), what should be returned if we query EXTCODECOPY or CODECOPY on current account? (and what if we do this outside of the CREATE frame?) What if we return empty code? Should we then interpret it as deploy the SETCODEFROM? (this is ambiguous and should be avoided)

I think it would also make sense to have an EIP (could be this one, or another) which proposes returning EIP-7702 delegators (maybe with a special type, so 0xef0102 to mark the account as non-EOA but rather as a deployed contract), which could make it possible to upgrade the code also (this ensures that existing code which is not EIP-7702 delgated cannot be upgraded). @wjmelements I suggest you draft an EIP for this :smiley: :+1:

1 Like

The former point makes sense. I’ll simplify the spec accordingly, so SETCODEFROM in initcode just terminates the CREATE path and deploys the code stored at the referenced address.

For the second point, it seems the current EIP-7851 did the same thing, or am I missing anything?

I believe we can completely omit SETCODEFROM in the constructor, This reduces the deployment vector for CREATE because we already have RETURN. Instead, we use an OpenZeppelin initializer written in bytecode, and it’s extremely efficient. Here’s an example:

// constructor
push1 0x03
dup1
push1 0x09
push0
codecopy
push0
return

// Only 3 bytes of runtime code are created, The additional costs are almost negligible
push0
calldataload
setcodefrom

For a 3-byte contract, there is a smaller constructor that uses PUSH3 instead of CODECOPY; it is one byte smaller (12 → 11).

PUSH3 MSIZE CALLDATALOAD SETCODEFROM
MSIZE MSTORE
RETURN(29, 3)

If you use my assembler (docs), it will use the smallest constructor by default.

1 Like

Nice considerations. And I think the following runtime code returned by the constructor might be safer:

PUSH20 <source_address>
SETCODEFROM

As mentioned above, this opcode should not compete with RETURN in the CREATE path. Keep it in the CALL path.

Yes. I mean this. This change will simplify some compatible considerations and keep the change minimal. The only tradeoff I can see now is that contract creations by sending nil to field transactions require two transactions now.

I believe that given the design of this opcode, common use cases are factory contracts and upgradeable contracts. I don’t expect direct deployments.

1 Like

This is slightly more expensive than supporting SETCODEFROM directly in CREATE/CREATE2 initcode, since the factory path needs a small initializer runtime and an extra call. I prefer the runtime-only design for now because it keeps contract creation semantics simple. We can revisit initcode support if the gas savings prove worth the added complexity.

I guess that the majority of the cost will come from state bytes temporarily created during the upgrade process. But since we’ve allowed code changes, considering a repricing of the cost would be helpful. Perhaps we should expand the EIP-8037 refill rule to include the case of updating code 0->x->0.

EIP-7851 lacks a path to set authorization indicators on contracts. One advantage of the EIP-7702 account over proxy contracts is reduced call depth. The call depth increases, the less gas is available for subframes due to the 63/64 rule. This may result in a larger minimum gas limit for valid transactions. The newer opcode you proposed further reduces gas usage during operation because we can work with monolithic contracts while still having the ability to upgrade.

I have some minor comments:

The ban of 0xEF means we also ban SETCODEFROM to these 3 specific addresses on mainnet asthey were deployed before the 0xEF code prefix ban. I don’t think this is a problem, but just so you know.

I feel like this EIP should require EIP-4758: Deactivate SELFDESTRUCT or should introduce a notice of this behavior if that EIP is not required:

The reason is that the assumption is that the client database already has a reference from codeHash => code and that this is unable to be deleted. Since we do not target EIP-7702 accounts (which codeHash can change) that is not a problem. However, this pattern is:

Call into contract A. Contract A deploys contract B with some code (this deployment returns to contract A). Now contract A creates another contract C, which in initcode SETCODEFROM contract B. Now from A call into contract B to SELFDESTRUCT B.

The client should realize that there is still a reference used codeHash => code in contract C.

In general I like this EIP. It should be updated to match Glamsterdam state gas pricing though, ping me if you need some help on that :smiley: :+1:

1 Like

Thanks for pointing these out!

I was not aware that three contracts had been deployed after the EIP-3541 state survey but before its activation. They are now documented this in the proposal and noted that excluding them has no negative effect on practical code reuse.

Since EIP-4758 is Stagnant, I did not add it as a dependency. Instead, I adapted the proposal to EIP-3529 and EIP-6780, added both as dependencies, and clarified that adopted code remains available even if a same-transaction source is deleted by SELFDESTRUCT. Let me know if there are better solutions.

I also updated the gas model to the Glamsterdam pricing introduced by EIP-8037 and EIP-8038. Would be glad if you can take a look.

The full changes are in PR #12259: Update EIP-8151: clarify code lifecycle and gas pricing by colinlyguo · Pull Request #12259 · ethereum/EIPs · GitHub

oh I see 4758 listed as A tier in the same H* upgrade, so if it will be shipped together, then the corner case handling can be simplified by a lot (without code changes, just spec changes). Will change the spec then.

Following up on my June comment (https://github.com/ethereum/EIPs/pull/11800#issuecomment-4708477441) about the EXTCODEHASH(self)/reentrant-CALL divergence — I want to correct my conclusion there.

Adoption was recorded during init but applied only at initcode completion, and I flagged that a deferred write leaves EXTCODEHASH(ADDRESS()) mid-init and reentrant calls into the under-construction account genuinely undefined.
But the fix that followed (banning SETCODEFROM) from initcode entirely goes far beyond the original observation.

The divergence only exists because of the deferred write. If SETCODEFROM writes codeHash immediately, in initcode exec. exactly as it already does at runtime, almost all of it disappears for free:

  • EXTCODEHASH/EXTCODESIZE(ADDRESS()) mid-init: return the adopted hash/size, same general clause already in the spec, no special case.
  • CODESIZE/CODECOPY inside the still-executing initcode frame: keep returning the initcode’s own bytes, since the frame keeps running its already-loaded code — same rule the spec already states for runtime adoption, and already how CREATE isolates initcode today IIUC.
  • Revert semantics: Fine, the codeHash write reverts with everything else in the frame.

What’s left af,ter making the write immediate?

CREATE’s deposit step unconditionally treats initcode’s RETURN bytes as the code to deploy. If SETCODEFROM already set the code hash mid-init, we might have a slight issue.

if codeHash(target) == EMPTYCODEHASH after initcode returns:
# unchanged today: RETURN bytes are the deployed code 
else:
# SETCODEFROM already adopted code; ignore RETURN’s bytes entirely
# no deposit charge, no set_code call, no re-check.

This isn’t a new shape of rule for CREATE either. We have another example with EIP-6780 already lets a second in-frame mechanism (SELFDESTRUCT in the same creation tx) override what the plain RETURN-deposit path would have installed. This indeed was suggested by @matt in a chat with the authors.

There’s another issue related to it.

This should fix any issues we have with SETCODEFROM

1 Like

Hi @colinlyguo,

Thank you for framing this proposal. Combining bytecode deduplication (EIP-8058) and permanent EOA migration (EIP-7851) into an EVM-level code adoption primitive is an elegant simplification, especially as Ethereum prepares for state growth repricing and post-quantum migration paths.

Below is an analysis of how this opcode balances state economics, execution semantics, and protocol security boundaries:

1. State Economics vs. Indirection Overhead (EIP-8037 Alignment)

  • Direct Execution over Indirection: While minimal clones (EIP-1167) minimize deployment bytes, they impose ongoing execution overhead via DELEGATECALL trampolines on every invocation. SETCODEFROM delivers native bytecode execution while keeping state duplication to zero.

  • Storage Deduplication: In client key-value databases (Pebble, RocksDB), contract code is keyed by code_hash. Under state growth repricing proposals like EIP-8037, penalizing duplicate bytecode becomes critical. SETCODEFROM allows thousands of account or token instances to share a single canonical code entry in client storage without paying repetitive code-deposit gas.

  • State Verification Safety: Sourcing bytecode from a live account address rather than an arbitrary 32-byte hash prevents state-bloat vulnerabilities where accounts point to unresolvable or missing code hashes.


2. Irrevocable EOA Migration & ECDSA De-authorization (EIP-7851 & EIP-3607)

The interaction between SETCODEFROM, EIP-3607, and EIP-7702 is one of the most compelling aspects of the design:

  • The EIP-7702 Security Gap: EIP-7702 provides powerful runtime delegation, but because the underlying ECDSA private key remains the cryptographic root of the account, it remains permanently vulnerable to future ECDSA derivation or post-quantum Shor’s algorithm recovery. An attacker with the private key can always broadcast a new EIP-7702 authorization tuple to override existing delegations.

  • Enforcing Permanent Irrevocability via EIP-3607: Under EIP-3607, transactions whose from address has non-empty deployed code are rejected at the consensus layer. By executing SETCODEFROM via an initialization transaction, an EOA transitions to an immutable contract account:

    1. ECDSA-originated transactions from that address are permanently disabled.

    2. Legacy private keys cannot broadcast subsequent EIP-7702 redelegations.

    3. Authentication is handed off entirely to smart contract validation logic (e.g., lattice-based schemes like ML-DSA / FIPS 204 or multi-party authenticators) initialized in account storage prior to code adoption.


3. Considerations for Standardization & Compilers

  1. Self-Mutation Constraints:

    • Does the current specification restrict SETCODEFROM execution exclusively to initialization contexts (e.g., during CREATE/CREATE2 or migration envelopes), or can an account execute SETCODEFROM post-deployment if the instruction exists in its runtime code?

    • Clarifying whether dynamic runtime re-adoption is permissible—and the security implications for static contract verification, auditing, and immutable guarantees—will be essential for tooling and compiler developers (Solidity, Vyper).

  2. Gas Pricing of SETCODEFROM:

    • Accessing the source account touches the state trie (an account lookup / warm-cold read) and reads its code_hash. Gas pricing should align with standard account access costs (e.g., EXTCODEHASH base cost + warm/cold read rules under EIP-2929).

However, older implementations do not benefit from this incentive; in this regard, EIP-8058 is vastly superior, as it allows legacy factories (such as Uniswap V3) to take advantage of the discount. These two EIPs are not necessarily mutually exclusive, as each has its own area of ​​specialization: 8058 focuses on deduplication, whereas 8298 is geared more towards quantum resistance and upgradability.