存储中状态变量
静态大小的变量(除映射和动态大小的数组类型之外的所有内容)在从位置开始的存储中连续布局0。根据以下规则,如果可能,将需要少于32个字节的多个项目打包到单个存储槽中:
• 存储槽中的第一项存储为低阶对齐。
• 基本类型仅使用存储它们所需的许多字节。
• 如果基本类型不适合存储槽的剩余部分,则将其移动到下一个存储槽。
• 结构和数组数据总是从一个新的槽开始并占据整个槽(但是根据这些规则,结构或数组中的项被紧密地打包)。
警告
使用小于32字节的元素时,合约的燃气使用量可能会更高。这是因为EVM一次运行32个字节。因此,如果元素小于该值,则EVM必须使用更多操作,以便将元素的大小从32字节减小到所需大小。
如果处理存储值,则使用缩小大小的参数是有益的,因为编译器会将多个元素打包到一个存储槽中,从而将多个读取或写入组合到单个操作中。处理函数参数或内存值时,没有固有的好处,因为编译器不会打包这些值。
最后,为了让EVM针对此进行优化,请确保您尝试订购存储变量和struct成员,以便可以紧密打包它们。例如,按照顺序声明存储变量,而 前者只占用两个存储槽,而后者占用三个。uint128, uint128, uint256uint128, uint256, uint128
结构和数组的元素彼此相继存储,就像它们是明确给出的一样。
映射和动态数组
由于其不可预测的大小,映射和动态大小的数组类型使用Keccak-256哈希计算来查找值或数组数据的起始位置。这些起始位置始终为完整堆栈插槽。
p 根据上述规则,映射或动态数组本身占据某个位置的存储槽(或者通过递归地应用该规则来映射映射或数组阵列)。对于动态数组,此槽存储数组中的元素数(字节数组和字符串是一个例外,见下文)。对于映射,插槽是未使用的(但需要使得两个相等的映射在彼此之后将使用不同的散列分布)。阵列数据位于keccak256§与对应于映射键的值 k位于其中是级联。如果该值再次是非基本类型,则通过添加偏移量来找到位置。keccak256(k . p).keccak256(k . p)
因此,对于以下合约代码段:
pragma solidity >=0.4.0 <0.6.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和string
bytes并且string编码相同。对于短字节数组,它们将数据存储在同样存储长度的槽中。特别是:如果数据最多为31字节长,则存储在高位字节(左对齐)和最低位字节存储中。对于存储长度或更长字节数据的字节数组,主插槽存储,数据按常规存储。这意味着您可以通过检查是否设置了最低位来区分短数组和长数组:short(未设置)和long(设置)。length * 232length * 2 + 1keccak256(slot)
注意
目前不支持处理无效编码的插槽,但将来可能会添加。
内存中的布局
Solidity保留四个32字节的插槽,具体字节范围(包括端点)的使用方法如下:
• 0x00- 0x3f(64字节):用于散列方法的临时空间
• 0x40- 0x5f(32字节):当前分配的内存大小(又名。空闲内存指针)
• 0x60- 0x7f(32字节):零时隙
可以在语句之间使用划痕空间(即在内联汇编中)。零槽用作动态存储器阵列的初始值,永远不应写入(空闲存储器指针0x80最初指向)。
Solidity总是将新对象放在空闲内存指针上,并且永远不会释放内存(这可能在将来发生变化)。
警告
Solidity中有一些操作需要一个大于64字节的临时存储区,因此不适合临时空间。它们将被放置在空闲内存指向的位置,但由于它们的生命周期很短,指针不会更新。存储器可能会或可能不会被清零。因此,不应期望空闲内存指向归零内存。
虽然使用msize来获得明确归零的内存区域似乎是个好主意,但是在不更新空闲内存指针的情况下非暂时使用这样的指针会产生不利的结果。
调用数据层
假定函数调用的输入数据采用ABI规范定义的格式。其中,ABI规范要求将参数填充为32字节的倍数。内部函数调用使用不同的约定。
合约构造函数的参数直接附加在合约代码的末尾,也是ABI编码。构造函数将通过硬编码的偏移量访问它们,而不是使用codesize操作码,因为当将数据附加到代码时,这当然会发生变化。
内部结构 - 清理变量
当值小于256位时,在某些情况下必须清除其余位。Solidity编译器用于在可能受到剩余位中潜在垃圾的不利影响的任何操作之前清除此类剩余位。例如,在将值写入存储器之前,需要清除剩余的位,因为存储器内容可用于计算哈希值或作为消息调用的数据发送。类似地,在将值存储在存储器中之前,需要清除剩余的位,因为否则可以观察到乱码值。
另一方面,如果紧接着的操作不受影响,我们不会清除这些位。例如,因为任何非零值被认为是true由JUMPI指令,我们不也被用作条件之前清理布尔值 JUMPI。
除了上面的设计原则,Solidity编译器在将输入数据加载到堆栈时清除输入数据。
不同类型有不同的规则来清理无效值:
类型是n个成员的枚举 ,有效值是0到n – 1 ,无效的值是例外。
类型是布尔,有效值是0或1,无效的值是1。
类型是签名整数,有效值是0或1,无效值是默认。
类型是无符号整数,有效值是高位归零,无效值是默认。
内部 - 优化器
Solidity优化器在汇编时运行,因此它可以并且也可以被其他语言使用。它将指令序列拆分为JUMPs和的基本块JUMPDESTs。在这些块中,分析指令并且对堆栈,存储器或存储的每个修改被记录为表达式,该表达式由指令和参数列表组成,这些参数基本上是指向其他表达式的指针。现在的主要思想是找到总是相等的表达式(在每个输入上)并将它们组合成表达式类。优化器首先尝试在已知表达式列表中查找每个新表达式。如果这不起作用,表达式将根据或等规则进行简化constant + constant = sum_of_constantsX * 1 = X。由于这是递归完成的,如果第二个因子是一个更复杂的表达式,我们也可以应用后一个规则,我们知道它总是会计算为一个。对存储和内存位置的修改必须删除有关存储和内存位置的知识,这些知识不为人知:如果我们先写入位置x然后写入位置y并且两者都是输入变量,第二个可能会覆盖第一个,所以我们实际上,在写完y之后,我不知道x处存储了什么。另一方面,如果表达式x-y的简化求值为非零常数,我们知道我们可以保持对x中存储内容的了解。
在这个过程结束时,我们知道最后哪些表达式必须在堆栈上,并且有一个对内存和存储的修改列表。此信息与基本块一起存储,用于链接它们。此外,有关堆栈,存储和内存配置的知识将转发到下一个块。如果我们知道所有目标JUMP和JUMPI指令,我们就可以构建一个完整的程序控制流程图。如果只有一个我们不知道的目标(原则上可能会发生这种情况,可以从输入计算跳转目标),我们必须删除有关块的输入状态的所有知识,因为它可能是未知的目标JUMP。如果JUMPI找到a的条件求值为常量,则将其转换为无条件跳转。
最后一步,完全重新生成每个块中的代码。从块末尾的堆栈表达式创建依赖关系图,并且基本上不删除不属于该图的每个操作。现在生成代码,按照原始代码中的顺序将修改应用于内存和存储(删除不需要的修改),最后生成所有需要在堆栈中正确的值地点。
这些步骤适用于每个基本块,如果较小,则新生成的代码用作替换。如果在a 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报告错误的静态分析工具和突出显示局部变量及其用途的调试工具。
此外,编译器还可以生成从字节码到生成指令的源代码中的范围的映射。这对于在字节码级别上运行的静态分析工具以及在调试器内部的源代码中显示当前位置或进行断点处理同样重要。
两种源映射都使用整数标识符来引用源文件。这些是常规数组索引到通常被调用的源文件列表中"sourceList",它是组合json和json / npm编译器输出的一部分。
注意
对于与任何特定源文件无关的指令,源映射将指定整数标识符-1。对于源自编译器生成的内联汇编语句的字节码部分,可能会发生这种情况。
AST内部的源映射使用以下表示法:
s:l:f
s源文件中范围起点的字节偏移量在哪里, l是源范围的长度(以字节f为单位),是上面提到的源索引。
字节码的源映射中的编码更复杂:它是一个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气体,因此这是您想要优化的)。使用汽油价格估算器(启用优化器)进行检查!
• 使状态变量公开 - 编译器将自动为您创建getter。
• 如果您最终检查输入条件或在函数开头大量说明,请尝试使用函数修饰符。
• 使用单个赋值初始化存储结构: x = MyStruct({a: 1, b: 2});
注意
如果存储结构具有紧密打包的属性,则使用单独的赋值对其进行初始化:。通过这种方式,优化器可以更容易地一次更新存储,从而使分配更便宜。x.a = 1; x.b = 2;
备忘单
运算符的优先顺序
以下是运算符的优先顺序,按评估顺序列出。
1、++, –
2、new
3、[]
4、.
5、(
6、()
7、++, –
8、-
9、delete
10、!
11、~
12、**
13、,/,%
14、+, -
15、<<, >>
16、&
17、^
18、|
19、<,>,<=,>=
20、==, !=
21、&&
22、||
23、 ? :
24、=,|=,^=,&=,<<=,>>=,+=,-=,=,/=, %=
25、,
全局变量
• abi.decode(bytes memory encodedData, (…)) returns (…):ABI - 对提供的数据进行解码。括号中的类型作为第二个参数给出。例:(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):执行给定参数的打包编码
• abi.encodeWithSelector(bytes4 selector, …) returns (bytes memory):ABI - 对给定的参数进行编码
从第二个开始,并预先给定给定的四字节选择器
• abi.encodeWithSignature(string memory signature, …) returns (bytes memory): 相当于 abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), …)`
• block.coinbase():当前块矿工的地址address payable
• block.difficulty(uint):当前块难度
• block.gaslimit(uint):当前阻止gaslimit
• block.number(uint):当前块号
• block.timestamp(uint):当前块时间戳
• gasleft() returns (uint256):剩余的气体
• msg.data(bytes):完整的calldata
• msg.sender():消息的发件人(当前通话)address payable
• msg.value(uint):用消息发送的wei数
• now(uint):当前块时间戳(别名block.timestamp)
• tx.gasprice(uint):交易的天然气价格
• tx.origin():交易的发件人(完整的通话链)address payable
• 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):计算以任意精度执行加法的位置,并且不包围at 。断言从版本0.5.0开始。(x + y) % k2256k != 0
• mulmod(uint x, uint y, uint k) returns (uint):计算以任意精度执行乘法的位置,并且不包围at 。断言从版本0.5.0开始。(x * y) % k2256k != 0
• this(当前合约的类型):当前合约,明确可转换为address或address payable
• super:继承层次结构中的合约高一级
• selfdestruct(address payable recipient):销毁当前合约,将其资金发送到指定地址
•
注意
不要依赖block.timestamp,now并blockhash作为随机的来源,除非你知道自己在做什么。
时间戳和块哈希都可以在某种程度上受到矿工的影响。挖掘社区中的不良演员可以例如在所选择的散列上运行赌场支付功能,并且如果他们没有收到任何钱,则重试不同的散列。
当前块时间戳必须严格大于最后一个块的时间戳,但唯一的保证是它将介于规范链中两个连续块的时间戳之间。
注意
出于可伸缩性原因,块哈希不适用于所有块。您只能访问最近256个块的哈希值,所有其他值将为零。
注意
在0.5.0版本中,下面的别名被拆除:suicide作为别名selfdestruct, msg.gas因为别名gasleft,block.blockhash因为别名blockhash和 sha3作为别名keccak256。
功能可见性说明
function myFunction() returns (bool) {
return true;
}
• public:外部和内部可见(为存储/状态变量创建一个getter函数)
• private:仅在当前合约中可见
• external:只在外部可见(仅用于函数) - 即只能被消息调用(通过this.func)
• internal:只在内部可见
修饰符
• pure for functions:不允许修改或访问状态。
• view for functions:不允许修改状态。
• payable 功能:允许他们与呼叫一起接收以太网。
• constant 对于状态变量:不允许分配(初始化除外),不占用存储槽。
• anonymous for events:不将事件签名存储为主题。
• indexed for event parameters:将参数存储为主题。
保留关键字
这些关键字在Solidity中保留。它们可能成为未来语法的一部分:
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,type,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? InlineAssemblyBlock
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]+ )
InlineAssemblyBlock = ‘{’ AssemblyItem* ‘}’
AssemblyItem = Identifier | FunctionalAssemblyExpression | InlineAssemblyBlock | AssemblyVariableDeclaration | AssemblyAssignment | AssemblyLabel | NumberLiteral | StringLiteral | HexLiteral
AssemblyExpression = Identifier | FunctionalAssemblyExpression | NumberLiteral | StringLiteral | HexLiteral
AssemblyVariableDeclaration = ‘let’ Identifier ‘:=’ AssemblyExpression
AssemblyAssignment = ( Identifier ‘:=’ AssemblyExpression ) | ( ‘=:’ Identifier )
AssemblyLabel = Identifier ‘:’
FunctionalAssemblyExpression = Identifier ‘(’ AssemblyItem? ( ‘,’ AssemblyItem )* ‘)’