Yul语言及对象说明——Solidity中文文档(9)

Posted by

Yul语言及对象说明——Solidity中文文档(9)

本文内容来源于HiBlock区块链社区翻译小组,感谢全体译者的辛苦工作。点击“阅读原文”即可查看完整中文文档。

Yul语言及对象说明——Solidity中文文档(9)

写在前面:HiBlock区块链社区成立了翻译小组,翻译区块链相关的技术文档及资料,本文为Solidity文档翻译的第九部分《Yul语言及对象说明》,特发布出来邀请solidity爱好者、开发者做公开的审校,您可以添加微信baobaotalk_com,验证输入“solidity”,然后将您的意见和建议发送给我们,也可以在文末“留言”区留言,有效的建议我们会采纳及合并进下一版本,同时将送一份小礼物给您以示感谢。

Yul (先前被也被称为 JULIA 或 IULIA)是一种可以编译到各种不同后端的中间语言(以太坊虚拟机Ethereum Virtual Machine(EVM)1.0,以太坊虚拟机Ethereum Virtual Machine(EVM)1.5,而 eWASM 也在计划中)。 正因为如此,它被设计成为这三种平台的可用的共同标准。 它已经可以用于 Solidity 内部的“内联汇编”,并且未来版本的 Solidity 编译器甚至会将 Yul 用作中间语言。 为 Yul 构建高级的优化器阶段也将会很容易。

Yul语言及对象说明——Solidity中文文档(9)

Yul 的核心组件是函数,代码块,变量,字面量,for 循环,if 条件语句,switch 条件语句,表达式和变量赋值。

Yul 是强类型的,变量和字面量都需要通过前缀符号来指明类型。支持的类型有:bool,u8,s8,u32,s32,u64,s64,u128,s128,u256和s256。

Yul 本身甚至不提供操作符。如果目标平台是以太坊虚拟机Ethereum Virtual Machine(EVM),则操作码将作为内置函数提供,但如果后端平台发生了变化,则可以重新实现它们。 有关强制性的内置函数的列表,请参阅下面的章节。

以下示例程序假定以太坊虚拟机Ethereum Virtual Machine(EVM)操作码mul,div和mo是原生支持或可以作为函数用以计算指数的。

{ function power(base:u256, exponent:u256) -> result:u256 { switch exponent case 0:u256 { result := 1:u256 } case 1:u256 { result := base } default: { result := power(mul(base, base), div(exponent, 2:u256)) switch mod(exponent, 2:u256) case 1:u256 { result := mul(base, result) } } }

}

也可用 for 循环代替递归来实现相同的功能。这里,我们需要以太坊虚拟机Ethereum Virtual Machine(EVM)操作码lt(小于)和add可用。

{ function power(base:u256, exponent:u256) -> result:u256 { result := 1:u256 for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) } { result := mul(result, base) } }

}

1

Yul语言说明

本章介绍 Yul 代码。Yul 代码通常放置在一个 Yul 对象中,它将在下一节中介绍。

语法:

代码块 = ‘{‘ 语句* ‘}’

语句 = 代码块 | 函数定义 | 变量声明 | 赋值 | 表达式 | Switch | For 循环 | 循环中断

函数定义 = ‘function’ 标识符 ‘(‘ 带类型的标识符列表? ‘)’ ( ‘->’ 带类型的标识符列表 )? 代码块

变量声明 = ‘let’ 带类型的标识符列表 ( ‘:=’ 表达式 )?

赋值 = 标识符列表 ‘:=’ 表达式

表达式 = 函数调用 | 标识符 | 字面量

If 条件语句 = ‘if’ 表达式 代码块

Switch 条件语句 = ‘switch’ 表达式 Case* ( ‘default’ 代码块 )?

Case = ‘case’ 字面量 代码块

For 循环 = ‘for’ 代码块 表达式 代码块 代码块

循环中断 = ‘break’ | ‘continue’

函数调用 = 标识符 ‘(‘ ( 表达式 ( ‘,’ 表达式 )* )? ‘)’

标识符 = [a-zA-Z_$] [a-zA-Z_0-9]*

标识符列表 = 标识符 ( ‘,’ 标识符)*

类型名 = 标识符 | 内置的类型名

内置的类型名 = ‘bool’ | [us] ( ‘8’ | ’32’ | ’64’ | ‘128’ | ‘256’ )

带类型的标识符列表 = 标识符 ‘:’ 类型名 ( ‘,’ 标识符 ‘:’ 类型名 )*

字面量 = (数字字面量 | 字符串字面量 | 十六进制字面量 | True字面量 | False字面量) ‘:’ 类型名

