Skip to content

Latest commit

 

History

History

21_Create

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

WTF Opcodes极简入门: 21. Create指令

我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。

推特:@0xAA_Science

社区:Discord微信群官网 wtf.academy

所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes


这一讲,我们介绍EVM中的CREATE指令,它可以让合约创建新的合约。

initcode 初始代码

之前我们提到过,以太坊有两种交易,一种是合约调用,而另一种是合约创建。在合约创建的交易中,to字段设为空,而data字段应填写为合约的初始代码(initcode)。initcode也是字节码,但它只在合约创建时执行一次,目的是为新合约设置必要的状态和返回最终的合约字节码(contract code)。

下面,我们看一个简单的initcode63ffffffff6000526004601cf3。它的指令形式为:

PUSH4 ffffffff
PUSH1	00
MSTORE	
PUSH1	04
PUSH1	1c
RETURN	

它先用MSTORE指令把ffffffff拷贝到内存中,然后用RETURN指令将它拷贝到返回数据中。这段initcode会把新合约的字节码设置为ffffffff

CREATE

在EVM中,当一个合约想要创建一个新的合约时,会使用CREATE指令。它的简化流程:

  1. 从堆栈中弹出value(向新合约发送的ETH)、mem_offsetlength(新合约的initcode在内存中的初始位置和长度)。
  2. 计算新合约的地址,计算方法为:
    address = keccak256(rlp([sender_address,sender_nonce]))[12:]
  3. 更新ETH余额。
  4. 初始化新的EVM上下文evm_create,用于执行initcode
  5. evm_create中执行initcode
  6. 如果执行成功,则更新创建的账户状态:更新balance,将nonce初始化为0,将code字段设为evm_create的返回数据,将storage字段设置为evm_createstorage
  7. 如果成功,则将新合约地址推入堆栈;若失败,将0推入堆栈。

下面,我们在极简EVM中实现CREATE指令:

def create(self):
    if len(self.stack) < 3:
        raise Exception('Stack underflow')

    # 弹出堆栈数据
    value = self.stack.pop()
    mem_offset = self.stack.pop()
    length = self.stack.pop()

    # 扩展内存
    if len(self.memory) < mem_offset + length:
        self.memory.extend([0] * (mem_offset + length - len(self.memory)))

    # 获取初始化代码
    init_code = self.memory[mem_offset: mem_offset + length]

    # 检查创建者的余额是否足够
    creator_account = account_db[self.txn.thisAddr]
    if creator_account['balance'] < value:
        raise Exception('Insufficient balance to create contract!')

    # 为创建者扣除指定的金额
    creator_account['balance'] -= value

    # 生成新的合约地址(参考geth中的方式,使用创建者的地址和nonce)
    creator_nonce = creator_account['nonce']
    new_contract_address_bytes = sha3.keccak_256(self.txn.thisAddr.encode() + str(creator_nonce).encode()).digest()
    new_contract_address = '0x' + new_contract_address_bytes[-20:].hex()  # 取后20字节作为地址

    # 使用txn构建上下文
    ctx = Transaction(to=new_contract_address,
                        data=init_code,
                        value=value,
                        caller=self.txn.thisAddr,
                        origin=self.txn.origin,
                        thisAddr=new_contract_address,
                        gasPrice=self.txn.gasPrice,
                        gasLimit=self.txn.gasLimit)

    # 创建并运行新的EVM实例
    evm_create = EVM(init_code, ctx)
    evm_create.run()

    # 如果EVM实例返回错误,压入0,表示创建失败
    if evm_create.success == False:
        self.stack.append(0)
        return

    # 更新创建者的nonce
    creator_account['nonce'] += 1

    # 存储合约的状态
    account_db[new_contract_address] = {
        'balance': value,
        'nonce': 0,  # 新合约的nonce从0开始
        'storage': evm_create.storage,
        'code': evm_create.returnData
    }
    
    # 压入新创建的合约地址
    self.stack.append(int(new_contract_address, 16))

测试

  1. 使用CREATE指令部署一个新合约,发送9 wei,但不部署任何代码:

    # CREATE (empty code, 9 wei balance)
    code = b"\x5f\x5f\x60\x09\xf0"
    evm = EVM(code, txn)
    evm.run()
    print(hex(evm.stack[-1]))
    # output: 0x260144093a2920f68e1ae2e26b3bd15ddd610dfe
    print(account_db[hex(evm.stack[-1])])
    # output: {'balance': 9, 'nonce': 0, 'storage': {}, 'code': bytearray(b'')}
  2. 使用CREATE指令部署一个新合约,并将代码设置为ffffffff:

    # CREATE (with 4x FF)
    code = b"\x6c\x63\xff\xff\xff\xff\x60\x00\x52\x60\x04\x60\x1c\xf3\x60\x00\x52\x60\x0d\x60\x13\x60\x00\xf0"
    # PUSH13 0x63ffffffff6000526004601cf3 PUSH1 0x00 MSTORE PUSH1 0x0d PUSH1 0x19 PUSH1 0x00 CREATE
    evm = EVM(code, txn)
    evm.run()
    print(hex(evm.stack[-1]))
    # output: 0x6dddd3288a19f0bf4eee7bfb9e168ad29e1395d0
    print(account_db[hex(evm.stack[-1])])
    # {'balance': 0, 'nonce': 0, 'storage': {}, 'code': bytearray(b'\xff\xff\xff\xff')}

总结

这一讲,我们介绍了EVM中创建合约的指令CREATE,通过它,合约可以创造其他合约,从而实现更为复杂的逻辑和功能。我们已经学习了144个操作码中的141个(98%)!