其他¶
存储中状态变量的布局¶
静态大小的变量(除映射和动态大小的数组类型外的所有变量)从位置开始在存储中连续分布。 0
.如果可能,需要少于32个字节的多个连续项将按照以下规则打包到单个存储槽中:
存储槽中的第一个项目按较低的顺序排列存储。
基本类型只使用存储它们所需的字节数。
如果基本类型与存储槽的其余部分不匹配,则将其移动到下一个存储槽。
结构和数组数据总是启动一个新的槽并占用整个槽(但结构或数组中的项根据这些规则被紧密打包)。
对于使用继承的契约,状态变量的顺序由从最基本的ward契约开始的c3线性化契约顺序决定。如果上述规则允许,来自不同契约的状态变量确实共享同一存储槽。
结构和数组的元素彼此存储在一起,就好像它们是显式给定的一样。
警告
当使用小于32字节的元素时,合同的气体使用量可能会更高。这是因为EVM一次操作32个字节。因此,如果元素小于该值,EVM必须使用更多的操作,以便将元素的大小从32字节减少到所需的大小。
只有在处理存储值时才使用减小大小的参数是有益的,因为编译器会将多个元素打包到一个存储槽中,从而将多个读或写组合到一个操作中。当处理函数参数或内存值时,由于编译器不打包这些值,因此没有固有的好处。
最后,为了让EVM对此进行优化,请确保尝试对存储变量进行排序,并 struct
使其能被紧紧地包装。例如,按以下顺序声明存储变量: uint128, uint128, uint256
而不是 uint128, uint256, uint128
因为前者只占用两个存储槽,而后者占用三个存储槽。
注解
存储中状态变量的布局被认为是solidity外部接口的一部分,因为存储指针可以传递给库。这意味着,本节所述规则的任何变更均被视为语言的重大变更,由于其关键性,在执行前应仔细考虑。
映射和动态数组¶
由于其不可预测的大小,映射和动态大小的数组类型使用keccak-256哈希计算来查找值或数组数据的起始位置。这些起始位置总是满的堆栈槽。
映射或动态数组本身在某个位置占用存储器中的一个插槽。 p
according to the above rule (or by recursively applying this rule for mappings of mappings or arrays of arrays). For dynamic arrays, this slot stores the number of elements in the array (byte arrays and strings are an exception, see below). For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different hash distribution). Array data is located at keccak256(p)
and the value corresponding to a mapping key k
is located at keccak256(k . p)
where .
是串联。如果值再次是非基本类型,则通过添加偏移量来找到位置。 keccak256(k . p)
.
因此,对于以下合同片段:
pragma solidity >=0.4.0 <0.7.0;
contract C {
struct S { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => S)) data;
}
的位置 data[4][9].b
位于 keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1
.
bytes
and string
¶
bytes
和 string
编码相同。对于短字节数组,它们将数据存储在同样存储长度的插槽中。尤其是:如果数据最多 31
字节长,存储在高位字节(左对齐)和低位字节存储中 length * 2
.对于存储数据的字节数组, 32
或更长的字节,主插槽存储 length * 2 + 1
数据像往常一样存储在 keccak256(slot)
.这意味着您可以通过检查是否设置了最低位来区分短数组和长数组:短(未设置)和长(设置)。
注解
目前不支持处理无效编码的插槽,但将来可能会添加这些插槽。
内存中的布局¶
solidity保留四个32字节的插槽,具体的字节范围(包括端点)使用如下:
0x00
-0x3f
(64字节):哈希方法的临时空间0x40
-0x5f
(32字节):当前分配的内存大小(aka.可用内存指针)0x60
-0x7f
(32字节):零插槽
可以在语句之间使用草稿空间(即在内联程序集中)。零槽用作动态内存数组的初始值,不应写入(空闲内存指针指向 0x80
最初)。
solidity总是将新对象放在空闲内存指针上,而内存永远不会被释放(这在将来可能会改变)。
警告
在solidity中有一些操作需要一个大于64字节的临时内存区域,因此无法放入临时空间。它们将被放置在空闲内存指向的地方,但是由于它们的生存期很短,指针不会更新。内存可以调零,也可以不调零。因此,人们不应该期望空闲内存指向已归零的内存。
虽然使用它似乎是个好主意 msize
为了得到一个明确归零的内存区域,在不更新空闲内存指针的情况下非临时使用这样的指针可能会产生不利的结果。
呼叫数据布局¶
函数调用的输入数据假定为 ABI specification .其中,ABI规范要求将参数填充为32字节的倍数。内部函数调用使用不同的约定。
契约构造函数的参数直接附加在契约代码的末尾,也使用ABI编码。构造函数将通过硬编码偏移量访问它们,而不是使用 codesize
操作码,因为当向代码附加数据时,这当然会改变。
内部-清理变量¶
当一个值小于256位时,在某些情况下,必须清除剩余的位。solidity编译器设计用于在可能受到剩余位中潜在垃圾的不利影响的任何操作之前清除这些剩余位。例如,在将值写入内存之前,需要清除剩余的位,因为内存内容可用于计算散列或作为消息调用的数据发送。同样,在将一个值存储到存储器中之前,需要清除剩余的位,否则会观察到零乱的值。
另一方面,如果后续操作不受影响,我们不清理位。例如,由于考虑了任何非零值 true
通过 JUMPI
说明,在将布尔值用作 JUMPI
.
除了上面的设计原则之外,solidity编译器在将输入数据加载到堆栈时会清除输入数据。
不同类型具有不同的清除无效值的规则:
类型 |
有效值 |
无效值的平均值 |
---|---|---|
n个成员的枚举 |
0到n-1 |
例外 |
布尔 |
0或1 |
1 |
有符号整数 |
符号扩展字 |
当前自动包装;将来将引发异常 |
无符号整数 |
高位归零 |
当前自动包装;将来将引发异常 |
内部-优化者¶
solidity优化程序对汇编进行操作,以便其他语言可以使用它。它将指令序列拆分为基本块 JUMPs
和 JUMPDESTs
.在这些块中,优化程序分析指令,并将对堆栈、内存或存储的每一次修改记录为一个表达式,该表达式由一条指令和一组参数组成,这些参数是指向其他表达式的指针。乐观者使用一个名为“CommonSubExpressioneLiminator”的组件,在其他任务中,找到始终相等的表达式(在每个输入上),并将它们组合到一个表达式类中。乐观主义者首先尝试在已经知道的表达式列表中找到每个新的表达式。如果这不起作用,它会根据如下规则简化表达式 constant + constant = sum_of_constants
或 X * 1 = X
.因为这是一个递归过程,所以如果第二个因子是一个更复杂的表达式,我们也可以应用后一个规则,因为我们知道它总是计算为一个。对存储器和存储器位置的修改必须删除关于存储器和存储器位置的知识,这些知识是已知的,是不同的。如果我们先写到位置x,然后写到位置y,两者都是输入变量,那么第二个变量可以覆盖第一个变量,所以我们不知道在写到y之后x中存储了什么。如果将表达式x-y简化为一个非零常量,我们知道我们可以保留我们的知识ab。在X处存储的内容。
在这个过程之后,我们知道哪些表达式必须在栈的末尾,并有一个对内存和存储的修改列表。这些信息与基本块一起存储,并用于链接它们。此外,有关堆栈、存储和内存配置的知识将转发到下一个块。如果我们知道所有人的目标 JUMP
和 JUMPI
说明:我们可以建立一个完整的程序控制流程图。如果只有一个我们不知道的目标(原则上这可以发生,跳跃目标可以从输入中计算),我们必须清除有关块输入状态的所有知识,因为它可能是未知的目标 JUMP
.如果乐观者发现 JUMPI
其条件计算为常量,它将其转换为无条件跳转。
最后一步是重新生成每个块中的代码。乐观器从块末尾堆栈上的表达式创建依赖关系图,并删除不属于此图的每个操作。它生成的代码按照原始代码的顺序将修改应用于内存和存储(删除发现不需要的修改)。最后,它生成所有需要在堆栈上正确位置的值。
这些步骤应用于每个基本块,如果新生成的代码较小,则将其用作替换代码。如果基本块在 JUMPI
在分析过程中,条件评估为一个常量, JUMPI
根据常量的值替换。因此代码类似
uint x = 7;
data[7] = 9;
if (data[x] != x + 2)
return 2;
else
return 1;
仍然可以简化为可以编译的代码,即使这些指令在进程开始时包含一个跳转:
data[7] = 9;
return 1;
源映射¶
作为AST输出的一部分,编译器提供由AST中的各个节点表示的源代码范围。这可以用于各种用途,从基于AST报告错误的静态分析工具,到突出显示局部变量及其用途的调试工具。
此外,编译器还可以生成从字节码到生成指令的源代码范围的映射。这对于在字节码级别上操作的静态分析工具以及在调试器内显示源代码中的当前位置或处理断点来说再次重要。
这两种源映射都使用整数标识符来引用源文件。源文件的标识符存储在 output['sources'][sourceName]['id']
在哪里? output
是被解析为JSON的标准JSON编译器接口的输出。
注解
对于不与任何特定源文件关联的指令,源映射将分配一个整数标识符 -1
.对于源自编译器生成的内联汇编语句的字节码部分,可能会发生这种情况。
AST内的源映射使用以下表示法:
s:l:f
Where s
is the byte-offset to the start of the range in the source file,
l
is the length of the source range in bytes and f
is the source
index mentioned above.
字节码源映射中的编码更加复杂:它是 s:l:f:j
分开 ;
.这些元素中的每一个都对应于一条指令,即不能使用字节偏移量,但必须使用指令偏移量(推送指令比单个字节长)。田野 s
, l
和 f
如上所述 j
要么可以 i
, o
或 -
表示跳转指令是进入函数、从函数返回还是作为循环的一部分的常规跳转。
为了压缩这些源映射,尤其是字节码映射,使用以下规则:
如果字段为空,则使用前面元素的值。
如果A
:
缺少,以下所有字段都视为空。
这意味着以下源映射表示相同的信息:
1:2:1;1:9:1;2:1:2;2:1:2;2:1:2
1:2:1;:9;2:1:2;;
提示和技巧¶
使用
delete
在数组上删除其所有元素。对结构元素使用较短的类型,并对其进行排序,以便将较短的类型分组在一起。这可以将天然气成本降低为倍数
SSTORE
操作可以合并为一个 (SSTORE
耗油5000或20000次,所以这是你想要优化的)。使用燃气价格估算器(启用了优化程序)进行检查!使状态变量公开-编译器创建 getters 为你自动。
如果您最终检查了输入的条件,或者在函数开始时有很多状态,请尝试使用 函数修饰符 .
使用单个分配初始化存储结构:
x = MyStruct({{a: 1, b: 2}});
注解
如果存储结构具有紧密打包的属性,请使用单独的分配对其进行初始化: x.a = 1; x.b = 2;
.这样,优化器一次更新存储将更容易,从而使分配更便宜。
备忘单¶
运算符的优先顺序¶
以下是按计算顺序列出的运算符的优先顺序。
优先 |
描述 |
算符 |
---|---|---|
1 |
后置增减量 |
|
新建表达式 |
|
|
数组订阅 |
|
|
成员访问 |
|
|
函数式调用 |
|
|
圆括号 |
|
|
2 |
前缀递增和递减 |
|
一元减 |
|
|
一元运算 |
|
|
逻辑非 |
|
|
按位取反 |
|
|
3 |
求幂 |
|
4 |
乘法、除法和模 |
|
5 |
加减法 |
|
6 |
位移位运算符 |
|
7 |
按位与 |
|
8 |
按位异或 |
|
9 |
按位或 |
|
10 |
不等式运算符 |
|
11 |
相等运算符 |
|
12 |
逻辑与 |
|
13 |
逻辑或 |
|
14 |
三元运算符 |
|
分配运算符 |
|
|
15 |
逗号运算符 |
|
全局变量¶
abi.decode(bytes memory encodedData, (...)) returns (...)
: ABI-decodes the provided data. The types are given in parentheses as second argument. Example:(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))
abi.encode(...) returns (bytes memory)
: ABI -对给定参数进行编码abi.encodePacked(...) returns (bytes memory)
:执行 packed encoding 给出的参数。请注意,此编码可能不明确!abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)
: ABI -对给定参数进行编码从第二个开始,并在给定的四字节选择器前面加上
abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)
: Equivalent toabi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)`
block.coinbase
(address payable
): current block miner's addressblock.difficulty
(uint
): current block difficultyblock.gaslimit
(uint
): current block gaslimitblock.number
(uint
): current block numberblock.timestamp
(uint
): current block timestampgasleft() returns (uint256)
:剩余气体msg.data
(bytes
): complete calldatamsg.sender
(address payable
): sender of the message (current call)msg.value
(uint
): number of wei sent with the messagenow
(uint
): current block timestamp (alias forblock.timestamp
)tx.gasprice
(uint
): gas price of the transactiontx.origin
(address payable
): sender of the transaction (full call chain)assert(bool condition)
:如果条件为,则中止执行并还原状态更改false
(用于内部错误)require(bool condition)
:如果条件为,则中止执行并还原状态更改false
(用于错误的输入或外部组件中的错误)require(bool condition, string memory message)
:如果条件为,则中止执行并还原状态更改false
(用于错误的输入或外部组件中的错误)。还提供错误消息。revert()
:中止执行并还原状态更改revert(string memory message)
:中止执行并还原状态更改,提供解释字符串blockhash(uint blockNumber) returns (bytes32)
:给定块的哈希-仅适用于最近256个块keccak256(bytes memory) returns (bytes32)
:计算输入的keccak-256哈希sha256(bytes memory) returns (bytes32)
:计算输入的sha-256哈希ripemd160(bytes memory) returns (bytes20)
:计算输入的ripemd-160哈希ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
:从椭圆曲线签名恢复与公钥关联的地址,出错时返回零addmod(uint x, uint y, uint k) returns (uint)
:计算(x + y) % k
如果加法是以任意精度执行的,并且在2**256
.断言k != 0
从0.5.0版开始。mulmod(uint x, uint y, uint k) returns (uint)
:计算(x * y) % k
其中乘法是以任意精度执行的,而不是在2**256
.断言k != 0
从0.5.0版开始。this
(current contract's type): the current contract, explicitly convertible toaddress
oraddress payable
super
:继承层次结构中更高一级的协定selfdestruct(address payable recipient)
:销毁当前合同,将其资金发送到指定地址<address>.balance
(uint256
): balance of the 地址 in Wei<address payable>.send(uint256 amount) returns (bool)
:将给定数量的wei发送到 地址 回报false
关于失败<address payable>.transfer(uint256 amount)
:将给定数量的wei发送到 地址 ,失败时引发type(C).name
(string
): the name of the contracttype(C).creationCode
(bytes memory
): creation bytecode of the given contract, see Type Information.type(C).runtimeCode
(bytes memory
): runtime bytecode of the given contract, see Type Information.
注解
不要依赖 block.timestamp
, now
和 blockhash
作为随机性的来源,除非你知道你在做什么。
时间戳和块散列在某种程度上都会受到矿工的影响。例如,采矿社区中的坏参与者可以在所选哈希上运行赌场付款函数,如果他们没有收到任何钱,只需重试其他哈希。
当前块时间戳必须严格大于最后一个块的时间戳,但唯一的保证是它将介于规范链中两个连续块的时间戳之间。
注解
由于可伸缩性的原因,块散列不能用于所有块。您只能访问最近256个块的哈希值,所有其他值都将为零。
注解
在0.5.0版中,删除了以下别名: suicide
作为别名 selfdestruct
, msg.gas
作为别名 gasleft
, block.blockhash
作为别名 blockhash
和 sha3
作为别名 keccak256
.
函数可见性说明符¶
function myFunction() <visibility specifier> returns (bool) {
return true;
}
public
:外部和内部可见(创建 getter function 存储/状态变量)private
:仅在当前合同中可见external
:仅在外部可见(仅用于函数)-即只能调用消息(通过this.func
)internal
:仅在内部可见
修饰语¶
pure
对于函数:不允许修改或访问状态。view
对于函数:不允许修改状态。payable
对于功能:允许他们在接到电话的同时接收 Ether 。constant
对于状态变量:不允许赋值(初始化除外),不占用存储槽。anonymous
对于事件:不将事件签名存储为主题。indexed
对于事件参数:将参数存储为主题。
保留关键字¶
这些关键字保持一致。它们将来可能成为语法的一部分:
abstract
, after
, alias
, apply
, auto
, case
, catch
, copyof
, default
, define
, final
, immutable
, implements
, in
, inline
, let
, macro
, match
, mutable
, null
, of
, override
, partial
, promise
, reference
, relocatable
, sealed
, sizeof
, static
, supports
, switch
, try
, typedef
, typeof
, unchecked
.
语言语法¶
SourceUnit = (PragmaDirective | ImportDirective | ContractDefinition)*
// Pragma actually parses anything up to the trailing ';' to be fully forward-compatible.
PragmaDirective = 'pragma' Identifier ([^;]+) ';'
ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
ContractPart = StateVariableDeclaration | UsingForDeclaration
| StructDefinition | ModifierDefinition | FunctionDefinition | EventDefinition | EnumDefinition
InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )* Identifier ('=' Expression)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* ) '}'
ModifierDefinition = 'modifier' Identifier ParameterList? Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
FunctionDefinition = 'function' Identifier? ParameterList
( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' )*
( 'returns' ParameterList )? ( ';' | Block )
EventDefinition = 'event' Identifier EventParameterList 'anonymous'? ';'
EnumValue = Identifier
EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}'
ParameterList = '(' ( Parameter (',' Parameter)* )? ')'
Parameter = TypeName StorageLocation? Identifier?
EventParameterList = '(' ( EventParameter (',' EventParameter )* )? ')'
EventParameter = TypeName 'indexed'? Identifier?
FunctionTypeParameterList = '(' ( FunctionTypeParameter (',' FunctionTypeParameter )* )? ')'
FunctionTypeParameter = TypeName StorageLocation?
// semantic restriction: mappings and structs (recursively) containing mappings
// are not allowed in argument lists
VariableDeclaration = TypeName StorageLocation? Identifier
TypeName = ElementaryTypeName
| UserDefinedTypeName
| Mapping
| ArrayTypeName
| FunctionTypeName
| ( 'address' 'payable' )
UserDefinedTypeName = Identifier ( '.' Identifier )*
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
( 'returns' FunctionTypeParameterList )?
StorageLocation = 'memory' | 'storage' | 'calldata'
StateMutability = 'pure' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | EmitStatement | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
InlineAssemblyStatement = 'assembly' StringLiteral? AssemblyBlock
DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
Continue = 'continue'
Break = 'break'
Return = 'return' Expression?
Throw = 'throw'
EmitStatement = 'emit' FunctionCall
VariableDefinition = (VariableDeclaration | '(' VariableDeclaration? (',' VariableDeclaration? )* ')' ) ( '=' Expression )?
// Precedence by order (see github.com/ethereum/solidity/pull/732)
Expression
= Expression ('++' | '--')
| NewExpression
| IndexAccess
| MemberAccess
| FunctionCall
| '(' Expression ')'
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
| Expression '**' Expression
| Expression ('*' | '/' | '%') Expression
| Expression ('+' | '-') Expression
| Expression ('<<' | '>>') Expression
| Expression '&' Expression
| Expression '^' Expression
| Expression '|' Expression
| Expression ('<' | '>' | '<=' | '>=') Expression
| Expression ('==' | '!=') Expression
| Expression '&&' Expression
| Expression '||' Expression
| Expression '?' Expression ':' Expression
| Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression
| PrimaryExpression
PrimaryExpression = BooleanLiteral
| NumberLiteral
| HexLiteral
| StringLiteral
| TupleExpression
| Identifier
| ElementaryTypeNameExpression
ExpressionList = Expression ( ',' Expression )*
NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
FunctionCall = Expression '(' FunctionCallArguments ')'
FunctionCallArguments = '{' NameValueList? '}'
| ExpressionList?
NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']'
BooleanLiteral = 'true' | 'false'
NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?
NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+ ( '.' [0-9]* )? ( [eE] [0-9]+ )?
TupleExpression = '(' ( Expression? ( ',' Expression? )* )? ')'
| '[' ( Expression ( ',' Expression )* )? ']'
ElementaryTypeNameExpression = ElementaryTypeName
ElementaryTypeName = 'address' | 'bool' | 'string' | Int | Uint | Byte | Fixed | Ufixed
Int = 'int' | 'int8' | 'int16' | 'int24' | 'int32' | 'int40' | 'int48' | 'int56' | 'int64' | 'int72' | 'int80' | 'int88' | 'int96' | 'int104' | 'int112' | 'int120' | 'int128' | 'int136' | 'int144' | 'int152' | 'int160' | 'int168' | 'int176' | 'int184' | 'int192' | 'int200' | 'int208' | 'int216' | 'int224' | 'int232' | 'int240' | 'int248' | 'int256'
Uint = 'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' | 'uint56' | 'uint64' | 'uint72' | 'uint80' | 'uint88' | 'uint96' | 'uint104' | 'uint112' | 'uint120' | 'uint128' | 'uint136' | 'uint144' | 'uint152' | 'uint160' | 'uint168' | 'uint176' | 'uint184' | 'uint192' | 'uint200' | 'uint208' | 'uint216' | 'uint224' | 'uint232' | 'uint240' | 'uint248' | 'uint256'
Byte = 'byte' | 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32'
Fixed = 'fixed' | ( 'fixed' [0-9]+ 'x' [0-9]+ )
Ufixed = 'ufixed' | ( 'ufixed' [0-9]+ 'x' [0-9]+ )
AssemblyBlock = '{' AssemblyStatement* '}'
AssemblyStatement = AssemblyBlock
| AssemblyFunctionDefinition
| AssemblyVariableDeclaration
| AssemblyAssignment
| AssemblyIf
| AssemblyExpression
| AssemblySwitch
| AssemblyForLoop
| AssemblyBreakContinue
AssemblyFunctionDefinition =
'function' Identifier '(' AssemblyIdentifierList? ')'
( '->' AssemblyIdentifierList )? AssemblyBlock
AssemblyVariableDeclaration = 'let' AssemblyIdentifierList ( ':=' AssemblyExpression )?
AssemblyAssignment = AssemblyIdentifierList ':=' AssemblyExpression
AssemblyExpression = AssemblyFunctionCall | Identifier | Literal
AssemblyIf = 'if' AssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' AssemblyExpression ( Case+ AssemblyDefault? | AssemblyDefault )
AssemblyCase = 'case' Literal AssemblyBlock
AssemblyDefault = 'default' AssemblyBlock
AssemblyForLoop = 'for' AssemblyBlock AssemblyExpression AssemblyBlock AssemblyBlock
AssemblyBreakContinue = 'break' | 'continue'
AssemblyFunctionCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')'
AssemblyIdentifierList = Identifier ( ',' Identifier )*