杂项

存储storage 中的状态变量储存结构

静态大小的变量(除 映射mapping 和动态数组之外的所有类型)都从位置 0 开始连续放置在 存储storage 中。如果可能的话,存储大小少于 32 字节的多个变量会被打包到一个 存储插槽storage slot 中,规则如下:

  • 存储插槽storage slot 的第一项会以低位对齐(即右对齐)的方式储存。
  • 基本类型仅使用存储它们所需的字节。
  • 如果 存储插槽storage slot 中的剩余空间不足以储存一个基本类型,那么它会被移入下一个 存储插槽storage slot
  • 结构体(struct)和数组数据总是会占用一整个新插槽(但结构体或数组中的各项,都会以这些规则进行打包)。

对于使用继承的合约,状态变量的排序由C3线性化合约顺序( 顺序从最基类合约开始)确定。如果上述规则成立,那么来自不同的合约的状态变量会共享一个 存储插槽storage slot

结构体和数组中的成员变量会存储在一起,就像它们在显式声明中的一样。

警告

在使用小于 32 字节的元素(变量)时,合约的 gas 使用量可能会高于使用 32 字节的元素。这是因为 以太坊虚拟机Ethereum Virtual Machine(EVM) 每次操作 32 个字节, 所以如果元素比 32 字节小,以太坊虚拟机Ethereum Virtual Machine(EVM) 必须执行额外的操作以便将其大小缩减到到所需的大小。

当我们在处理状态变量时,只有当编译器会将多个元素打包到一个 存储插槽storage slot 中,使用缩减的大小(小于32字节)的变量才更有益处。因为它会将多个读或写合并为单次操作。 而在处理函数参数或 内存memory 中的值时,因为编译器不会打包这些值,所以没有什么益处。

最后,为了允许 以太坊虚拟机Ethereum Virtual Machine(EVM) 对此进行优化,请确保 存储storage 中的变量和 struct 成员的书写顺序允许它们被紧密地打包。 例如,应该按照 uint128,uint128,uint256 的顺序来声明状态变量,而不是使用 uint128,uint256,uint128 , 因为前者只占用两个 存储插槽storage slot,而后者将占用三个。

注解

由于 存储storage 中的指针可以传递给库(library) ,所以 存储storage 中状态变量的布局被认为是 solidity 外部接口的一部分。这意味着,本节所述规则的任何变更均被视为语言的重大变更,由于其关键性,请在执行前应仔细考虑。

映射和动态数组

由于 映射mapping 和动态数组的大小是不可预知的,他们使用 Keccak-256 哈希计算来找到值的位置或数组的起始位置。 这些起始位置本身的数值总是会占满堆栈插槽。

映射mapping 或动态数组本身会根据上述规则来在某个位置 p 处占用一个(未填充的)存储中的插槽(或递归地将该规则应用到 映射mapping映射mapping 或数组的数组)。 对于动态数组,此插槽中会存储数组中元素的数量(字节数组和字符串除外,见下文)。 对于 映射mapping ,该插槽未被使用(但它仍是需要的,以使两个相同的 映射mapping 在彼此之后会使用不同的散列分布)。数组的数据会位于 keccak256(p)映射mapping 中的键 k 所对应的值会位于 keccak256(k . p) , 其中 . 是连接符。如果该值又是一个非基本类型,则通过添加 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

bytesstring

bytesstring 编码是一样的。 对于短字节数组,它们将数据和它们的长度存储在同一个插槽中。 具体地说:如果数据长度小于等于 31 字节,则它存储在高位字节(左对齐),最低位字节存储 length * 2。 如果数据长度超出 31 字节,则在主插槽存储 length * 2 + 1 ,数据照常存储在 keccak256(slot) 中。

这意味着可以通过检查是否设置了最低位:短(未设置)和长(设置)来区分短数组和长数组。

注解

目前不支持处理无效编码的插槽,但可能在将来添加。

JSON Output

The storage layout of a contract can be requested via the standard JSON interface. The output is a JSON object containing two keys, storage and types. The storage object is an array where each element has the following form:

{
    "astId": 2,
    "contract": "fileA:A",
    "label": "x",
    "offset": 0,
    "slot": "0",
    "type": "t_uint256"
}

The example above is the storage layout of contract A { uint x; } from source unit fileA and

  • astId is the id of the AST node of the state variable’s declaration
  • contract is the name of the contract including its path as prefix
  • label is the name of the state variable
  • offset is the offset in bytes within the storage slot according to the encoding
  • slot is the storage slot where the state variable resides or starts. This number may be very large and therefore its JSON value is represented as a string.
  • type is an identifier used as key to the variable’s type information (described in the following)

