Solidity – How to Read a Private Immutable Field in Smart Contracts

bytecodeethers.jsetherscanevmsolidity

In solidity, immutable fields are stored on bytecode and not at the storage slot. So, if I want to query how can I do it?

eg:

pragma solidity >=0.7.0 <0.9.0;

contract MyContract {
  uint private immutable myValue;

  constructor (uint someValue) {
    myValue = someValue;
  }
}

So, in this case, how can I read myValue ??

In the Bytecodes view in etherscan I can see PUSH32 0x000000000000000000000000000000000000000000000ff2056c038062100000 which is the hex of the someValue that I passed to the constructor.

But is there a better/simpler way to have a getter for the private immutable fields?

Best Answer

Summary

You can examine the bytecode of the contract to find the value, the same applies for constants. This way, you could find any value.

You would have to examine the contract bytecode to figure these values out.

Explained

A private immutable is stored in the same location as a constant variable - in the contract bytecode, not in storage.

Take the following contract:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

contract MyContract {
  uint256 immutable private i_myNum;

  constructor() {
    i_myNum = 7;
  }
}

After compilation, you will get the following opcodes:

PUSH1 0xA0 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x7 PUSH1 0x80 DUP2 DUP2 MSTORE POP POP PUSH1 0x80 MLOAD PUSH1 0x3F PUSH1 0x2E PUSH1 0x0 CODECOPY PUSH1 0x0 POP POP PUSH1 0x3F PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 SAR 0xF8 0xCC SWAP3 EXP 0xD5 CALL SIGNEXTEND PUSH24 0x87D051DBA8AE93B2D48F9CB166A944EC16E3EDE76D5BD64 PUSH20 0x6F6C634300081200330000000000000000000000

In solidity, we can split this up into it's 3 sections (construction, runtime, metadata) by finding the 3 INVALID opcodes.

Contract Construction:

PUSH1 0xA0 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x7 PUSH1 0x80 DUP2 DUP2 MSTORE POP POP PUSH1 0x80 MLOAD PUSH1 0x3F PUSH1 0x2E PUSH1 0x0 CODECOPY PUSH1 0x0 POP POP PUSH1 0x3F PUSH1 0x0 RETURN INVALID 

Runtime:

PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID 

Metadata:

LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 SAR 0xF8 0xCC SWAP3 EXP 0xD5 CALL SIGNEXTEND PUSH24 0x87D051DBA8AE93B2D48F9CB166A944EC16E3EDE76D5BD64 PUSH20 0x6F6C634300081200330000000000000000000000

We know that immutable variables are set in the contract construction, so we can start looking at just that code, and breaking it out into what each set of opcodes is "doing".

The first few opcodes are just setting up a free memory pointer.

PUSH1 0xA0 PUSH1 0x40 MSTORE 

Then, we do some checks to see if msg.value > 0

CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT

Then we see these opcodes:

JUMPDEST POP PUSH1 0x7 

And after breaking down the rest of the opcodes, we find out they are loading 0x7 (aka, 7) into memory to assign to our private variable.

Related Topic