Solidity 源文件结构

源文件中可以包含任意多个 合约定义导入源文件指令版本标识 指令。

Pragmas

关键字 pragma 版本标识指令,用来启用某些编译器检查, 版本 标识pragma 指令通常只对本文件有效,所以我们需要把这个版本 标识pragma 添加到项目中所有的源文件。 如果使用了 import 导入 其他的文件, 标识pragma 并不会从被导入的文件,加入到导入的文件中。

版本标识

为了避免未来被可能引入不兼容更新的编译器所编译,源文件可以(也应该)使用版本 标识pragma 所注解。 我们力图把这类不兼容变更做到尽可能小,但是,Solidity 本身就处在快速的发展之中,所以我们很难保证不引入修改语法的变更。 因此对含重大变更的版本,通读变更日志永远是好办法,变更日志通常会有版本号表明更新点。

版本号的形式通常是 0.x.0 或者 x.0.0

版本标识使用如下:

pragma solidity ^0.5.2;

这样,源文件将既不允许低于 0.5.2 版本的编译器编译, 也不允许高于(包含) 0.6.0 版本的编译器编译(第二个条件因使用 ^ 被添加)。 这种做法的考虑是,编译器在 0.6.0 版本之前不会有重大变更,所以可确保源代码始终按预期被编译。 上面例子中不固定编译器的具体版本号,因此编译器的补丁版也可以使用。

可以使用更复杂的规则来指定编译器的版本,表达式遵循 npm 版本语义。

注解

Pragma 是 pragmatic information 的简称,微软 Visual C++ 文档 中译为标识。 Solidity 中沿用 C ,C++ 等中的编译指令概念,用于告知编译器 如何 编译。 ——译者注

注解

使用版本标准不会改变编译器的版本,它不会启用或关闭任何编译器的功能。 他仅仅是告知编译器去检查版本是否匹配, 如果不匹配,编译器就会提示一个错误。

标注实验性功能

第2个标注是用来标注实验性阶段的功能,它可以用来启用一些新的编译器功能或语法特性。 当前支持下面的一些实验性标注:

ABIEncoderV2

新的 ABI 编码器可以用来编码和解码嵌套的数组和结构体,当然这部分代码还在优化之中,他没有像之前 ABI 编码器 那样经过严格的测试,我们可以使用下面的语法来启用它 pragma experimental ABIEncoderV2;

SMTChecker

当我们使用自己编译的 Solidity 编译器( 参考 build instructions) 的时候,这个模块可以启用。 在 Ubuntu PPA 发布的编译器的版本里 SMT solver 功能是启用的,但是其他版本如:solc-js, Docker 镜像版本, Windows 二进制版本,静态编译的 Linux 二进制版本 是没有启用的。

注解

译者注: SMT 全称是:Satisfiability modulo theories,用来“验证程序等价性”。

使用 pragma experimental SMTChecker;, 就可以获得 SMT solver 额外的安全检查。但是这个模块目前不支持 Solidity 的全部语法特性,因此有可能输出一些警告信息。

导入其他源文件

语法与语义

Solidity 支持的导入语句来模块化代码,其语法跟 JavaScript(从 ES6 起)非常类似。 尽管 Solidity 不支持 default export

注解

ES6 即 ECMAScript 6.0,ES6是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布。 ——译者注

在全局层面上,可使用如下格式的导入语句:

import "filename";

此语句将从 “filename” 中**导入所有的全局符号到当前全局作用域**中(不同于 ES6,Solidity 是向后兼容的)。

这种形式已经不建议使用,因为它会无法预测地污染当前命名空间。 如果在“filename”中添加新的符号,则会自动添加出现在所有导入 “filename” 的文件中。 更好的方式是明确导入的具体 符号。

向下面这样,创建了新的 symbolName 全局符号,他的成员都来自与导入的 "filename" 文件中的全局符号,如:

import * as symbolName from "filename";

然后所有全局符号都以``symbolName.symbol``格式提供。 此语法的变体不属于ES6,但可能有用:

import "filename" as symbolName;

它等价于 import * as symbolName from "filename";

如果存在命名冲突,则可以在导入时重命名符号。例如,下面的代码创建了新的全局符号 aliassymbol2 ,引用的 symbol1symbol2 来自 “filename” 。

import {symbol1 as alias, symbol2} from "filename";

路径

