静态大小的变量(除 映射 和动态数组之外的所有类型)都从位置 0 开始连续放置在 存储 中。如果可能的话,存储需求少于 32 字节的多个变量会被打包到一个 存储插槽 中,规则如下:
Warning
使用小于 32 字节的元素时,你的合约的 gas 使用量可能高于使用 32 字节的元素时。这是因为 以太坊虚拟机 每次会操作 32 个字节, 所以如果元素比 32 字节小,以太坊虚拟机 必须使用更多的操作才能将其大小缩减到到所需的大小。
仅当你处理 存储插槽 中的值时候,使用缩减大小的参数才是有益的。因为编译器会将多个元素打包到一个 存储插槽 中, 从而将多个读或写合并到一次对存储的操作中。而在处理函数参数或 内存 中的值时,因为编译器不会打包这些值,所以没有什么益处。
最后,为了允许 以太坊虚拟机 对此进行优化,请确保你对 存储 中的变量和 struct 成员的书写顺序允许它们被紧密地打包。 例如,按照 uint128,uint128,uint256 的顺序声明你的存储变量,而不是 uint128,uint256,uint128, 因为前者只占用两个 存储插槽,而后者将占用三个。
结构和数组中的元素都是顺序存储的,就像它们被明确给定的那样。
由于 映射 和动态数组的大小是不可预知的,所以我们使用 Keccak-256 哈希计算来找到具体数值或数组数据的起始位置。 这些起始位置本身的数值总是会占满堆栈插槽。
映射 或动态数组本身会根据上述规则来在某个位置 p 处占用一个(未填充的)存储中的插槽(或递归地将该规则应用到 映射 的 映射 或数组的数组)。 对于动态数组,此插槽中会存储数组中元素的数量(字节数组和字符串在这里是一个例外,见下文)。对于 映射 ,该插槽未被使用(但它仍是需要的, 以使两个相同的 映射 在彼此之后会使用不同的散列分布)。数组的数据会位于 keccak256(p); 映射 中的键 k 所对应的值会位于 keccak256(k . p), 其中 . 是连接符。如果该值又是一个非基本类型,则通过添加 keccak256(k . p) 作为偏移量来找到位置。
如果 bytes 和 string 的数据很短,那么它们的长度也会和数据一起存储到同一个插槽。具体地说:如果数据长度小于等于 31 字节, 则它存储在高位字节(左对齐),最低位字节存储 length * 2。如果数据长度超出 31 字节,则在主插槽存储 length * 2 + 1, 数据照常存储在 keccak256(slot) 中。
所以对于以下合约片段:
pragma solidity ^0.4.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。
Solidity 保留了 4 个 32 字节的插槽(slot):
临时空间可以在语句之间使用(即在内联汇编之中)。0 值插槽则用来对动态内存数组进行初始化,且永远不会写入数据(因而可用的初始内存指针为 0x80)。
Solidity 总会把新对象保存在空闲 内存 指针的位置,所以这段内存实际上从来不会空闲(在未来可能会修改这个机制)。
Warning
Solidity 中有一些操作需要大于 64 字节的临时内存区域,因此这种数据无法保存到临时空间里。它们将被放置在空闲内存指向的位置,但由于这种数据的生命周期较短,这个指针不会即时更新。这部分内存可能会被清零也可能不会。所以我们不应该期望这些所谓的空闲内存总会被清零。
尽管使用 msize 来到达非零内存区域是个好主意,然而非临时性地使用这样的指针,而不更新可用内存指针也会产生有害的结果。
当从一个账户调用已部署的 Solidity 合约时,调用数据的格式被认为会遵循 ABI 说明。 根据 ABI 说明的规定,参数需要被整理为 32 字节的倍数。而内部函数调用会使用不同规则。
如果一个数值不足 256 位,那么在某些情况下,不足的位必须被清除。 Solidity 编译器设计用于在执行任何操作之前清除这些剩余位中可能会造成不利影响的潜在垃圾。 例如,因为 内存 中的内容可以用于计算散列或作为消息调用的数据发送,所以在向 内存 写入数值之前,需要清除剩余的位。 同样,在向 存储 中保存数据之前,剩余的位也需要清除,否则就会看到被混淆的数值。
另一方面,如果接下来的操作不会被影响,那我们就不用清除这些位的数据。例如,因为任何非零值都会被 JUMPI 指令视为 true, 所以在布尔数据用做 JUMPI 的条件之前,我们就不用清除它们。
除了以上设计原理之外,Solidity 编译器在把输入数据加载到堆栈时会对它们进行清除剩余位的处理。
不同的数据类型有不同的清除无效值的规则:
类型 | 合法数值 | 无效值会导致 |
---|---|---|
n 个成员的 enum | 0 到 n - 1 | exception |
bool | 0 或 1 | 1 |
signed integers | 以符号开头的 字(32字节) | 目前会直接打包; 未来会抛出 exception |
unsigned integers | 高位补 0 | 目前会直接打包; 未来会抛出 exception |
Solidity 优化器是在汇编语言级别工作的,所以它可以并且也被其他语言所使用。它通过 JUMP 和 JUMPDEST 语句将指令集序列分割为基础的代码块。在这些代码块内的指令集会被分析,并且对堆栈、内存或存储的每个修改都会被记录为表达式,这些表达式由一个指令和基本上是指向其他表达式的参数列表所组成。现在,主要的想法就是找到始终相等的表达式(在每个输入上)并将它们组合到一个表达式类中。优化器首先尝试在已知的表达式列表中查找每个新表达式。如果这不起作用,表达式会以 constant + constant = sum_of_constants 或 X * 1 = X 这样的规则进行简化。由于这是递归完成的,所以在我们知道第二个因子是一个更复杂的表达式,且此表达式总是等于 1 的情况下,也可以应用后一个规则。对存储和内存上某个具体位置的修改必须删除有关存储和内存位置的认知,这里边的区别并不为人所知:如果我们先在 x 位置写入,然后在 y 位置写入,且都是输入变量,则第二个可能会覆盖第一个,所以我们实际上并不知道在写入到 y 位置之后在 x 位置存储了什么。另一方面,如果对表达式 x - y 的简化,其结果为非零常数,那么我们知道我们可以保持关于 x 位置存储内容的认知。
在这个过程结束时,我们会知道最后哪些表达式必须在栈上,并且会得到一个修改内存和存储的列表。该信息与基本代码块一起存储并用来链接它们。此外,关于栈、存储和内存的配置信息会被转发到下一个代码块。如果我们知道所有 JUMP 和 JUMPI 指令的目标,我们就可以构建一个完整的程序流程图。 如果只有一个我们不知道的目标(原则上可能发生,跳转目标可以基于输入来计算),我们必须消除关于代码块输入状态的所有信息,因为它可能是未知的 JUMP 目标。如果一个 JUMPI 的条件等于一个常量,它将被转换为无条件跳转。
作为最后一步,每个块中的代码都会被完全重新生成。然后会从代码块的结尾处在栈上的表达式开始创建依赖关系图,且不是该图组成部分的每个操作实质上都会被丢弃。现在,生成的代码将按照原始代码中的顺序对内存和存储进行修改(舍弃不需要的修改),最终,生成需要在栈中的当前位置保存的所有值。
这些步骤适用于每个基本代码块,如果代码块较小,则新生成的代码将用作替换。如果一个基本代码块在 JUMPI 处被分割,且在分析过程中被评估为一个常数,则会根据常量的值来替换 JUMPI,因此,类似于
var x = 7;
data[7] = 9;
if (data[x] != x + 2)
return 2;
else
return 1;
的代码也就被简化地编译为
data[7] = 9;
return 1;
即使原始代码中包含一个跳转。
作为 AST 输出的一部分,编译器提供 AST 中相应节点所代表的源代码范围。这可以用于多种用途,比如从用于报告错误的 AST 静态分析工具到可以突出显示局部变量及其用途的调试工具。
此外,编译器还可以生成从字节码到生成该指令的源代码范围的映射。对于在字节码级别上运行的静态分析工具以及在调试器中显示源代码中的当前位置或处理断点,这都是同样重要的。
这两种源映射都使用整数标识符来引用源文件。这些是通常称为 “sourceList” 的源文件列表的常规数组索引,它们是 combined-json 和 json / npm 编译器输出的一部分。
Note
在指令没有与任何特定的代码文件关联的情况下,源代码映射会将 -1 赋值给一个整数标识符。这会在字节码阶段发生,源于由编译器生成的内联汇编语句。
AST 内的源代码映射使用以下表示法:
s:l:f
其中,s 是源代码文件中范围起始处的字节偏移量,l 是源代码范围的长度(以字节为单位),f 是上述源代码索引。
针对字节码的源代码映射的编码方式更加复杂:它是由 ; 分隔的 s:l:f:j 列表。每个元素都对应一条指令,即不能使用字节偏移量,但必须使用指令偏移量(push 指令长于一个字节)。字段 s,l 和 f 如上所述,j 可以是 i,o 或 -,表示一个跳转指令是否进入一个函数、是否从一个函数返回或者是否是一个常规跳转的一部分,例如一个循环。
为了压缩这些源代码映射,特别是对字节码的映射,我们将使用以下规则:
- 如果一个字段为空,则使用前一个元素中对应位置的值。
- 如果缺少 :,则后续所有字段都被视为空。
这意味着以下的源代码映射是等价的:
1:2:1;1:9:1;2:1:2;2:1:2;2:1:2
1:2:1;:9;2:1:2;;
Note
如果存储结构具有“紧打包(tightly packed)”,可以用分开的赋值语句来初始化:x.a = 1; x.b = 2;。这样可以使优化器更容易地一次性更新存储,使赋值的开销更小。
以下是按评估顺序列出的操作符优先级。
优先级 | 描述 | 操作符 |
---|---|---|
1 | 后置自增和自减 | ++, -- |
创建类型实例 | new <typename> | |
数组元素 | <array>[<index>] | |
访问成员 | <object>.<member> | |
函数调用 | <func>(<args...>) | |
小括号 | (<statement>) | |
2 | 前置自增和自减 | ++, -- |
一元运算的加和减 | +, - | |
一元操作符 | delete | |
逻辑非 | ! | |
按位非 | ~ | |
3 | 乘方 | ** |
4 | 乘、除和模运算 | *, /, % |
5 | 算术加和减 | +, - |
6 | 移位操作符 | <<, >> |
7 | 按位与 | & |
8 | 按位异或 | ^ |
9 | 按位或 | | |
10 | 非等操作符 | <, >, <=, >= |
11 | 等于操作符 | ==, != |
12 | 逻辑与 | && |
13 | 逻辑或 | || |
14 | 三元操作符 | <conditional> ? <if-true> : <if-false> |
15 | 赋值操作符 | =, |=, ^=, &=, <<=, >>=, +=, -=, *=, /=, %= |
16 | 逗号 | , |
Note
不要用 block.timestamp、now 或者 blockhash 作为随机种子,除非你明确知道你在做什么。
时间戳和区块哈希都可以在一定程度上被矿工所影响。如果你用哈希值作为随机种子,那么例如挖矿团体中的坏人就可以使用给定的哈希来执行一个赌场功能,如果他们没赢钱,他们可以简单地换一个哈希再试。
当前区块的时间戳必须比前一个区块的时间戳大,但唯一可以确定的就是它会是权威链(主链或者主分支)上两个连续区块时间戳之间的一个数值。
Note
出于扩展性的原因,你无法取得所有区块的哈希。只有最新的 256 个区块的哈希可以拿到,其他的都将为 0。
function myFunction() <visibility specifier> returns (bool) {
return true;
}
以下是 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
UserDefinedTypeName = Identifier ( '.' Identifier )*
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
( 'returns' FunctionTypeParameterList )?
StorageLocation = 'memory' | 'storage' | 'calldata'
StateMutability = 'pure' | 'constant' | '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 = ('var' IdentifierList | VariableDeclaration | '(' VariableDeclaration? (',' VariableDeclaration? )* ')' ) ( '=' Expression )?
IdentifierList = '(' ( Identifier? ',' )* Identifier? ')'
// 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' | 'var'
| 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 | AssemblyLocalBinding | AssemblyAssignment | AssemblyLabel | NumberLiteral | StringLiteral | HexLiteral
AssemblyLocalBinding = 'let' Identifier ':=' FunctionalAssemblyExpression
AssemblyAssignment = ( Identifier ':=' FunctionalAssemblyExpression ) | ( '=:' Identifier )
AssemblyLabel = Identifier ':'
FunctionalAssemblyExpression = Identifier '(' AssemblyItem? ( ',' AssemblyItem )* ')'