WTF Opcodes极简入门: 14. 交易指令 我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。 推特:@0xAAScience 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes 在这一讲,我们将探索EVM中与交易(Transaction)上下文相关的4个指令,包括 , , 等。我们能利用这些指令访问当前交易或调用者的信息。 交易的基本结构 在深入学习这些指令之前,让我们先了解以太坊交易的基本结构。每一笔以太坊交易都包含以下属性: :一个与发送者账户相关的数字,表示该账户已发送的交易数。
我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。
所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes
在这一讲,我们将探索EVM中与交易(Transaction)上下文相关的4个指令,包括ADDRESS, ORIGIN, CALLER等。我们能利用这些指令访问当前交易或调用者的信息。

在深入学习这些指令之前,让我们先了解以太坊交易的基本结构。每一笔以太坊交易都包含以下属性:
nonce:一个与发送者账户相关的数字,表示该账户已发送的交易数。gasPrice:交易发送者愿意支付的单位gas价格。gasLimit:交易发送者为这次交易分配的最大gas数量。to:交易的接收者地址。当交易为合约创建时,这一字段为空。value:以wei为单位的发送金额。data:附带的数据,通常为合约调用的输入数据(calldata)或新合约的初始化代码(initcode)。
v, r, s:与交易签名相关的三个值。在此基础上,我们可以在极简EVM中添加一个交易类,除了上述的信息以外,我们还把一些交易上下文分信息包含其中,包含当前调用者caller,原始发送者origin(签名者),和执行合约地址,thisAddr(Solidity中的address(this)):
class Transaction: def __init__(self, to = '', value = 0, data = '', caller='0x00', origin='0x00', thisAddr='0x00', gasPrice=1, gasLimit=21000, nonce=0, v=0, r=0, s=0): self.nonce = nonce self.gasPrice = gasPrice self.gasLimit = gasLimit self.to = to self.value = value self.data = data self.caller = caller self.origin = origin self.thisAddr = thisAddr self.v = v self.r = r self.s = s
当初始化evm对象时,需要传入Transaction对象:
class EVM: def __init__(self, code, txn = None): # 初始化其他变量... self.txn = txn # 示例 code = b"\x73\x9b\xbf\xed\x68\x89\x32\x2e\x01\x6e\x0a\x02\xee\x45\x9d\x30\x6f\xc1\x95\x45\xd8\x31" txn = Transaction(to='0x9bbfed6889322e016e0a02ee459d306fc19545d8', value=10, data='', caller='0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', origin='0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045') vm = EVM(code, txn)
0x30def address(self): self.stack.append(self.txn.thisAddr)
0x32def origin(self): self.stack.append(self.txn.origin)
0x33def caller(self): self.stack.append(self.txn.caller)
0x34def callvalue(self): self.stack.append(self.txn.value)
0x35data字段加载数据。它从堆栈中弹出calldata的偏移量(offset),然后从calldata的offset位置读取32字节的数据并压入堆栈。如果calldata剩余不足32字节,则补0。def calldataload(self): if len(self.stack) < 1: raise Exception('Stack underflow') offset = self.stack.pop() # 从字符形式转换为bytes数组 calldata_bytes = bytes.fromhex(self.txn.data[2:]) # 假设由 '0x' 开头 data = bytearray(32) # 复制calldata for i in range(32): if offset + i < len(calldata_bytes): data[i] = calldata_bytes[offset + i] self.stack.append(int.from_bytes(data, 'big'))
0x36data字段的字节长度,并压入堆栈。def calldatasize(self): # Assuming calldata is a hex string with a '0x' prefix size = (len(self.transaction.data) - 2) // 2 self.stack.append(size)
0x37data中的数据复制到内存中。它会从堆栈中弹出3个参数(mem_offset, calldata_offset, length),分别对应写到内存的偏移量,读取calldata的偏移量和长度。def calldatacopy(self): # 确保堆栈中有足够的数据 if len(self.stack) < 3: raise Exception('Stack underflow') mem_offset = self.stack.pop() calldata_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))) # 从字符形式转换为bytes数组. calldata_bytes = bytes.fromhex(self.txn.data[2:]) # Assuming it's prefixed with '0x' # 将calldata复制到内存 for i in range(length): if calldata_offset + i < len(calldata_bytes): self.memory[mem_offset + i] = calldata_bytes[calldata_offset + i]
0x38def codesize(self): addr = self.txn.thisAddr self.stack.append(len(account_db.get(addr, {}).get('code', b'')))
0x39mem_offset)、代码的开始偏移量(code_offset)、以及要复制的长度(length)。def codecopy(self): if len(self.stack) < 3: raise Exception('Stack underflow') mem_offset = self.stack.pop() code_offset = self.stack.pop() length = self.stack.pop() # 获取当前地址的code addr = self.txn.thisAddr code = account_db.get(addr, {}).get('code', b'') # 拓展内存 if len(self.memory) < mem_offset + length: self.memory.extend([0] * (mem_offset + length - len(self.memory))) # 将代码复制到内存 for i in range(length): if code_offset + i < len(code): self.memory[mem_offset + i] = code[code_offset + i]
0x3Adef gasprice(self): self.stack.append(self.txn.gasPrice)
在这一讲,我们详细介绍了EVM中与交易有关的10个指令。这些指令为智能合约提供了与其环境交互的能力,使其能够访问调用者,calldata,和代码等信息。目前,我们已经学习了144个操作码中的126个!