跳转至

坚实度 101

在前几章中,我们详细了解了以太坊。我们阅读了以太坊网络、客户端、gas、以太坊虚拟机和以太坊区块链的其他元素。以太坊的一个有趣的事实是,任何人都可以使用以太坊创建自己的区块链。

以太坊在其平台上运行智能合约;这些应用程序使用区块链技术来执行所需的操作,使用户能够创建自己的区块链,并发行自己的替代加密货币。这是通过在 Solidity 中编码实现的,Solidity 是一种面向契约的编程语言,用于编写将在以太坊区块链上执行的智能契约,并执行编程任务。

Solidity 是一种静态类型的编程语言,运行在以太坊虚拟机上。它受 C++、Python 和 JavaScript 的影响,于 2014 年 8 月提出,由以太坊项目的 solidity 团队开发。完整的应用程序部署在区块链上,包括智能合同、前端接口和其他模块;这被称为 DApp分散式应用。

我们将在本章中讨论以下主题:

  • 坚实的基础
  • 实体文件的布局
  • 智能合同的结构
  • 变量和函数
  • 值类型
  • 参考类型
  • 值映射的关键

坚实的基础

Solidity 并不是以太坊智能合约的唯一工作语言;在 solidity 之前,还有其他不那么成功的语言。以下是目前(截至 2018 年 8 月)与以太坊兼容的语言的简要列表:

  • Mutan :灵感来自 Golang,2015 年 3 月弃用。
  • LLL:类似 lisp 语言的简称。虽然它仍受支持,但很少使用。
  • Serpent :虽然这种语言类似于 Python,但不再推荐使用。
  • Solidity :以太坊基金会推出的第四种语言,也是迄今为止开发智能合约最成功的语言。它是记录最完整、最稳定的,并且拥有大量的社区支持。
  • Vyper :新推出的,比 Solidity 简单易用得多,尽管它还没有太多的社区支持。它受到 Python 的影响。

Solidity 也称为面向契约的语言,因为契约类似于面向对象语言中的类。Solidity 语言松散地基于 ECMAScript(JavaScript);因此,预先了解这一点将有助于理解坚固性。以下是在 Solidity 中开发、测试和部署智能合约所需的一些工具:

  • TestNet :选择要工作的 TestNet,指定要使用的网络 ID。
  • 编译器:选择需要的编译器,比如solc,是 solidity 编译器;它包含在大多数节点中,也可以作为独立的软件包使用。
  • Web3.js :帮助以太坊网络和我们的 DApp 通过 HTTP 或 IPC 网络连接的库。
  • 框架:从可用于合同编制和部署等任务的框架中选择一个框架很重要。使用的一些框架是 Truffle、Embark、Dapple 等。

除了我们已经提到的重要工具之外,还有各种其他工具可以帮助开发在以太坊区块链上运行的智能合约,用于理解合约流、发现安全漏洞、运行测试应用程序、编写文档等任务。请看下图:

坚实的基础

如果你经常编程,你应该已经知道代码编辑器或者集成开发环境。对于已经存在的各种 ide,有一个集成列表;除此之外,以太坊基金会还发布了一个基于浏览器的 IDE,集成了编译器和 Solidity 运行时环境,没有用于编写和测试智能合约的服务器组件。可以在 remix.ethereum.org 的找到。

使用编译器

对于小型和基于学习的 DApps 项目,建议在以太坊基金会基于浏览器的编译器上工作: Remix 。另一种方法是在你的机器上安装 Solidity 编译器。使用以下命令可从npm安装solc:

npm install -g solc

通过克隆 GitHub 链接上的 Git 存储库,也可以从源代码构建 solidity:https://github.com/ethereum/solidity.git

可靠性编程

在本节中,我们将讨论 Solidity 源文件的结构和元素;我们将讨论布局,结构,数据类型,它的类型,单位,控制,表达式和其他方面的可靠性。实体文件的格式扩展名是.sol

布置实体文件

Solidity 正在经历积极的发展,并且有许多来自一个庞大社区的定期变化和建议;因此,在源文件的开头指定实体文件的版本以避免任何冲突是很重要的。这是通过 Pragma 版本实现的。这是在 solidity 文件的开头定义的,这样任何想要运行该文件的人都知道以前的版本。看一下这段代码:

pragma solidity ^0.4.24;

通过指定版本号,该特定源文件将使用比指定版本号更早或更晚的版本进行编译。

导入文件

与 ECMAScript 类似,Solidity 文件是使用如下的import语句声明的:

import "filename.sol";

前面的语句将把所有符号从filename.sol文件作为全局语句导入到当前文件中。

导入文件时也支持路径,所以可以使用类似 JavaScript 的/...

评论

