Solidity 源文件布局

源文件可以包含任意数量的 合约定义importpragmausing for 指令以及 structenumfunctionerror常量变量 定义。

SPDX 许可证标识符

如果智能合约的源代码可用,则可以更好地建立对智能合约的信任。由于使源代码可用总是会触及与版权相关的法律问题,因此 Solidity 编译器鼓励使用机器可读的 SPDX 许可证标识符。每个源文件都应以指示其许可证的注释开头

// SPDX-License-Identifier: MIT

编译器不验证许可证是否为 SPDX 允许的列表 中的一部分,但它确实会在 字节码元数据 中包含提供的字符串。

如果您不想指定许可证,或者源代码不是开源的,请使用特殊值 UNLICENSED。请注意,UNLICENSED(不允许使用,不在 SPDX 许可证列表中)与 UNLICENSE(授予每个人所有权利)不同。Solidity 遵循 npm 建议

提供此注释当然不会让您免于与许可相关的其他义务,例如必须在每个源文件中提及特定许可证标题或原始版权持有者。

编译器在文件级别的文件中的任何位置都会识别该注释,但建议将其放在文件顶部。

有关如何使用 SPDX 许可证标识符的更多信息,请访问 SPDX 网站

编译指示

pragma 关键字用于启用某些编译器功能或检查。编译指示始终针对特定源文件,因此如果您希望在整个项目中启用它,则必须将编译指示添加到所有文件中。如果您 导入 另一个文件,该文件的编译指示不会自动应用于导入文件。

版本编译指示

源文件可以(也应该)用版本编译指示进行注释,以拒绝与可能引入不兼容更改的将来编译器版本进行编译。我们尽量将这些更改降至最低,并以使语义更改也需要语法更改的方式引入它们,但并非总是可行。因此,始终建议至少阅读包含重大更改的版本的更改日志。这些版本总是具有 0.x.0x.0.0 形式的版本。

版本编译指示的使用方法如下:pragma solidity ^0.5.2;

包含以上行的源文件不会使用早于 0.5.2 版本的编译器进行编译,并且它也不适用于从 0.6.0 版本开始的编译器(使用 ^ 添加了第二个条件)。由于在版本 0.6.0 之前不会有重大更改,因此您可以确保您的代码以您预期的​​方式进行编译。编译器的确切版本没有固定,因此仍然可以进行错误修复版本。

可以为编译器版本指定更复杂的规则,这些规则遵循与 npm 使用的相同语法。

注意

使用版本编译指示不会更改编译器的版本。它也不会启用或禁用编译器功能。它只是指示编译器检查其版本是否与编译指示要求的版本匹配。如果不匹配,编译器会发出错误。

ABI 编码器编译指示

通过使用 pragma abicoder v1pragma abicoder v2,您可以选择 ABI 编码器和解码器的两种实现之一。

新的 ABI 编码器 (v2) 能够对任意嵌套的数组和结构进行编码和解码。除了支持更多类型外,它还涉及更广泛的验证和安全检查,这可能会导致更高的 gas 成本,但也提高了安全性。从 Solidity 0.6.0 开始,它被认为是非实验性的,并且从 Solidity 0.8.0 开始默认启用。可以使用 pragma abicoder v1; 仍然选择旧的 ABI 编码器。

新编码器支持的类型集是旧编码器支持的类型的严格超集。使用它的合约可以与不使用它的合约进行交互,没有任何限制。只有当非 abicoder v2 合约不尝试进行需要解码新编码器才支持的类型的调用时,反过来才有可能。编译器可以检测到这一点,并将发出错误。只需为您的合约启用 abicoder v2 就足以使错误消失。

注意

此编译指示适用于在其中激活它的文件定义的所有代码,而不管该代码最终位于何处。这意味着一个合约(其源文件被选中使用 ABI 编码器 v1 进行编译)仍然可以包含使用新编码器的代码,方法是通过从另一个合约继承它。如果新类型仅在内部使用,而不是在外部函数签名中使用,则允许这样做。

注意

在 Solidity 0.7.4 之前,可以使用 pragma experimental ABIEncoderV2 选择 ABI 编码器 v2,但无法显式选择编码器 v1,因为它默认使用。

实验性编译指示

第二个编译指示是实验性编译指示。它可以用于启用尚未默认启用的编译器或语言的功能。目前支持以下实验性编译指示

ABIEncoderV2

由于 ABI 编码器 v2 不再被认为是实验性的,因此从 Solidity 0.7.4 开始,可以使用 pragma abicoder v2(请参见上文)进行选择。

SMTChecker

此组件必须在构建 Solidity 编译器时启用,因此并非所有 Solidity 二进制文件都可用。 构建说明 解释了如何激活此选项。它在大多数版本中为 Ubuntu PPA 版本激活,但对于 Docker 映像、Windows 二进制文件或静态构建的 Linux 二进制文件则不激活。如果您在本地安装了 SMT 求解器并通过节点(而不是通过浏览器)运行 solc-js,则可以通过 smtCallback 为 solc-js 激活它。

如果您使用 pragma experimental SMTChecker;,那么您将获得额外的 安全警告,这些警告是通过查询 SMT 求解器获得的。该组件尚不支持 Solidity 语言的所有功能,并且可能会输出许多警告。如果它报告不支持的功能,则分析可能不完全可靠。

导入其他源文件

语法和语义

Solidity 支持导入语句来帮助模块化您的代码,这些语句类似于 JavaScript 中的导入语句(从 ES6 开始)。但是,Solidity 不支持 默认导出 的概念。

在全局级别,您可以使用以下形式的导入语句

import "filename";

名为 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,它们分别引用 "filename" 中的 symbol1symbol2

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

导入路径

为了能够在所有平台上支持可重复构建,Solidity 编译器必须抽象化源文件存储在文件系统中的细节。出于这个原因,导入路径不直接引用主机文件系统中的文件。相反,编译器维护一个内部数据库(简称为虚拟文件系统VFS),其中每个源单元都被分配一个唯一的源单元名称,它是一个不透明且非结构化的标识符。导入语句中指定的导入路径被转换为源单元名称,并用于在此数据库中查找相应的源单元。

使用 标准 JSON API,可以将所有源文件的名称和内容直接作为编译器输入的一部分提供。在这种情况下,源单元名称确实是任意的。但是,如果你希望编译器自动查找并加载源代码到 VFS 中,你的源单元名称需要以一种允许 导入回调 定位它们的方式进行结构化。使用命令行编译器时,默认的导入回调只支持从主机文件系统加载源代码,这意味着你的源单元名称必须是路径。一些环境提供更通用的自定义回调。例如,Remix IDE 提供了一个回调,它允许你 从 HTTP、IPFS 和 Swarm URL 导入文件,或直接引用 NPM 注册表中的包.

有关虚拟文件系统和编译器使用的路径解析逻辑的完整描述,请参阅 路径解析.

注释

单行注释 (//) 和多行注释 (/*...*/) 是可能的。

// This is a single-line comment.

/*
This is a
multi-line comment.
*/

注意

单行注释以任何 unicode 行终止符(LF、VF、FF、CR、NEL、LS 或 PS)在 UTF-8 编码中终止。终止符仍然是注释后源代码的一部分,因此如果它不是 ASCII 字符(这些是 NEL、LS 和 PS),它会导致解析器错误。

此外,还有另一种类型的注释,称为 NatSpec 注释,在 样式指南 中有详细说明。它们使用三个斜杠 (///) 或双星号块 (/** ... */) 编写,应该直接放在函数声明或语句的上面。