# EVM学习——合约创建 **Published by:** [rbtree](https://paragraph.com/@rbtree/) **Published on:** 2022-09-20 **URL:** https://paragraph.com/@rbtree/evm ## Content 本文从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就会被写入区块链,从而完成合约部署过程。 ## Publication Information - [rbtree](https://paragraph.com/@rbtree/): Publication homepage - [All Posts](https://paragraph.com/@rbtree/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@rbtree): Subscribe to updates