数字字面量 = 十六进制数字 | 十进制数字

十六进制字面量 = ‘hex’ (‘”‘ ([0-9a-fA-F]{2})* ‘”‘ | ”’ ([0-9a-fA-F]{2})* ”’)字符串字面量 = ‘”‘ ([^”rn\] | ‘\’ .)* ‘”‘

True字面量 = ‘true’

False字面量 = ‘false’

十六进制数字 = ‘0x’ [0-9a-fA-F]+

十进制数字 = [0-9]+

语法层面的限制

Switches 必须至少有一个 case(包括 default )。 如果表达式的所有可能值都被覆盖了,那么不应该允许使用 default (即带bool表达式的 switch 语句同时具有 true case 和 false case 的情况下不应再有 default 语句)。

每个表达式都求值为零个或多个值。 标识符和字面量求值为一个值,函数调用求值为所调用函数的返回值。

在变量声明和赋值中,右侧表达式(如果存在)求值后,必须得出与左侧变量数量相等的值。 这是唯一允许求值出多个值的表达式。

那种同时又是语句的表达式(即在代码块的层次)求值结果必须只有零个值。

在其他所有情况中,表达式求值后必须仅有一个值。

continue和break语句只能用在循环体中,并且必须与循环处于同一个函数中(或者两者都必须在顶层)。

for 循环的条件部分的求值结果只能为一个值。

字面量不可以大于它们本身的类型。已定义的最大类型宽度为 256 比特。

作用域规则

Yul 中的作用域是与块(除了函数和 for 循环,如下所述)和所有引入新的标识符到作用域中的声明 (FunctionDefinition,VariableDeclaration)紧密绑定的。

标识符在将其定义的块中可见(包括所有子节点和子块)。 作为例外,for 循环的 “init” 部分中(第一个块)定义的标识符在 for 循环的所有其他部分(但不在循环之外)中都是可见的。 在 for 循环的其他部分声明的标识符遵守常规的作用域语法规则。 函数的参数和返回参数在函数体中可见,并且它们的名称不能相同。

变量只能在声明后引用。 尤其是,变量不能在它们自己的变量声明的右边被引用。 函数可以在声明之前被引用(如果它们是可见的)。

Shadowing 是不被允许的,即是说,你不能在同名标识符已经可见的情况下又定义该标识符,即使它是不可访问的。

在函数内,不可能访问声明在函数外的变量。

形式规范

我们通过在 AST 的各个节点上提供重载的求值函数 E 来正式指定 Yul。 任何函数都可能有副作用,所以 E 接受两个状态对象和 AST 节点作为它的参数,并返回两个新的状态对象和数量可变的其他值。

这两个状态对象是全局状态对象(在以太坊虚拟机Ethereum Virtual Machine(EVM)的上下文中是内存memory,存储storage和区块链的状态)和本地状态对象(局部变量的状态,即以太坊虚拟机Ethereum Virtual Machine(EVM)中堆栈的某个段)。 如果 AST 节点是一个语句,E 将返回两个状态对象和一个用于 break 和 continue 语句的 “mode”。 如果 AST 节点是表达式,则 E 返回两个状态对象,并返回与表达式求值结果相同数量的值。

在这份高层次的描述中,并没有对全局状态的确切本质进行说明。 本地状态L是标识符i到值v的映射,表示为L[i]=v。 对于标识符v, 我们用$v作为标识符的名字。

我们将为 AST 节点使用解构符号。

