-
-
Published
Linked with GitHub
# SETCODE (EIP-6913) security considerations
## It breaks expectations for existing non-proxy contracts
- With an upgradable proxy, users know that the contract is a proxy and can evaluate it as such. They can check who is authorized to set the proxy implementation and make an informed decision about trusting that entity.
- With SETCODE, any DELEGATECALL can potentially change the implementation.
- ...recursively: If contract A has a DELEGATECALL to B, then the user must not only check for SETCODE in B, but also for DELEGATECALLs in B, which could update B's implementation.
- In practice this could make previously deployed contracts potentially-mutable - delegating to a library that suddenly becomes mutable.
- The DELEGATECALL-in-library issue was already bad due to the possibility of SELFDESTRUCT (e.g. the epic [I accidentally killed it](https://github.com/openethereum/parity-ethereum/issues/6995)), and we're finally removing that risk with the removal of SELFDESTRUCT. But SETCODE makes it much worse than SELFDESTRUCT - making the library mutable, recursively.
## Pricing discrepancy - DoS vector on EL nodes
- Code read is cheap, storage is expensive, for a good reason.
- Mutable code would need to be as expensive as storage, or else it would be abused as cheap mutable storage.
- We would have to adjust the price CODECOPY or SLOAD since they become equivalent. Either make SLOAD cost 3 gas, or CODECOPY cost 2100/100 per word (cold/warm). Both seem impractical.
- How can we break the equivalence of mutable code and mutable storage if they're priced differently?
## It breaks account abstraction.
Specifically it breaks ERC-4337, but I'd argue that it may be impossible to design a permissionless mempool for any form of account abstraction if SETCODE is allowed.
#### Mempool DoS with account abstraction
- With account abstraction, transaction validity depends on the state.
- If the validity of N transactions depend on a single storage slot, then an attacker can fill the mempool with valid transactions and have the first one change the storage slot, invalidating all the rest.
- ERC-4337 (and previous AA proposals like EIP-2938) go to great lengths to prevent DoS through mass invalidations (an O(1) operation by an attacker which results in O(n) invalidations of previously validated transactions in the mempool).
- They do so by enforcing certain rules during validation, e.g. 4337 allows each account to access only its [own storage or storage associated with its address (as mapping) in other contracts](https://eips.ethereum.org/EIPS/eip-4337#storage-associated-with-an-address). The attacker would have to mutate N slots in order to invalidate the transactions of N accounts.
#### SETCODE makes such protection impossible.
- Attack example:
- Create N accounts that use library L. The library may not even have SETCODE, but has a DELEGATECALL to a user-provided address so it's impossible to detect through static analysis.
- Send N valid transactions.
- The first transaction calls L and make it change its code, invalidating all the subsequent transactions for N-1 accounts.
- Keep flipping it back and forth, DoSing the mempool.
- To mitigate this, we would have to prevent accounts from being proxies or accessing libraries. In practice it would make account abstraction severely limited.
- Existing rollups with enshrined account abstraction (zkSync, Starknet) adopted [similar protection rules](https://era.zksync.io/docs/dev/developer-guides/aa.html#extending-eip4337), so SETCODE would introduce a DoS vector against them.
It appears that code mutability is mutually exclusive with permissionless mempools for any form of transactions with state-dependent validity.
## Code mutability is a bug, not a feature
- Smart contracts were meant to be immutable and provide certain guarantees.
- Contracts can make themselves explicitly mutable by implementing a proxy pattern. It's fine because users can see it, but by default they should guarantee immutability.
- SETCODE (or any other form of code mutability) makes it impractical for users to verify immutability (due to recursive DELEGATECALLs).
- We accidentally enabled code mutability through SELFDESTRUCT+CREATE2. [Metamorphic contracts](https://github.com/0age/metamorphic) are an unintended consequence, not an explicit design choice. We should fix this oversight, not enshrine it.
# Update:
We discussed a change to this EIP: SETCODE would revert if called in a DELEGATECALL context. It will only be able to operate on the contract where it occurs.
It makes mutability explicit and easy to spot in static analysis, this solving most of the issues above, except for the pricing issue.