Summary
You can examine the bytecode of the contract to find the value, the same applies for constant
s. 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.
public
instead ofprivate
to let the compiler create a getter for you. Otherwise, the only way to get that value is inspecting the code: the value assigned to an "immutable variable" at construction time is actually used to inject that fixed value inside the final runtime bytecode of the smart contract, everywhere it's used (i.e., in a getter function). Please note in your example you don't use that value, so no injection is made to the runtime code.