If I’ve understood correctly then for it to be possible to mutate contract A’s byte code, contract A must somewhere call selfdestruct. If that’s the case, a big red warning on etherscan.io for post-C contracts which contain selfdestruct opcode would probably make this exploit / obfuscation near useless.

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

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 contrac

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 or callcode opcodes appear in the contract,

If there are delegatecall or callcode opcodes, ensure that it’s not possible to somehow reach a selfdestruct 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 a require 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.

Ensure that no selfdestruct , delegatecall or callcode opcodes appear in the contract,

If there are delegatecall or callcode opcodes, ensure that it’s not possible to somehow reach a selfdestruct 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 a require statement in the constructor or some other means.

Ensure that no selfdestruct , delegatecall or callcode opcodes appear in the contract,

If there are delegatecall or callcode opcodes, ensure that it’s not possible to somehow reach a selfdestruct opcode

I think there are two issues at stake here, that need some thought before committing to a course of action.

Ethereum, and crypto in general, is already seen as very beginner-unfriendly. I completely understand, and largely support, the underlying reasons for this, but allowing this pattern of contract creation would move the bar higher. Before interacting with a contract, a user will either need to trust some audit process, which I really feel goes against the spirit of Ethereum, or have a good understanding of contract operation and commit a considerable amount of time to check a potentially long chain of contracts.

The second - for the additional functionality provided by CREATE2, we effectively lose selfdestruct as a useable function, and delegatecall and callcode will become so suspicious as to become unusable in well-intentioned contracts. Are the benefits worth it?

In summary, I feel like this would take Ethereum in a direction that aligns with the worst of its public perception, as well as losing functionality.

Before interacting with a contract, a user will either need to trust some audit process, which I really feel goes against the spirit of Ethereum, or have a good understanding of contract operation and commit a considerable amount of time to check a potentially long chain of contracts.

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?

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 use EXTCODEHASH 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 receivingcalls.

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 calls. Alternately, you can deploy contracts with actual logic that employs “power features” and processes incoming calls, 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, its EXTCODEHASH 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 the delegatecall 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 calls, 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.