ERC-8152: Content-Addressable Logic Modules (CALM)

UPDATE: The discussion below resulted in tthe following ERC PR: Add ERC: Content-Addressable Logic Modules (CALM) by radeksvarz · Pull Request #1521 · ethereum/ERCs · GitHub


I propose to align on the following potential EIP that enables etching of the runtime bytecode onto the derived address by anyone.

I have been thinking about this since the creation of the YAC3F CREATE3 factory ( GitHub - radeksvarz/yac3f: Yet Another CREATE3 Factory ) and discussion on Pascal’s and Matt’s CreateX ( 📖 Private Key Management of `CreateX` Deployer · pcaversaccio/createx · Discussion #61 · GitHub ) seeing how the deterministic deployment infra must be over complicated and cost ineffective (e.g. multiple passes of bytecode in calldata).

I also wonder why such an uncomplicated improvement was not put on the table with EIP 7702 and other AA, which would benefit from it for its adoption.

Therefore I am inviting at least @pcaversaccio + @mds1 (CreateX deployments), @PaulRBerg + @zerosnacks and others (Foundry and Reth), @holiman and others (Geth), @mudgen (Diamonds), @yoavw and others (AA) and anyone to give their opinion / co-author and advocate such EIP.


Abstract

This EIP proposes a new precompile that allows contracts to be created at addresses derived directly from their runtime bytecode. This facilitates deterministic deployments based on code content and enhances the capabilities of externally owned accounts (EOAs), in line with the concepts of EIP-7702.

Motivation

Deterministic deployments: Addresses the challenge of deploying contracts deterministically across different Ethereum Virtual Machine (EVM) compatible chains, especially when specific deployment factories (e.g. CREATE2, CREATE3) are not available.

Trustless and independent deployments: Allows anyone to deploy the same contract to the identical address across compatible EVM chains.

Enhanced EOAs: Supports the adoption of EIP-7702 enhanced EOAs / Smart accounts.

Reduction of the calldata: Eliminates the need to include initcode, reducing calldata size during deployment and multiple calldata passes when using factories

Improved security: Intrinsically linking code to its address can improve security and limit phishing attempts by making it harder to deploy malicious code under a seemingly benign address.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Precompile Address: yet TBD (example: 0x00000000000000000000000000000000000000ee).

Input Parameters: runtimeBytecode: bytes - The runtime bytecode of the contract to be deployed.

Output Parameters: contractAddress: address - The address of the deployed contract.

Functionality

Address derivation: The contract address is calculated as the rightmost 20 bytes (160 bits) of the Keccak-256 hash of the provided runtimeBytecode.

Contrary to the CREATE opcode, neither msg.sender, nor sender’s nonce is taken into consideration. Contrary to the CREATE2 opcode neither msg.sender, nor initcode, nor salt are taken into account.

Deployment: The runtimeBytecode is deployed to the derived contractAddress.

Error handling:

The precompile should handle errors similarly to the CREATE/CREATE2 opcode.

State changes done by the current context are reverted in these cases:

Not enough gas.

Not enough values on the stack.

The current execution context is from a STATICCALL (since Byzantium fork) or an EXTSTATICCALL (since EOF fork).

Provided runtimeBytecode size is greater than the chain’s maximum runtimecode size.

Collisions of resulting address with the sanctioned range.

Sanctioned address range:

0x0 … 0xff

0x0000000000000000000000000000000000000100 and 0x00000000000000000000000000000000000001ff as defined in EIP-7587

In the case the resulting address is calculated within these ranges, the precompile reverts. Authors of the contract are advised to add an arbitrary byte to the runtimebyteCode as a workaround.

Gas cost:

Similar to the CREATE opcode with modifications, where init code cost and deployment_code_execution_cost are 0 as there is no initialization code to execute.

The new contract address is added to the warm addresses.

Rationale

No initcode

Initcode traditionally serves two primary purposes: initializing contract storage and generating runtime bytecode. However, this precompile is designed to bypass both of these steps. The runtime bytecode is directly provided as input, and the intention is to deploy it without any initial storage setup. This design choice is deliberate, aiming to create contracts with immutable bytecode at addresses derived solely from their code content. Consequently, initcode becomes redundant and irrelevant in this context.

Precompile

The choice to implement this functionality as a precompile, rather than a new opcode:

Precompiles can be used by all contracts, and EOAs directly. This EIP mitigates the problems with the CREATE2 opcode that led to the need for the CREATE2 factory.

Introducing a new opcode increases the complexity of the core EVM. Precompiles, being external to the core execution engine, minimize the impact on the protocol’s complexity.

Backwards Compatibility

This EIP introduces a new precompile and does not affect existing contracts or functionality.

Security Considerations

Immutable code:

Any change to the runtime bytecode, would change the address of the contract.

1 Like

If the chain doesn’t have CREATE2, it’s even less likely to have your precompile.

There are already deterministic deployment schemes using CREATE2. Which EVM chains don’t have CREATE2?

1 Like

Thanks for the good point.

At first I would like not to call it “my” precompile :wink:

Indeed - I also built that CREATE3 factory using CREATE2 opcode. If you mean CREATE2 Nick’s factory - it has a few drawbacks:

a) chaining CREATE2 opcode is passing initcode several times in the calldata (once from EOA, 2nd time from factory to created address)

