Description
Here I am again with another issue with extra files in compilation causing trouble 🫡
By extra files I mean files that the target contract does not depend on.
Recap
First a recap on previous issues similarly on extra files in compilation:
- Different bytecode with same metadata on 0.6.12 #12281
This was an older issue that seems to affect the AST IDs in0.6.12
and0.7.0
. We also encountered this and handled in Sourcify's case Handle Solidityv0.6.12
andv0.7.0
extra files bytecode mismatch sourcify#618 - Contract bytecode changes vastly when independent contracts added to the compiler #14250
I've encountered this in a0.8.19
contract and got fixed in0.8.21
with Deterministically choose memory slots for variables during stack-to-memory. #14311.
Current Issue
I've encountered the current issue in ethereum/sourcify#1065.
My initial suspicion was that it's an instance of the 2. case because the contract was a 0.8.19
contract. Unfortunately, I was able to reproduce the case in 0.8.21
.
Although the 1. case should have been resolved, I suspect it still can be related because the metadata hashes are identical but the bytecodes differ.
To reproduce
I named inputs as "hardhat" and "sourcify". "Sourcify" uses the minimum number of source files, because it is based on the ones listed in the contract metadata. "Hardhat" uses extra because by default Hardhat inputs everything to the compiler. Sourcify has two different inputs 0.8.19
and 0.8.21
because it includes the evmVersion
inside the input i.e. paris
vs shanghai
.
N2MERC721-hardhat-input.json.txt
N2MERC721-sourcify-input-0.8.21.json.txt
N2MERC721-sourcify-input-0.8.19.json.txt
You can see the two inputs differ only in some extra files + some of the compiler settings
These are the extra files in Hardhat input
[
'@nfts2me/contracts/interfaces/IN2MBeaconFactory.sol',
'@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol',
'@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155SupplyUpgradeable.sol',
'@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/IERC1155MetadataURIUpgradeable.sol',
'@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155ReceiverUpgradeable.sol',
'@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol',
'@openzeppelin/contracts/governance/utils/IVotes.sol',
'@openzeppelin/contracts/proxy/Proxy.sol',
'@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol',
'@openzeppelin/contracts/token/ERC1155/IERC1155.sol',
'@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol',
'@openzeppelin/contracts/token/ERC721/IERC721.sol',
'@openzeppelin/contracts/utils/introspection/IERC165.sol',
'contracts/beacon/N2MERC1155Upgradeable.sol',
'contracts/beacon/N2MERC721Upgradeable.sol',
'contracts/beacon/N2MUpgradeable.sol',
'contracts/interfaces/IN2M_ERCBase.sol',
'contracts/interfaces/IN2M_ERCCommon.sol',
'contracts/interfaces/IN2M_ERCLibrary.sol',
'contracts/interfaces/IN2M_ERCStorage.sol',
'contracts/interfaces/IN2MBeaconFactory.sol',
'contracts/interfaces/IN2MCrossFactory.sol',
'contracts/interfaces/IN2MERC1155.sol',
'contracts/interfaces/IN2MERC721.sol',
'contracts/interfaces/IOperatorFilterRegistry.sol',
'contracts/interfaces/IReverseRegistrar.sol',
'contracts/N2MERC1155.sol',
'contracts/ownable/NFTOwnableUpgradeable.sol',
'contracts/TextUtils.sol'
]
Also leaving the JS script I used to check the differences of the keys of `.sources` in the JSON inputs
Usage:
node checkFileKeysDifferences.js solcInput-1.json solcInput-2.json
const fs = require("fs");
function findKeyDifferences(file1, file2) {
const json1 = JSON.parse(fs.readFileSync(file1, "utf8"));
const json2 = JSON.parse(fs.readFileSync(file2, "utf8"));
const sources1 = new Set(Object.keys(json1.sources) || []);
const sources2 = new Set(Object.keys(json2.sources) || []);
const keysOnlyInFile1 = Array.from(sources1).filter(
(key) => !sources2.has(key)
);
const keysOnlyInFile2 = Array.from(sources2).filter(
(key) => !sources1.has(key)
);
return {
keysOnlyInFile1,
keysOnlyInFile2,
};
}
// Retrieve file paths from command-line arguments
const [file1Path, file2Path] = process.argv.slice(2);
if (!file1Path || !file2Path) {
console.log("Please provide two file paths as command-line arguments.");
process.exit(1);
}
const { keysOnlyInFile1, keysOnlyInFile2 } = findKeyDifferences(
file1Path,
file2Path
);
console.log("Keys only in file 1:", keysOnlyInFile1);
console.log("Keys only in file 2:", keysOnlyInFile2);
To extract the runtime bytecode:
cat N2MERC721-sourcify-input-0.8.19.json | solc --standard-json | jq '.contracts."contracts/N2MERC721.sol".N2MERC721.evm.deployedBytecode.object' > N2MERC721-0.8.19-sourcify-runtime-bytecode-from-solc.txt
My Bytecode outputs
N2MERC721-0.8.21-sourcify-runtime-bytecode.txt
N2MERC721-0.8.21-hardhat-runtime-bytecode.txt
N2MERC721-0.8.19-sourcify-runtime-bytecode.txt
N2MERC721-0.8.19-hardhat-runtime-bytecode.txt
To check the diff:
git diff --word-diff --word-diff-regex=. N2MERC721-0.8.19-hardhat-runtime-bytecode.txt N2MERC721-0.8.19-sourcify-runtime-bytecode.txt
Will give you a diff like this:
Environment
- Compiler version:
0.8.19
and0.8.21
(both Emscripten and Darwin.appleclang) - Target EVM version (as per compiler settings):
paris
andshanghai
- Framework/IDE (e.g. Truffle or Remix): none
- EVM execution environment / backend / blockchain client:
- Operating system: MacOS
Again, one particular thing that got my attention was that the metadata hashes were identical in all cases. This leaves me thinking if this is similar to the 1. case I mentioned.
Metadata
Metadata
Assignees
Type
Projects
Status