`deployedBytecode` output is empty in Yul mode for contracts with bytecode dependencies
- Dominant language
- C++
- Stars
- 25.7k
- Forks
- 6.2k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 21
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](https://github.com/ethereum/solidity/blob/v0.8.28/libyul/YulStack.cpp#L330-L350) 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](https://docs.soliditylang.org/en/develop/yul.html#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`
```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
```
```json
{
"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: https://github.com/ethereum/solidity/blob/7893614a31fbeacd1966994e310ed4f760772658/libsolidity/interface/CompilerStack.cpp#L1592-L1594
### Two-step compilation
`step2.json`
```json
{
"language": "Yul",
"sources": {
"D.yul": {"urls": ["D.yul"]}
},
"settings": {
"outputSelection": {"*": {"*": ["*"]}}
}
}
```
```bash
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
```
```json
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: https://github.com/ethereum/solidity/blob/7893614a31fbeacd1966994e310ed4f760772658/libsolidity/interface/StandardCompiler.cpp#L1659
Contributor guide
Research direction
Reproduce the discrepancy with the provided step1.json and step2.json inputs, then start at the Yul object-selection heuristic in libyul/YulStack.cpp and its use in libsolidity/interface/StandardCompiler.cpp. Compare it with the _deployed-suffix selection in CompilerStack.cpp; done means the two-step Yul compilation returns deployedBytecode for the same contract instead of null.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, solidity
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100