Description
Description
The evm.deployedBytecode
output in Standard JSON is available both for Yul and Solidity, but works reliably only for Solidity. In Yul a heuristic is used to find the runtime object and the presence of more than one nested object is enough to confuse it. The result is that for contracts with bytecode dependencies the output is empty. This makes the result of two-stage compilation via Yul different than single-step compilation.
The heuristic is inconsistent with Specification of Yul Object, which documents the _deployed
suffix as the factor determining the runtime object.
The problem exists only in the Standard JSON interface because CLI does not provide outputs for the deployed bytecode.
Environment
- Compiler version: 0.8.28
- Compilation pipeline: Yul via IR
Steps to Reproduce
Single-step compilation
step1.json
{
"language": "Solidity",
"sources": {
"input.sol": {"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity *; contract C {} contract D { bytes b = type(C).creationCode; }"}
},
"settings": {
"outputSelection": {"*": {"*": ["evm.deployedBytecode", "ir"]}}
}
}
solc --standard-json step1.json | jq '.contracts."input.sol".D.evm.deployedBytecode' --indent 4
{
"functionDebugData": {},
"generatedSources": [],
"immutableReferences": {},
"linkReferences": {},
"object": "60806040525f5ffdfea2646970667358221220441547fad1e807dbbd779e12ccb81af8fce9f606e7c163311e37379dcbba1c5364736f6c634300081c0033",
"opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH0 PUSH0 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 PREVRANDAO ISZERO SELFBALANCE STATICCALL 0xD1 0xE8 SMOD 0xDB 0xBD PUSH24 0x9E12CCB81AF8FCE9F606E7C163311E37379DCBBA1C536473 PUSH16 0x6C634300081C00330000000000000000 ",
"sourceMap": "69:46:0:-:0;;;;;"
}
Deployed bytecode is present as expected. The compiler selects the right nested object for D
because it relies on the _deployed
suffix:
solidity/libsolidity/interface/CompilerStack.cpp
Lines 1592 to 1594 in 7893614
Two-step compilation
step2.json
{
"language": "Yul",
"sources": {
"D.yul": {"urls": ["D.yul"]}
},
"settings": {
"outputSelection": {"*": {"*": ["*"]}}
}
}
solc --standard-json step1.json | jq --raw-output '.contracts."input.sol".D.ir' > D.yul
solc --standard-json step2.json | jq --raw-output '.contracts."D.yul".[].evm.deployedBytecode' --indent 4
null
Deployed bytecode is missing even though we're compiling the same exact contract, just in two steps. This is because in Yul mode the name of the nested object is not given and we fall back to the simplistic heuristic: