Skip to content

Commit a19ba2c

Browse files
committed
add bytecode verification script
Signed-off-by: Ihor Farion <ihor@umaproject.org>
1 parent 30ea851 commit a19ba2c

File tree

1 file changed

+92
-0
lines changed

1 file changed

+92
-0
lines changed

deploy/120_verify_bytecode.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { DeployFunction } from "hardhat-deploy/types";
2+
import { HardhatRuntimeEnvironment } from "hardhat/types";
3+
import { ethers } from "hardhat";
4+
import fs from "fs";
5+
import path from "path";
6+
7+
/**
8+
* Hardhat-deploy script that verifies that the bytecode of a live deployment matches the byte-code
9+
* produced by the current compilation artefacts in the repo.
10+
*
11+
* Usage example (Arbitrum_Adapter on mainnet):
12+
* CONTRACT=Arbitrum_Adapter yarn hardhat deploy --network mainnet --tags verifyBytecode
13+
*
14+
* The script will:
15+
* 1. Read the compiled artefact for `CONTRACT` (artifacts/<source>/<Contract>.json) in the build
16+
* folder and grab the creation byte-code and constructor ABI.
17+
* 2. Read the hardhat-deploy deployment file for the same network (`deployments/<network>/<CONTRACT>.json`)
18+
* to obtain the constructor arguments that were used for the actual deployment together with the
19+
* deployed address.
20+
* 3. Encode the constructor arguments with the ABI and append them to the creation byte-code – this
21+
* is identical to the calldata used when the contract was created on-chain.
22+
* 4. Simulate a CREATE call (eth_call) with this creation code to obtain the runtime byte-code that
23+
* would be stored on-chain.
24+
* 5. Fetch the real runtime byte-code from the chain via `eth_getCode`.
25+
* 6. Keccak-hash both byte-codes and print a comparison so reviewers can quickly see whether they
26+
* match.
27+
*/
28+
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
29+
const contractName = process.env.CONTRACT;
30+
if (contractName === undefined) {
31+
throw new Error("Please provide CONTRACT env var, e.g. Arbitrum_Adapter");
32+
}
33+
34+
// ---------------------------------------------------------------------------
35+
// 1. Read compiled artefact (creation bytecode & constructor ABI)
36+
// ---------------------------------------------------------------------------
37+
const artifact = await hre.artifacts.readArtifact(contractName);
38+
const creationBytecode: string = artifact.bytecode; // hex-string starting with 0x
39+
const constructorFragment = artifact.abi.find((e: any) => e.type === "constructor");
40+
const constructorInputs = constructorFragment?.inputs || [];
41+
42+
// ---------------------------------------------------------------------------
43+
// 2. Read deployment JSON for the current network (constructor args & address)
44+
// ---------------------------------------------------------------------------
45+
const networkName = hre.network.name;
46+
const deploymentInfo = await hre.deployments.get(contractName);
47+
const deployedAddress: string = deploymentInfo.address;
48+
const constructorArgs: any[] = deploymentInfo.args || [];
49+
50+
// ---------------------------------------------------------------------------
51+
// 3. Encode constructor args and build full creation bytecode
52+
// ---------------------------------------------------------------------------
53+
const argTypes = constructorInputs.map((c: any) => c.type);
54+
const encodedArgs =
55+
argTypes.length > 0 ? ethers.utils.defaultAbiCoder.encode(argTypes, constructorArgs).slice(2) : "";
56+
const creationCodeWithArgs = creationBytecode + encodedArgs; // strip 0x from encodedArgs already
57+
58+
// ---------------------------------------------------------------------------
59+
// 4. Simulate CREATE to obtain runtime bytecode (set generous gas limit)
60+
// ---------------------------------------------------------------------------
61+
const gasLimitEnv = process.env.GAS_LIMIT || "5000000"; // default 5M
62+
const gasLimit = ethers.BigNumber.from(gasLimitEnv);
63+
const runtimeBytecodeSim = await hre.ethers.provider.call({ data: creationCodeWithArgs, gasLimit });
64+
const runtimeBytecodeSimHash = ethers.utils.keccak256(runtimeBytecodeSim);
65+
66+
// ---------------------------------------------------------------------------
67+
// 5. Fetch on–chain runtime bytecode for deployed address
68+
// ---------------------------------------------------------------------------
69+
const onchainRuntimeBytecode = await hre.ethers.provider.getCode(deployedAddress);
70+
const onchainRuntimeBytecodeHash = ethers.utils.keccak256(onchainRuntimeBytecode);
71+
72+
// ---------------------------------------------------------------------------
73+
// 6. Print comparison for reviewers
74+
// ---------------------------------------------------------------------------
75+
console.log("\n================ Bytecode Verification ================");
76+
console.log(`Contract : ${contractName}`);
77+
console.log(`Network : ${networkName}`);
78+
console.log(`Deployed address : ${deployedAddress}`);
79+
console.log("-------------------------------------------------------");
80+
console.log(`On-chain code hash : ${onchainRuntimeBytecodeHash}`);
81+
console.log(`Simulated code hash : ${runtimeBytecodeSimHash}`);
82+
console.log("-------------------------------------------------------");
83+
console.log(
84+
onchainRuntimeBytecodeHash === runtimeBytecodeSimHash
85+
? "✅ MATCH"
86+
: "❌ MISMATCH – deployment does not match current artefacts"
87+
);
88+
console.log("=======================================================\n");
89+
};
90+
91+
module.exports = func;
92+
func.tags = ["verifyBytecode"];

0 commit comments

Comments
 (0)