The given type, in this case t_uint256 represents an element in types, which has the form:

{
    "encoding": "inplace",
    "label": "uint256",
    "numberOfBytes": "32",
}

where

  • encoding how the data is encoded in storage, where the possible values are:
    • inplace: data is laid out contiguously in storage (see above).
    • mapping: Keccak-256 hash-based method (see above).
    • dynamic_array: Keccak-256 hash-based method (see above).
    • bytes: single slot or Keccak-256 hash-based depending on the data size (see above).
  • label is the canonical type name.
  • numberOfBytes is the number of used bytes (as a decimal string). Note that if numberOfBytes > 32 this means that more than one slot is used.

Some types have extra information besides the four above. Mappings contain its key and value types (again referencing an entry in this mapping of types), arrays have its base type, and structs list their members in the same format as the top-level storage (see above).

注解

The JSON output format of a contract’s storage layout is still considered experimental and is subject to change in non-breaking releases of Solidity.

The following example shows a contract and its storage layout, containing value and reference types, types that are encoded packed, and nested types.

pragma solidity >=0.4.0 <0.7.0;
contract A {
    struct S {
        uint128 a;
        uint128 b;
        uint[2] staticArray;
        uint[] dynArray;
    }

    uint x;
    uint y;
    S s;
    address addr;
    mapping (uint => mapping (address => bool)) map;
    uint[] array;
    string s1;
    bytes b1;
}
"storageLayout": {
  "storage": [
    {
      "astId": 14,
      "contract": "fileA:A",
      "label": "x",
      "offset": 0,
      "slot": "0",
      "type": "t_uint256"
    },
    {
      "astId": 16,
      "contract": "fileA:A",
      "label": "y",
      "offset": 0,
      "slot": "1",
      "type": "t_uint256"
    },
    {
      "astId": 18,
      "contract": "fileA:A",
      "label": "s",
      "offset": 0,
      "slot": "2",
      "type": "t_struct(S)12_storage"
    },
    {
      "astId": 20,
      "contract": "fileA:A",
      "label": "addr",
      "offset": 0,
      "slot": "6",
      "type": "t_address"
    },
    {
      "astId": 26,
      "contract": "fileA:A",
      "label": "map",
      "offset": 0,
      "slot": "7",
      "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))"
    },
    {
      "astId": 29,
      "contract": "fileA:A",
      "label": "array",
      "offset": 0,
      "slot": "8",
      "type": "t_array(t_uint256)dyn_storage"
    },
    {
      "astId": 31,
      "contract": "fileA:A",
      "label": "s1",
      "offset": 0,
      "slot": "9",
      "type": "t_string_storage"
    },
    {
      "astId": 33,
      "contract": "fileA:A",
      "label": "b1",
      "offset": 0,
      "slot": "10",
      "type": "t_bytes_storage"
    }
  ],
  "types": {
    "t_address": {
      "encoding": "inplace",
      "label": "address",
      "numberOfBytes": "20"
    },
    "t_array(t_uint256)2_storage": {
      "base": "t_uint256",
      "encoding": "inplace",
      "label": "uint256[2]",
      "numberOfBytes": "64"
    },
    "t_array(t_uint256)dyn_storage": {
      "base": "t_uint256",
      "encoding": "dynamic_array",
      "label": "uint256[]",
      "numberOfBytes": "32"
    },
    "t_bool": {
      "encoding": "inplace",
      "label": "bool",
      "numberOfBytes": "1"
    },
    "t_bytes_storage": {
      "encoding": "bytes",
      "label": "bytes",
      "numberOfBytes": "32"
    },
    "t_mapping(t_address,t_bool)": {
      "encoding": "mapping",
      "key": "t_address",
      "label": "mapping(address => bool)",
      "numberOfBytes": "32",
      "value": "t_bool"
    },
    "t_mapping(t_uint256,t_mapping(t_address,t_bool))": {
      "encoding": "mapping",
      "key": "t_uint256",
      "label": "mapping(uint256 => mapping(address => bool))",
      "numberOfBytes": "32",
      "value": "t_mapping(t_address,t_bool)"
    },
    "t_string_storage": {
      "encoding": "bytes",
      "label": "string",
      "numberOfBytes": "32"
    },
    "t_struct(S)12_storage": {
      "encoding": "inplace",
      "label": "struct A.S",
      "members": [
        {
          "astId": 2,
          "contract": "fileA:A",
          "label": "a",
          "offset": 0,
          "slot": "0",
          "type": "t_uint128"
        },
        {
          "astId": 4,
          "contract": "fileA:A",
          "label": "b",
          "offset": 16,
          "slot": "0",
          "type": "t_uint128"
        },
        {
          "astId": 8,
          "contract": "fileA:A",
          "label": "staticArray",
          "offset": 0,
          "slot": "1",
          "type": "t_array(t_uint256)2_storage"
        },
        {
          "astId": 11,
          "contract": "fileA:A",
          "label": "dynArray",
          "offset": 0,
          "slot": "3",
          "type": "t_array(t_uint256)dyn_storage"
        }
      ],
      "numberOfBytes": "128"
    },
    "t_uint128": {
      "encoding": "inplace",
      "label": "uint128",
      "numberOfBytes": "16"
    },
    "t_uint256": {
      "encoding": "inplace",
      "label": "uint256",
      "numberOfBytes": "32"
    }
  }
}