b) there is the prebuilt transaction, that needs to pass and not fail to deploy the factory - that is a risk (I remember Gnosis does not have one deployment on some chain due to failed transaction - low gas. That impacted nonce of the factory deployer.

c) It does require a non-EIP155 transaction to get deployed. So for EIP155 compliant chains it requires “nonstandard state-change” to deploy this factory (as it was on Base).

If you mean CREATE2 opcode - I am not aware of any not having that. If you mean Nick’s CREATE2 factory - I remember some CREATE3 projects menitoned Nick’s tx is not deployable on certain chains. Unfortunately I do not remember exactly which ones, as that was almost 2 years ago.

Regarding the precompile

there are also other options - Anvil/Foundry has the setCode RPC method. Any of such options has some negatives:

  • Opcode - not callable by EOA
  • RPC method - not callable by other contract

In general I might be wrong in the terminology - it is not to be “precompile of the solidity code”. The general idea is that calling the certain address with bytecode in calldata would etch such bytecode onto the keccak(runtimecode) derived address.

I hope it makes more sense now.

Regarding the failed tx of CREATE2 factory deployment - here is some related discussion: Gnosis Safe: Chiado v.1.3.0 - unsupported base contract - Ethereum Stack Exchange

IMHO this is a huge risk if you need to ensure the same address for your runtime code on every possible chain.

1 Like

We should move away from precompiles IMHO. Both EIP-7266 (blake2f removal) and EIP-7666 (EVM-ify identity precompile) make a solid case for it. Some thoughts:

  • ZKStack-based EVMs aren’t fully EVM-equivalent when it comes to CREATE and CREATE2. How do we deal with this?
  • No more precompiles. If something is truly essential, it should be either deployed as EVM code or made an opcode (at the risk of a consensus bug which is another discussion).
  • Multichain rollouts are a nightmare. Instead of trying to standardise precompiles across an increasingly fragmented ecosystem, it’s better to push for factory contracts as predeploys—like how CreateX is a predeploy (they call it preinstall) on OP Stack.

You write:

Trustless and independent deployments: Allows anyone to deploy the same contract to the identical address across compatible EVM chains.

CreateX is now deployed on 137 chains, is completely stateless and trustless at runtime. How many more do we really need?

My overall biggest challenge here is based on practical experience of being the author of CreateX: The truth is that EIPs and keyless deployments don’t scale in such a fragmented world. We have sacrificed the beauty of keyless deployment for a fallback option in CreateX, particularly to be future-proof and scalable. As a reminder, there are presigned txs available, but e.g. on Filecoin you need to set 120m gaslimit… - the world is dirty out there and no EIP will solve this problem perfectly across all EVM chains as it requires all EVM chains to upgrade.

Also, without going into my major concerns regarding EOF, let me link here a relevant EIP

See the section: Creator Contract. They plan to introduce a predeployed Creator Contract.

1 Like

Thx. And related interesting discussion: Update EIP-7873: Creator Contract - revert reason & magic value by pdobacz · Pull Request #9391 · ethereum/EIPs · GitHub

From which it looks like the similar bootstrapping address is to be existing for EOF deployments.

IMHO still too much complex comparing to just pure etching on keccak(runtimecode).

How about something like this?

It has a deterministic address and uses 7702 on top of an EOA with a provably-unknown private key in a way that is trustlessly (and cheaply) verifiable on chain.

Just a side note - the predeployed Creator Contract is being dropped from EIP-7873 and the EOF plan for Osaka (see PR and some discussion on that, as it is not absolutely necessary to bootstrap EOF. Its spec is planned to be re-added in a separate EIP or ERC, but would become optional to EOF roll-out.

OTOH you could consider leveraging the InitcodeTransaction type from EIP-7873, in order to make the design EOF-compatible, i.e. read the runtimeBytecode from there, rather than calldata.

Interesting. Will have to make some tests with that concept.

I can see only 2 limitations now:

a) requires 7702 → limits projects on which chains to deploy - but that would be the problem with other EIP implementations anyway
b) additional delegation gas for each tx

Hopefully a) will diminish in due time.

Thanks!

Since the adoption of 7702 is very slow and uncertain across many chains, following related ERC PR is submitted: Add ERC: Content-Addressable Logic Modules (CALM) by radeksvarz · Pull Request #1521 · ethereum/ERCs · GitHub

