For this to work, you would also need to check for delegatecall
and callcode
This might sound crazy, but hear me out: couldnât this also be considered a feature? Obviously, there will be a lot of education, mitigation, etc. required in cases where immutability is the desired behavior, but in the case of upgradeable contracts this could be a great way to cut down on the overhead of needing to delegatecall
out to a logic contract, especially in cases where persistent contract storage isnât required.
I know this is likely a controversial perspective, and any upgradeability mechanism should of course be paired with adequate precautionary measures and / or governance, but if it works, it would be a pretty cool positive unintended consequence!
Interesting thread. I suggest modifying the semantics of CREATE2 so that the created contract starts with a unique nonce, using mechanism similar to what was suggested in dust removal replay protection EIPs
disagree.
We need a robust immutable identity for the things we are assume to be immutable. Immutability check should work without lookup through the chain history.
In case of upgradable contracts we should just follow version paradigm: any change is a new version (with own immutable ID). It should be an explicit decision which version (inclusive HEAD) to follow. That is why I am not quite happy with current proxy based solution, but your proposal will make things even worse.
For my taste I would implement upgradable contracts by using ENS resolution.
Look, I agree that this will make things a little trickier than before. However, there will still be ways to perform the immutability check without looking at the history:
- Ensure that no
selfdestruct
,delegatecall
orcallcode
opcodes appear in the contract, - If there are
delegatecall
orcallcode
opcodes, ensure that itâs not possible to somehow reach aselfdestruct
opcode, - Enforce that the contract has been created by some trusted source (such as an established
CREATE2
factory contract that maintains a mapping of deployed contract addresses and will not allow a contract to be redeployed) via arequire
statement in the constructor or some other means.
The need for upgradeable contracts has been well-established at this point, and many of the most high-impact issues in Ethereumâs history thus far could have been mitigated with adequate upgradeability mechanisms in place. Obviously itâs not a paradigm that should be forced onto everyone, but if there are ways to implement it more efficiently, it will greatly benefit those that do need it. Both the proxy pattern and ENS resolution result in increased overhead vs. a direct equivalent, but an optimal upgradeable contract would operate on par with a standard contract from an efficiency standpoint. This could be a way to achieve that aim, assuming the corresponding risks can be properly addressed.
This is a good idea! A proxy contract which is rather easy - you send the seed and the init_code to this contract and it has a mapping(address => bool)
which holds if a contract is deployed at this address (hence the return code of CREATE2 or generated by using the definition of address generation by CREATE2). It sets the value of the address key to true if a contract has been created successfully at this address. If the value of this key is true before creating the contract it reverts.
Is there any design pattern that would want a contract deployed with CREATE1
by a contract deployed with CREATE2
that isnât looking to take advantage of this? If there isnât, any contract deployed by this method can just be flagged as being able to have its code change.
I think it might end up being a lot trickier than before. For example, it seems that the only possible secure contract is one that contains no selfdestruct
, delegatecall
, or callcode
. Otherwise, youâd need to verify not only that delegated calls cannot possibly hit a selfdestruct
opcode but also that the delegate contract contains no selfdesctruct
opcode itself. What if the delegate contract, itself, contains delegated calls? The dynamics of this question get complicated pretty rapidly.
Also, as @carver pointed out, there are cases where you have to perform a similarly chained verification process in the reverse direction, verifying the properties of creator contracts and the creators of those creator contracts, and so on.
The recursive nature of this problem gives me the intuition that there are even more complex dynamics still lurking beneath the surface that may yet be discovered.
We can deploy a âSafe CREATE2 deploymentâ contract where it is not possible to recreate at the same address.
Just in case this is actually a debate about removing CREATE2 from Constantinople, or delaying it, then I oppose both.
I think the checks @0age lists are easy enough to make:
Any system not following those parameters should raise red flags.
Not to disagree with myself, but I will admit I am worried about an attack that includes some social engineering.
Specifically:
- Project team gets and audit on system using a DELEGATECALL proxy pattern for upgradability
- Project team âForgetsâ to mention that it will be deployed using CREATE2.
- System looks OK to auditors.
- ⌠the rest is self explanatory
The only real protection against this requires a lot of UX and engineering work by etherscan et al.
From @rajeevgopalakrishna:
For example, the init code could load bytecode from another contract and return it. So the init code doesnât change, but returns arbitrary contract code to deploy.
Iâm assuming this refers to the EXTCODECOPY
opcode. Wouldnât the returned value always be the either:
- The same code or
- Empty (in the case that contract has been selfdestructed)
This is with the assumption that the init_code is exactly the same, due to the collision resistance property of the keccak hash function.
However, (I realized this as I was typing this reply) maybe this can be used as a branch in the init_code to deploy different bytecode?
Good question, it doesnât have to use EXTCODECOPY. A couple things you could do with identical init code to deploy different code:
- init code returns a different value after a specific block height
- init code calls to a contract which returns code from an address held in storage, which can be updated.
Iâm sure thereâs more.
By âaudit processâ do you mean something wallets and interfaces do automatically to see if it was instantiated by CREATE2? I agree, that would be hard, and probably poorly standardized.
Would it be a bad idea to somehow mark contracts as originating from a CREATE2 op?
Is it intentional that this EIP allows selfdestructed contracts to be redeployed? It seems to me like more of an unintended side effect.
Thatâs my understanding.
Iâve been thinking more about this edge case over the last few days (as Iâm sure many of us have been), and I do agree that this is going to complicate things significantly on multiple fronts. That being said, Iâm starting to realize that this could open up a whole new paradigm of contract that could improve the user experience immensely, by enabling the creation of transient workspaces that can be used in place of EOAs or simple proxies.
Hereâs what I mean by transient workspaces: right now, if you want to interact with a contract, you have three main methods of doing so, each with its own limitations:
-
Call right into the smart contract from the originating EOA, passing in the function selector and arguments as
calldata
. This is the standard method, but prevents the caller from accessing many âpower featuresâ like preconditions based on current EVM state (for instance, as discussed above you cannot useEXTCODEHASH
when calling from an EOA), requires that the entity in question is intrinsically tied to a particular keypair, and does not allow for the same address to also perform its own processing when receivingcall
s. -
Deploy a static contract that will relay the call into the target smart contract for you. Basic proxies and multisigs that simply pass along a bytes payload to the target will solve the issue with keypair coupling, but still cannot adequately tap into âpower featuresâ or intelligently send and receive
call
s. Alternately, you can deploy contracts with actual logic that employs âpower featuresâ and processes incomingcall
s, but then you are trapped: the logic will be permanent, and the identity associated with that address will not be portable in the same way that an EOA would be. -
Deploy an upgradeable contract (such as a transparent proxy with assignable âimplementationâ logic contracts) that will relay the call into the target smart contract. At first glance, this method seems to solve both the feature-deficit of EOAs and basic proxies as well as the malleability-deficit of standard contracts, but it falls short of the ideal outcome for both. In the former case, a proxy will not accurately convey its true behavior when it is the target of operations other than a standard
call
- for instance, itsEXTCODEHASH
will remain unchanged even when the implementation has been altered - and resists straightforward introspection. (If weâre really optimizing for the end-user and want to make contracts auditable, then this upgradeability pattern is not exactly making it easy!) For the latter, the logic may be upgradeable but the state is still persistent, which is also difficult to properly manage even with the proper education and tooling. On top of all that, the added overhead from thedelegatecall
to the implementation contract makes it more expensive to use.
Armed with a transient workspace, a user can set up an identity contract that contains whatever logic they want and track internal state however they want, accessing all the âpower featuresâ available to contracts and handling incoming call
s, then tear the whole thing down and start from scratch while still retaining ERC20 balances, accumulated reputation, or other external state referencing the address. Applications can set up âguard-railsâ for new users, helping to on-board them with a few pre-loaded methods while they start to accrue a reputation but enabling them to wipe the slate clean and take full control of the address when theyâre ready. And once the âtransientâ aspect is no longer desired, the deployed code can be made immutable by restricting access to selfdestruct
or removing it entirely.
An article on this topic from @carver which explains the various scenarios in detail with some examples: https://medium.com/@jason.carver/defend-against-wild-magic-in-the-next-ethereum-upgrade-b008247839d2
I think that would be helpful, as long as you mean that a contract is flagged if it or an ancestor contract was deployed with CREATE2
.