内存memory 中的存储结构

Solidity 保留了 4 个 32 字节的插槽(slot),特定字节范围(及端点)使用如下:

  • 0x00 - 0x3f (64个字节):用于保存方法(函数)哈希的临时空间
  • 0x40 - 0x5f (32个字节):当前已分配的 内存memory 大小(又名,空闲 内存memory 指针)
  • 0x60 - 0x7f (32个字节):0 值插槽

临时空间可以在语句之间使用(即在内联汇编之中)。0 值插槽则用来对动态内存数组进行初始化,且永远不会写入数据(因而可用的初始内存指针为 0x80)。

Solidity 总是将新对象放在空闲 内存memory 指针上,并且永远不会释放内存在未来可能会修改这个机制)。

警告

Solidity 中有一些操作需要大于 64 字节的临时内存区域,因此这种数据无法保存到临时空间里。它们将被放置在空闲内存指向的位置,但由于这种数据的生命周期较短,这个指针不会即时更新。这部分内存可能会被清零也可能不会。所以我们不应该期望这些所谓的空闲内存总会被清零。

尽管使用 msize 来到达非零内存区域是个好主意,然而非临时性地使用这样的指针,而不更新可用内存指针也会产生有害的结果。

调用数据(calldata)存储结构

当从一个账户调用已部署的 Solidity 合约时,调用数据的格式被认为会遵循 ABI 说明 。 根据 ABI 说明的规定,参数需要被整理为 32 字节的倍数。而内部函数调用会使用不同规则。

合约中构造函数的参数直接追加在合约代码的末尾,也使用ABI编码。构造器将通过硬编码的偏移量访问它们,而不是使用 codesize 操作码,因为如果把数据插入到代码中间,会让代码发生改变。

内部机制 - 清理变量

如果一个数值不足 256 位,那么在某些情况下,不足的位必须被清除。

Solidity 编译器设计用于在执行任何操作之前清除这些剩余位中可能会造成不利影响的潜在垃圾。 例如,因为 内存memory 中的内容可以用于计算散列或作为消息调用的数据发送,所以在向 内存memory 写入数值之前,需要清除剩余的位。 同样,在向 存储storage 中保存数据之前,剩余的位也需要清除,否则就会看到被混淆的数值。

另一方面,如果接下来的操作不会被影响,那我们就不用清除这些位的数据。例如,因为任何非零值都会被 JUMPI 指令视为 true, 所以在布尔数据用做 JUMPI 的条件之前,我们就不用清除它们。

除了以上设计原理之外,Solidity 编译器在把输入数据加载到堆栈时会对它们进行清除剩余位的处理。

不同的数据类型有不同的清除无效值的规则:

类型 合法数值 无效值会导致
n 个成员的 enum 0 到 n - 1 exception
bool 0 或 1 1
signed integers 以符号开头的 字(32字节) 目前会直接打包; 未来会抛出 exception
unsigned integers 高位补 0 目前会直接打包; 未来会抛出 exception

内部机制 - 优化器