@SamWilsn KIndly asking for the help in how one should link to other ERCs now - see HTMLProofer error - internally linking to ./erc-7201.md, which does not exist
and EIP links

Fixed up what I could, but you’ll need to remove the external links please!

1 Like

@SamWilsn I cleaned up ERC to pass through, now only HTMLProofer raises a strange error.

@wjmelements and others - this ERC is also related to the discussion regarding Diamond facets in ERC 8109 - ERC-8109: Diamonds, Simplified - #70 by mudgen

I plan to move this ERC into the final state rather quickly and start deploying corresponding infra.

So any suggestions / remarks are welcome.

I think you should require the CREATE2 salt MUST be 0.

If you would standardize the one-size-fits-all constructor (600b380380600b3d393df3), it would be possible to calculate the code deployed from a particular sender. If you standardized the sender, it would be possible to calculate the address. That these aren’t standardized is a step away from the registryless-registry concept. Maybe it’s fine to allow these as degrees of freedom though.

Other benefits of standardizing the runtime constructor:

  • it is easy to identify and verify CALM delegates from their initcode
  • it is guaranteed that constructors won’t set immutables, call external functions, or otherwise conditionalize the code

I wouldn’t worry that the standard runtime constructor might not be the one used by solidity or vyper. If you open a PR for it they may add support.

On the other hand, since these delegates were keyed by their initcode rather than their bytecode, you could support immutables, which are common ways to parameterize code that people find useful. Perhaps solidity will provide other ways to parameterize code in the future, such as C++-style generics.

@wjmelements Great points.

I wrote those constants (salt = 0, constructor = 0x600b380380600b3d393df3 ) as the example in order for the standard to be flexible for the future (like if/whether EOF is implemented).

In fact those constants are within HashCarve (CALMs deployer): GitHub - radeksvarz/hashcarve: A multichain-consistent deployer for content-addressable runtime bytecode on the EVM.
but the standard checks did not allow to make links to the HashCarve repo.

So the question is whether the standard should be specific (i.e. having stated the deployer address MUST be 0x9c8D020b832Ee8AAF92cB555819Dc8a0c1097F56, within which salt = 0, constructor replacement = 0x600b380380600b3d393df3) or to keep the standard generic with these as the example?

I felt standards should be rather generic / flexible and that HashCarve is rather the implementation (that might get traction or be replaced by something better).

I have not thought about such support, but you are right - once CALMs with the deployer’s address gain traction, frameworks might provide that direct support.

1 Like

Also, since CALM contracts are to be multichain ready for the broad spectrum of chains, I am considering to declare standard MUST condition of the target architecture declaration

A) either within the verified source code (for CBOR-less contracts)

B) or to be ensured within the published Metadata file linked from CBOR.

C) Or to declare that the target architecture MUST be paris.

1 Like

@vbaranov what would be the preference of the explorers?

As of some feedback option C) “target evm = paris” is not suitable as the standard would not be compatible with multichain evm evolution. Thx @mudgen and a BeerFi community for this point.

So it is much better to have CALM contracts self-report the evm version. ( And it is crucial for the multichain reusage - as we need to avoid the issues when a contract is replicated to the slightly incompatible evm chain - think PUSH0 / CLZ opcodes . )

As shown at https://playground.sourcify.dev/ CBOR itself does not contain evm version (yet it could), whille it includes hash of metadata, which contains “settings.evmVersion” if compiled by Solidity.

@wjmelements @pcaversaccio What would be the best way to store the info about evm version attached to the contract? Can we ask compiler teams to include the evm version directly into the CBOR as some version byte like solc is?

hmm I’m not fully convinced this is sufficient tbh. A CALM contract can simply lie about its EVM version; I mean a runtime self-report is just code, so there is no cryptographic guarantee that the declared revision matches the actual opcode assumptions. Also, even metadata is only reliable if the contract is properly source-verified (e.g. via Sourcify; fun fact I removed the bytecodeHash in CreateX).

Otherwise, it’s just another “claim” IMO.

Strictly speaking, the only trustless way to determine the minimum required EVM revision is static bytecode analysis: inspect which opcodes and features are actually used and derive the minimum compatible fork from that.

So perhaps CALM should:

  1. Allow self-reporting for UX/tooling purposes,
  2. But require tooling to independently verify opcode compatibility,
  3. Or even move toward capability-based declarations instead of fork names (in theory it’s possible that a contract only supports a partial EVM version).
2 Likes

Love the idea of having IPFS-like list of contracts addressed by their logic.

Since the biggest problem is deployment, would it make sense to propose a new CREATE opcode? No constructor, no deployer address, only runtime bytecode. It would guarantee that CALM contract addresses are the same throughout different chains, not depending on the public constants (which are for some reason inputs in the address calculating function) or CALM factory evm version.

Create2/X factories can be deployed by this new opcode.