zoukankan      html  css  js  c++  java
  • 以太坊:深入理解Solidity-Solidity 源文件结构

    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";

    如果存在命名冲突,则可以在导入时重命名符号。例如,下面的代码创建了新的全局符号 alias 和 symbol2 ,引用的 symbol1 和 symbol2 来自 “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);
        }
    }
  • 相关阅读:
    css3记事
    ele
    vue记事1
    HBuilder
    继承与面向对象设计
    实现
    设计与声明
    资源管理
    构造/析构/赋值运算
    让自己习惯C++
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13313129.html
Copyright © 2011-2022 走看看