Skip to content

deployedBytecode output is empty in Yul mode for contracts with bytecode dependencies #15745

Open
@cameel

Description

@cameel

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:

std::string deployedName = IRNames::deployedObject(_contract);
solAssert(!deployedName.empty(), "");
tie(compiledContract.evmAssembly, compiledContract.evmRuntimeAssembly) = stack.assembleEVMWithDeployed(deployedName);

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:

std::tie(object, deployedObject) = stack.assembleWithDeployed();

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug 🐛low effortThere is not much implementation work to be done. The task is very easy or tiny.low impactChanges are not very noticeable or potential benefits are limited.must have eventuallySomething we consider essential but not enough to prevent us from releasing Solidity 1.0 without it.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions