EVM学习——合约创建
September 20th, 2022

本文从EVM操作码的角度,研究合约创建的详细过程。

solidity version = 0.8.15,optimizer = true,optimizer_runs = 200,evm_version = "london"

在线反汇编 https://ethervm.io/decompile

evm执行模拟使用foundry环境https://book.getfoundry.sh/reference/forge/forge-debug

合约代码如下

pragma solidity 0.8.15;

contract Test {
    uint256 public val;
    uint256 immutable c;

    constructor(uint256 _val, uint256 _c) {
        val = _val;
        c = _c;
    }

    function setNumber(uint256 newNumber) public {
        val = newNumber + c;
    }
}

我们在在foundry环境中,在script文件里,使用new进行部署。

new的部署方式和外部账户通过交易部署合约其实是很类似的,都是message call的过程。

contract TestScript is Script {
    function setUp() public view {}

    function run() public {
       vm.startBroadcast(); // record call information so deploy to chain

       Test t = new Test(0x1234, 0x5678);

       vm.stopBroadcast();
    }
}

在TestScript执行new Test的时候,

我们可以观察一下memory的情况,这里存储的是calldata内容。

0x080~0x1fe: 这一段是deploy code(60a0开始 一直到 0033为之)

接在后面的还有两个uint256的数字,0x1234和0x5678,这两个是我们即将部署的合约的构造函数实参。

继续往下走,我们就会进入真正的deploy阶段。

把deploy code进行反汇编。

contract Contract {
    function main() {
        memory[0x40:0x60] = 0xa0;
        var var0 = msg.value;
    
        if (var0) { revert(memory[0x00:0x00]); }
    
        var temp0 = memory[0x40:0x60];
        var temp1 = code.length - 0x017e;
        memory[temp0:temp0 + temp1] = code[0x017e:0x017e + temp1];
        var var1 = temp0 + temp1;
        memory[0x40:0x60] = var1;
        var0 = 0x002f;
        var var2 = temp0;
        var0, var1 = func_003D(var1, var2);
        storage[0x00] = var0;
        memory[0x80:0xa0] = var1;
        var temp2 = memory[0x80:0xa0];
        memory[0x00:0x0103] = code[0x7b:0x017e];
        memory[0x66:0x86] = temp2;
        return memory[0x00:0x0103];
    }
    
    function func_003D(var arg0, var arg1) returns (var r0, var arg0) {
        var var0 = 0x00;
        var var1 = var0;
    
        if (arg0 - arg1 i< 0x40) { revert(memory[0x00:0x00]); }
    
        var temp0 = arg1;
        r0 = memory[temp0:temp0 + 0x20];
        arg0 = memory[temp0 + 0x20:temp0 + 0x20 + 0x20];
        return r0, arg0;
    }
}

代码的第1个关键点在storage[0x00] = var0;

这里实际上对应构造函数中的val = _val;

代码的第2个关键点是memory[0x00:0x0103] = code[0x7b:0x017e];

这里是把deploy code中的一部分代码拷贝到memory中。

deploy code其实有两部分,第一部分是用于deploy的代码部分,这些对应上面的反汇编代码。

60a060405234801561001057600080fd5b5060405161017e38038061017e8339 8101604081905261002f9161003d565b600091909155608052610061565b6000 806040838503121561005057600080fd5b505080516020909101519092909150 565b60805161010361007b6000396000606601526101036000f3fe

另一部分是将会被部署上链的代码,被叫做deployed code。这些代码就是在合约上链之后,后面与之交互时所使用的部分。

6080604052348015600f57600080fd5b506004361060325760003560e01c8063 3c6bb4361460375780633fb5c1cb146051575b600080fd5b603f60005481565b 60405190815260200160405180910390f35b6060605c3660046090565b606256 5b005b608a7f0000000000000000000000000000000000000000000000000000 0000000000008260a8565b60005550565b60006020828403121560a157600080 fd5b5035919050565b6000821982111560c857634e487b7160e01b6000526011 60045260246000fd5b50019056fea26469706673582212200b220ab5e3b5c534 b47030c3b89735e224c0d52e5454511a0274bc5bbbfd2abd64736f6c63430008 0f0033

在memory[0x00:0x0103] = code[0x7b:0x017e];执行之后,我们可以看到deployed code已经被拷贝到memory中。在操作码中,对应CODECOPY。

第3个关键点是memory[0x66:0x86] = temp2;

这一句对应构造函数中的c = _c;

因为c是immutable,所以c的值并不会占据storage空间,而是会被在字节码中写死。所以还需要在字节码中,把c的值赋上。

最后的形态如下:

memory中时最终的deployed code,而栈中的两个数字则指定了memory中deployed code的位置。

0x103 = 259,这就是deployed code的大小。

deploy函数的返回值就是最终的deployed code,紧接着deployed code就会被写入区块链,从而完成合约部署过程。

Subscribe to rbtree
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from rbtree

Skeleton

Skeleton

Skeleton