MONOKH

نخبه break things
Github @monokh Twitter @mo_nokh

Ethereum Contract Creation - Explained from bytecode

Incredibly, it's near impossible to find a simple explanation of the the bytecode involved in creating an ethereum smart contract. This may prove useful if your purpose is to write highly optimised contracts using just EVM bytecode.

The anatomy of contract deployment

Deploying an ethereum smart contract involves sending a data payload to the null address. The data is constructed of 2 sections. The "runtime code" is the code that the EVM evaluates when a contract on chain has been called. The "init code" sets up (constructs) the contract and returns the runtime code to be stored on chain.

{
  "to": null,
  "value": 0,
  "data": "<init_code><runtime_code>"
}

Runtime code

Checkout this reference for EVM op codes: https://ethervm.io/

This is what you would typically expect to see on a block explorer. It is code that the EVM evaluates every time a contract is called. The following contract adds the numbers 2 & 4 and returns the result.

60 02 // PUSH1 2 - Push 2 on the stack
60 04 // PUSH1 4 - Push 4 on the stack
01 // ADD - Add stack[0] to stack[1]

60 00 // PUSH1 0 - Push 0 on the stack (destination in memory)
53 // MSTORE - Store result to memory

60 20 // PUSH1 32 - Push 32 on the stack (length of data to return)
60 00 // PUSH1 00 - Push 0 on the stack (location in memory)
F3 // Return

Note that our contract is 13 bytes long.

Now that we have the body of our code, let's see how we can deploy this to the chain.

Init Code

The purpose of the init code in the contract creation is to return the runtime code to the EVM. This is essentially the constructor function of a contract.

First we must copy the contract's runtime code to memory. Remember that the runtime code is positioned after the init code in our transaction data? We'll need its position to copy it.

60 0D // PUSH1 13 (The length of our runtime code)
60 0C // PUSH1 12 (The position of the runtime code in the transaction data)
60 00 // PUSH1 00 (The destination in memory)
39 // CODECOPY

Now that we have the contract code in memory, we simply return it.

60 0D // PUSH1 13 (The length of our runtime code)
60 00 // PUSH1 00 (The memory location holding our runtime code)
F3 // RETURN

The returning data is accepted as the runtime code of the contract.

The complete contract creation data

0x600D600C600039600D6000F3600260040160005360206000F3
-------^init code^-------|------^runtime code^-----

Bonus: constructor parameters?

What if we actually want our contract to be configurable with some parameters? Let's say we wanted to create the same addition contract as above, but be able to specify which numbers it will add.

Typically this can be achieved by appending the constructor parameters after the runtime code.

{
  "to": null,
  "value": 0,
  "data": "<init_code><runtime_code>0000000200000004"
                                  // param1^|param2^
}

Then:

  1. init code will CODECOPY these parameters into memory.
  2. SSTORE will persist them into the contract state.
  3. runetime code will SLOAD the numbers stored in state onto the stack and then perform the addition.