Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

Yul 语言说明

一夜雨十年灯潞
235 0 0
Yul (先前被也被称为 JULIA 或 IULIA)是一种可以编译到各种不同后端的中间语言( |evm| 1.0,|evm| 1.5,而 eWASM 也在计划中)。
  c: K0 N( W2 {. }* D# {正因为如此,它被设计成为这三种平台的可用的共同标准。9 Z# H* y) T$ F; T, B* I
它已经可以用于 Solidity 内部的“内联汇编”,并且未来版本的 Solidity 编译器甚至会将 Yul 用作中间语言。 为 Yul 构建高级的优化器阶段也将会很容易。" U* B7 n1 \- [* l9 d3 ?! }
… note::
( X* ~) ]) i. ~: I请注意,用于“内联汇编”的书写风格是不带类型的(所有的都是 ``u256``),内置函数与 |evm| 操作码相同。
6 _: H& M+ |1 l6 N( W* H+ M有关详细信息,请参阅内联汇编文档。# x+ ]& m8 o' \& E
Yul 的核心组件是函数,代码块,变量,字面量,for 循环,if 条件语句,switch 条件语句,表达式和变量赋值。2 c  Z- K' X/ S  P% c
Yul 是强类型的,变量和字面量都需要通过前缀符号来指明类型。支持的类型有:bool, u8, s8, u32, s32,  Q4 }9 u1 o; i" \8 R
u64, s64, u128, s128, u256 和 s256。1 A: S$ Y. V  B! }) o1 N
Yul 本身甚至不提供操作符。如果目标平台是 |evm|,则操作码将作为内置函数提供,但如果后端平台发生了变化,则可以重新实现它们。
4 Y; e  \: y' [( N% u% ~有关强制性的内置函数的列表,请参阅下面的章节。
  y, ~# k( h4 m2 `! b2 m以下示例程序假定 |evm| 操作码 mul,div 和 mo 是原生支持或可以作为函数用以计算指数的。
& Y+ @) G2 |/ {4 P/ W3 Y8 h* i# n… code::
) {5 x' Z9 t& `2 `% l# M{
( n+ f  H! B7 T& w+ ?7 ?  h4 [* a    function power(base:u256, exponent:u256) -> result:u256% R' E# c# c5 f5 ]# k2 P
    {
4 e4 N3 ~  S$ b1 x: A4 K        switch exponent
( W+ t( g  `0 d1 Q- T5 j( T. B        case 0:u256 { result := 1:u256 }2 Y+ @8 u' P, x. m: q5 O
        case 1:u256 { result := base }" t! Z/ l# ]" i+ R/ |
        default:! a. w. P- L* }* m5 E7 J
        {
! A/ g( c, G+ K/ j$ t            result := power(mul(base, base), div(exponent, 2:u256))2 T+ h! N6 r! z0 f
            switch mod(exponent, 2:u256)
" u: ~2 L% d7 B. }3 V$ Y                case 1:u256 { result := mul(base, result) }2 m7 A! a# T( {. K* U) ?
        }) i# w* g1 v7 R
    }- A1 A* }  M+ T$ B% I4 V% s3 G
}( N2 k; N1 ~. [, Q: ]. ?5 }: C
也可用 for 循环代替递归来实现相同的功能。这里,我们需要 |evm| 操作码 lt (小于)和 add 可用。
) |, ~2 {% L$ e% {( K… code::
* T9 q$ Q. m- o& `0 |2 d{
. J4 g+ ?8 z5 e/ Q" s: E    function power(base:u256, exponent:u256) -> result:u256! _! T5 p0 M/ t* p, M8 m. s
    {
+ m) x* x6 `* _1 ?& {, y+ `& V        result := 1:u256$ T: n$ [; Q, h5 n
        for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) }
4 g- o0 D# Q- L1 U* y        {1 a/ G/ y% C) j- x% M9 ]
            result := mul(result, base)
1 D- Y# a5 b9 F1 X7 \* w  u        }7 D2 L4 r' j8 @* Q( e; J
    }
, ^! l) U' A' A& ]. T  n( i}
( |3 B5 V1 y- [8 d$ i7 ~Yul 语言说明
* T* z0 o4 p1 _8 e9 I2 c* n  |7 O本章介绍 Yul 代码。Yul 代码通常放置在一个 Yul 对象中,它将在下一节中介绍。
+ H% j6 d0 ]- l7 s/ Q" f语法::
% Y3 N, U) _. T6 z1 Y' B3 {' {代码块 = '{' 语句* '}'" ~  C' ~$ s( S0 f
语句 =
9 z: s5 Z- q; z6 w! K' N3 w    代码块 |& ?: a$ G: q# v8 y3 S" ^, u; f
    函数定义 |# G, y" j: ?9 G2 U1 @) B8 l9 X
    变量声明 |! ^. f! N2 \  w7 r7 ?3 k
    赋值 |
) O5 g) S3 n7 S/ p. ?2 A9 P    表达式 |
7 D1 c. I& C8 K4 v9 N$ T    Switch |
5 Y  L& J2 M  K) K: u' X# T7 k    For 循环 |
5 }6 @/ }: G& {1 k4 l. x    循环中断5 o# ]# i, O) s/ j6 K1 y
函数定义 =
2 j  [, K4 ~. n& j, K    'function' 标识符 '(' 带类型的标识符列表? ')'$ o/ a6 @3 T; S/ o
    ( '->' 带类型的标识符列表 )? 代码块
' D4 V. e2 N$ u6 I" Q变量声明 =: Y* C  n( ], `5 ~+ m
    'let' 带类型的标识符列表 ( ':=' 表达式 )?: W) p5 ]+ c  F! c. f; J
赋值 =
( l8 t6 m1 S4 H    标识符列表 ':=' 表达式# v$ q/ D0 q! n9 v
表达式 =. J8 f" h& l0 {4 R
    函数调用 | 标识符 | 字面量
' _& {& t6 j# mIf 条件语句 =
1 N+ I/ u* p5 }1 _5 d9 K; j, s/ u6 ?    'if' 表达式 代码块
; B; q4 o3 B) H: V1 ^  hSwitch 条件语句 =& k) O% j7 t/ i2 c
    'switch' 表达式 Case* ( 'default' 代码块 )?$ W! ^4 e, I" n, f4 J; q0 @7 m
