Diving Into The Ethereum VM Part 5 — The Smart Contract Creation Process

A Contract’s Birth Certificate

pragma solidity ^0.4.11;contract C {
}
solc --bin --asm c.sol
60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00a165627a7a723058209747525da0f525f1132dde30c8276ec70c4786d4b08a798eda3c8314bf796cc30029
{
"from": "0xbd04d16f09506e80d1fd1fd8d0c79afa49bd9976",
"to": null,
"gas": "68653", // 30400,
"gasPrice": "1", // 10000000000000
"data": "0x60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a723058204bf1accefb2526a5077bcdfeaeb8020162814272245a9741cc2fddd89191af1c0029"
}
  • Transferring Ether to an account or contract.
  • Calling a contract’s method with parameters.

What The Bytecode Is Doing

// Deploy code
60606040523415600e57600080fd5b5b603680601c6000396000f300
// Contract code
60606040525b600080fd00
// Auxdata
a165627a7a723058209747525da0f525f1132dde30c8276ec70c4786d4b08a798eda3c8314bf796cc30029
  • Deploy code runs when the contract is being created.
  • Contract code runs after the contract had been created, when its methods are called.
  • (optional) Auxdata is the cryptographic fingerprint of the source code, used for verification. This is just data, and never executed by the EVM.
  1. Runs the constructor function, and sets up initial storage variables (like contract owner).
  2. Calculates the contract code, and returns it to the EVM.

Contract Creation

  • Check whether caller has enough balance to make a transfer:
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, common.Address{}, gas, ErrInsufficientBalance
}
  • Derive the new contract’s address from the caller’s address (passing in the creator account’s nonce):
contractAddr = crypto.CreateAddress(caller.Address(), nonce)
  • Create the new contract account using the derived contract address (changing the “world state” StateDB):
evm.StateDB.CreateAccount(contractAddr)
  • Transfer the initial Ether endowment from caller to the new contract:
evm.Transfer(evm.StateDB, caller.Address(), contractAddr, value)
  • Set input data as contract’s deploy code, then execute it with EVM. The ret variable is the returned contract code:
contract := NewContract(caller, AccountRef(contractAddr), value, gas)
contract.SetCallCode(&contractAddr, crypto.Keccak256Hash(code), code)
ret, err = run(evm, snapshot, contract, nil)
  • Check for error. Or if the contract code is too big, fail. Charge the user gas then set the contract code:
if err == nil && !maxCodeSizeExceeded {
createDataGas := uint64(len(ret)) * params.CreateDataGas
if contract.UseGas(createDataGas) {
evm.StateDB.SetCode(contractAddr, ret)
} else {
err = ErrCodeStoreOutOfGas
}
}

Code That Deploys Code

pragma solidity ^0.4.11;contract C {
}
// Deploy code
60606040523415600e57600080fd5b5b603680601c6000396000f300
// Contract code
60606040525b600080fd00
// Auxdata
a165627a7a723058209747525da0f525f1132dde30c8276ec70c4786d4b08a798eda3c8314bf796cc30029
// Reserve 0x60 bytes of memory for Solidity internal uses.
mstore(0x40, 0x60)
// Non-payable contract. Revert if caller sent ether.
jumpi(tag_1, iszero(callvalue))
0x0
dup1
revert
// Copy contract code into memory, and return.
tag_1:
tag_2:
dataSize(sub_0)
dup1
dataOffset(sub_0)
0x0
codecopy
0x0
return
stop
// 60 36 (PUSH 0x36)
dataSize(sub_0)
stack: [0x36]
dup1
stack: [0x36 0x36]
// 60 1c == (PUSH 0x1c)
dataOffset(sub_0)
stack: [0x1c 0x36 0x36]
0x0
stack: [0x0 0x1c 0x36 0x36]
codecopy
// Consumes 3 arguments
// Copy `length` of data from `codeOffset` to `memoryOffset`
// memoryOffset = 0x0
// codeOffset = 0x1c
// length = 0x36
stack: [0x36]
0x0
stack: [0x0 0x36]
memory: [
0x0:0x36 => calldata[0x1c:0x36]
]
return
// Consumes 2 arguments
// Return `length` of data from `memoryOffset`
// memoryOffset = 0x0
// length = 0x36
stack: []
memory: [
0x0:0x36 => calldata[0x1c:0x36]
]
memory = []
calldata = bytes.fromhex("60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00a165627a7a72305820b5090d937cf89f134d30e54dba87af4247461dd3390acf19d4010d61bfdd983a0029")
size = 0x36 // dataSize(sub_0)
offset = 0x1c // dataOffset(sub_0)
// Copy substring of calldata to memory
memory[0:size] = calldata[offset:offset+size]
// Instead of return, print the memory content in hex
print(bytes(memory[0:size]).hex())
60606040525b600080fd00
a165627a7a72305820b5090d937cf89f134d30e54dba87af4247461dd3390acf19d4010d61bfdd983a0029
// 6060604052600080fd00
mstore(0x40, 0x60)
tag_1:
0x0
dup1
revert
auxdata: 0xa165627a7a723058209747525da0f525f1132dde30c8276ec70c4786d4b08a798eda3c8314bf796cc30029

CODECOPY

func opCodeCopy(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
var (
memOffset = stack.pop()
codeOffset = stack.pop()
length = stack.pop()
)
codeCopy := getDataBig(contract.Code, codeOffset, length)
memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
evm.interpreter.intPool.put(memOffset, codeOffset, length)
return nil, nil
}

Constructor Argument

{
"from": "0xbd04d16f09506e80d1fd1fd8d0c79afa49bd9976"
"data": hexencode(compiledByteCode + encodedParams),
}
pragma solidity ^0.4.11;contract C {
uint256 a;
function C(uint256 _a) {
a = _a;
}
}
0x60606040523415600e57600080fd5b6040516020806073833981016040528080519060200190919050508060008190555050603580603e6000396000f3006060604052600080fd00a165627a7a7230582062a4d50871818ee0922255f5848ba4c7e4edc9b13c555984b91e7447d3bb0e7400290000000000000000000000000000000000000000000000000000000000000042
0000000000000000000000000000000000000000000000000000000000000042

A Contract That Creates Contracts

pragma solidity ^0.4.11;contract Foo {
}
contract FooFactory {
address fooInstance;
function makeNewFoo() {
fooInstance = new Foo();
}
}
FooFactoryDeployCode
FooFactoryContractCode
FooDeployCode
FooContractCode
FooAUXData
FooFactoryAUXData
res, addr, returnGas, suberr := evm.Create(contract, input, gas, value)

AUXDATA

0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29`
a1 65
// b z z r 0 (ASCII)
62 7a 7a 72 30
58 20
// 32 bytes hash
62a4d50871818ee0922255f5848ba4c7e4edc9b13c555984b91e7447d3bb0e74
00 29

Conclusion

  • There is enforced separation between “install time” and “run time”. No way to run the constructor twice.
  • Smart contracts can use the same process to create other smart contracts.
  • It is easy for a non-Solidity languages to implement.
{
"data": constructorCode + contractCode + auxdata + constructorData
}
{
// For "install time" bytecode
"constructorCode": ...,
// For "run time" bytecode
"constructorBody": ...,
// For encoding arguments
"data": ...,
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store