E(G, L, <{St1, …, Stn}>: Block) = let G1, L1, mode = E(G, L, St1, …, Stn) let L2 be a restriction of L1 to the identifiers of L G1, L2, modeE(G, L, St1, …, Stn: Statement) = if n is zero: G, L, regular else: let G1, L1, mode = E(G, L, St1) if mode is regular then E(G1, L1, St2, …, Stn) otherwise G1, L1, modeE(G, L, FunctionDefinition) = G, L, regularE(G, L, <let var1, …, varn := rhs>: VariableDeclaration) = E(G, L, <var1, …, varn := rhs>: Assignment)E(G, L, <let var1, …, varn>: VariableDeclaration) = let L1 be a copy of L where L1[$vari] = 0 for i = 1, …, n G, L1, regularE(G, L, <var1, …, varn := rhs>: Assignment) = let G1, L1, v1, …, vn = E(G, L, rhs) let L2 be a copy of L1 where L2[$vari] = vi for i = 1, …, n G, L2, regularE(G, L, <for { i1, …, in } condition post body>: ForLoop) = if n >= 1: let G1, L1, mode = E(G, L, i1, …, in) // 由于语法限制,mode 必须是规则的 let G2, L2, mode = E(G1, L1, for {} condition post body) // 由于语法限制,mode 必须是规则的 let L3 be the restriction of L2 to only variables of L G2, L3, regular else: let G1, L1, v = E(G, L, condition) if v is false: G1, L1, regular else: let G2, L2, mode = E(G1, L, body) if mode is break: G2, L2, regular else: G3, L3, mode = E(G2, L2, post) E(G3, L3, for {} condition post body)E(G, L, break: BreakContinue) = G, L, breakE(G, L, continue: BreakContinue) = G, L, continueE(G, L, <if condition body>: If) = let G0, L0, v = E(G, L, condition) if v is true: E(G0, L0, body) else: G0, L0, regularE(G, L, <switch condition case l1:t1 st1 … case ln:tn stn>: Switch) = E(G, L, switch condition case l1:t1 st1 … case ln:tn stn default {})E(G, L, <switch condition case l1:t1 st1 … case ln:tn stn default st’>: Switch) = let G0, L0, v = E(G, L, condition) // i = 1 .. n // 对字面量求值,上下文无关 let _, _, v1 = E(G0, L0, l1) … let _, _, vn = E(G0, L0, ln) if there exists smallest i such that vi = v: E(G0, L0, sti) else: E(G0, L0, st’)E(G, L, <name>: Identifier) = G, L, L[$name]E(G, L, <fname(arg1, …, argn)>: FunctionCall) = G1, L1, vn = E(G, L, argn) … G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2) Gn, Ln, v1 = E(G(n-1), L(n-1), arg1) Let <function fname (param1, …, paramn) -> ret1, …, retm block> be the function of name $fname visible at the point of the call. Let L’ be a new local state such that L'[$parami] = vi and L'[$reti] = 0 for all i. Let G”, L”, mode = E(Gn, L’, block) G”, Ln, L”[$ret1], …, L”[$retm]E(G, L, l: HexLiteral) = G, L, hexString(l), where hexString decodes l from hex and left-aligns it into 32 bytesE(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l), where utf8EncodeLeftAligned performs a utf8 encoding of l and aligns it left into 32 bytesE(G, L, n: HexNumber) = G, L, hex(n) where hex is the hexadecimal decoding functionE(G, L, n: DecimalNumber) = G, L, dec(n), where dec is the decimal decoding function

类型转换函数

Yul 不支持隐式类型转换,因此存在提供显式转换的函数。 在将较大类型转换为较短类型时,如果发生溢出,则可能会发生运行时异常。

下列类型的“截取式”转换是允许的:

  • bool

  • u32

  • u64

  • u256

  • s256

Yul语言及对象说明——Solidity中文文档(9)

低级函数

以下函数必须可用:

逻辑操作

not(x:bool) -> z:bool

逻辑非

and(x:bool, y:bool) -> z:bool

逻辑与

or(x:bool, y:bool) -> z:bool

逻辑或

xor(x:bool, y:bool) -> z:bool

异或

算术操作

addu256(x:u256, y:u256) -> z:u256

x + y

subu256(x:u256, y:u256) -> z:u256

x – y

mulu256(x:u256, y:u256) -> z:u256

x * y

divu256(x:u256, y:u256) -> z:u256

x / y

divs256(x:s256, y:s256) -> z:s256

x / y, 有符号数用补码形式

modu256(x:u256, y:u256) -> z:u256

x % y

mods256(x:s256, y:s256) -> z:s256

x % y, 有符号数用补码形式

signextendu256(i:u256, x:u256) -> z:u256

从第 (i*8+7) 位开始进行符号扩展,从最低符号位开始计算

expu256(x:u256, y:u256) -> z:u256

x y 次方

addmodu256(x:u256, y:u256, m:u256) -> z:u256

任意精度的数学模运算 (x + y) % m

mulmodu256(x:u256, y:u256, m:u256) -> z:u256

任意精度的数学模运算 (x * y) % m

ltu256(x:u256, y:u256) -> z:bool

x < y true, 否则为 false

gtu256(x:u256, y:u256) -> z:bool

x > y true, 否则为 false

sltu256(x:s256, y:s256) -> z:bool

x < y true, 否则为 false 有符号数用补码形式

sgtu256(x:s256, y:s256) -> z:bool

x > y true, 否则为 false 有符号数用补码形式

equ256(x:u256, y:u256) -> z:bool

x == y true, 否则为 false

iszerou256(x:u256) -> z:bool

x == 0 true, 否则为 false

notu256(x:u256) -> z:u256

~x, x 按位非

andu256(x:u256, y:u256) -> z:u256

