以太坊黄皮书------合约创建
前面的一篇一到六章的黄皮书是转载,通过作者自己对于文章的简化,对于初学者很好理解,现在以太坊黄皮书(中文版)已经发布,所以接下来的文章,我们直接都中文版。虽然都英文版的更好些,但作为初学者,中文版,更好理解相关的知识体系和专业语言。话不多说,我们开始今天的文章阅读。
首先,我们来看一个公式:
(
σ
′
,
g
′
,
A
)
≡
Λ
(
σ
,
s
,
o
,
g
,
p
,
v
,
i
,
e
)
(1)
(\sigma',g',A)\equiv \Lambda(\sigma,s,o,g,p,v,i,e) \tag{1}
(σ′,g′,A)≡Λ(σ,s,o,g,p,v,i,e)(1)
当创建一个账户时会有很多参数: 发送者(
s
s
s)、原始执行者(
o
o
o)、可用的燃料(
g
g
g)、燃料价格(
p
p
p)、转账额度(
v
v
v)、任意长度的字节数组(
i
i
i)、EVM 初始化代码、消息调用/合约创建的当前栈深度 (
e
e
e) 、创建函数为函数 (
Λ
Λ
Λ),相关的变量有状态(
σ
σ
σ) , 包含新状态、剩余燃料及交易子状态
(
σ
′
,
g
′
,
A
)
(σ', g', A)
(σ′,g′,A)。
这个新账户的地址被定义为包含发送者和随机数
R
L
P
RLP
RLP编码的
K
e
c
c
a
k
Keccak
Keccak 哈希的最边 160 位。因此我们定义新帐户
a
a
a的地址:
a
≡
B
96..255
(
K
E
C
(
R
L
P
(
(
s
,
σ
[
s
]
n
−
1
)
)
)
(2)
a \equiv B_{96..255}(KEC(RLP((s,\sigma[s]_n-1))) \tag{2}
a≡B96..255(KEC(RLP((s,σ[s]n−1)))(2)
其中
K
E
C
KEC
KEC是
K
e
c
c
a
k
256
Keccak256
Keccak256位哈希函数,
R
L
P
RLP
RLP是
R
L
P
RLP
RLP编码函数 ,
B
a
.
.
b
B_{a..b}
Ba..b表示取二进制数据
X
X
X位数为范围
[
a
,
b
]
[a,b]
[a,b]的值,
σ
[
x
]
\sigma[x]
σ[x]是地址
x
x
x的状态,
ϕ
\phi
ϕ表示地址不存在时的状态。注意, 我们使用的是一个比发送者随机数要小的值;我们断言会在这个合约创建之前会增加发送者账户的随机数,因此在刚开始在交易或 VM 操作中使用的随机数就是发送者的随机数。
账户的随机数开始被定义为 0,余额为传递的转账值,存储空间为空, 哈希代码为 Keccak 256 位的空字符串哈希值;发送者的余额会减去转账值。这个变化的状态变成
σ
∗
σ^∗
σ∗:
σ
∗
≡
σ
e
x
c
e
p
t
:
(3)
\sigma^* \equiv \sigma \qquad except:\tag{3}
σ∗≡σexcept:(3)
σ
∗
[
a
]
≡
(
0
,
v
+
v
′
.
T
R
L
E
(
ϕ
)
,
K
E
C
(
(
)
)
)
(4)
\sigma^*[a] \equiv (0,v+v'.TRLE(\phi),KEC(())) \tag{4}
σ∗[a]≡(0,v+v′.TRLE(ϕ),KEC(()))(4)
σ
∗
[
s
]
b
−
v
(5)
\sigma^*[s]_b - v \tag{5}
σ∗[s]b−v(5)
v’是账户在交易之前就有的余额:
v
′
≡
{
0
,
if
σ
[
a
]
=
ϕ
σ
[
a
]
b
,
otherwise
(6)
v' \equiv \begin{cases} 0, & \text {if $\sigma[a]=\phi$} \\ \sigma[a]_b, & \text{otherwise} \end{cases} \tag{6}
v′≡{0,σ[a]b,if σ[a]=ϕotherwise(6)
最后,这个账户是通过执行初始化账户的 EVM 代码
i
i
i(执行模型详见小结 9 )来初始化的。代码执行可以影响一些事件:可以改变当前账户的存储,能创建更多的账户, 执行更多的消息调用。代码执行函数
Ξ
\Xi
Ξ 可以得到一个元组, 包括结果状态
σ
∗
∗
σ^{∗∗}
σ∗∗, 可用的剩余燃料
g
∗
∗
g^{∗∗}
g∗∗, 子状态
A
A
A, 以及账户
o
o
o 的代码。赋值到最后的状态的数组中,有效的剩余燃料,当前产生的子状态 A 和账号代码本身
o
o
o。
其中
I
I
I 包含执行环境的相关参数, 这些参数在小结 9 中有详细定义:
I
a
≡
a
(7)
I_a \equiv a \tag{7}
Ia≡a(7)
I
o
≡
o
(8)
I_o \equiv \large o\tag{8}
Io≡o(8)
I
p
≡
p
(9)
I_p \equiv \large p\tag{9}
Ip≡p(9)
I
d
≡
(
)
(10)
I_d \equiv ()\tag{10}
Id≡()(10)
I
s
≡
s
(11)
I_s \equiv \large s\tag{11}
Is≡s(11)
I
v
≡
v
(12)
I_v \equiv \large v\tag{12}
Iv≡v(12)
I
b
≡
i
(13)
I_b \equiv \large i\tag{13}
Ib≡i(13)
I
e
≡
e
(14)
I_e \equiv \large e\tag{14}
Ie≡e(14)
如果没有输入数据, 使用
I
d
I_d
Id 表示空的元组。
I
H
I_H
IH 没有什么特别的, 它由区块链定。代码执行会消耗燃料,且燃料不能低于 0,因此程序执行可能会在自然终止之前退出。在这个(以及其它几个)异常情况, 我们称发生了燃料不足
(
o
u
t
−
o
f
−
g
a
s
,
O
O
G
)
(out-of-gas, OOG)
(out−of−gas,OOG)异常:演进的状态变成空集合
ϕ
\phi
ϕ, 整个创建操作对状态没有影响,状态就像尝试创建合约之前一样。如果这个初始化代码成功地执行完,那么对应的合约创建也会被支付。代码保存费用
c
c
c 和创建的合约代码大小成正比:
c
≡
G
c
o
d
e
d
e
p
o
s
i
t
×
∣
o
∣
(15)
c \equiv G_{codedeposit} \times|o| \tag{15}
c≡Gcodedeposit×∣o∣(15)
如果没有足够的燃料支付这些, 例如
g
∗
∗
<
c
g^{∗∗} < c
g∗∗<c, 就会抛出燃料不足异常。
发生这样的异常后, 剩余燃料将变为 0。如果合约创建是用于接受一个交易, 它不会影响合约创建内部消耗的燃料,无论如何都必须支付。然而,当燃料不足时, 并不会给这个合约地址转账。
如果没有出现这样的异常, 那么剩余的燃料会退会给最原始的发送者, 改变后的新状态也会永久保存。最终状态、燃料和子状态
(
σ
′
,
g
′
,
A
)
(σ', g', A)
(σ′,g′,A) 的关系如下:
g
′
≡
{
0
,
if
F
g
∗
∗
−
c
,
otherwise
(16)
g' \equiv \begin{cases} 0, & \text {if \quad$F$} \\ g^{**}-c, & \text{otherwise} \end{cases} \tag{16}
g′≡{0,g∗∗−c,if Fotherwise(16)
σ
′
≡
{
σ
,
if
F
σ
∗
∗
,
except
σ
′
[
a
]
c
=
K
E
C
(
o
)
,
otherwise
(17)
\sigma' \equiv \begin{cases} \sigma, & \text {if \quad$F$} \\ \sigma^{**}, & \text{except} \\ \sigma'[a]_c = KEC(o), &\text{otherwise} \end{cases} \tag{17}
σ′≡⎩⎪⎨⎪⎧σ,σ∗∗,σ′[a]c=KEC(o),if Fexceptotherwise(17)
其中
F
≡
(
σ
∗
∗
=
∅
∨
g
∗
∗
<
c
∨
∣
o
∣
>
24576
)
(18)
F \equiv(\sigma^{**}=\emptyset\vee g^{**}<c \vee |o|>24576) \tag{18}
F≡(σ∗∗=∅∨g∗∗<c∨∣o∣>24576)(18)
上述 σ′ 的公式, 表明了执行代码初始化得到的最终字节序列 o, 就是新创建账户的代码体。当合约成功创建时, 如果有转账, 对应的转账也会转到合约中; 如果没有转账, 则仅是合约创建。
7.1. 细微之处. 有一种情况需要注意, 当初始化代码正在执行时,新创建的地址出现了, 但还没有内部的代码时。在这个时间内, 任意消息调用不会引起代码执行。如果这个初始化执行结束于一个 SELFDESTRUCT 指令,这个账号会在交易执行完前将被删除, 这个行为是无意义的。对于一个正常的 STOP 指令代码,或者返回的代码是空的, 这时候会出现一个僵尸账户,而且账户中剩余的余额将被永远被锁定在这个僵尸账户中。