上文中的 filename 总是会按路径来处理,以 / 作为目录分割符、以 . 标示当前目录、以 .. 表示父目录。 当 ... 后面跟随的字符是 / 时,它们才能被当做当前目录或父目录。 只有路径以当前目录 . 或父目录 .. 开头时,才能被视为相对路径。

import "./filename" as symbolName; 语句导入当前源文件同目录下的文件 filename 。 如果用 import "filename" as symbolName; 代替,可能会引入不同的(如在全局 include directory 中)文件。

最终导入哪个文件取决于编译器(见下文 在实际的编译器中使用)到底是怎样解析路径的。 通常,目录层次不必严格映射到本地文件系统, 它也可以映射到能通过诸如 ipfs,http 或者 git 发现的资源。

注解

通常使用相对引用 import "./filename.sol"; 并且避免使用 .. ,后面这种方式可以使用全局路径并设置映射,下面会有解释。

在实际的编译器中使用

当运行编译器时,它不仅能指定如何发现路径的第一个元素,还可指定路径前缀 重映射remapping。 例如,github.com/ethereum/dapp-bin/library 会被重映射到 /usr/local/dapp-bin/library , 此时编译器将从重映射位置读取文件。如果重映射到多个路径,优先尝试重映射路径最长的一个。 这允许将比如 "" 被映射到 "/usr/local/include/solidity" 来进行“回退重映射”。 同时,这些重映射可取决于上下文,允许你配置要导入的包,比如同一个库的不同版本。

solc:

对于 solc(命令行编译器),这些重映射以 context:prefix=target 形式的参数提供。 其中,context:=target 部分是可选的(此时 target 默认为 prefix )。 所有重映射的值都是被编译过的常规文件(包括他们的依赖),这个机制完全是向后兼容的(只要文件名不包含 = 或 : ), 因此这不是一个破坏性修改。 在 content 目录或其子目录中的源码文件中,所有导入语句里以 prefix 开头的导入文件都将被用 target 替换 prefix 来重定向。

举个例子,如果你已克隆 github.com/ethereum/dapp-bin/ 到本地 /usr/local/dapp-bin , 可在源文件中使用:

import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;

然后运行编译器:

solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol

举个更复杂的例子,假设你依赖了一些使用了非常旧版本的 dapp-bin 的模块。 旧版本的 dapp-bin 已经被 checkout 到 /usr/local/dapp-bin_old ,此时你可使用:

solc module1:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ \
module2:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin_old/ \
source.sol

这样, module2 中的所有导入都指向旧版本,而 module1 中的导入则获取新版本。

注解

solc 只允许包含来自特定目录的文件:它们必须位于显式地指定的源文件目录(或子目录)中,或者重映射的目标目录(或子目录)中。 如果你想直接用绝对路径来包含文件,只需添加重映射 /=/

如果有多个重映射指向一个有效文件,那么具有最长公共前缀的重映射会被选用。

Remix:

Remix 提供一个为 github 源代码平台的自动重映射,它将通过网络自动获取文件: 比如,你可以使用:

import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;

导入一个 map 迭代器。

未来, Remix 可能支持其他源代码平台。

注释

可以使用单行注释(//)和多行注释(/*...*/

// 这是一个单行注释。

/*
这是一个
多行注释。
*/

注解

单行注释由任何 unicode 行终止符(如采用utf8编码的:LF,VF,FF,CR,NEL,LS或PS) 终止。 在注释之后终止符代码仍然是源码的一部分。如果它不是ascii符号(NEL,LS和PS),它会导致解析器错误。

此外,有另一种注释称为 natspec 注释,可参考 风格指南- 描述注释

它们是用三个反斜杠(///)或双星号开头的块(/** ... */)书写,它们应该直接在函数声明或语句上使用。 可在注释中使用 Doxygen 样式的标签来文档化函数、 标注形式校验通过的条件,和提供一个当用户试图调用一个函数时显示给用户的 确认性文字

在下面的例子中,我们记录了合约的标题,并解释了两个传入参书和两个返回值的翻译。

pragma solidity ^0.4.0;

/** @title 形状计算器。 */
contract tinyCalculator {
    /** @dev 求矩形表明面积与周长。
    * @param w 矩形宽度。
    * @param h 矩形高度。
    * @return s 求得表面积。
    * @return p 求得周长。
    */
    function rectangle(uint w, uint h) returns (uint s, uint p) {
        s = w * h;
        p = 2 * (w + h);
    }
}