Solidity 优化器是在汇编语言级别工作的,所以它可以并且也被其他语言所使用。它通过 JUMPJUMPDEST 语句将指令集序列分割为基础的代码块。在这些代码块内的指令集会被分析,并且对堆栈、内存或存储的每个修改都会被记录为表达式,这些表达式由一个指令和基本上是指向其他表达式的参数列表所组成。现在,主要的想法就是找到始终相等的表达式(在每个输入上)并将它们组合到一个表达式类中。优化器首先尝试在已知的表达式列表中查找每个新表达式。如果这不起作用,表达式会以 constant + constant = sum_of_constantsX * 1 = X 这样的规则进行简化。由于这是递归完成的,所以在我们知道第二个因子是一个更复杂的表达式,且此表达式总是等于 1 的情况下,也可以应用后一个规则。对存储和内存上某个具体位置的修改必须删除有关存储和内存位置的认知,这里边的区别并不为人所知:如果我们先在 x 位置写入,然后在 y 位置写入,且都是输入变量,则第二个可能会覆盖第一个,所以我们实际上并不知道在写入到 y 位置之后在 x 位置存储了什么。另一方面,如果对表达式 x - y 的简化,其结果为非零常数,那么我们知道我们可以保持关于 x 位置存储内容的认知。

在这个过程结束时,我们会知道最后哪些表达式必须在栈上,并且会得到一个修改内存和存储的列表。该信息与基本代码块一起存储并用来链接它们。此外,关于栈、存储和内存的配置信息会被转发到下一个代码块。如果我们知道所有 JUMPJUMPI 指令的目标,我们就可以构建一个完整的程序流程图。 如果只有一个我们不知道的目标(原则上可能发生,跳转目标可以基于输入来计算),我们必须消除关于代码块输入状态的所有信息,因为它可能是未知的 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 静态分析工具和调试工具到可以突出显示局部变量及其用途的调试工具。

此外,编译器还可以生成从字节码到生成该指令的源代码范围的映射。对于在字节码级别上运行的静态分析工具以及在调试器中显示源代码中的当前位置或处理断点,这都是同样重要的。

这两种源映射都使用整数标识符来引用源文件。源文件的整数标识符存储在 output['sources'][sourceName]['id']output 标准json编译器接口解析的JSON输出。

注解

在指令没有与任何特定的代码文件关联的情况下,源代码映射会将 -1 赋值给一个整数标识符。这可能发生在编译器生成的内联汇编语句的字节码部分。

AST 内的源代码映射使用以下表示法:

s:l:f

其中,s 是源代码文件中范围起始处的字节偏移量,l 是源代码范围的长度(以字节为单位),f 是上述源代码索引。

针对字节码的源代码映射的编码方式更加复杂:它是由 ; 分隔的 s:l:f:j 列表。每个元素都对应一条指令,即不能使用字节偏移量,但必须使用指令偏移量(push 指令长于一个字节)。字段 slf 如上所述,j 可以是 io-,表示一个跳转指令是否进入一个函数、是否从一个函数返回或者是否是一个常规跳转的一部分,例如一个循环。

为了压缩这些源代码映射,特别是对字节码的映射,我们将使用以下规则:

  • 如果一个字段为空,则使用前一个元素中对应位置的值。
  • 如果缺少 :,则后续所有字段都被视为空。

这意味着以下的源代码映射是等价的:

1:2:1;1:9:1;2:1:2;2:1:2;2:1:2

1:2:1;:9;2:1:2;;

技巧和窍门

  • 可以使用 delete 来删除数组中的所有元素。
  • 对 struct 中的元素使用更短的数据类型,并对它们进行排序,以便将短数据类型组合在一起。这可以降低 gas 消耗,因为多个 SSTORE 操作可能会被合并成一个(SSTORE 消耗 5000 或 20000 的 gas,所以这应该是你想要优化的)。使用 gas 估算器(启用优化器)来检查效果!
  • 将你的状态变量设置为 public ——编译器会为你自动创建 getters
  • 如果你最终需要在函数开始位置检查很多输入条件或者状态变量的值,你可以尝试使用 函数
  • 使用一个赋值语句就可以初始化 struct:x = MyStruct({a: 1, b: 2});

注解

