笔者最近在结合以太坊黄皮书读以太坊源码,结合自己的理解解析下黄皮书内的公式,与大家共同学习进步,若大家在阅读以太坊黄皮书时,对公式产生理解上的困惑,可以参阅本文一起看。文章基于当前(2022/1/10)的黄皮书,版本号为 BERLIN VERSION fabef25 ,若有不准确之处,欢迎指出。由于该黄皮书内除附录外有 183 个公式,为了让文章篇幅不过长,该解析会由三部分组成一个系列,每个系列解析约 60 个公式,本文为中篇。
公式 (79) 定义了创建合约时的参数 salt 是可选的,若提供 slat,需要是一个长度为 32 的字节。
Λ 为创建合约函数。
σ, A, s, o, g, p, v, i, e, ζ, w 为创建合约函数的参数,分别是:状态,子状态,发送者,原始发送者(考虑通过合约创建的情况),GasLimit,GasPrice,EVM 初始代码化代码,创建合约的调用所处的当前栈深度,salt 和 对状态进行修改的许可。
σ', g', A', z, o 为函数返回值,分别是:新的状态,剩余 Gas ,新的子状态,状态码,输出(output)内容。
公式 (80) 给出了创建合约函数的定义(输入,输出)。
\
s 为交易发起者(sender)地址。
n 为发起交易的 nonce 。
ζ 已在公式 (79) 定义。
i 为 EVM 初始化代码。
公式 (81) - (83) 为合约地址的产生规则。首先定义了函数 LA ,若未提供 salt ,则输出 n 与 s 的 RLP 编码结果,否则输出 (255) · s · ζ · KEC(i)(公式(83)),这也意味着同一个账户,对于同一个合约代码,可以创建可预见的合约地址。然后将 LA 的输出结果经过 Keccak-256 哈希后,取右边 160 位(公式(82))即可得到地址(公式(82))。公式 (81) 则是描述了需带入的实际参数。
σ, A, s, o, r, c, g, p, v, v˜ d, e, w 为消息调用函数参数,分别为:执行当前世界状态,子状态,发送人(sender)地址,原始发送者(考虑通过合约创建的情况),接收地址,执行代码位置(通常与 r 一致),GasLimit,GasPrice,发送的以太币数量(msg.value),通过 DELEGATECALL 而出现在新的执行上下文中的以太币数量,调用入参 input data 数据,当前堆栈深度和对状态进行修改的许可。
σ', g', A', z, o 为函数返回值,分别是:新的状态,剩余 Gas ,新的子状态,状态码,输出(output)内容。
前言与版本
笔者最近在结合以太坊黄皮书读以太坊源码,结合自己的理解解析下黄皮书内的公式,与大家共同学习进步,若大家在阅读以太坊黄皮书时,对公式产生理解上的困惑,可以参阅本文一起看。文章基于当前(2022/1/10)的黄皮书,版本号为
BERLIN VERSION fabef25
,若有不准确之处,欢迎指出。由于该黄皮书内除附录外有 183 个公式,为了让文章篇幅不过长,该解析会由三部分组成一个系列,每个系列解析约 60 个公式,本文为中篇。公式解析
公式 (61) 定义了一个交易的起始状态,即会被扣除预付款(公式(62)),并且 nonce + 1 (公式(63))。
公式 (66) 定义了 g 为交易的 GasLimit 减去执行交易的基本费用。
公式 (64) 表达了交易后的临时状态 σp,根据是合约创建还是普通交易,而又不同的入参。公式 (65) 定义当前入参子状态是公式 (55) 中定义的空子状态中各值与当前交易子状态中各值的与集(and)。
公式 (67) 表示,在交易过程中,调用者若有通过调用
selfdestruct(addr);
销毁合约,则合约内的以太币会累计到退还余额中。公式 (68) 定义了总退还 Gas 数量的计算,与执行交易后剩余 Gas 数量 g' ,执行合约花费的 Gas 数量 (Tg - g')和销毁合约退还数量 A'r 相关。
公式 (69) - (72) 定义了从交易临时中间状态到预备最终状态的转换。首先在交易者的余额中加上应退还的数量(公式(70))。在矿工的余额中加上消耗的以太币数量(公式(71))。并将矿工收益记录在区块的 beneficiary 值上(公式(72))。
公式 (73) - (75) 定义了交易从预备最终状态到最终状态的转换。会先删除需要自毁的合约(公式(74)),再删除接触到的死合约(公式(75)),死合约的定义在公式 (15)。
公式 (76) - (78) 给出了三个交易相关状态的定义。
公式 (79) 定义了创建合约时的参数 salt 是可选的,若提供 slat,需要是一个长度为 32 的字节。
公式 (80) 给出了创建合约函数的定义(输入,输出)。
\
公式 (81) - (83) 为合约地址的产生规则。首先定义了函数 LA ,若未提供 salt ,则输出 n 与 s 的 RLP 编码结果,否则输出 (255) · s · ζ · KEC(i)(公式(83)),这也意味着同一个账户,对于同一个合约代码,可以创建可预见的合约地址。然后将 LA 的输出结果经过 Keccak-256 哈希后,取右边 160 位(公式(82))即可得到地址(公式(82))。公式 (81) 则是描述了需带入的实际参数。
公式 (84) 则表示新创建的合约地址,会被存入 Aa 交易子状态中(于公式 (54) 中定义)。
公式 (89) 定义了 v' ,若地址在之前就有余额,则会继承。
公式 (85) 定义了新的世界状态,在创建的地址上会出现一个新的合约,nonce 为 1, 余额为 v' 加上创建交易传入的以太币,以及空的 storageRoot 和 codeHash (公式(86))。创建者的地址上会扣除发送的以太币(公式(88)),然后保存其状态(公式(87))。
公式 (90) 定义了初始化代码执行函数的输入与输出。
公式 (91) - (99) 定义了参数 I 所包含的项。
公式 (100) 表示合约创建开销,与合约代码大小成正比。
公式 (105) 定义了创建失败的一些场景:原地址不为空,且有 codeHash 或有 nonce;创建合约代码为空;gas 费不足;代码过大;
公式 (104) 定义了状态码 z ,如果创建失败,则为 0 ,成功则为 1 。
公式 (102) (103) 定义了,若创建失败,则不更新状态和子状态。
公式 (101) 定义了若创建失败,则不会收取代码创建开销。
公式 (107) 定义了 a1 这个交易中的第一个临时状态:除非发送者和接收者是同一个地址,否则跟随交易发送的以太币(msg.value)会被发送。
公式 (108) - (113) 描述的是公式 (107) 的具体流程。如果账户 a1[r] 是一个新地址,则会对账户进行状态初始化(公式(112)),并且在余额中加上交易转入的以太币(公式(113)),然后更新到临时状态(公式(111))。相应地,在发送人那边也减少对应的以太币(公式(110)),更新临时状态(公式(108))。
公式 (118) 定义了执行消息调用函数 Ξ ,将会输出 σ (执行后世界状态),g(执行后剩余 Gas),A(执行后子状态码)和 o (调用结果输出(output))。公式 (127) 描述了定义在地址 1 - 8 上的预留函数(参阅黄皮书附录 E),以及常规执行函数。公式 (120) 表达了客户端在部署完合约后,会在本地存储交易调用代码的地址及其哈希。公式 (114) - (117) 则表达了会根据 Ξ 函数输出的 σ (执行后世界状态)是否为空来判断是否要用 Ξ 函数的其他输出来更新自身对应状态。
公式 (129) 定义了公式 (127) 中的地址集合为 π 。