Etherscan won't handle constructor arg correctly

Hey there,

I'm having some issues verifying my contract through Etherscan - I've verified many before, but this one specifically is giving me issues.

To make a long story short, my contract is cloned from this one on mainnet. The constructor only takes one arg, which is an address.

I deployed using an address for that arg in my deploy script, and I checked my deploy transaction to make sure this was done correctly (it was - the ABI encoded address is appended to the end of the input data, as expected). Let's call this arg "0xf4d2" to keep it short.

However, when I try to verify through Etherscan, the bytecode that it generates/wants is different than what is generated from what I'm providing. There is only one diff (though it occurs three times): The bytecode that Etherscan generates/wants contains "0xf4d2" three times, while bytecode generated from my input doesn't contain it once, despite my submitting for verification with the constructor argument included. Everywhere the "0xf4d2" should show up in my generated bytecode, there's just the zero address. The length of each bytecode is exactly the same, there's just that difference in three places.

It's as if Etherscan is failing to account for the constructor argument that I'm submitting. However, when generating the bytecode it's looking for, it correctly substitutes in "0xf4d2" (three times), which is an immutable param that gets set in the constructor.

I've tried verifying through hardhat, remix, manually through Etherscan (multiple ways), all to no avail. Could this be an issue on Etherscan's side, or is there something I'm doing wrong?

Any help would be much appreciated, thanks!

I've been using clones on my current project and been thinking about a way to verify the clones. I'm generating the clone bytecode on a contract, but the reasoning should also apply.

To create a contract, a transaction is sent to address 0x00 with the data field set to EVM bytecode. Then the EVM executes it from offset 0, this is generally the constructor code. Then if finishes by returning a chunk of bytes which the EVM considers the contract bytecode

During the execution of the constructor immutable variables are replaced in the chunk of bytes returned, and the compiler sets the constants, this is probably why you are seeing 0xf4d2 instead of 0x00...

Can you post the code for the clone. I assume it looks something like this:

contract Clone {
  address immutable a;
  constructor (address b) { a = b; }
}

changing it to

contract Clone {
  address constant a=0xf4d2...;
}

and compiling it with the same settings might generate the required bytecode to verify

1 Like

@helio.rosa This is a great point! You're right, that's exactly what's happening - the immutable variable, which is set in the constructor, gets replaced during the actual contract creation. Hence why 0xf4d2s are seen in the bytecode that Etherscan wants...but Etherscan's verification system doesn't seem to be doing that replacement for my own contract+constructor argument that I upload.

The original code is here - mine that I deployed is the same. Though I also just tried verifying using this instead:

contract MultiRewardsDistributor {
// code here...
IERC20 public immutable looksRareToken = IERC20(0xf4d2888d29D722226FafA5d9B24F9164c092421E);
// code here...
constructor () { // removing 'address _looksRareToken' from constructor, bc can't set initialized immutable
_pause();
}
// code here...

Which sadly didn't work. I also tried keeping the constructor argument in the constructor definition, but simply not using it, and that also didn't work.

I'm leaning towards this potentially being an Etherscan issue, where (for one reason or another) constructor variables that set immutables and are passed in for verification do not correctly get considered to be immutables, and therefore do not correctly get substituted into the contract's bytecode. Which would explain why the bytecode it is outputting for my submitted contract + constructor argument does not have the 0xf4d2s in it :smiling_face_with_tear:

I'll also note that the original contract is compiled with 0.8.7, mine with 0.8.14

When you mentioned clone i was under the impression that you meant what we call clone in the EVM ecosystem (openzeppelin docs, EIP1167)

If you just forked the contract, the process should be simple. are you setting the correct compiler version, optimization runs and constructor args in the verification page ? Have you tried comparing the bytecode and checking if there is any other difference besides the address? You should be able to generate the same bytecode with the addr set to zeros.

Are you using the multifile verification ? it has failed for me in the past with the correct args. Flattening solved it then.

Ahh no that's my bad, should've said fork rather than clone.

I was setting compiler (0.8.14), runs (20000), optimization (on), and args correctly, but still wasn't able to get anything that wasn't containing that small diff where 0xf4d2s are all zero addresses.

However, flattening & going through hardhat worked this time :grinning:Very strange, but happy to have it verified. I really appreciated the help!