Case =3 u! h4 U) H) z7 C0 N
    'case' 字面量 代码块
# T4 v- k, T; v( ?For 循环 =
8 H6 C6 T. Q& o% F2 l# [    'for' 代码块 表达式 代码块 代码块
2 T4 w% G1 C; p& Q- k% R循环中断 =6 B) Z! F% p& \+ j+ U! ~
    'break' | 'continue'8 Z8 ]5 h# A: r, U( c" z
函数调用 =% a9 O" H# q: a' @
    标识符 '(' ( 表达式 ( ',' 表达式 )* )? ')'6 ^  `7 L3 ?/ o; S4 I. U
标识符 = [a-zA-Z_$] [a-zA-Z_0-9]*: O! ]" s. N; L* ]
标识符列表 = 标识符 ( ',' 标识符)*% ^" H  Q0 ]5 T5 U7 i
类型名 = 标识符 | 内置的类型名
6 _- D1 d/ c3 B" ]! |. m- T6 I5 u' D内置的类型名 = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' )
$ s8 S- e: e* w带类型的标识符列表 = 标识符 ':' 类型名 ( ',' 标识符 ':' 类型名 )*
3 o5 m( v! }8 x( g9 c字面量 =
) a+ X1 L1 T, i. A/ n* w7 J/ v    (数字字面量 | 字符串字面量 | 十六进制字面量 | True字面量 | False字面量) ':' 类型名% Z4 r% U0 j8 h. S  e
数字字面量 = 十六进制数字 | 十进制数字, G7 F' g7 Z/ y, s# e
十六进制字面量 = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
2 O3 b" _' x4 L- \, W5 o字符串字面量 = '"' ([^"\r\n\\] | '\\' .)* '"'# m- T- Z. }$ ?% S7 H
True字面量 = 'true'3 e$ N# N. H6 [  ?0 c& g; S- c
False字面量 = 'false'/ |! z- [& k1 V* z- K0 X! \1 }
十六进制数字 = '0x' [0-9a-fA-F]++ ?# P( W( u) g7 h
十进制数字 = [0-9]+* s* D; }( ]9 ^2 T% q$ T
语法层面的限制
4 C4 ~& }% ~% K$ S/ }, HSwitches 必须至少有一个 case(包括 default )。, v) A+ J' l; {
如果表达式的所有可能值都被覆盖了,那么不应该允许使用 default
' f' o" R7 `1 s9 m2 y; x# c! _(即带 bool 表达式的 switch 语句同时具有 true case 和 false case 的情况下不应再有 default 语句)。
' C: r( V; r9 b* e  p每个表达式都求值为零个或多个值。 标识符和字面量求值为一个值,函数调用求值为所调用函数的返回值。
' t( ^' }+ E, C) v. x4 X1 n在变量声明和赋值中,右侧表达式(如果存在)求值后,必须得出与左侧变量数量相等的值。. Q' J& O0 |4 @* h1 |
这是唯一允许求值出多个值的表达式。9 G, S8 z" [& e8 A" N2 _
那种同时又是语句的表达式(即在代码块的层次)求值结果必须只有零个值。
' P3 Q% |* ?1 f! t# n# D在其他所有情况中,表达式求值后必须仅有一个值。
! |3 ~& d3 N* h8 c( Ycontinue 和 break 语句只能用在循环体中,并且必须与循环处于同一个函数中(或者两者都必须在顶层)。$ R9 v$ k3 c( x- H
for 循环的条件部分的求值结果只能为一个值。
* Q2 d6 U3 N* O9 z字面量不可以大于它们本身的类型。已定义的最大类型宽度为 256 比特。
) d2 ^  B( g& O: c  m7 x2 }作用域规则8 V5 U7 D% f7 r; t
Yul 中的作用域是与块(除了函数和 for 循环,如下所述)和所有引入新的标识符到作用域中的声明' \: C1 T# f# _/ U$ M" o2 c
( FunctionDefinition ,VariableDeclaration )紧密绑定的。
5 ~5 h% g% Y0 o+ c标识符在将其定义的块中可见(包括所有子节点和子块)。
' p0 d1 Y5 X5 V, C! F; U! w作为例外,for 循环的 “init” 部分中(第一个块)定义的标识符在 for 循环的所有其他部分(但不在循环之外)中都是可见的。
2 X- D5 }' v. c8 \9 X! Z在 for 循环的其他部分声明的标识符遵守常规的作用域语法规则。
1 ?4 y7 D' |4 W+ A: C  F函数的参数和返回参数在函数体中可见,并且它们的名称不能相同。
+ z% M. C* v; G, h变量只能在声明后引用。 尤其是,变量不能在它们自己的变量声明的右边被引用。0 @0 m1 Y) ?1 i
函数可以在声明之前被引用(如果它们是可见的)。8 t# u4 g, @! ?* P" P# s
Shadowing 是不被允许的,即是说,你不能在同名标识符已经可见的情况下又定义该标识符,即使它是不可访问的。' ~7 I6 R# R$ `$ D! j
在函数内,不可能访问声明在函数外的变量。, M3 p5 h5 t" L. M+ x: g! L' m
形式规范
, Z  E* P; r; S4 Z; m( |4 e我们通过在 AST 的各个节点上提供重载的求值函数 E 来正式指定 Yul。
# b" W  d& l6 `% T- U$ ]任何函数都可能有副作用,所以 E 接受两个状态对象和 AST 节点作为它的参数,并返回两个新的状态对象和数量可变的其他值。9 N4 Z. S. y5 e& z- J
这两个状态对象是全局状态对象(在 |evm| 的上下文中是 |memory|,|storage| 和区块链的状态)和本地状态对象(局部变量的状态,即 |evm| 中堆栈的某个段)。
. a3 E) P3 T9 a/ G! y1 N+ ?* d, F6 ]如果 AST 节点是一个语句,E 将返回两个状态对象和一个用于 break 和 continue 语句的 “mode”。: A. }2 _, p  B) u4 T
如果 AST 节点是表达式,则 E 返回两个状态对象,并返回与表达式求值结果相同数量的值。
5 g# W2 q; ]: p+ i' b5 U% m  J在这份高层次的描述中,并没有对全局状态的确切本质进行说明。
6 C8 c1 K  m5 a4 O本地状态 L 是标识符 i 到值 v 的映射,表示为 L = v。9 d: S7 C3 C/ |/ R' p6 ?0 i! o
对于标识符 v, 我们用 $v 作为标识符的名字。
) ]4 y* P) I7 r, Q/ ?; ~: B# F我们将为 AST 节点使用解构符号。0 v' R# l; g+ |
… code::
; i" n+ M: q0 b7 O! z. NE(G, L, : Block) =
9 h" b. \+ W0 K  i7 W    let G1, L1, mode = E(G, L, St1, ..., Stn)& o0 U8 z; E% L2 u
    let L2 be a restriction of L1 to the identifiers of L/ n3 i) b7 r- @6 E) h* U+ X
    G1, L2, mode, X& l) t5 B+ s5 Y$ K* c
