trufflesuite / truffle-compile

Compiler helper and artifact manager
22 stars 46 forks source link

truffle compile generates bytecode different from solc #77

Open barakman opened 5 years ago

barakman commented 5 years ago

The bytecode generated by truffle compile and the bin generated by solc are always different.

I am using:

It appears that the difference is always in the 64 characters which appear right before the last 4 characters in each output. Is that possibly just metadata?

Anyhow, you can reproduce this as follows:

Input file MyContract.sol:

pragma solidity 0.4.24;
contract MyContract {
    uint public x = 42;
}

Truffle command-line:

truffle compile

Solc command-line:

solc --bin --output-dir=build/contracts contracts/MyContract.sol

Truffle output file MyContract.json:

{
  ...
  "bytecode": "0x6080604052602a600055348015601457600080fd5b50609e806100236000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630c55699c146044575b600080fd5b348015604f57600080fd5b506056606c565b6040518082815260200191505060405180910390f35b600054815600a165627a7a7230582085d8b01651d23ddd6a24f980e190a61c86b692278463007d8ac2f5c74d4693100029",
  ...
}

Solc output file MyContract.bin:

6080604052602a600055348015601457600080fd5b50609e806100236000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630c55699c146044575b600080fd5b348015604f57600080fd5b506056606c565b6040518082815260200191505060405180910390f35b600054815600a165627a7a72305820f18509eb242360daf2e9973ce555fa9a12f0e24589132507643b11daeef3e0440029

The difference:

T: 6080604052602a600055348015601457600080fd5b50609e806100236000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630c55699c146044575b600080fd5b348015604f57600080fd5b506056606c565b6040518082815260200191505060405180910390f35b600054815600a165627a7a7230582085d8b01651d23ddd6a24f980e190a61c86b692278463007d8ac2f5c74d4693100029
S: 6080604052602a600055348015601457600080fd5b50609e806100236000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630c55699c146044575b600080fd5b348015604f57600080fd5b506056606c565b6040518082815260200191505060405180910390f35b600054815600a165627a7a72305820f18509eb242360daf2e9973ce555fa9a12f0e24589132507643b11daeef3e0440029
barakman commented 5 years ago

OK, the answer is yes, this is just metadata. For any future readers, it is stated explicitly in the Solidity documentation here. Closing this issue.

barakman commented 5 years ago

Second thought, why should Truffle generate different metadata than Solc? Perhaps one of Truffle contributors would care to explain this, so I'm reopening this.

barakman commented 5 years ago

Update: The metadata seems to change when I rename the root folder, which implies that truffle compile embeds this information (path name) into the metadata. This is not very useful when it comes to version control (also true with regards to the "updatedAt" field in each json file created by truffle compile, but at least that one doesn't impact the bytecode). Any idea how to disable these features (i.e., force truffle compile to generate the same output for two identical input files, regardless of their location in the file system, and regardless of the time of compilation)?

barakman commented 5 years ago

Update #2:

I have located the reason for which truffle compile ultimately embeds the absolute path name of each input source file into the metadata part of the generated bytecode field.

It is located in the compile.with_dependencies function:

Object.keys(result).sort().forEach(function(import_path) {
  var display_path = import_path;
  if (path.isAbsolute(import_path)) {
    display_path = "." + path.sep + path.relative(options.working_directory, import_path);
  }
  options.logger.log("Compiling " + display_path + "...");
});

I order to prevent this undesired impact, the above code should be extended to:

Object.keys(result).sort().forEach(function(import_path) {
  var display_path = import_path;
  if (path.isAbsolute(import_path)) {
    display_path = "." + path.sep + path.relative(options.working_directory, import_path);
    result[display_path] = result[import_path];
    delete result[import_path];
  }
  options.logger.log("Compiling " + display_path + "...");
});

As you can see, on each iteration, if a key represents an absolute path, then I replace it with a relative path:

result[display_path] = result[import_path];
delete result[import_path];

It took me several hours to find the source of this problem and fix it.

With this "patch" applied, the only remaining feature which preserves the undesired behavior of truffle compile generating the same output for two identical input files (thus making it hard to manage the output under version control), is the "updatedAt" output field.

The fix for this one is much more straightforward (just look for this field in Truffle's source code).

barakman commented 5 years ago

Update #3:

Turns out the the fix above "causes havoc" when running solidity-coverage (which in turn, invokes truffle compile).

Therefore, a safer fix for this would be to use:

if (options.fix_paths) {
    result[display_path] = result[import_path];
    delete result[import_path];
}

And then, only when specifically desired, call truffle compile --fix_paths.

wjmelements commented 5 years ago

Thanks for documenting this!

TT1943 commented 4 years ago

I got the same issue. The bytecode generated is different from Waffle. This is critical when CREATE2 relies on the bytecode generated by different frameworks.

enderphan94 commented 3 years ago

Facing the same issue, too annoying

cameel commented 3 years ago

https://github.com/trufflesuite/truffle/issues/4119 has now been implemented in Truffle meaning that there are no more absolute paths in metadata and bytecode does not depend on the project location. It will still be different from what solc produces by default due to the project:/ prefix added by Truffle to all files from project directory in compiler's JSON input.

The bytecode can be reproduced if you use solc --standard-json and manually add the prefix.

For example, if you have C.sol and D.sol inside contracts/, this is how the paths in the JSON input should look like to match Truffle:

{
    "language": "Solidity",
    "sources": {
        "project:/contracts/C.sol": { "content": "import \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";" },
        "project:/contracts/D.sol": { "content": "contract D {}" },
        "@openzeppelin/contracts/token/ERC20/IERC20.sol": { "content": "interface IERC20 { ... }" },
    },
    "settings": {"outputSelection": {"*": {"*": ["metadata", "evm.bytecode"]}}}
}