The article is fairly old. I'd suggest taking a look at the current docs on Contract Medatata. They contain these notes:
The CBOR mapping can also contain other keys, so it is better to fully decode the data instead of relying on it starting with 0xa264
. For example, if any experimental features that affect code generation are used, the mapping will also contain "experimental": true
.
The compiler currently uses the IPFS hash of the metadata by default, but it may also use the bzzr1 hash or some other hash in the future, so do not rely on this sequence to start with 0xa2 0x64 'i' 'p' 'f' 's'
. We might also add additional data to this CBOR structure, so the best option is to use a proper CBOR parser.
In 0.4.24 metadata used to contain only the Swarm hash, which is why the starting byte was 0xa1
. The three higher bits say that it's a map (101
= 5
= map in CBOR) and five lower ones say that the map contains only one key (00001
= 1
). In contracts compiled with later versions the map contains 2 items (IPFS hash and the compiler version) which is why it changed to 0xa2
.
The following byte (0x65
) is packed in a similar way - it represents the type and the length of the first mapping key. It changed to 0x64
because the Swarm hash stored under bzzr0
key has been replaced with an IPFS hash stored under a key that is one byte shorter (ipfs
).
For robust detection it's best not to make too many assumptions about how this metadata is structured. Use a heuristic to detect where it starts but then use a proper CBOR parser to decode it. I think it might be best to look only for keys and ignore the type/length bytes. Looking at CompilerStack::createCBORMetadata()
in solc 0.8.9, I see just a few possibilities:
ipfs
bzzr1
experimental
solc
bzzr0
(was present in older versions)
Changes to metadata are always listed in compiler's changelog so keep an eye for newer versions but for the existing ones this should be about it.
Also, you only really need to strip this from the bytecode you get from the blockchain. For the sources you are compiling yourself you can use the --metadata-hash none
flag to get bytecode without a metadata hash.
Having said that, I'd strongly recommend not to ignore metadata during verification. If you use the JSON input that the compiler accepts (rather than flattened sources) and use the right version of the compiler, you should really get the same exact bytecode, including metadata hash embedded in it. Metadata contains hashes of all source files, their paths and compiler options used to build the bytecode so if you ignore these differences, you'll be potentially accepting modified sources - files with misleading comments, completely different variable and contract names, changed licensing info or even extra code that got optimized out. Actually, having the metadata JSON and original sources is always enough to recreate compiler input that exactly reproduces the bytecode. This is why it's recommended to keep and publish the metadata (e.g. using Sourcify).
The practice of ignoring metadata is outdated and comes from the limitations of older versions of the compiler and the verification tools. For example, in the past etherscan required you to submit a single source file, which is why a common strategy used by verification tools is to combine all your files into one. This requires an assumption that you can simply replace any import with the content of the corresponding file. This might have been true in the past but is not guaranteed to work now and new features that depend on separation between source units are planned. Since version 0.4.11 the compiler provides a standardized JSON interface and etherscan supports it too and this is the safer way to do verification.
Best Answer
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 aconstant
variable - in the contract bytecode, not in storage.Take the following contract:
After compilation, you will get the following opcodes:
In solidity, we can split this up into it's 3 sections (construction, runtime, metadata) by finding the 3
INVALID
opcodes.Contract Construction:
Runtime:
Metadata:
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.
Then, we do some checks to see if
msg.value > 0
Then we see these opcodes:
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.