x y 按位与

oru256(x:u256, y:u256) -> z:u256

x y 按位或

xoru256(x:u256, y:u256) -> z:u256

x y 按位异或

shlu256(x:u256, y:u256) -> z:u256

x 逻辑左移 y

shru256(x:u256, y:u256) -> z:u256

x 逻辑右移 y

saru256(x:u256, y:u256) -> z:u256

x 算术右移 y

byte(n:u256, x:u256) -> v:u256

x 的第 n 字节,这里的索引位置是从 0 开始的;能否用 and256(shr256(n, x), 0xff) 来替换它,并使它在 EVM 后端之外被优化呢?

内存和存储

mload(p:u256) -> v:u256

mem[p..(p+32))

mstore(p:u256, v:u256)

mem[p..(p+32)) := v

mstore8(p:u256, v:u256)

mem[p] := v & 0xff – 仅修改单个字节

sload(p:u256) -> v:u256

storage[p]

sstore(p:u256, v:u256)

storage[p] := v

msize() -> size:u256

内存的大小, 即已访问过的内存的最大下标,因为内存扩展的限制(只能按字进行扩展)返回值永远都是 32 字节的倍数

执行控制

create(v:u256, p:u256, s:u256)

mem[p..(p+s)) 上的代码创建一个新合约,发送 v wei,并返回一个新的地址

call(g:u256, a:u256, v:u256, in:u256, insize:u256, out:u256, outsize:u256) -> r:u256

调用地址 a 上的合约,以 mem[in..(in+insize)) 作为输入一并发送 g gas v wei ,以 mem[out..(out+outsize)) 作为输出空间。若错误,返回 0 (比如,gas 用光成功,返回 1

callcode(g:u256, a:u256, v:u256, in:u256, insize:u256, out:u256, outsize:u256) -> r:u256

相当于call但仅仅使用地址 a 上的代码,而留在当前合约的上下文当中

delegatecall(g:u256, a:u256, in:u256, insize:u256, out:u256, outsize:u256) -> r:u256

相当于callcode但同时保留callercallvalue

abort()

终止 (相当于EVM上的非法指令)

return(p:u256, s:u256)

终止执行,返回 mem[p..(p+s)) 上的数据

revert(p:u256, s:u256)

终止执行,恢复状态变更,返回 mem[p..(p+s)) 上的数据

selfdestruct(a:u256)

终止执行,销毁当前合约,并且将余额发送到地址 a

log0(p:u256, s:u256)

mem[p..(p+s)] 上的数据产生日志,但没有 topic

log1(p:u256, s:u256, t1:u256)

mem[p..(p+s)] 上的数据和 topic t1 产生日志

log2(p:u256, s:u256, t1:u256, t2:u256)

mem[p..(p+s)] 上的数据和 topic t1t2 产生日志

log3(p:u256, s:u256, t1:u256, t2:u256, t3:u256)

mem[p..(p+s)] 上的数据和 topic t1t2t3 产生日志

log4(p:u256, s:u256, t1:u256, t2:u256, t3:u256, t4:u256)

mem[p..(p+s)] 上的数据和 topic t1t2t3t4 产生日志

状态查询

blockcoinbase() -> address:u256

当前的矿工

blockdifficulty() -> difficulty:u256

当前区块的难度

blockgaslimit() -> limit:u256

当前区块的区块 gas 限制

blockhash(b:u256) -> hash:u256

区块号为 b 的区块的哈希,仅可用于最近的 256 个区块,不包含当前区块

blocknumber() -> block:u256

当前区块号

blocktimestamp() -> timestamp:u256

epoch 开始的,当前块的时间戳,以秒为单位

txorigin() -> address:u256

交易的发送方

txgasprice() -> price:u256

交易中的 gas 价格

gasleft() -> gas:u256

还可用于执行的 gas

balance(a:u256) -> v:u256

地址 a 上的 wei 余额

this() -> address:u256

当前合约/执行上下文的地址

caller() -> address:u256

调用的发送方 (不包含委托调用)

callvalue() -> v:u256

与当前调用一起发送的 wei

calldataload(p:u256) -> v:u256

position p 开始的 calldata (32 字节)

calldatasize() -> v:u256

以字节为单位的 calldata 的大小

calldatacopy(t:u256, f:u256, s:u256)

从位置为 f calldata 中,拷贝 s 字节到内存位置 t

codesize() -> size:u256

当前合约/执行上下文的代码大小

codecopy(t:u256, f:u256, s:u256)

code 位置 f 拷贝 s 字节到内存位置 t

extcodesize(a:u256) -> size:u256

地址 a 上的代码大小

extcodecopy(a:u256, t:u256, f:u256, s:u256)

相当于 codecopy(t, f, s),但从地址 a 获取代码

其他

discard(unused:bool)

丢弃值

discardu256(unused:u256)

丢弃值

splitu256tou64(x:u256) -> (x1:u64, x2:u64,

x3:u64, x4:u64)

将一个 u256 拆分为四个 u64

combineu64tou256(x1:u64, x2:u64, x3:u64,

x4:u64) -> (x:u256)

将四个 u64 组合为一个 u256

keccak256(p:u256, s:u256) -> v:u256

keccak(mem[p…(p+s)))

后端

后端或目标负责将 Yul 翻译到特定字节码。 每个后端都可以暴露以后端名称为前缀的函数。 我们为两个建议的后端保留evm_和ewasm_前缀。

后端: EVM

目标以太坊虚拟机Ethereum Virtual Machine(EVM)将具有所有用evm_前缀暴露的以太坊虚拟机Ethereum Virtual Machine(EVM)底层操作码。

后端: “EVM 1.5”

TBD

后端: eWASM

TBD

2

Yul对象说明

语法:

顶层对象 = ‘object’ ‘{‘ 代码? ( 对象 | 数据 )* ‘}’

对象 = ‘object’ 字符串字面量 ‘{‘ 代码? ( 对象 | 数据 )* ‘}’

代码 = ‘code’ 代码块

数据 = ‘data’ 字符串字面量 十六进制字面量

十六进制字面量 = ‘hex’ (‘”‘ ([0-9a-fA-F]{2})* ‘”‘ | ”’ ([0-9a-fA-F]{2})* ”’)

字符串字面量 = ‘”‘ ([^”rn\] | ‘\’ .)* ‘”‘

在上面,代码块指的是前一章中解释的 Yul 代码语法中的代码块。

Yul 对象示例如下:

..code:

// 代码由单个对象组成。 单个 “code” 节点是对象的代码。// 每个(其他)命名的对象或数据部分都被序列化// 并可供特殊内置函数:datacopy / dataoffset / datasize 用于访问object { code { let size = datasize(“runtime”) let offset = allocate(size) // 这里,对于 eWASM 变为一个内存到内存的拷贝,对于 EVM 则相当于 codecopy datacopy(dataoffset(“runtime”), offset, size) // 这是一个构造函数,并且运行时代码会被返回 return(offset, size) } data “Table2″ hex”4123” object “runtime” { code { // 运行时代码 let size = datasize(“Contract2”) let offset = allocate(size) // 这里,对于 eWASM 变为一个内存到内存的拷贝,对于 EVM 则相当于 codecopy datacopy(dataoffset(“Contract2”), offset, size) // 构造函数参数是一个数字 0x1234 mstore(add(offset, size), 0x1234) create(offset, add(size, 32)) } // 内嵌对象。使用场景是,外层是一个工厂合约,而 Contract2 将是由工厂生成的代码 object “Contract2” { code { // 代码在这 … } object “runtime” { code { // 代码在这 … } } data “Table1″ hex”4123” } }

}

