Skip to main content

Verification with Immutable Varibles

Sourcify can verify contracts with immutable variables by comparing the creation bytecodes of the recompiled contract and the deployed contract. To get the creation bytecode of the deployed contract, we scrape the block explorers for the transaction that created the contract (that has the creation bytecode as tx.input) as a workaround.

Factory contracts with immutables

It is not possible to verify factory contracts with immutables (contracts created by another contract) on Sourcify, because the tx.input of a factory contract does not contain the creation bytecode but rather the function calldata. Also, sometimes the block explorer scraping fails when a CAPTCHA is required.

verifying older contracts with immutables

For older transactions the JSON RPC call eth_getTransactionByHash fails for geth full nodes (this is related to the geth flag --txlookuplimit), but works for archive nodes. This functionality is needed in order to provide verification for contracts with immutable that are older. Please check that at least one provided RPC is an archive node.

More details on how immutables affect verification below:

Immutable variables

It is not as straightforward to verify contracts with immutable variables because of their nature. Quoting from the Solidity docs:

The contract creation code generated by the compiler will modify the contract’s runtime code before it is returned by replacing all references to immutables by the values assigned to the them.

That means the deployed bytecode generated after recompiling will be different than the one already deployed on chain, because the references will have been "filled" with the values of the immutables when deploying the code.

For example in the bytecode excerpt from the Görli contract 0xbdde4d595f2cdda92ca274423374e0e1c7286426 (Etherscan, Sourcify) the reference of the simple immutable variable will be the 64 consecutive 0 in hex in the recompiled bytecode:

...282565b5050565b7f000000000000000000000000000000000000000000000000000000000000000081565b828054600181600116156101...

...which will be modified in runtime when deploying the contract to the value 2. So the deployed bytecode will be:

...282565b5050565b7f000000000000000000000000000000000000000000000000000000000000000281565b828054600181600116156101...

If the lengths of the deployed bytecodes of the recompiled contract and the deployed contract are the same, Sourcify will suspect this is because of immutable variables. It will then try to find the tx that created the contract by scraping the blockexplorer pattern given in sourcify-chains.ts. Some networks such as Telos, Meter etc. have APIs that provide the creation tx hash of contracts that we use instead of scraping.

After getting the creation code on-chain, Sourcify checks if this matches with the recompiled contract's, and verifies. The onchain data will also have contructor arguments to initialize the immutable values when deploying the contract. This is appended at the end of the bytecode when deploying and these are saved in the constructor-args.txt file of the contract in the repo.