I want to be able to quickly identify where constructor arguments
start, as well as where actual code starts and ends.
This part is relatively easy, but not trivial. I'm assuming that you don't have the contract solidity code, neither the ABI or anything else. The compiler follows a well defined set of rules which lead to opcode patterns that you can recognize and split the deployement bytecode into 3 sections :
- Constructor Code
- Contract Code
- Constructor Arguments
Let me first take an arbitrary contract :
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Example {
constructor(uint256 a, uint256 b) {
}
function aRandomFunction() public view {
}
}
Which I deployed here on Ropsten with a = 1
and b = 2
for the example.
We can use a pattern that is an integral part of the deployment bytecode : the constructor CODECOPY block. Which should be located right before the Contract starting offset. There are others, but the longer the better (less chances of false positive + this one actually includes information that we need to find for the split) It has the following format :
- 0x5B JUMPDEST (OPTIONAL)
- 0x60 PUSH1 / 0x61 PUSH2 / 0x62 PUSH3
- 0x80 DUP1
- 0x60 PUSH1 / 0x61 PUSH2 / 0x62 PUSH3
- 0x60 0x00 PUSH1 (0)
- 0x39 CODECOPY
- 0x60 0x00 PUSH1 (0)
- 0xF3 RETURN
- 0xFE INVALID
The first PUSHX is actually the contract bytecode length, and the second is its offset in the txdata. Since the constructor starts at offset 0 we can deduce the position of the arguments too.
- constructor code = data[0:contract offset]
- contract code = data[contract offset: contract offset + contract length]
- arguments = data[contract offset + contract length:]
I made a JavaScript version of this process targeting the contract deployment tx of the example contract :
const Web3 = require('web3');
const ENDPOINT = "THE-ENDPOINT-ADDRESS"
const TX_HASH = "0xf4dce67834b31918c84cb9041cf910bb34e53319fe08550fce24fa623a536274"
function splitContractDeployment(input) {
input = input.toLowerCase().replace("0x", "");
// Look for a special pattern of contract deployement tx present at the end of the constructor bytecode
// 0x5B JUMPDEST (OPTIONAL)
// 0x60 PUSH1 / 0x61 PUSH2 / 0x62 PUSH3 : This is the contract bytecode length
// 0x80 DUP1
// 0x60 PUSH1 / 0x61 PUSH2 / 0x62 PUSH3 : This the the contract bytecode offset
// 0x60 0x00 PUSH1 (0)
// 0x39 CODECOPY
// 0x60 0x00 PUSH1 (0)
// 0xF3 RETURN
// 0xFE INVALID
const CODECOPY_REGEX = /(?:5b)?(?:60([a-z0-9]{2})|61([a-z0-9_]{4})|62([a-z0-9_]{6}))80(?:60([a-z0-9]{2})|61([a-z0-9_]{4})|62([a-z0-9_]{6}))6000396000f3fe/gm;
m = CODECOPY_REGEX.exec(input)
if (m == null || m == undefined) {
throw Error("Input is not a standard deployement bytecode");
}
const CONSTRUCTOR_OFFSET = 0
const CONTRACT_LENGTH = parseInt(m[1] || m[2] || m[3], 16) * 2
const CONTRACT_OFFSET = parseInt(m[4] || m[5] || m[6], 16) * 2
// Also get embedded metadata from the contract's code : located at the end of it
const METADATA_LENGTH = parseInt(input.slice(CONTRACT_OFFSET + CONTRACT_LENGTH - 4, CONTRACT_OFFSET + CONTRACT_LENGTH), 16) * 2 + 4;
return {
constructor: input.slice(CONSTRUCTOR_OFFSET, CONTRACT_OFFSET),
contract: input.slice(CONTRACT_OFFSET, CONTRACT_OFFSET + CONTRACT_LENGTH),
metadata: input.slice(CONTRACT_OFFSET + CONTRACT_LENGTH - METADATA_LENGTH, CONTRACT_OFFSET + CONTRACT_LENGTH),
arguments: input.slice(CONTRACT_OFFSET + CONTRACT_LENGTH),
};
}
async function main() {
const web3 = new Web3(ENDPOINT);
const tx = await web3.eth.getTransaction(TX_HASH);
// split contains :
// - constructor : the constructor bytecode
// - contract : the contract bytecode
// - metadata: the contract metadata CBOR encoded
// - arguments : the constructor arguments
const split = splitContractDeployment(tx.input)
// Decoding the arguments requires you to know the constructor ABI
// Outputs : 1 & 2
console.log(web3.eth.abi.decodeParameters(["uint256", "uint256"], split.arguments))
}
main();
As I commented, to decode the arguments, you must know the constructor ABI. I tested it on several deployment tx, but if you happen to find a mistake, don't hesitate to let me know.
I hope that answers your question.