延伸阅读:智能合约-Solidity官方文档(1)

安装Solidity编译器-Solidity官方文档(2)

根据例子学习Solidity-Solidity官方文档(3)

深入理解Solidity之源文件及合约结构——Solidity中文文档(4)

安全考量——Solidity中文文档(5)

合约的元数据——Solidity中文文档(6)

应用二进制接口(ABI)说明——Solidity中文文档(7)

使用编译器——Solidity中文文档(8)

点击“阅读原文”即可查看完整中文文档

Yul语言及对象说明——Solidity中文文档(9)

:本文为solidity翻译的第九部分《Yul语言及对象说明》,特发布出来邀请solidity爱好者、开发者做公开的审校,您可以添加微信baobaotalk_com,验证输入“solidity”,然后将您的意见和建议发送给我们,也可在文末“留言”区留言,或通过原文链接访问我们的Github。有效的建议我们会收纳并及时改进,同时将送一份小礼物给您以示感谢。

— 线上课程推荐 —

线上课程:《8小时区块链智能合约开发实践》

培训讲师:《白话区块链》作者 蒋勇

课程原价:999元,现价 399元

更多福利:

  • @所有人,识别下图二维码转发课程邀请好友报名,即可获得报名费50%返利

  • @学员,报名学习课程并在规定时间内完成考试即可瓜分10000元奖金

Yul语言及对象说明——Solidity中文文档(9)

点击“阅读原文”即可查看完整中文文档

始发于微信公众号: 区块链社区HiBlock