使用单行(//)注释和多行(/* ... */)注释,尽管除此之外还有另一种称为 Natspec 注释的注释样式,这也是可能的;在这种类型的注释中,我们要么使用///要么使用/** ... */,它们只在早期的函数声明或语句中使用。

Natspec 是 natural specification 的缩写;根据最新的 solidity 版本(0.4.24 ),这些注释不适用于变量,即使变量是公共的。下面是一小段代码,其中包含这类注释的示例:

pragma solidity ^0.4.19;

/// @title A simulator for Batman, Gotham's Hero
/// @author DC-man
/// @notice You can use this contract for only the most basic simulation
/// @dev All function calls are currently implement without side effects
contract Batman {
 /// @author Samanyu Chopra
 /// @notice Determine if Bugs will accept `(_weapons)` to kill
 /// @dev String comparison may be inefficient
 /// @param _weapons The name weapons to save in the repo (English)
 /// @return true if Batman will keep it, false otherwise
 function doesKeep(string _weapons) external pure returns (bool) {
 return keccak256(_weapons) == keccak256("Shotgun");
 }
}

标签

它们在 Natspec 注释中使用;每个标签都有基于其用法的自己的上下文,如下表所示:

| 标签 | 用于 | | @title | 智能合同的标题 | | @author | 智能合同的作者 | | @notice | 功能说明 | | @dev | 对开发者的解释 | | @param | 参数的解释 | | @return | 退货类型的说明 |

合同的结构

Solidity 中的每个契约都类似于类的概念。契约可以从其他契约继承,方式类似于类。合同可以包含以下声明:

  • 状态变量
  • 功能
  • 功能修饰符
  • 事件
  • 结构类型
  • 枚举类型

状态变量

这些是永久存储在合同存储中的值,例如:

pragma solidity ^0.4.24;

contract Gotham {
 uint storedData; // State variable
 // ...
}

功能

函数可以在内部或外部调用,例如:

pragma solidity ^0.4.24;

contract Gotham {
 function joker() public Bat { // Function
 // ...
 }
}

功能修饰符

函数修饰符可以用来修改声明中函数的语义。也就是说,它们用于改变函数的行为。例如,它们用于在执行功能之前自动检查条件,或者它们可以根据需要在给定的时间范围内解锁功能。它们可以被衍生合同覆盖,如下所示:

pragma solidity ^0.4.24;

contract Gotham {
 address public weapons;

modifier Bank() { // Modifier
 require(
     msg.sender == coins,
     "Only coins can call this."
     );
     _;
 }

    function abort() public coinsbuyer { // Modifier usage
 // ...
 }
}

事件

事件允许通过 DApp 的前端方便地使用 EVM。事件可以被听到和维护。看一下这段代码:

pragma solidity ^0.4.24;

contract Attendance {
      event Mark_attendance(string name, uint ID); // Event

    function roll_call() public marking {
             // ...
                 emit Mark_attendance(Name, ID); //Triggering event
         }
}

类型

在 Solidity 中,每个变量的类型需要在编译时指定。通过组合复杂类型,也可以在 Solidity 中创建复杂类型。Solidity 中有两类数据类型:值类型引用类型

值类型

值类型被称为值类型,因为这些类型的变量将数据保存在它自己分配的内存中。

布尔代数学体系的

这种类型的数据有两个值,true 或 false,例如:

bool b = false;

前面的语句将false分配给布尔数据类型b

Solidity 中的运算符类似于 JavaScript 运算符,如算术运算符、赋值运算符、字符串运算符、比较运算符、逻辑运算符、类型运算符和按位运算符。根据允许的用法,这些运算符可以与各种值类型一起使用。

整数

该值类型分配整数。整数有两个子类型,即intuint,分别是有符号整数和无符号整数类型。内存大小是在编译时分配的;使用int8int256指定,其中数字代表内存中分配的大小。仅使用intunit分配内存,默认分配最大的内存大小。

地址

该值类型包含一个 20 字节的值,这是以太坊地址的大小(40 个十六进制字符或 160 位)。看看这个:

address a = 0xe2793a1b9a149253341cA268057a9EFA42965F83

此类型有几个成员可用于与协定进行交互。这些成员如下:

  • balance
  • transfer
  • send
  • call
  • callcode
  • delegatecall

balance返回以卫为单位的地址余额,例如:

address a = 0xe2793a1b9a149253341cA268057a9EFA42965F83;
uint bal = a.balance;

transfer用于从一个地址转移到另一个地址,例如:

address a = 0xe2793a1b9a149253341cA268057a9EFA42965F83;
address b = 0x126B3adF2556C7e8B4C3197035D0E4cbec1dBa83;
if (a.balance > b.balance) b.transfer(6);

当我们使用transfersend成员时,消耗的气体量几乎相同。transfer是从 Solidity 0.4.13 引入的,因为 send 不发送任何气体,也不传播异常。Transfer被认为是将以太网从一个地址发送到另一个地址的安全方式,因为它会引发错误并允许某人传播错误。

callcallcodedelegatecall用于与没有应用二进制接口 ( ABI )的函数进行交互。call返回一个布尔值,表明函数是成功运行还是在 EVM 中终止。

a调用b时,代码在b的上下文中运行,使用b的存储。另一方面,当ab上做callcode时,代码在a的上下文中运行,使用a的存储,但是使用a的与存储的代码。

delegatecall函数用于根据需要委托一个合同使用另一个合同的存储。

所有这些成员:calldelegatecallcallcode不建议使用,除非真的有必要,因为它们会破坏坚固性的类型安全。有可能callcode在不久的将来会被弃用。

数组值类型

Solidity 有一个固定和动态的数组值类型。在固定大小的字节数组中,关键字的范围从bytes1bytes32。另一方面,在动态大小的字节数组中,关键字可以包含字节或字符串。bytes用于原始字节数据,strings用于在UTF-8中编码的字符串。

length是返回固定大小字节数组或动态大小字节数组的长度的成员。

固定大小数组初始化为test[10],动态大小数组初始化为test2[

逐字的

文字用于表示固定值;使用了多种类型的文字;它们如下:

  • 整数文字
  • 字符串文字
  • 十六进制文字
  • 地址文字

整数文字由从 0 到 9 的数字序列组成。八进制文字和以0开头的文字是无效的,因为以太坊中的地址是以0开头的。看看这个:

int a = 11;

字符串文字用一对双引号("...")或单引号('...')声明,例如:

Test = 'Batman';
Test2 = "Batman";

十六进制文字以关键字hex为前缀,并用双引号(hex"69ed75")或单引号(hex'69ed75')括起来。

通过地址校验和测试的十六进制文字属于address类型文字,例如:

0xe2793a1b9a149253341cA268057a9EFA42965F83;
0x126B3adF2556C7e8B4C3197035D0E4cbec1dBa83;

枚举数

枚举允许在 Solidity 中创建用户定义的类型。枚举可以与所有整数类型相互转换。下面是 Solidity 中的一个枚举示例:

enum Action {jump, fly, ride, fight};

功能

函数有两种类型:内部函数和外部函数。只能从当前协定内部调用内部函数。外部函数可以通过外部函数调用来调用。

功能修饰符

对于基于实体的函数,有各种可用的修改器,但不要求必须使用。看看这些:

  • pure
  • constant
  • view
  • payable

pure函数不能从存储器中读取或写入;它们只是根据内容返回一个值。constant修改函数不能以任何方式写入存储器。虽然,后 Solidity 版本 0.4.17 constant被弃用,为pureview功能让路。view的作用与constant一样,它的功能不能以任何方式改变存储。payable允许函数在被调用时接收以太。

通过用空格分隔来指定每个修饰符,可以在一个函数中使用多个修饰符;它们按照书写的顺序进行评估。

参考类型

这些是通过引用传递的;由于它们所构成的内存分配,它们非常占用内存。

结构

结构是在逻辑组下声明的复合数据类型。结构用于定义新类型。尽管结构可以是映射成员的值类型,但结构不可能包含其自身类型的成员。下面是一个结构示例:

struct Gotham {

address Batcave;
uint cars;
uint batcomputer;
uint enemies;
string gordon;
address twoface;

}

数据单元

这指定了特定数据类型的存储位置。它适用于数组和结构。使用storagememory关键字指定数据位置。还有第三个数据位置calldata,它是不可修改和不持久的。外部函数的参数使用calldata内存。默认情况下,函数的参数存储在memory中;其他局部变量使用storage

绘图

映射用于键到值的映射。映射可以被看作是虚拟初始化的散列表,使得每个可能的键都存在并被映射到默认值。默认值是全零。键从不存储在映射中,只有keccak256散列用于值查找。映射的定义与任何其他变量类型一样。看一下这段代码:

contract Gotham {

    struct Batman {
        string friends;
        string foes;
        int funds;
        string fox;
    }

    mapping (address => Batman) Catwoman;
   address[] public Batman_address;
}

前面的代码示例显示了Catwoman正被初始化为mapping

单位和全局变量

全局变量可以被任何 Solidity 智能契约调用。它们主要用于返回有关以太坊区块链的信息。这些变量中的一些也可以执行各种功能。时间单位和以太单位也是全球通用的。不带后缀的以太币数字被假定为 wei。与时间相关的单位也可以使用,就像货币一样,它们之间的转换是允许的。

摘要

在这一章中,我们详细讨论了 solidity,我们阅读了编译器,我们详细研究了 solidity 中的编程,包括研究 Solidity 文件的布局、契约的结构以及值和引用的类型。我们还学习了映射。

在下一章中,我们将应用本章中的新知识来开发一个实际的合同,并在测试网络上部署该合同。


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组