EVM Opcodes 101

1. Hello Opcodes
2. Opcodes分类
3. 堆栈指令
4. 算数指令
5. 比较指令
6. 位级指令
7. 内存指令
8. 存储指令
9. 控制流指令
10. 区块信息指令
11. 堆栈指令2
12. SHA3指令
13. 账户指令
14. 交易指令
15. Log指令
10. 区块信息指令
区块信息指令

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

推特:@0xAA_Science

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

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


在这一讲,我们将介绍EVM中用于查询区块信息的9个指令,包括BLOCKHASH,COINBASE,PREVRANDAO等。我们将在用Python写的极简版EVM中添加对这些操作的支持。

区块信息

我们在写智能合约时经常会用到区块链信息,比如生成伪随机数时我们会使用blockhash,block.number,和block.timestamp:

/** 
    * 链上伪随机数生成
    * keccak256(abi.encodePacked())中填上一些链上的全局变量/自定义变量
    * 返回时转换成uint256类型
    */
    function getRandomOnchain() public view returns(uint256){
        /*
         * 本例链上随机只依赖区块哈希,调用者地址,和区块时间,
         * 想提高随机性可以再增加一些属性比如nonce等,但是不能根本上解决安全问题
         */
        bytes32 randomBytes = keccak256(abi.encodePacked(blockhash(block.number-1), msg.sender, block.timestamp));
        return uint256(randomBytes);
    }

EVM提供了一系列指令让智能合约访问当前或历史区块的信息,包括区块哈希、时间戳、coinbase等。

这些信息一般保存在区块头(Header)中,但我们可以为在极简EVM中添加current_block属性来模拟这些区块信息:

def __init__(self, code):
    self.code = code
    self.pc = 0
    self.stack = []
    self.memory = bytearray()
    self.current_block = {
        "blockhash": 0x7527123fc877fe753b3122dc592671b4902ebf2b325dd2c7224a43c0cbeee3ca,
        "coinbase": 0x388C818CA8B9251b393131C08a736A67ccB19297,
        "timestamp": 1625900000,
        "number": 17871709,
        "prevrandao": 0xce124dee50136f3f93f19667fb4198c6b94eecbacfa300469e5280012757be94,
        "gaslimit": 30,
        "chainid": 1,
        "selfbalance": 100,
        "basefee": 30,
    }

区块信息指令

下面,我们介绍这些区块信息指令:

  1. BLOCKHASH: 查询特定区块(最近的256个区块,不包括当前区块)的hash,它的操作码为0x40,gas消耗为20。。它从堆栈中弹出一个值作为区块高度(block number),然后将该区块的hash压入堆栈,如果它不属于最近的256个区块,则返回0(你可以使用NUMBER指令查询当前区块高度)。但是为了简化,我们在这里只考虑当前块。

    def blockhash(self):
        if len(self.stack) < 1:
            raise Exception('Stack underflow')
        number = self.stack.pop()
        # 在真实场景中, 你会需要访问历史的区块hash
        if number == self.current_block["number"]:
            self.stack.append(self.current_block["blockhash"])
        else:
            self.stack.append(0)  # 如果不是当前块,返回0
  2. COINBASE: 将当前区块的coinbase(矿工/受益人)地址压入堆栈,它的操作码为0x41,gas消耗为2。

    def coinbase(self):
        self.stack.append(self.current_block["coinbase"])
  3. TIMESTAMP: 将当前区块的时间戳压入堆栈,它的操作码为0x42,gas消耗为2。

    def timestamp(self):
        self.stack.append(self.current_block["timestamp"])
  4. NUMBER: 将当前区块高度压入堆栈,它的操作码为0x43,gas消耗为2。

    def number(self):
        self.stack.append(self.current_block["number"])
  5. PREVRANDAO: 替代了原先的DIFFICULTY(0x44) 操作码,其返回值是beacon链随机性信标的输出。此变更允许智能合约在以太坊转向权益证明(PoS)后继续从原本的DIFFICULTY操作码处获得随机性。它的操作码为0x44,gas消耗为2。

    def prevrandao(self):
        self.stack.append(self.current_block["prevrandao"])
  6. GASLIMIT: 将当前区块的gas限制压入堆栈,它的操作码为0x45,gas消耗为2。

    def gaslimit(self):
        self.stack.append(self.current_block["gaslimit"])
  7. CHAINID: 将当前的链ID压入堆栈,它的操作码为0x46,gas消耗为2。

    def chainid(self):
        self.stack.append(self.current_block["chainid"])
  8. SELFBALANCE: 将合约的当前余额压入堆栈,它的操作码为0x47,gas消耗为5。

    def selfbalance(self):
        self.stack.append(self.current_block["selfbalance"])
  9. BASEFEE: 将当前区块的基础费(base fee)压入堆栈,它的操作码0x48,gas消耗为2。

    def basefee(self):
        self.stack.append(self.current_block["basefee"])

下面,我们在极简EVM中添加对这些操作码的支持:

BLOCKHASH = 0x40
COINBASE = 0x41
TIMESTAMP = 0x42
NUMBER = 0x43
PREVRANDAO = 0x44
GASLIMIT = 0x45
CHAINID = 0x46
SELFBALANCE = 0x47
BASEFEE = 0x48

def run(self):
    while self.pc < len(self.code):
        op = self.next_instruction()

        # ... 其他指令的处理 ...

        elif op == BLOCKHASH:
            self.blockhash()
        elif op == COINBASE:
            self.coinbase()
        elif op == TIMESTAMP:
            self.timestamp()
        elif op == NUMBER:
            self.number()
        elif op == PREVRANDAO:
            self.prevrandao()
        elif op == GASLIMIT:
            self.gaslimit()
        elif op == CHAINID:
            self.chainid()
        elif op == SELFBALANCE:
            self.selfbalance()
        elif op == BASEFEE:
            self.basefee()

总结

这一讲,我们介绍了EVM中与区块链信息相关的指令,这些指令允许智能合约访问与其所在区块链相关的信息。这些信息有很多用途,比如判断交易是否超时,或者检查合约的余额。

课后习题: 请尝试写出一段字节码,该字节码会先压入当前区块链的高度,然后获取它的区块哈希。

PreviousNext