# 以太坊黄皮书公式解析（中）

By [DavidCai.eth](https://paragraph.com/@davidcai) · 2022-01-13

---

前言与版本
-----

笔者最近在结合[以太坊黄皮书](https://ethereum.github.io/yellowpaper/paper.pdf)读[以太坊源码](https://github.com/ethereum/go-ethereum)，结合自己的理解解析下黄皮书内的公式，与大家共同学习进步，若大家在阅读以太坊黄皮书时，对公式产生理解上的困惑，可以参阅本文一起看。文章基于当前（2022/1/10）的黄皮书，版本号为 `BERLIN VERSION fabef25` ，若有不准确之处，欢迎指出。由于该黄皮书内除附录外有 183 个公式，为了让文章篇幅不过长，该解析会由三部分组成一个系列，每个系列解析约 60 个公式，本文为中篇。

公式解析
----

![(61) (62) (63)](https://storage.googleapis.com/papyrus_images/61fe083b6844909bbcc05a7cbf6227c15570fea733fe9b2ab7f9d3e831cef963.png)

(61) (62) (63)

*   S(T)b 为交易发送者账户余额。
    
*   S(T)n 为交易发送者账户 nonce 。
    
*   TgTp 为 GasPrice \* GasLimit ，即交易预付款。
    

公式 (61) 定义了一个交易的起始状态，即会被扣除预付款（公式(62)），并且 nonce + 1 （公式(63)）。

![(66)](https://storage.googleapis.com/papyrus_images/8cf24b6cf3963cf2cf84bf0049b9d61e41da09e73900f55ab7323031d2338b21.png)

(66)

*   g0 为需要支付执行合约的基本费用。
    

公式 (66) 定义了 g 为交易的 GasLimit 减去执行交易的基本 Gas 数量。

![(64) (65)](https://storage.googleapis.com/papyrus_images/a2074dabc5580bf1fcfa15dadcadfc58ea8f91bc504c0b3e304aa465d82c784f.png)

(64) (65)

*   σp 是交易执行后的账户临时状态。
    
*   g' 是交易后剩余 Gas 。
    
*   A 是交易子状态。
    
*   z 是状态代码。
    
*   To 是交易的原始发起人，若是合约间交易调用，那么该值就是发起调用的合约地址。
    
*   g 在公式 (66) 已定义。
    
*   Λ4 是合约创建函数（Tt，即接收者地址为空），该函数定义后文会详细阐述，下标的 4 表示只取原函数返回值的前四个值。
    
*   Θ4 表示普通交易、发送调用消息函数，该函数定义后文会详细阐述，下标的 4 表示只取原函数返回值的前四个值。
    
*   T 为一个布尔值，表示对状态进行修改的许可。在后文对 Λ 和 Θ 的入参定义中，通常记作 w 。
    

公式 (64) 表达了交易后的临时状态 σp，根据是合约创建还是普通交易，而会有不同的入参。公式 (65) 定义当前入参子状态是公式 (55) 中定义的空子状态中各值与当前交易子状态中各值的与集（and）。

![(67)](https://storage.googleapis.com/papyrus_images/0e01eadf3c74d03d84265d58c13d14f13d1cc6ae90e1853a95dc1c4ad562be41.png)

(67)

*   Ar 交易应返还的 Gas 数量。
    

公式 (67) 表示，在交易过程中，调用者若有通过调用 `selfdestruct(addr);` 销毁合约，则合约内的以太币（balance）会累计到退还余额中。

![(68)](https://storage.googleapis.com/papyrus_images/d93d2a5e89ece32501751a6add4078625a6065e5e372bbdad145568908bc7d21.png)

(68)

*   g\* 为总计退还 Gas 数量。
    

公式 (68) 定义了总退还 Gas 数量的计算，与执行交易后剩余 Gas 数量 g' ，执行合约花费的 Gas 数量 （Tg - g'）和包含了销毁合约退还数量的累积计数 A'r 相关。

![(69) - (72)](https://storage.googleapis.com/papyrus_images/925bed66381127112b115e1b4a25cedb47ae69af8b7c38fd2f3f7380df9b190e.png)

(69) - (72)

*   σ\* 为交易预备最终状态。
    
*   σp 为交易临时中间状态。
    
*   BHc 为区块的 beneficiary 值。
    

公式 (69) - (72) 定义了从交易临时中间状态到预备最终状态的转换。首先在交易者的余额中加上应退还的数量（公式(70)）。在矿工的余额中加上消耗的以太币数量（公式(71)）。并将矿工收益记录在区块的 beneficiary 值上（公式(72)）。

![(73) - (75)](https://storage.googleapis.com/papyrus_images/b1caca5e773d15d13ae5be454baffbc0487f0d5c2ea7dc7b96790583cbb7cc5f.png)

(73) - (75)

*   σ' 为交易最终状态。
    

公式 (73) - (75) 定义了交易从预备最终状态到最终状态的转换。会先删除需要自毁的合约（公式(74)），再删除接触到的死合约（公式(75)），死合约的定义在公式 (15)。

![(76) - (78)](https://storage.googleapis.com/papyrus_images/25e9ee9b5f64d360a5cde3e7858259449029679817f30c0966f78f53e029a890.png)

(76) - (78)

*   Υg 为交易总共使用的 Gas 。
    
*   Υl 为交易所创建的所有日志。
    
*   Υz 为交易状态码。
    

公式 (76) - (78) 给出了三个交易相关状态的定义。

![(79)](https://storage.googleapis.com/papyrus_images/10e4caa9284231bb5f068c10f973cd6e7519fd195a38575c0c538d301b5056df.png)

(79)

*   ζ 为合约创建时可选的一个参数，salt ，用于创建可预见的地址。使用场景可参阅[这篇文章](https://mirror.xyz/0x56706F118e42AE069F20c5636141B844D1324AE1/_Ftvns66cYFuKuHDvPEWxOV_QZId-9O6b1hKzx6ke7A)。
    

公式 (79) 定义了创建合约时的参数 salt 是可选的，若提供 slat，需要是一个长度为 32 的字节。

![(80)](https://storage.googleapis.com/papyrus_images/65cfbafa373e34fc259fdaa813f080b546096f25d21f76de666c0d15c649b203.png)

(80)

*   Λ 为创建合约函数。
    
*   σ, A, s, o, g, p, v, i, e, ζ, w 为创建合约函数的参数，分别是：状态，子状态，发送者，原始发送者（考虑通过合约创建的情况），GasLimit，GasPrice，EVM 初始代码化代码，创建合约的调用所处的当前栈深度，salt 和 对状态进行修改的许可。
    
*   σ', g', A', z, o 为函数返回值，分别是：新的状态，剩余 Gas ，新的子状态，状态码，输出（output）内容。
    

公式 (80) 给出了创建合约函数的定义（输入，输出）。

![(81) - (83)](https://storage.googleapis.com/papyrus_images/3d51538faaa6f2c7d89a9380d78f87cac2d4880885da6b03d2c097d71e2ded8f.png)

(81) - (83)

*   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) 则是描述了需带入的实际参数。

![(84)](https://storage.googleapis.com/papyrus_images/3f93fba35a134a7d7227e4c45364500e65614e3df21ca369b4b9b3f02f0cbb1e.png)

(84)

公式 (84) 则表示新创建的合约地址，会被存入 Aa 交易子状态中（于公式 (54) 中定义）。

![(89)](https://storage.googleapis.com/papyrus_images/d0cf6e0d8bf63c7706755af23d9ecea3c7f46c2f47f8e88be8814e9634c0b53f.png)

(89)

公式 (89) 定义了 v' ，若地址在之前就有余额，则会继承。

![(85) - (88)](https://storage.googleapis.com/papyrus_images/4399743778f3a1932ce9685619c19d1531606364f6dcd11c8e17f79476e49c98.png)

(85) - (88)

公式 (85) 定义了新的世界状态，在创建的地址上会出现一个新的合约，nonce 为 1， 余额为 v' 加上创建交易传入的以太币，以及空的 storageRoot 和 codeHash （公式(86)）。创建者的地址上会扣除发送的以太币（公式(88)），然后保存其状态（公式(87)）。

![(90)](https://storage.googleapis.com/papyrus_images/9294ae1e6feda512ae5f350ef811aee1bc9e97c7626e58409234327df170fcfe.png)

(90)

*   Ξ 为执行初始化 EVM 代码的代码执行函数。
    
*   σ\*\* 合约初始化后的状态。
    
*   g\*\* 为初始化后可用的剩余 Gas 。
    
*   A 为累计子状态。
    
*   o 为账户代码。
    
*   I 为一系列输入参数变量，会在后文阐述。
    

公式 (90) 定义了初始化代码执行函数的输入与输出。

![(91) - (99)](https://storage.googleapis.com/papyrus_images/a095a50a2bfa8b82f946d7940c85a8df772a1840cdbaaa117237a95953cb16ce.png)

(91) - (99)

*   I 为 Ξ 的其中一个参数，包含一系列变量。
    
*   Ia 为新创建合约的地址。
    
*   Io 为交易的原始发起人。
    
*   Ip 为交易的 Gas Price 。
    
*   Id 函数调用参数，这里为空，这个交易不可能会有具体函数调用参数 Input 。
    
*   Is 交易发送者（sender）地址。
    
*   Iv 发送给合约的初始以太币。
    
*   Ie 当前调用栈深度。
    
*   Iw 对状态进行修改的许可。
    

公式 (91) - (99) 定义了参数 I 所包含的项。

![(100)](https://storage.googleapis.com/papyrus_images/02cc829d7548e0f70a39f1bbead86f613ffcdb94250990256af9832d12bb54aa.png)

(100)

公式 (100) 表示合约创建开销，与合约代码大小成正比。

![(101) - (105)](https://storage.googleapis.com/papyrus_images/015a560dfaa6946faa4e9a7b97ee94a18fd57a96a54fbcb325d25437e382a18e.png)

(101) - (105)

公式 (105) 定义了创建失败的一些场景：原地址不为空，且有 codeHash 或有 nonce；创建合约代码为空；gas 费不足；代码过大；

公式 (104) 定义了状态码 z ，如果创建失败，则为 0 ，成功则为 1 。

公式 (102) (103) 定义了，若创建失败，则不更新状态和子状态。

公式 (101) 定义了若创建失败，则不会收取代码创建开销。

![(106)](https://storage.googleapis.com/papyrus_images/52bd85686a595bbe0807998ac122e2bd7a8df51b8b1e6169399a45c8535c1126.png)

(106)

*   Θ 为消息调用函数
    
*   σ, 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）内容。
    

![(107)](https://storage.googleapis.com/papyrus_images/872a2eb8c721270e19fbee4531f64c6b0f1f93478a928d50a5e1e506f7e0a599.png)

(107)

公式 (107) 定义了 a1 这个交易中的第一个临时状态：除非发送者和接收者是同一个地址，否则跟随交易发送的以太币（msg.value）会被发送。

![(108) - (113)](https://storage.googleapis.com/papyrus_images/18746eb7ab0c3e7268b558a29045fbc2f9cdda686e7d1480fb66b05069f8a2db.png)

(108) - (113)

公式 (108) - (113) 描述的是公式 (107) 的具体流程。如果账户 a1\[r\] 是一个新地址，则会对账户进行状态初始化（公式(112)），并且在余额中加上交易转入的以太币（公式(113)），然后更新到临时状态（公式(111)）。相应地，在发送人那边也减少对应的以太币（公式(110)），更新临时状态（公式(108)）。

![(114) - (128)](https://storage.googleapis.com/papyrus_images/d1447e34311cca076afce31104a316dbd2b81313a9c677bac4e4809ea0f17fd3.png)

(114) - (128)

*   I 中各参数的定义可以参阅公式 (91) - (99) 。
    

公式 (118) 定义了执行消息调用函数 Ξ ，将会输出 σ\*\* （执行后世界状态），g\*\*（执行后剩余 Gas），A\*\*（执行后子状态码）和 o （调用结果输出（output））。公式 (127) 描述了定义在地址 1 - 8 上的预留函数（参阅黄皮书附录 E），以及常规执行函数。公式 (120) 表达了客户端在部署完合约后，会在本地存储交易调用代码的地址及其哈希。公式 (114) - (117) 则表达了会根据 Ξ 函数输出的 σ\*\* （执行后世界状态）是否为空来判断是否要用 Ξ 函数的其他输出来更新自身对应状态。

![(129)](https://storage.googleapis.com/papyrus_images/2a1cc44b6d20cf9d8a02e5b25df1eab863a26bcd0562f6e6a09a73e1adfdd534.png)

(129)

公式 (129) 定义了公式 (127) 中的地址集合为 π 。

---

*Originally published on [DavidCai.eth](https://paragraph.com/@davidcai/j8dLWePhpkDPwaShiHGZ)*