E(G, L, St1, ..., Stn: Statement) =
1 w( m  T# G  L2 A7 T    if n is zero:, T! i& B& |! }+ a* G. X  `
        G, L, regular
- Y9 J2 f( d9 C( l! C' k    else:7 W' K6 P# x& B2 j* E* h
        let G1, L1, mode = E(G, L, St1)
$ \, u& E+ U4 h. z2 Q        if mode is regular then7 M0 s) I' T/ O' {. }
            E(G1, L1, St2, ..., Stn)3 Y( G% Z& o, I  E: Z4 y9 |9 F
        otherwise
2 e$ x, X. E4 R3 [5 M            G1, L1, mode1 n9 f4 s5 [4 j6 g+ B- b5 j; U
E(G, L, FunctionDefinition) =' }1 t2 I8 U3 J0 l8 x! z
    G, L, regular
4 p$ _9 ~9 g! _/ PE(G, L, : VariableDeclaration) =
8 n7 D4 Z/ S5 {& h; k    E(G, L, : Assignment)
. `. I& w9 @5 Z  fE(G, L, : VariableDeclaration) =
) o; R' r: w& b5 k) Q, G    let L1 be a copy of L where L1[$vari] = 0 for i = 1, ..., n/ d$ {3 R$ U" y5 b4 @' [
    G, L1, regular
+ ], W+ w+ |( EE(G, L, : Assignment) =) C, m2 x2 _. I) [, W' r
    let G1, L1, v1, ..., vn = E(G, L, rhs)
; _1 A7 M6 l+ E! r    let L2 be a copy of L1 where L2[$vari] = vi for i = 1, ..., n  `# p8 _2 ^+ O; ]3 ~) ^2 {
    G, L2, regular
+ R- S: @1 H: P' nE(G, L, : ForLoop) =
& O# J8 [& N; S+ M$ A- M* K    if n >= 1:$ O* @" r9 J" v' I- W+ ]
        let G1, L1, mode = E(G, L, i1, ..., in)$ W4 B$ X( W6 O$ O4 h4 J
        // 由于语法限制,mode 必须是规则的' n! S+ o, S* P
        let G2, L2, mode = E(G1, L1, for {} condition post body)
* J/ p% K% _! u& X2 v        // 由于语法限制,mode 必须是规则的
+ }( l; F4 H5 @2 B$ E) `6 C; I; u        let L3 be the restriction of L2 to only variables of L
) H# t# U- E7 t5 ]        G2, L3, regular
* I. ?3 \. h( P8 S    else:
, t9 F, H8 w. o& x, w3 c        let G1, L1, v = E(G, L, condition)
( p7 A, m4 D7 @2 ~9 b- W        if v is false:8 {7 y/ _* I  N
            G1, L1, regular
$ e6 C! B- B* H) l7 i        else:
, G  P! w/ q# R& r0 ]            let G2, L2, mode = E(G1, L, body)" q; D; E9 L% a1 a, O0 x
            if mode is break:/ t5 U4 n; r3 R. x( w( u: v4 O$ e
                G2, L2, regular$ d# ?$ j% y: M; }' ]* I
            else:
3 R( [/ P9 i) O7 n) S4 T3 b2 h; [2 D1 k                G3, L3, mode = E(G2, L2, post)" \9 U7 Z. u6 j7 Y2 O" _( l
                E(G3, L3, for {} condition post body)5 z% x! C) y# c
E(G, L, break: BreakContinue) =5 ~' a, y% h/ y+ T- g
    G, L, break
$ @# T. K6 p4 {- PE(G, L, continue: BreakContinue) =
" V! d. @; e. x( o; z6 W    G, L, continue
- [0 Q5 N* O$ f- F6 y6 n+ sE(G, L, : If) =* r3 W  t1 J' ^& N
    let G0, L0, v = E(G, L, condition)  P& ^  J9 c! E* p2 K* i
    if v is true:
: |; l& }; M+ @+ P  v) k        E(G0, L0, body)" d( q7 h# D8 ^. W3 u
    else:% T! d9 {, O! e. h9 U9 p
        G0, L0, regular
8 |+ H, Z9 ?; kE(G, L, : Switch) =- @8 ?2 B$ f8 {3 N0 `7 ]6 [& h
    E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {})2 y2 x% C7 g# K' E6 c
E(G, L, : Switch) =
# s+ E, T, n: f# P/ @/ n8 a9 {3 ]3 U    let G0, L0, v = E(G, L, condition)
3 G8 q/ x3 F: d8 z3 x1 s3 \    // i = 1 .. n7 \9 Z* J# w" H! T) p; I
    // 对字面量求值,上下文无关
% O# w& E  ^, Z% D2 N9 W6 d$ ?3 [    let _, _, v1 = E(G0, L0, l1)1 N! n4 m$ f& R8 j/ i5 z
    ...
8 y  C4 p$ ]6 y% v9 b$ g1 ~0 h    let _, _, vn = E(G0, L0, ln)5 A9 T. x/ h0 i6 P/ G
    if there exists smallest i such that vi = v:
& i. j5 g' {/ N8 q        E(G0, L0, sti)
7 R+ d; a7 F" k) E; f% J5 m    else:7 C( G5 a9 a. F7 [& R7 {, |5 X
        E(G0, L0, st')( \2 s! e! `. z
E(G, L, : Identifier) =
- ~0 ?1 A+ J: h    G, L, L[$name]' U; o# c% N+ j1 I. `$ @1 G) }
E(G, L, : FunctionCall) =/ ^8 q5 A3 |2 H9 e, i6 C" W+ @
    G1, L1, vn = E(G, L, argn)
, d' m/ w& P, T* i2 h    ...
8 |2 n, l) I, P% }- W& e    G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2). T) T9 A4 H" [6 v
    Gn, Ln, v1 = E(G(n-1), L(n-1), arg1)8 f4 c7 N9 n* q3 ]
    Let  ret1, ..., retm block>6 G0 N3 j0 ^$ w1 D8 y
    be the function of name $fname visible at the point of the call.$ n8 [; {& U: G4 h: a. ?
    Let L' be a new local state such that
% J' y) E. N. a3 G    L'[$parami] = vi and L'[$reti] = 0 for all i.
% _1 `4 Y- u- d. E. G, q    Let G'', L'', mode = E(Gn, L', block)6 j1 T% i( W$ [% @2 i
    G'', Ln, L''[$ret1], ..., L''[$retm]# t# E/ L5 q/ s5 [5 I% c) w3 l
E(G, L, l: HexLiteral) = G, L, hexString(l),1 W: Q. G. E  {2 |3 i. p- y
    where hexString decodes l from hex and left-aligns it into 32 bytes5 s) b# v2 {8 ]4 f
E(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l),
% h& T0 Y7 T: N* G/ C6 P    where utf8EncodeLeftAligned performs a utf8 encoding of l* I/ N& P7 [8 `. ?: z- S0 {' B( e
    and aligns it left into 32 bytes
# Q( }2 q: s& n! uE(G, L, n: HexNumber) = G, L, hex(n)) W) T4 Z9 |# `7 ?- a* _
    where hex is the hexadecimal decoding function
: k( p# i& l, p  eE(G, L, n: DecimalNumber) = G, L, dec(n),
# }* j9 |, o' K& W! a    where dec is the decimal decoding function
( ?- ?- z, t: k7 Z类型转换函数
" u9 R- h2 @0 G$ @( L9 UYul 不支持隐式类型转换,因此存在提供显式转换的函数。
. P- Y+ }  n. s( A1 v1 P" h( K在将较大类型转换为较短类型时,如果发生溢出,则可能会发生运行时异常。9 F! I& _% l; m+ d. B
下列类型的“截取式”转换是允许的:5 J2 f! X4 }% o0 T( R4 j6 \
  • bool
  • u32
  • u64
  • u256
  • s256! a- Z, X" l' g8 H# m

    0 |7 D, }! `; G, r# X; W. ?这里的每种类型的转换函数都有一个格式为 to(x:) -> y: 的原型,
    9 h5 y9 I- ^7 z0 B* a比如 u32tobool(x:u32) -> y:bool、u256tou32(x:u256) -> y:u32 或 s256tou256(x:s256) -> y:u256。8 w( v; l8 |! F8 m1 G2 N6 e) \
    … note::$ I! {) T! i( _5 U
    ``u32tobool(x:u32) -> y:bool`` 可以由 ``y := not(iszerou256(x))`` 实现,并且
    ; h# z2 O. t8 {- m``booltou32(x:bool) -> y:u32`` 可以由 ``switch x case true:bool { y := 1:u32 } case false:bool { y := 0:u32 }`` 实现
    3 f1 g* f4 ~1 g* g, y低级函数: j/ C( F4 V9 G5 z
    以下函数必须可用:
    $ n# d9 {) {3 A* }+ h# ?3 Q±--------------------------------------------------------------------------------------------------------------+2 s" z. X7 C! B& `$ E- O
    | 逻辑操作                                                                                                    |
    / f0 }7 Q& A  b1 b8 z8 y±--------------------------------------------±----------------------------------------------------------------++ e: H( K/ L# L& {) @% E1 j
    | not(x:bool) -> z:bool                       | 逻辑非                                                          |* J4 N/ Z9 a; }1 w
    ±--------------------------------------------±----------------------------------------------------------------+
    / n8 x4 a0 t" A/ q6 p4 @| and(x:bool, y:bool) -> z:bool               | 逻辑与                                                          |$ F  [) O# V; D6 O" r
    ±--------------------------------------------±----------------------------------------------------------------+& M$ s% y. S2 m  J+ E! p
    | or(x:bool, y:bool) -> z:bool                | 逻辑或                                                          |
    # j$ O9 N) s$ p! S9 ?) L6 a±--------------------------------------------±----------------------------------------------------------------+
    8 l0 T; [# Y, P| xor(x:bool, y:bool) -> z:bool               | 异或                                                            |
    1 K: v4 s3 q+ e3 n±--------------------------------------------±----------------------------------------------------------------+
    - a" r( y; o9 y3 t6 A8 [8 y# x| 算术操作                                                                                                    |4 F5 R0 f/ [7 q, \2 C6 R5 E
    ±--------------------------------------------±----------------------------------------------------------------+; ?* _. _$ P$ e' w8 Q/ ?
    | addu256(x:u256, y:u256) -> z:u256           | x + y                                                           |
    - O3 K; }! r- g3 z& l±--------------------------------------------±----------------------------------------------------------------+/ H5 C0 Q# I- P/ r7 \6 M8 C
    | subu256(x:u256, y:u256) -> z:u256           | x - y                                                           |  A) I& r/ @& I* d) R5 |
    ±--------------------------------------------±----------------------------------------------------------------+. t# b, t  R5 J8 Q) ^4 C% p# \
    | mulu256(x:u256, y:u256) -> z:u256           | x * y                                                           |8 C+ h( K( z& S7 n, z7 k
    ±--------------------------------------------±----------------------------------------------------------------+3 H, S( u( j/ P/ w+ l+ V$ T
    | divu256(x:u256, y:u256) -> z:u256           | x / y                                                           |. }) ^! h' V1 M5 t# F' F
    ±--------------------------------------------±----------------------------------------------------------------+
    9 {% B/ M! X  d5 D| divs256(x:s256, y:s256) -> z:s256           | x / y, 有符号数用补码形式                                       |, k! y- M5 |% F8 d, h& J1 l! {
    ±--------------------------------------------±----------------------------------------------------------------+
    ) H0 T( Z/ }8 y2 ^$ C| modu256(x:u256, y:u256) -> z:u256           | x % y                                                           |9 p, V* K2 |" y, X' }) a% z" A6 d, s
    ±--------------------------------------------±----------------------------------------------------------------+
    5 y2 s5 ^$ ]/ W3 H- \| mods256(x:s256, y:s256) -> z:s256           | x % y, 有符号数用补码形式                                       |
    3 g' B& f; N( O; h. H2 X+ p±--------------------------------------------±----------------------------------------------------------------+& Y9 G/ k5 T& w- S/ R$ v
    | signextendu256(i:u256, x:u256) -> z:u256    | 从第 (i*8+7) 位开始进行符号扩展,从最低符号位开始计算           |
    3 s7 F; m2 y2 ^) o) q±--------------------------------------------±----------------------------------------------------------------+
    5 ~  r0 J) s/ |' j9 v| expu256(x:u256, y:u256) -> z:u256           | x 的 y 次方                                                     |# g$ M# v! p5 M2 n( `
    ±--------------------------------------------±----------------------------------------------------------------++ E2 u4 i8 R/ D8 i0 n) ?" |
    | addmodu256(x:u256, y:u256, m:u256) -> z:u256| 任意精度的数学模运算 (x + y) % m                                |
    & z" m2 g; t2 ^, V9 r5 a±--------------------------------------------±----------------------------------------------------------------+- }/ B: y: ]- J- n" i
    | mulmodu256(x:u256, y:u256, m:u256) -> z:u256| 任意精度的数学模运算 (x * y) % m                                |* u* f/ {* f" B( K
    ±--------------------------------------------±----------------------------------------------------------------+6 n5 o5 g  Q7 M
    | ltu256(x:u256, y:u256) -> z:bool            | 若 x  z:bool            | 若 x > y 为 true, 否则为 false                                  |$ N8 N/ x3 L9 H5 ]; P! L
    ±--------------------------------------------±----------------------------------------------------------------+
    . P( R; C& f9 I" [' || sltu256(x:s256, y:s256) -> z:bool           | 若 x  z:bool           | 若 x > y 为 true, 否则为 false                                  |
      }" }/ E9 y& H% P, Q4 w& E|                                             | 有符号数用补码形式                                              |
    , W9 k! ~1 U% L6 F±--------------------------------------------±----------------------------------------------------------------+1 M# w! Y% X0 j/ }9 r$ |
    | equ256(x:u256, y:u256) -> z:bool            | 若 x == y 为 true, 否则为 false                                 |
    0 Q. J- k( x1 t9 a. q; b, X±--------------------------------------------±----------------------------------------------------------------+
    ) b: R) v' m3 |' G7 P| iszerou256(x:u256) -> z:bool                | 若 x == 0 为 true, 否则为 false                                 |& D  U2 P& t9 R& @. L5 H8 J! Y
    ±--------------------------------------------±----------------------------------------------------------------+
    2 p, D* o5 W, E0 G6 i| notu256(x:u256) -> z:u256                   | ~x, 对 x 按位非                                                 |
    , ]0 m' M$ c3 _±--------------------------------------------±----------------------------------------------------------------+
    6 z; w" x: x' w$ p. ^' t0 y| andu256(x:u256, y:u256) -> z:u256           | x 和 y 按位与                                                   |. b, e0 f) x& W' J
    ±--------------------------------------------±----------------------------------------------------------------+
    $ {& y0 w. v0 ~' A+ B. V7 ?| oru256(x:u256, y:u256) -> z:u256            | x 和 y 按位或                                                   |
    - S+ a) g/ x$ z' P7 p" X9 q) i±--------------------------------------------±----------------------------------------------------------------+
    , ^' C9 q0 s. ^| xoru256(x:u256, y:u256) -> z:u256           | x 和 y 按位异或                                                 |
    1 P. d2 U3 T/ G& x( ?2 V±--------------------------------------------±----------------------------------------------------------------+
    1 A0 S0 k! s  t& \6 e| shlu256(x:u256, y:u256) -> z:u256           | 将 x 逻辑左移 y 位                                              |
    2 D9 k  `' a) u& Y9 z% ~" @±--------------------------------------------±----------------------------------------------------------------+
    5 l3 j  k/ V! N| shru256(x:u256, y:u256) -> z:u256           | 将 x 逻辑右移 y 位                                              |
    & z1 s/ q0 K( @±--------------------------------------------±----------------------------------------------------------------+
    ) C6 ^% S& J( M8 h- e" w( Q| saru256(x:u256, y:u256) -> z:u256           | 将 x 算术右移 y 位                                              |( s" _0 y+ m# x  \9 q  _6 Y
    ±--------------------------------------------±----------------------------------------------------------------+
    $ U# J$ p, h* ?% q| byte(n:u256, x:u256) -> v:u256              | x 的第 n 字节,这里的索引位置是从 0 开始的;                    |. ^& d: c  L& Y! w6 Q1 {
    |                                             | 能否用 and256(shr256(n, x), 0xff) 来替换它,                    |
    5 q. D& u* W( f6 ]  G5 E|                                             | 并使它在 EVM 后端之外被优化呢?                                 |
    - H: e5 s& k3 o6 g4 Z- k$ N/ [±--------------------------------------------±----------------------------------------------------------------+
    1 y4 P8 s) l  J! D! u| 内存和存储                                                                                                  |' {- d  q" q8 J) s3 t
    ±--------------------------------------------±----------------------------------------------------------------+
    1 o) O. ]1 @1 m) R| mload(p:u256) -> v:u256                     | mem[p…(p+32))                                                  |
    5 w0 o5 ]( R. N" B0 w/ U$ D& w±--------------------------------------------±----------------------------------------------------------------+
    ) f- ]/ A) O+ N/ L| mstore(p:u256, v:u256)                      | mem[p…(p+32)) := v                                             |; |9 p6 I/ Y# @4 B5 V
    ±--------------------------------------------±----------------------------------------------------------------+
    7 E$ q6 H# n. I( u" ]8 O( F| mstore8(p:u256, v:u256)                     | mem := v & 0xff    - 仅修改单个字节                          |
    ' [" n( |8 w- l1 N, o7 g7 J0 F±--------------------------------------------±----------------------------------------------------------------+. h# n2 \$ D& I( P9 D& z
    | sload(p:u256) -> v:u256                     | storage                                                      |
    6 Q* D3 i% c1 G7 C±--------------------------------------------±----------------------------------------------------------------+
    ' `, S) f& Y- ~3 {| sstore(p:u256, v:u256)                      | storage := v                                                 |
    1 p- b9 f& }( V6 [8 p. G7 U±--------------------------------------------±----------------------------------------------------------------+
    5 G9 z5 O" L, A( {' m7 a| msize() -> size:u256                        | 内存的大小, 即已访问过的内存的最大下标,                        |8 t0 S5 J: o. c' `5 p
    |                                             | 因为内存扩展的限制(只能按字进行扩展)                          |5 ~) T5 O% U, `$ _! V
    |                                             | 返回值永远都是 32 字节的倍数                                    |8 w% S  n; y" I0 Q; O: E
    ±--------------------------------------------±----------------------------------------------------------------+
    ( M; |# L7 |) R4 D3 w/ r| 执行控制                                                                                                    |
      J5 e( F! l" }±--------------------------------------------±----------------------------------------------------------------+) W, P4 X0 O3 U# o- K7 _( \* Q- B
    | create(v:u256, p:u256, s:u256)              | 以 mem[p…(p+s)) 上的代码创建一个新合约,发送                   |8 ^) W( |/ h: ~9 W
    |                                             | v 个 wei,并返回一个新的地址                                    |
    # X: L4 n; E8 |±--------------------------------------------±----------------------------------------------------------------+
    3 U9 U5 h2 V7 w. t* l" V+ r% F% g| call(g:u256, a:u256, v:u256, in:u256,       | 调用地址 a 上的合约,以 mem[in…(in+insize)) 作为输入           |8 e8 \2 X( U& S- j
    | insize:u256, out:u256,                      | 一并发送 g gas 和 v wei ,以 mem[out…(out+outsize))            |! q! A) _5 x! `) w
    | outsize:u256)                               | 作为输出空间。若错误,返回 0 (比如,gas 用光                   |5 E: h! H, L$ ~% O- m5 A2 m
    | -> r:u256                                   | 成功,返回 1                                                    |9 M' V; M3 K4 p0 q$ Z1 l0 I$ L+ c3 J  f. b
    ±--------------------------------------------±----------------------------------------------------------------+
    ' e  _8 c% m3 N) I+ T6 \| callcode(g:u256, a:u256, v:u256, in:u256,   | 相当于 call 但仅仅使用地址 a 上的代码,                     |
    8 \8 _5 j: g5 }; g| insize:u256, out:u256,                      | 而留在当前合约的上下文当中                                      |
    - h' ^% \% g3 ^( q3 M) T3 ]| outsize:u256) -> r:u256                     |                                                                 |
    + K. D& ?3 s: d* C7 p±--------------------------------------------±----------------------------------------------------------------+( c1 Q% Z3 \/ ?0 R2 n( ?9 a" m: d
    | delegatecall(g:u256, a:u256, in:u256,       | 相当于 callcode,                                           |" s# J- l& o9 O  o- w0 K9 u
    | insize:u256, out:u256,                      | 但同时保留 caller                                           |# Z) a6 T3 B3 {1 ^6 A
    | outsize:u256) -> r:u256                     | 和 callvalue                                                |5 e& D: N9 o$ l# I  ]) p! d
    ±--------------------------------------------±----------------------------------------------------------------+8 |$ C; a; B2 V( L7 b* s* w
    | abort()                                     | 终止 (相当于EVM上的非法指令)                                    |
    ; S/ ~; E. c( L$ ]: |) l1 N±--------------------------------------------±----------------------------------------------------------------+
    ! R$ s/ ~4 C) || return(p:u256, s:u256)                      | 终止执行,返回 mem[p…(p+s)) 上的数据                           |* _: F3 D. J* n5 w% Y) L; t
    ±--------------------------------------------±----------------------------------------------------------------+% J7 g/ R) ~6 B- M. x
    | revert(p:u256, s:u256)                      | 终止执行,恢复状态变更,返回 mem[p…(p+s)) 上的数据             |
    - g  ?1 N% ~4 t( g, y9 o, d% e$ t±--------------------------------------------±----------------------------------------------------------------+
    . t& B* \' I  }; x# R9 X| selfdestruct(a:u256)                        | 终止执行,销毁当前合约,并且将余额发送到地址 a                  |
    1 p" D1 N* ]- Y- ^# _±--------------------------------------------±----------------------------------------------------------------+  [" }9 g) `- P% i# B
    | log0(p:u256, s:u256)                        | 用 mem[p…(p+s)] 上的数据产生日志,但没有 topic                 |
    ) L" m; h$ U: F$ a$ ~0 \±--------------------------------------------±----------------------------------------------------------------+3 Q/ [# z9 {. a; I$ z# ?
    | log1(p:u256, s:u256, t1:u256)               | 用 mem[p…(p+s)] 上的数据和 topic t1 产生日志                   |7 M* h2 {+ }! u) F
    ±--------------------------------------------±----------------------------------------------------------------+
    - ?2 p- P! l2 Y7 Q$ _8 @0 w| log2(p:u256, s:u256, t1:u256, t2:u256)      | 用 mem[p…(p+s)] 上的数据和 topic t1,t2 产生日志               |2 _/ b  r$ _7 r6 G! c) `; X
    ±--------------------------------------------±----------------------------------------------------------------+
    ' ?# u7 G6 n2 K$ N| log3(p:u256, s:u256, t1:u256, t2:u256,      | 用 mem[p…(p+s)] 上的数据和 topic t1,t2,t3 产生日志           |
    * w6 r3 d+ M9 [: @. R2 {+ b| t3:u256)                                    |                                                                 |- m' i+ Q) {4 N. [" [# x& |  I
    ±--------------------------------------------±----------------------------------------------------------------+! t# }; \  e6 i! m% ~+ d
    | log4(p:u256, s:u256, t1:u256, t2:u256,      | 用 mem[p…(p+s)] 上的数据和 topic t1,t2,t3,t4                |3 v: F0 X$ U' _. r9 M5 m0 ], {6 F3 V! s
    | t3:u256, t4:u256)                           | 产生日志                                                        |2 d/ T4 z8 _8 ^3 D8 e, f: @$ D
    ±--------------------------------------------±----------------------------------------------------------------+1 ]2 p, K) \. O3 B6 c+ q7 }2 ~: z
    | 状态查询                                                                                                    |
    * @* m3 Q) {% s% c1 h) q±--------------------------------------------±----------------------------------------------------------------+% j% \* e' D) M  O6 {2 [! F0 R  F
    | blockcoinbase() -> address:u256             | 当前的矿工                                                      |5 p% k( v5 b' u+ M" D: d6 s  Q
    ±--------------------------------------------±----------------------------------------------------------------+# i( ~5 E# E; v$ K+ q/ L0 y' ?
    | blockdifficulty() -> difficulty:u256        | 当前区块的难度                                                  |
    9 M+ `3 N) [; A±--------------------------------------------±----------------------------------------------------------------+
    3 L4 n+ X2 q8 b% S. X| blockgaslimit() -> limit:u256               | 当前区块的区块 gas 限制                                         |* ~* I9 H9 j  L
    ±--------------------------------------------±----------------------------------------------------------------+
    - V7 L5 j3 {; ]: @6 ^" R) O' `$ k| blockhash(b:u256) -> hash:u256              | 区块号为 b 的区块的哈希,                                       |
    3 y+ b7 H% Q, U: l|                                             | 仅可用于最近的 256 个区块,不包含当前区块                       |
    # }) |0 O9 l& U+ T7 [* S+ t" l) |±--------------------------------------------±----------------------------------------------------------------+7 w4 V2 C! m% N9 l. p& G
    | blocknumber() -> block:u256                 | 当前区块号                                                      |
    2 [) T9 ~# @; D! l; b( \±--------------------------------------------±----------------------------------------------------------------+
    & d) ^8 l: I$ @' k) S| blocktimestamp() -> timestamp:u256          | 自 epoch 开始的,当前块的时间戳,以秒为单位                     |
    ' [5 K4 o7 e) J: [±--------------------------------------------±----------------------------------------------------------------+6 B( s6 O) C1 j/ x! o$ d
    | txorigin() -> address:u256                  | 交易的发送方                                                    |( W3 q: U$ p' s
    ±--------------------------------------------±----------------------------------------------------------------+; @" G0 I! F# y; g) p
    | txgasprice() -> price:u256                  | 交易中的 gas 价格                                               |
    5 T" n; z3 H' ^& ?- K: o±--------------------------------------------±----------------------------------------------------------------+
    ; v9 U  k- N7 G| gasleft() -> gas:u256                       | 还可用于执行的 gas                                              |! }$ v- j# r, X% h6 O  D
    ±--------------------------------------------±----------------------------------------------------------------+8 [3 `' g* m7 B& t, v1 \  V
    | balance(a:u256) -> v:u256                   | 地址 a 上的 wei 余额                                            |! X6 D9 p4 t, z, Q6 [3 I
    ±--------------------------------------------±----------------------------------------------------------------+" `2 y, l1 R8 `  A! N
    | this() -> address:u256                      | 当前合约/执行上下文的地址                                      |
    : l4 b; L' ?6 {* M±--------------------------------------------±----------------------------------------------------------------+- [' t' ^7 s2 h
    | caller() -> address:u256                    | 调用的发送方 (不包含委托调用)                                   |
    9 U1 |# J! \9 q1 U6 o7 Z, @/ b±--------------------------------------------±----------------------------------------------------------------+
    8 L( q! D8 R  [- a% T( `- p| callvalue() -> v:u256                       | 与当前调用一起发送的 wei                                        |
    ) ~$ }6 D+ Y' j" l  p7 C; d±--------------------------------------------±----------------------------------------------------------------+4 }2 b" `7 u1 b( U
    | calldataload(p:u256) -> v:u256              | 从 position p 开始的 calldata (32 字节)                         |6 N- S- g  a% h( Y+ \
    ±--------------------------------------------±----------------------------------------------------------------+8 p) Q8 I2 d% `* b* s! K/ X
    | calldatasize() -> v:u256                    | 以字节为单位的 calldata 的大小                                  |$ A7 G+ k/ B5 Y
    ±--------------------------------------------±----------------------------------------------------------------+1 {. T+ W* P& H& l3 L" s$ z
    | calldatacopy(t:u256, f:u256, s:u256)        | 从位置为 f 的 calldata 中,拷贝 s 字节到内存位置 t              |
    ) j+ @' L2 t1 G# Z9 m±--------------------------------------------±----------------------------------------------------------------+# q0 _3 J+ e7 `( I; f) Q8 L, t, \% _
    | codesize() -> size:u256                     | 当前合约/执行上下文的代码大小                                  |
    ( n6 E7 d# s' T5 ^' s/ U. \±--------------------------------------------±----------------------------------------------------------------+
    7 E% w) T* b: @3 u| codecopy(t:u256, f:u256, s:u256)            | 从 code 位置 f 拷贝 s 字节到内存位置 t                          |9 f) B. E5 B/ c' W0 g; _/ x5 y4 ^8 O
    ±--------------------------------------------±----------------------------------------------------------------+3 F! \# G  }. P4 h' {! a
    | extcodesize(a:u256) -> size:u256            | 地址 a 上的代码大小                                             |8 |$ J" e) ^0 I; s' k* ^1 g
    ±--------------------------------------------±----------------------------------------------------------------+
    3 |2 W8 n0 o$ Q1 _: b: `| extcodecopy(a:u256, t:u256, f:u256, s:u256) | 相当于 codecopy(t, f, s),但从地址 a 获取代码                   |" A0 m: @1 Z8 M8 s. M' C
    ±--------------------------------------------±----------------------------------------------------------------+
    5 h; |+ @  E* B/ L+ U, S| 其他                                                                                                        |8 s$ e( |1 S' G; G) [3 G
    ±--------------------------------------------±----------------------------------------------------------------+
    $ ?+ p. A. T$ L2 O| discard(unused:bool)                        | 丢弃值                                                          |- M3 m  C  o* u7 M/ N/ u  u# [: [* O: u
    ±--------------------------------------------±----------------------------------------------------------------+
    . Y* _! R$ G4 I+ e| discardu256(unused:u256)                    | 丢弃值                                                          |2 ?: ]0 `. U# Y' R' ?8 Q# e* G
    ±--------------------------------------------±----------------------------------------------------------------+/ e) I6 x1 f+ `2 ~% Q, N
    | splitu256tou64(x:u256) -> (x1:u64, x2:u64,  | 将一个 u256 拆分为四个 u64                                      |
    ; r5 l3 P  M! R- r0 Q|                            x3:u64, x4:u64)  |                                                                 |
    5 h& g# @! `  F6 M±--------------------------------------------±----------------------------------------------------------------+) y: Q. `1 S& N( d
    | combineu64tou256(x1:u64, x2:u64, x3:u64,    | 将四个 u64 组合为一个 u256                                      |
    , h& Z( s; m. _. q|                  x4:u64) -> (x:u256)        |                                                                 |- v7 L1 L4 u1 s! m" C
    ±--------------------------------------------±----------------------------------------------------------------+& p* s7 Q: r. b  L& F2 ^
    | keccak256(p:u256, s:u256) -> v:u256         | keccak(mem[p…(p+s)))                                          |
    . G' s2 S5 l7 U" h% n, e±--------------------------------------------±----------------------------------------------------------------+
    ! V% `" p& o  y* z9 g! q9 l后端+ h. L1 q3 I% t6 h5 J1 d
    后端或目标负责将 Yul 翻译到特定字节码。 每个后端都可以暴露以后端名称为前缀的函数。 我们为两个建议的后端保留 evm_ 和 ewasm_ 前缀。& T. Z+ |6 O3 b4 I
    后端: EVM, p, n3 v3 m/ U1 A7 A1 }5 r
    目标 |evm| 将具有所有用 evm_ 前缀暴露的 |evm| 底层操作码。& l- H2 G$ r" S1 m0 K% J' h7 A
    后端: “EVM 1.5”
    , M8 D1 M4 f4 |TBD
    / o. `+ `: j# [$ z% x- G后端: eWASM
    / L7 }0 w( e* v6 @# y( @TBD
    + y0 A- m' K9 B* I6 F& EYul 对象说明/ @) m& {/ M% h6 c% M9 s
    语法::
    6 P. `# a* ^4 O顶层对象 = 'object' '{' 代码? ( 对象 | 数据 )* '}'6 B# o7 H2 L' g# Y$ k  J& r8 X
    对象 = 'object' 字符串字面量 '{' 代码? ( 对象 | 数据 )* '}'
    ! K2 g- Q1 w! X: s0 r  Y6 V; E代码 = 'code' 代码块$ P8 I" b& R% B- [# v8 }. [2 V+ h
    数据 = 'data' 字符串字面量 十六进制字面量
    ' \" `$ _: x- O  `3 i1 |8 B- g十六进制字面量 = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
    4 |* g$ N5 P( F7 ?字符串字面量 = '"' ([^"\r\n\\] | '\\' .)* '"'
    & u* o  X9 k0 ]5 R( ]+ l- ^! ?. _在上面,代码块 指的是前一章中解释的 Yul 代码语法中的 代码块。
    % ]1 u, r$ R, U. wYul 对象示例如下:
    4 W/ @. P5 A/ O8 l2 J…code::
      z' Y6 D$ y; a; \- O- @: A// 代码由单个对象组成。 单个 “code” 节点是对象的代码。  f( @! H1 j7 W. w( Z1 A
    // 每个(其他)命名的对象或数据部分都被序列化
    6 s+ i3 w4 [" S5 r3 l7 D' Q8 J// 并可供特殊内置函数:datacopy / dataoffset / datasize 用于访问5 B* @6 ^$ Y+ \
    object {/ b' ]$ ?; o: s3 X* X" u, a) t
        code {
    : R2 J5 e' q: C5 N) }* W        let size = datasize("runtime")
    1 \6 f8 |% T  L        let offset = allocate(size)
    5 m9 H5 B4 a* A        // 这里,对于 eWASM 变为一个内存到内存的拷贝,对于 EVM 则相当于 codecopy
    / z9 g3 N  c( x' R        datacopy(dataoffset("runtime"), offset, size)
    1 i: K' @( u4 p4 d        // 这是一个构造函数,并且运行时代码会被返回- E/ ?* S. ^& b% c1 o( K
            return(offset, size)2 \4 s0 O4 N5 g; P8 `
        }
    ! i2 r: T" t# n' B+ ^- c( z    data "Table2" hex"4123"3 ~7 ~' G$ S0 C5 J: P/ f
        object "runtime" {9 c. s( u; P" z9 R& {6 X
            code {
    # V% y* e0 ?  X            // 运行时代码
    ! i+ w+ `; W3 m$ s            let size = datasize("Contract2"), M* Q5 d) w6 P+ j! ?6 R0 v4 K
                let offset = allocate(size)( g1 E! ?; H; c: s4 p( w' `+ v! i3 y
                // 这里,对于 eWASM 变为一个内存到内存的拷贝,对于 EVM 则相当于 codecopy0 v6 L7 R& \! c4 y: _, S
                datacopy(dataoffset("Contract2"), offset, size)& B, Q# X) \$ p- f& h
                // 构造函数参数是一个数字 0x12345 J- U$ m* c) R/ O4 d4 Z
                mstore(add(offset, size), 0x1234)7 B& h( N' z! z1 h. A, }" r# ]
                create(offset, add(size, 32))
      K" N4 ^4 E# ?/ d2 q/ Z# p* }; |; P        }
    9 V, n0 |: e1 g* u' l; a        // 内嵌对象。使用场景是,外层是一个工厂合约,而 Contract2 将是由工厂生成的代码( F; B5 E& Y" K) R$ A( Z7 [- Q
            object "Contract2" {- `# O8 v, H" O$ s9 B; g  g! F
                code {6 g4 V$ _" _+ G1 J& k
                    // 代码在这 ...
    7 P: s; @  K7 K2 c; n7 W' W; }; H3 w" V            }
    , D' J. p9 }/ |  z            object "runtime" {4 z  m' S, @% ^7 k; f& ]% t
                    code {
    & l  V8 h- b5 T5 j* V  F                    // 代码在这 ...7 x9 H" a$ y- Y1 h
                    }
    4 I- a) \+ M1 e             }
    ! m  p( w7 L' W  Q* q* C             data "Table1" hex"4123"
    * L# L# |4 O, b- ]/ B! K1 ^        }
    4 ]0 X* m; C1 U$ W    }: l4 j. |9 z) P, M6 L1 k/ Q9 H
    }
  • BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
    声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    成为第一个吐槽的人