本文详细介绍了如何在以太坊虚拟机(EVM)中直接编写字节码以返回"Hello World"字符串。作者通过解释每个EVM指令的功能,展示了如何在不使用Solidity的情况下创建合约,并且解释了与字节码交互的过程和注意事项。
由于我不想冒被从教学社区开除的风险,我们将在本文中编写一个程序来返回字符串“Hello World”。好吧,你可以争论,但你已经知道了。这在 Solidity 中编写 Hello World 程序非常简单。我同意。但是在本课中,我们将直接用字节码编写一个程序,这在 Solidity 中是无法实现的。我们在本文中所做的,编译器是绝对不敢做的。
在上一节课中,我们已经看到了本文所需的所有必要材料,但让我们回顾几个主题。首先,让我写一个返回 Hello World 字符串的字节码。为了更好地可读性,我将使用操作码而不是直接写字节码。
PUSH11 0x48656C6C6F20576F726C64
PUSH1 0x00
MSTORE
PUSH1 0x0b
PUSH1 0x15
RETURN
PUSHX 操作码用于将 X 字节放入栈中。因此,我们首先将 2 个值放入栈中。第一个是 11 字节,第二个仅为 1 字节。如果你仔细看,这 11 字节代表编码为 UTF-8 的 Hello World
字符串。在前两行的末尾,栈将包含 0x00
和 0x48656C6C6F20576F726C64
。
MSTORE 用于在内存中存储一个值。栈中的第一个值指示将在内存中的何处存储某个值,而栈中的第二个值指示将存储什么。由于栈由 32 字节字 组成,内存是以 32 字节为单位组织的。因此,在执行此操作码结束时,栈将为空,并且内存将具有以下值:
00000000000000000000000000000000000000000048656c6c6f20576f726c64
记得 空闲内存指针 吗?那是给失败者的。这里我们大胆地做得不同。或者说,我们的代码太小,以至于无须担心它。
之后,我们将 2 个值放入栈中:0b
和 15
。这些是十六进制值。在十进制中,它们的值分别为 11 和 21,分别是 Hello World
字符串的长度和字符串在内存中的起始位置(记住,第一块内存是 32 字节,我们将字符串放在最低有效字节中)。栈现在再次有 2 个项。
最后一个操作码 RETURN 返回一个值。它是一个需要 2 个操作数的操作符:栈中的第一个值指示返回在内存中开始的位置(偏移量),第二个值指示返回的大小(字节)。
执行此字节码的返回值将是 48656c6c6f20576f726c64
,这就是 UTF-8 中的字符串 Hello World
。
这就是我们想要的,但这还不是我们想要的字节码。为了查看这一点,让我们使用此字节码进行部署。不幸的是,Remix 不会为此提供帮助(至少据我所知),但我们可以显式使用 ethersJs 或其他库发送交易。我将向 Sepolia 发送以下交易。
const tx = {
from: "0xC66d07097f4823343bf116463070B3be5e941C4E",
data: "0x6a48656c6c6f20576f726c64600052600b6015f3"
}
这将创建一个新的合约账户。在我的案例中,它创建了如下账户 0x1D0bB653bf7051B2BfFE55aa8a40B3C5b379C3BE
。如下图所示,我们可以验证写入区块链的字节码。
我们已将 Hello World
字符串写入区块链,但我们无法通过交易(或调用)检索该值。我的目标是创建一个合约,当任何人向合约发送调用时返回 Hello World
字符串。我们必须将整个字节码保存在区块链中,而不仅仅是 Hello World
字符串。
你应该明白,在部署时,写入的(已部署的)字节码就是执行程序后返回的内容。因此,我们需要编写一个返回字节码 0x6a48656c6c6f20576f726c64600052600b6015f3
的代码。
但我们知道怎么做!这实际上是第一次代码的重复。让我写下来。
PUSH20 0x6a48656c6c6f20576f726c64600052600b6015f3
PUSH1 0x00
MSTORE
PUSH1 0x14
PUSH1 0x0c
RETURN
你应该能够理解上述代码的执行流程。我们现在将通过创建合约账户时发送这些字节码。新的交易将如下所示。
const tx = {
from: "0xC66d07097f4823343bf116463070B3be5e941C4E",
data: "0x736a48656c6c6f20576f726c64600052600b6015f36000526014600cf3"
}
再次,我在 Sepolia 发送了一个用于账户创建的交易(King Goerli 已死,万岁新国王),合约在地址 0x6C0C201cc0Ab73361D8912AD2379617833D6F2cD
创建。已部署的字节码可以在下图中看到。
现在我们的字节码将返回字符串 Hello World,无论负载如何。没有 ABI,也没有检查是否发送了以太币。此合约的目的只有一个:响应 Hello World
。
为了证明这一点,我将向该地址发送一个完全任意的负载进行调用。见下文。
{
"jsonrpc": "2.0",
"id": 0,
"method": "eth_call",
"params": [{\
"to": "0x6C0C201cc0Ab73361D8912AD2379617833D6F2cD",\
"from": "0xC66d07097f4823343bf116463070B3be5e941C4E",\
"data": "0xaa11bb22cc"\
}]
}
你可以使用 restninja.io 网站,比如。或者使用像 postman 这样的程序。请看下面的响应。
而答案是 Hello World!这个合约将始终返回 Hello World。
在本课中,我们了解了如何编写一个始终返回常量字符串的程序。我的目标是让你理解 EVM 是如何向调用返回值的,以及合约账户创建回调是写入区块链的代码。我还希望你理解,像 ABI 和检查资金是否已发送到合约等问题是 Solidity 问题。可以编写一个不考虑负载的合约,也不了解函数签名,然而在实践中,这类合约并没有太多相关性。
感谢你的阅读!
欢迎对本文提出评论和建议。
任何支持都是欢迎的。 www.buymeacoffee.com/jpmorais 。
- 原文链接: medium.com/coinmonks/lea...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!