SETCODEFROM Code Reuse Instruction
Adds an instruction that sets an account's code hash from an existing deployed contract.
Abstract
This EIP introduces SETCODEFROM, an EVM instruction that sets the current account's code hash to the code hash of a source account with existing deployed code. The new code is visible to later code execution.
Motivation
This EIP has two primary use cases.
First, it improves code reuse and deployment economics. Deploying many contracts with identical runtime code is expensive, especially when contract deployment is repriced more closely to state growth, for example under EIP-8037. Initcode can initialize per-instance storage and then adopt shared deployed code, without paying code-deposit gas for identical runtime code. Existing contracts can also adopt a new shared implementation through their own upgrade logic.
Second, it provides an account migration path for disabling ECDSA-based EOA transaction authority. Migration code can store account-specific wallet state, including post-quantum (PQ) wallet state, then adopt regular wallet code. After that update, account control is through the installed wallet code rather than ECDSA transaction origination. This is because the account now has regular deployed code, not an EIP-7702 delegation indicator, so ECDSA-authenticated transactions remain invalid under EIP-3607. EIP-7702 authorization processing can no longer redelegate the account because the authority code check only accepts empty code or an existing delegation indicator. The account can still upgrade through upgrade logic in the installed code, for example by calling SETCODEFROM again or by using other methods. Protocol-level ECDSA transaction origination remains permanently disabled.
SETCODEFROM provides the code-adoption step for both cases after any per-instance state has been initialized.
Specification
Parameters
The opcode is defined as follows:
| Parameter | Value |
|---|---|
SETCODEFROM_OPCODE | TBD |
SETCODEFROM
SETCODEFROM(source) takes one stack item. The low 160 bits of the stack item are interpreted as source.
Before execution:
[..., source]After execution:
[..., success]success is 1 if source is a valid source account, and 0 otherwise.
The instruction is address-based: its input is source address, not a raw code hash. It reads source.codeHash from a live account in consensus state.
For this instruction, the current account is the current execution-environment address, meaning the account returned by ADDRESS and whose storage is affected by SSTORE. If the executing code was loaded from another account, including through EIP-7702 delegated execution, SETCODEFROM updates the execution-environment account, not the code source account.
A source account is valid for SETCODEFROM if all of the following are true:
- The source account exists in the state.
source.codeHash != EMPTYCODEHASH, whereEMPTYCODEHASH = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470.source.codeis valid regular deployed code under the active fork, e.g. it does not start with0xEF, which is reserved by EIP-3541 and used by EIP-7702 delegation indicators.- The source account was not created in the current transaction. An account counts as created in the current transaction exactly when EIP-6780 would let
SELFDESTRUCTdelete it. This restriction applies to the source only: the current account may itself have been created in the current transaction, including during its own initcode.
If executed in a static context, SETCODEFROM causes an exceptional halt.
If source is not a valid source account, SETCODEFROM pushes 0 and makes no state change.
If source is a valid source account, SETCODEFROM sets the current account's codeHash to source.codeHash and pushes 1. Subsequent changes to source must not affect the current account's codeHash and must not affect the availability of the bytecode referenced by that code hash.
State changes made by SETCODEFROM follow normal EVM revert semantics. If the frame or transaction reverts, the code update reverts.
Deployed Code Execution
When SETCODEFROM succeeds while executing deployed code, it updates the code hash of the current execution-context account.
The current execution frame continues running the code that was already loaded for that frame. The updated code hash is used by later code executing operations, including later calls to the same account in the same transaction, subject to normal revert semantics.
Contract Creation
SETCODEFROM is permitted during initcode execution, both in a contract-creation transaction and during CREATE and CREATE2. It behaves as specified above: the current account is the account being created, the code hash update takes effect immediately, and the creation frame continues executing the initcode already loaded for it. CODESIZE and CODECOPY in that frame continue to refer to the initcode being executed, while EXTCODESIZE, EXTCODEHASH, EXTCODECOPY and calls that target the account being created observe the adopted code.
When initcode execution completes successfully, contract creation installs code as follows:
- If the created account's
codeHashisEMPTYCODEHASH, the initcode's return data is validated, charged code-deposit gas, and installed as the account's code. - Otherwise the created account has adopted code through
SETCODEFROM. The initcode's return data is treated as empty for contract-creation completion and is not validated, hashed, installed, or charged code-deposit gas. No code-deposit cost is charged as part of this creation for the adopted code, and the account'scodeHashis left as adopted.
The account-creation costs of the creation itself are unchanged. In particular, if creation adds a new account leaf, the normal account-creation state-gas charge still applies. If the destination account already exists, no account-creation state-gas is charged, as usual.
When creation completes with code adopted through SETCODEFROM, no code-deposit state gas or code-deposit execution gas is charged for the adopted code. SETCODEFROM only updates the created account's codeHash to reference code that already exists.
Failure handling is unchanged: if the creation frame reverts or halts exceptionally, the code hash update reverts with every other state change in that frame.
ECDSA Transaction Origination
After SETCODEFROM installs regular deployed code on an account, the account is controlled through that code. The account has nonempty regular code, so an ECDSA-authenticated transaction whose recovered sender is that account is invalid under EIP-3607. It also disables EIP-7702 redelegation, because EIP-7702 authorization processing accepts only empty code or an existing valid delegation indicator for the authority account.
Gas Costs
The following gas parameters are used:
SOURCE_ACCOUNT_ACCESS_COSTisCOLD_ACCOUNT_ACCESSifsourceis cold, otherwiseWARM_ACCESSSETCODEFROM_SOURCE_GASisSOURCE_ACCOUNT_ACCESS_COST + WARM_ACCESSSETCODEFROM_CURRENT_ACCESS_GASisWARM_ACCESSSETCODEFROM_WRITE_GASisACCOUNT_WRITESETCODEFROM_STATE_GASis0
SETCODEFROM charges SETCODEFROM_SOURCE_GAS in execution gas for each execution attempt. This covers the source lookup and validation:
- carrying the
sourceaddress =0, charged by ordinary calldata or deployed-code costs - loading the source account leaf =
SOURCE_ACCOUNT_ACCESS_COST - validating source code =
WARM_ACCESS, a fixed charge covering the additional code-store access required when deployed bytecode must be inspected
If source is valid, the instruction charges SETCODEFROM_CURRENT_ACCESS_GAS before comparing source.codeHash with the current account's codeHash:
- accessing the already warm current account leaf =
WARM_ACCESS
If the two code hashes differ, the instruction charges SETCODEFROM_WRITE_GAS before updating the current account's code hash. Its write component follows EIP-8038:
- updating the current account's code hash =
ACCOUNT_WRITE
An invalid source charges neither SETCODEFROM_CURRENT_ACCESS_GAS nor SETCODEFROM_WRITE_GAS. A valid source with the same code hash charges SETCODEFROM_CURRENT_ACCESS_GAS but not SETCODEFROM_WRITE_GAS. Under the current EIP-8038 parameters, ACCOUNT_WRITE is 9000, COLD_ACCOUNT_ACCESS is 3000, and WARM_ACCESS is 100. The resulting execution-gas costs are:
| Result | Cold source | Warm source |
|---|---|---|
| Invalid source | 3100 | 200 |
| Valid source with unchanged code hash | 3200 | 300 |
| Current account's code hash is updated | 12200 | 9300 |
Under EIP-8037, SETCODEFROM_STATE_GAS is 0. SETCODEFROM performs no code-deposit operation. It only updates the current account leaf to reference the source's existing or pending codeHash => code mapping. The gas cost is not affected by source code size.
This also applies when SETCODEFROM executes in initcode. The enclosing contract creation may independently incur the normal state-gas charge for creating a new account leaf, but adopting code through SETCODEFROM incurs no code-deposit state gas and no code-deposit execution gas. In particular, neither cost depends on the size of the adopted code.
Block-Level Access Lists
For block-level access lists (BALs) as defined by EIP-7928, this EIP extends CodeChange with an optional trailing element, new_code_hash. A code hash change made by SETCODEFROM is recorded as that hash, never as bytecode:
# CodeChange: [block_access_index, new_code] code installed by contract creation or EIP-7702
# or [block_access_index, b"", new_code_hash] code adopted by SETCODEFROM
CodeChange = [BlockAccessIndex, Bytecode, Optional[Hash32]]If an account's code hash changes in a transaction and its value at the end of the transaction was set by SETCODEFROM, the change must be recorded as [block_access_index, b"", code_hash]. Every other code change keeps the two-element form. A BAL that records a code change in any other way is invalid.
The bytecode behind new_code_hash is in the pre-block state or in the new_code of a code_changes entry with a lower block access index; no entry refers to bytecode recorded at the same or a higher index.
Rationale
Using an address rather than a raw code hash avoids depending on client-local code database contents, which may differ across nodes. A newly synced node may have a smaller local code database than a long-running node because it may not keep historical or unreferenced bytecode entries. For this reason, this EIP makes SETCODEFROM copy the code hash from a live source account, so the adopted code hash is confirmed by current consensus state. A valid source cannot be deleted, but it can replace its own code hash later in the same block; the bytecode it deposited at creation must then still be persisted for the accounts that adopted it.
SETCODEFROM requires that the source account exists, even though a nonexistent account has no code and is already excluded by the EMPTYCODEHASH condition in a state model where a nonexistent account reads as an empty account. Implementations that represent a nonexistent account's code hash as zero rather than as EMPTYCODEHASH would otherwise accept such an account as a valid source and install a code hash with no code behind it. Stating existence separately removes that reading.
A SETCODEFROM code change records a code hash instead of bytecode because the bytecode already exists, in the pre-block state or in the code_changes entry of the creation that deposited it; repeating it would price BAL bytes far below any other operation (see Security Considerations). The change still needs an entry, because consumers that read an account's code at a block access index, such as parallel executors or nodes that apply the BAL without executing, would otherwise see the old code. The hash is a separate element of CodeChange because a 32-byte new_code value is indistinguishable from 32 bytes of runtime code, and keeping it inside CodeChange leaves each account with one list of code changes. With no bytecode fallback, an entry's form identifies its origin, two elements for creation or an EIP-7702 delegation and three for adoption, and its size is fixed, so a byte-floor scheme such as EIP-8279 can meter the instruction when it executes.
A source created in the current transaction is rejected because its code is transient: under EIP-6780 the account can be deleted before the transaction ends, and it can replace its own code hash with SETCODEFROM. Either way an adopter keeps a code hash whose bytecode is in neither the pre-block state nor the BAL, which records end-of-transaction values only and nothing for a deleted account. The gap is transitive, since a pre-existing account can adopt from the created source, be adopted from in turn, and then adopt something else, so checking the immediate source is not enough. Rejecting created sources is: every adopted code hash was then held, at the start of the adopting transaction, by an account not created in it, so its bytecode is in the pre-block state, in the new_code of an earlier creation, or was itself adopted earlier, to which the same argument applies. The cost is that a template cannot be adopted in the transaction that deploys it.
The instruction is self-only during deployed-code execution to keep authority local to the account whose code is executing. It does not allow the executing code to update any other account's code hash.
Allowing SETCODEFROM during initcode keeps one code-installation rule for creation: creation installs either the bytes returned by initcode or the code hash adopted during it, never both. The created account's code hash at the end of initcode execution decides which, so the two paths cannot interleave. Contract creation is not the first operation whose outcome is decided by state changes made inside the creation frame: under EIP-6780, SELFDESTRUCT executed in the transaction that created an account deletes that account after creation completes, discarding the code that creation installed.
Initcode support makes per-instance initialization atomic on every creation path. Initcode writes per-instance storage, adopts shared code with SETCODEFROM, and returns no data. A contract-creation transaction with a nil to field therefore initializes and adopts in one transaction, and a factory does not need to deploy an initializer runtime and call back into it.
For example, the instruction can be used in the following patterns.
An account migration function can store account-specific wallet state, then adopt a shared wallet implementation:
SSTORE(pq_pubkey_slot, userPubkey)
SSTORE(recovery_slot, recoveryConfig)
if SETCODEFROM(PQ_WALLET_TEMPLATE) == 0: REVERT()
STOP
An ERC-20 clone factory can deploy a tiny initializer with CREATE2, then call it to initialize token metadata and ownership before adopting a shared token implementation:
SSTORE(name_slot, "MyToken")
SSTORE(symbol_slot, "MYT")
SSTORE(owner_slot, msg.sender)
if SETCODEFROM(ERC20_TEMPLATE) == 0: REVERT()
STOP
Backwards Compatibility
This EIP requires a hard fork to implement because it introduces a new instruction which did not exist previously. As a result, already deployed contracts using this instruction could change their behavior after this EIP.
This EIP also alters the EIP-7928 BAL encoding by adding an optional new_code_hash element to CodeChange. A client implementing only EIP-7928 will reject blocks that use it, and vice versa.
The 0xEF source restriction also excludes three contracts whose code starts with 0xEF. These contracts were deployed on Ethereum mainnet after EIP-3541's state survey but before its activation. Because 0xEF is an undefined instruction that causes an exceptional halt, none of them provides executable source code. Excluding these contracts therefore has no negative effect on practical code reuse.
Test Cases
Security Considerations
SETCODEFROM can change the code used by later executions of an account. Contracts that expose this instruction must restrict access to trusted control paths, because a successful call can permanently change account behavior if the transaction does not revert.
SETCODEFROM changes account-code identity semantics. Code executing with an account's authority can replace the code identity visible to later execution and code-inspection with regular code from another account. This also departs from EIP-7702's conservative codehash-introspection design by introducing a related codehash-presentation risk through explicit code adoption, rather than through EXTCODEHASH following delegation.
Unlike EIP-6913, which forbids code replacement when the executing code differs from the account code, this EIP allows delegated execution to update the caller's account code. This preserves existing proxy upgrade patterns, which already rely on delegated code running with the caller's authority. Wallets must treat any such module as upgrade-authorized code, restrict delegatecall targets accordingly, and require explicit authorization before execution.
The source restriction requires valid regular deployed code under the active fork, e.g. code that does not use the 0xEF prefix reserved by EIP-3541 and used by EIP-7702 delegation indicators. It also excludes empty code. Precompiles are excluded without a separate condition: a precompile has no deployed code in state, so its address either does not exist in the state or has EMPTYCODEHASH. This prevents SETCODEFROM from bypassing deployed-code validity rules or treating precompile behavior as deployed code.
Protocol-level ECDSA transaction origination is disabled once the account has regular deployed code, meaning code that is not an EIP-7702 delegation indicator. Contracts that verify ECDSA signatures directly through ecRecover may still recover that address. This affects signature-based authorization such as permit. A companion ecRecover change, such as EIP-8151, can reject recovered addresses whose account code is regular deployed code rather than an EIP-7702 delegation indicator, and return 32 zero bytes.
Because the current frame keeps executing already-loaded code, implementations must clearly separate the executing code for the current frame from the account code visible to later calls. Re-entrant calls after a successful SETCODEFROM observe the updated code.
An account under construction can be called before its initcode returns, for example through a callback from a contract that the initcode calls. Such a call executes the code adopted by a successful SETCODEFROM earlier in the same initcode, against storage that the initcode has not finished writing. Initcode should complete per-instance initialization before adopting code, or otherwise guard against re-entrancy.
Recording adopted bytecode in the BAL would let one SETCODEFROM, at 9300 gas for a warm source, add up to the maximum code size to the BAL, below one gas per byte, where the EIP-7928 bal_items bound assumes small items. An adoption instead adds a 32-byte hash, and because a source created in the current transaction is invalid, the hash always resolves, so no adoption adds bytecode.
Copyright
Copyright and related rights waived via CC0.