如果存储结构具有“紧打包(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 逗号 ,

全局变量

  • 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)ABI - 对给定参数进行编码
  • abi.encodePacked(...) returns (bytes):对给定参数执行 紧打包编码
  • abi.encodeWithSelector(bytes4 selector, ...) returns (bytes)ABI - 对给定参数进行编码,并以给定的函数选择器作为起始的 4 字节数据一起返回
  • abi.encodeWithSignature(string memory signature, ...) returns (bytes):等价于 abi.encodeWithSelector(bytes4(keccak256(signature), ...)
  • block.coinbaseaddress payable):挖出当前区块的矿工的地址
  • block.difficultyuint):当前区块的难度值
  • block.gaslimituint):当前区块的 gas 上限
  • block.numberuint):当前区块的区块号
  • block.timestampuint):当前区块的时间戳
  • gasleft() returns (uint256):剩余的 gas
  • msg.databytes):完整的 calldata
  • msg.senderaddress payable):消息发送方(当前调用)
  • msg.valueuint):随消息发送的 wei 的数量
  • nowuint):当前区块的时间戳(等价于 block.timestamp
  • tx.gaspriceuint):交易的 gas price
  • tx.originaddress):交易发送方(完整调用链上的原始发送方)
  • assert(bool condition):如果条件值为 false 则中止执行并回退所有状态变更(用做内部错误)
  • require(bool condition):如果条件值为 false 则中止执行并回退所有状态变更(用做异常输入或外部组件错误)
  • require(bool condition, string 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 memory, uint8 v, bytes32 r, bytes32 s) returns (address):基于椭圆曲线签名找回与指定公钥关联的地址,发生错误的时候返回 0
  • addmod(uint x, uint y, uint k) returns (uint):计算 (x + y) % k 的值,其中加法的结果即使超过 2**256 也不会被截取。从 0.5.0 版本开始会加入对 k != 0 的 assert(即会在此函数开头执行 assert(k != 0); 作为参数检查,译者注)。
  • mulmod(uint x, uint y, uint k) returns (uint):计算 (x * y) % k 的值,其中乘法的结果即使超过 2**256 也不会被截取。从 0.5.0 版本开始会加入对 k != 0 的 assert(即会在此函数开头执行 assert(k != 0); 作为参数检查,译者注)。
  • this (类型为当前合约的变量):当前合约实例,可以准确地转换为 address
  • super:当前合约的上一级继承关系的合约
  • selfdestruct(address recipient):销毁当前合约,把余额发送到给定地址
  • <address>.balanceuint256): 地址类型 Address 的余额,以 Wei 为单位
  • <address payable>.send(uint256 amount) returns (bool):向 地址类型 Address 发送给定数量的 Wei,失败时返回 false
  • <address payable>.transfer(uint256 amount):向 地址类型 Address 发送给定数量的 Wei,失败时会把错误抛出(throw)
  • type(C).name (string): 智能合约的名字
  • type(C).creationCode (bytes memory): 合约的创建字节码, 参考 类型信息.
  • type(C).runtimeCode (bytes memory): 合约的运行字节码, 参考 类型信息.

注解

不要用 block.timestampnow 或者 blockhash 作为随机种子,除非你明确知道你在做什么。

时间戳和区块哈希都可以在一定程度上被矿工所影响。如果你用哈希值作为随机种子,那么例如挖矿团体中的坏人就可以使用给定的哈希来执行一个赌场功能,如果他们没赢钱,他们可以简单地换一个哈希再试。

当前区块的时间戳必须严格大于最后一个区块的时间戳,但这里能确保也需要它是在权威链(主链或者主分支)上的两个连续区块的时间戳。

注解

出于扩展性的原因,你无法取得所有区块的哈希。只有最新的 256 个区块的哈希可以拿到,其他的都将为 0。

注解

在0.5.0中, 下面关键字被去除了: suicideselfdestruct 代替, msg.gasgasleft 代替, block.blockhashblockhash 代替, sha3keccak256 代替.

函数可见性说明符

function myFunction() <visibility specifier> returns (bool) {
    return true;
}
  • public:内部、外部均可见(参考为存储/状态变量创建 getter 函数
  • private:仅在当前合约内可见
  • external:仅在外部可见(仅可修饰函数)——就是说,仅可用于消息调用(即使在合约内调用,也只能通过 this.func 的方式)
  • internal:仅在内部可见(也就是在当前 Solidity 源代码文件内均可见,不仅限于当前合约内,译者注)

修改器

  • pure 修饰函数时:不允许修改或访问状态——但目前并不是强制的。
  • view 修饰函数时:不允许修改状态——但目前不是强制的。
  • payable 修饰函数时:允许从调用中接收 以太币Ether
  • constant 修饰状态变量时:不允许赋值(除初始化以外),不会占据 存储插槽storage slot
  • anonymous 修饰事件时:不把事件签名作为 topic 存储。
  • indexed 修饰事件时:将参数作为 topic 存储。

保留字

以下是 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 )* ')'