Solidity 快速指南



Solidity - 概览

Solidity 是一种面向合约的高级编程语言,用于实现智能合约。Solidity 受到 C++、Python 和 JavaScript 的很大影响,并且被设计为针对以太坊虚拟机 (EVM)。

Solidity 是静态类型的,支持继承、库和复杂的用户定义类型编程语言。

您可以使用 Solidity 创建用于投票、众筹、盲拍和多签钱包等用途的合约。

什么是以太坊?

以太坊是一个去中心化的,即区块链平台,它运行智能合约,即按编程方式运行的应用程序,没有任何停机、审查、欺诈或第三方干预的可能性。

以太坊虚拟机 (EVM)

以太坊虚拟机,也称为 EVM,是以太坊中智能合约的运行时环境。以太坊虚拟机专注于提供安全性并通过世界各地的计算机执行不受信任的代码。

EVM 专注于防止拒绝服务攻击,并确保程序无法访问彼此的状态,从而确保通信可以在没有任何潜在干扰的情况下建立。

以太坊虚拟机旨在作为基于以太坊的智能合约的运行时环境。

什么是智能合约?

智能合约是一种计算机协议,旨在以数字方式促进、验证或执行合同的协商或执行。智能合约允许在没有第三方的情况下执行可信交易。这些交易是可跟踪且不可逆的。

智能合约的概念最早由尼克·萨博 (Nick Szabo) 于 1994 年提出。萨博是一位法律学者和密码学家,以奠定数字货币的基础而闻名。

如果您现在不理解智能合约,没关系,我们稍后会详细介绍。

Solidity - 环境设置

本章说明如何在 CentOS 机器上设置 Solidity 编译器。如果您没有 Linux 机器,则可以使用我们的在线编译器来编译小型合约并快速学习 Solidity。

方法 1 - npm / Node.js

这是在您的 CentoS 机器上安装 Solidity 编译器的最快方法。我们有以下步骤来安装 Solidity 编译器:

安装 Node.js

首先确保您的 CentOS 机器上已安装 node.js。如果尚未安装,请使用以下命令安装:

# First install epel-release
$sudo yum install epel-release

# Now install nodejs
$sudo yum install nodejs

# Next install npm (Nodejs Package Manager )
$sudo yum install npm

# Finally verify installation
$npm --version

如果一切安装成功,您将看到类似以下的输出:

3.10.10

安装 solc

安装 Node.js 包管理器后,您可以继续如下安装 Solidity 编译器:

$sudonpm install -g solc

上述命令将安装 solcjs 程序,并使其在整个系统中全局可用。现在,您可以通过发出以下命令来测试您的 Solidity 编译器:

$solcjs-version

如果一切顺利,则将打印如下内容:

0.5.2+commit.1df8f40c.Emscripten.clang

现在您可以使用 solcjs 了,它比标准 Solidity 编译器功能更少,但它将为您提供一个良好的起点。

方法 2 - Docker 镜像

您可以拉取一个 Docker 镜像并开始使用它来开始 Solidity 编程。以下是简单的步骤。以下是拉取 Solidity Docker 镜像的命令。

$docker pull ethereum/solc:stable

下载 Docker 镜像后,我们可以使用以下命令验证它。

$docker run ethereum/solc:stable-version

这将打印如下内容:

$ docker run ethereum/solc:stable -version

solc, the solidity compiler commandlineinterfaceVersion: 0.5.2+commit.1df8f40c.Linux.g++

方法 3:二进制包安装

如果您愿意在 Linux 机器上安装功能齐全的编译器,请查看官方网站安装 Solidity 编译器。

Solidity - 基本语法

Solidity 源文件可以包含任意数量的合约定义、导入指令和 pragma 指令。

让我们从一个简单的 Solidity 源文件开始。以下是一个 Solidity 文件的示例:

pragma solidity >=0.4.0 <0.6.0;
contract SimpleStorage {
   uint storedData;
   function set(uint x) public {
      storedData = x;
   }
   function get() public view returns (uint) {
      return storedData;
   }
}

Pragma

第一行是一个 pragma 指令,它表示源代码是为 Solidity 版本 0.4.0 或任何不破坏功能的更新版本(不包括版本 0.6.0)编写的。

pragma 指令始终是特定于源文件的,如果您导入另一个文件,则该文件中的 pragma 不会自动应用于导入文件。

因此,一个文件在版本 0.4.0 之前无法编译,并且在版本 0.5.0 及更高版本的编译器上也无法工作的 pragma 将按如下方式编写:

pragma solidity ^0.4.0;

这里第二个条件是使用 ^ 添加的。

合约

Solidity 合约是代码(其函数)和数据(其状态)的集合,驻留在以太坊区块链上的特定地址。

uint storedData 行声明了一个名为 storedData 的状态变量,其类型为 uint,函数 set 和 get 可用于修改或检索变量的值。

导入文件

尽管上面的示例没有导入语句,但 Solidity 支持与 JavaScript 中提供的非常相似的导入语句。

以下语句从“文件名”导入所有全局符号。

import "filename";

以下示例创建一个新的全局符号 symbolName,其成员是“文件名”中的所有全局符号。

import * as symbolName from "filename";

要从与当前文件相同的目录导入文件 x,请使用 import "./x" as x;。如果您改为使用 import "x" as x;,则可能会在全局“包含目录”中引用不同的文件。

保留关键字

以下是 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
typedef typeof unchecked

Solidity - 第一个应用程序

我们正在使用 Remix IDE 来编译和运行我们的 Solidity 代码库。

步骤 1 - 将给定的代码复制到 Remix IDE 代码部分。

示例

pragma solidity ^0.5.0;
contract SolidityTest {
   constructor() public{
   }
   function getResult() public view returns(uint){
      uint a = 1;
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

步骤 2 - 在“编译”选项卡下,单击“开始编译”按钮。

步骤 3 - 在“运行”选项卡下,单击“部署”按钮。

步骤 4 - 在“运行”选项卡下,在下拉列表中选择“SolidityTest at 0x...”

步骤 5 - 单击“getResult”按钮以显示结果。

输出

0: uint256: 3

Solidity - 注释

Solidity 支持 C 样式和 C++ 样式的注释,因此:

  • // 和行尾之间的任何文本都被视为注释,并且会被 Solidity 编译器忽略。

  • /* 和 */ 字符之间的任何文本都被视为注释。这可能跨越多行。

示例

以下示例显示了如何在 Solidity 中使用注释。

function getResult() public view returns(uint){
   // This is a comment. It is similar to comments in C++

   /*
      * This is a multi-line comment in solidity
      * It is very similar to comments in C Programming
   */
   uint a = 1;
   uint b = 2;
   uint result = a + b;
   return result;
}

Solidity - 数据类型

在任何语言中编写程序时,您都需要使用各种变量来存储各种信息。变量只不过是保留的内存位置,用于存储值。这意味着当您创建变量时,您会在内存中保留一些空间。

您可能希望存储各种数据类型的信息,例如字符、宽字符、整数、浮点数、双精度浮点数、布尔值等。根据变量的数据类型,操作系统分配内存并决定可以在保留的内存中存储什么。

值类型

Solidity 为程序员提供了丰富的内置和用户定义数据类型。下表列出了七种基本 C++ 数据类型:

类型 关键字
布尔值 bool true/false
整数 int/uint 不同大小的有符号和无符号整数。
整数 int8 到 int256 从 8 位到 256 位的有符号 int。int256 与 int 相同。
整数 uint8 到 uint256 从 8 位到 256 位的无符号 int。uint256 与 uint 相同。
定点数 fixed/unfixed 不同大小的有符号和无符号定点数。
定点数 fixed/unfixed 不同大小的有符号和无符号定点数。
定点数 fixedMxN 有符号定点数,其中 M 表示类型占用的位数,N 表示小数位数。M 应为 8 的倍数,范围从 8 到 256。N 可以是 0 到 80。fixed 与 fixed128x18 相同。
定点数 ufixedMxN

无符号定点数,其中 M 表示类型占用的位数,N 表示小数位数。M 必须是 8 的倍数,范围从 8 到 256。N 的范围可以是 0 到 80。ufixed 与 ufixed128x18 相同。

注意:您还可以将有符号和无符号定点数表示为 fixedMxN/ufixedMxN,其中 M 表示类型占用的位数,N 表示小数位数。M 必须是 8 的倍数,范围从 8 到 256。N 的范围可以是 0 到 80。

地址

address 存储表示以太坊地址大小的 20 字节值。可以使用 .balance 方法获取地址的余额,并可以使用 .transfer 方法将余额转账到另一个地址。

address x = 0x212;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);

Solidity - 变量

Solidity 支持三种类型的变量。

  • 状态变量 - 值永久存储在合约存储中的变量。

  • 局部变量 - 值在函数执行期间存在的变量。

  • 全局变量 - 存在于全局命名空间中用于获取有关区块链信息的特殊变量。

Solidity 是一种静态类型语言,这意味着在声明期间需要指定状态或局部变量的类型。每个声明的变量都根据其类型具有默认值。没有“未定义”或“空”的概念。

状态变量

值永久存储在合约存储中的变量。

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData;      // State variable
   constructor() public {
      storedData = 10;   // Using State variable
   }
}

局部变量

仅在定义它的函数内可用的变量。函数参数始终是该函数的局部变量。

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData; // State variable
   constructor() public {
      storedData = 10;   
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return result; //access the local variable
   }
}

示例

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData; // State variable
   constructor() public {
      storedData = 10;   
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return storedData; //access the state variable
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

输出

0: uint256: 10

全局变量

这些是存在于全局工作区中的特殊变量,提供有关区块链和交易属性的信息。

名称 返回值
blockhash(uint blockNumber) returns (bytes32) 给定区块的哈希值 - 仅适用于最近的 256 个区块(不包括当前区块)
block.coinbase (address payable) 当前区块矿工的地址
block.difficulty (uint) 当前区块难度
block.gaslimit (uint) 当前区块 gas 限制
block.number (uint) 当前区块编号
block.timestamp (uint) 自 Unix 纪元以来的当前区块时间戳(以秒为单位)
gasleft() returns (uint256) 剩余 gas
msg.data (bytes calldata) 完整的 calldata
msg.sender (address payable) 消息发送者(当前调用者)
msg.sig (bytes4) calldata 的前四个字节(函数标识符)
msg.value (uint) 与消息一起发送的 Wei 数
now (uint) 当前区块时间戳
tx.gasprice (uint) 交易的 gas 价格
tx.origin (address payable) 交易发送者

Solidity 变量命名

在 Solidity 中命名变量时,请记住以下规则。

  • 您不应使用任何 Solidity 保留关键字作为变量名。这些关键字将在下一节中提到。例如,break 或 boolean 变量名无效。

  • Solidity 变量名不能以数字 (0-9) 开头。它们必须以字母或下划线字符开头。例如,123test 是无效的变量名,但 _123test 是有效的变量名。

  • Solidity 变量名区分大小写。例如,Name 和 name 是两个不同的变量。

Solidity - 变量作用域

局部变量的作用域仅限于其定义的函数,但状态变量可以有三种类型的作用域。

  • 公共 - 公共状态变量可以在内部以及通过消息访问。对于公共状态变量,会生成一个自动的 getter 函数。

  • 内部 - 内部状态变量只能从当前合约或从中派生的合约内部访问,无需使用 this。

  • 私有 - 私有状态变量只能从定义它们的当前合约内部访问,不能在从中派生的合约中访问。

示例

pragma solidity ^0.5.0;
contract C {
   uint public data = 30;
   uint internal iData= 10;
   
   function x() public returns (uint) {
      data = 3; // internal access
      return data;
   }
}
contract Caller {
   C c = new C();
   function f() public view returns (uint) {
      return c.data(); //external access
   }
}
contract D is C {
   function y() public returns (uint) {
      iData = 3; // internal access
      return iData;
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return storedData; //access the state variable
   }
}

Solidity - 运算符

什么是运算符?

让我们来看一个简单的表达式4 + 5 等于 9。这里 4 和 5 称为操作数,'+' 称为运算符。Solidity 支持以下类型的运算符。

  • 算术运算符
  • 比较运算符
  • 逻辑(或关系)运算符
  • 赋值运算符
  • 条件(或三元)运算符

让我们逐一了解所有运算符。

算术运算符

Solidity 支持以下算术运算符 -

假设变量 A 持有 10,变量 B 持有 20,则 -

显示示例

序号 运算符和描述
1

+ (加法)

将两个操作数相加

例如:A + B 将得到 30

2

- (减法)

从第一个操作数中减去第二个操作数

例如:A - B 将得到 -10

3

* (乘法)

将两个操作数相乘

例如:A * B 将得到 200

4

/ (除法)

将分子除以分母

例如:B / A 将得到 2

5

% (模)

输出整数除法的余数

例如:B % A 将得到 0

6

++ (自增)

将整数值增加 1

例如:A++ 将得到 11

7

-- (自减)

将整数值减少 1

例如:A-- 将得到 9

比较运算符

Solidity 支持以下比较运算符 -

假设变量 A 持有 10,变量 B 持有 20,则 -

显示示例

序号 运算符和描述
1

= = (等于)

检查两个操作数的值是否相等,如果相等,则条件变为真。

例如:(A == B) 为假。

2

!= (不等于)

检查两个操作数的值是否相等,如果值不相等,则条件变为真。

例如:(A != B) 为真。

3

> (大于)

检查左侧操作数的值是否大于右侧操作数的值,如果大于,则条件变为真。

例如:(A > B) 为假。

4

< (小于)

检查左侧操作数的值是否小于右侧操作数的值,如果小于,则条件变为真。

例如:(A < B) 为真。

5

>= (大于或等于)

检查左侧操作数的值是否大于或等于右侧操作数的值,如果大于或等于,则条件变为真。

例如:(A >= B) 为假。

6

<= (小于或等于)

检查左侧操作数的值是否小于或等于右侧操作数的值,如果小于或等于,则条件变为真。

例如:(A <= B) 为真。

逻辑运算符

Solidity 支持以下逻辑运算符 -

假设变量 A 持有 10,变量 B 持有 20,则 -

显示示例

序号 运算符和描述
1

&& (逻辑与)

如果两个操作数均不为零,则条件变为真。

例如:(A && B) 为真。

2

|| (逻辑或)

如果两个操作数中的任何一个不为零,则条件变为真。

例如:(A || B) 为真。

3

! (逻辑非)

反转其操作数的逻辑状态。如果条件为真,则逻辑非运算符将使其变为假。

例如:! (A && B) 为假。

位运算符

Solidity 支持以下位运算符 -

假设变量 A 持有 2,变量 B 持有 3,则 -

显示示例

序号 运算符和描述
1

& (按位与)

它对每个整数参数的每一位执行布尔 AND 运算。

例如:(A & B) 为 2。

2

| (按位或)

它对每个整数参数的每一位执行布尔 OR 运算。

例如:(A | B) 为 3。

3

^ (按位异或)

它对每个整数参数的每一位执行布尔异或运算。异或意味着操作数一或操作数二为真,但两者不能同时为真。

例如:(A ^ B) 为 1。

4

~ (按位非)

它是一个一元运算符,通过反转操作数中的所有位来操作。

例如:(~B) 为 -4。

5

<< (左移)

它将第一个操作数中的所有位向左移动第二个操作数中指定的位数。新位用零填充。将值左移一位相当于将其乘以 2,左移两位相当于将其乘以 4,依此类推。

例如:(A << 1) 为 4。

6

>> (右移)

二进制右移运算符。左侧操作数的值向右移动右侧操作数指定的位数。

例如:(A >> 1) 为 1。

7

>>> (带零的右移)

此运算符与 >> 运算符类似,只是左侧移入的位始终为零。

例如:(A >>> 1) 为 1。

赋值运算符

Solidity 支持以下赋值运算符 -

显示示例

序号 运算符和描述
1

= (简单赋值)

将右侧操作数的值赋给左侧操作数

例如:C = A + B 将 A + B 的值赋给 C

2

+= (加法赋值)

它将右侧操作数加到左侧操作数,并将结果赋给左侧操作数。

例如:C += A 等价于 C = C + A

3

−= (减法赋值)

它从左侧操作数中减去右侧操作数,并将结果赋给左侧操作数。

例如:C -= A 等价于 C = C - A

4

*= (乘法赋值)

它将右侧操作数乘以左侧操作数,并将结果赋给左侧操作数。

例如:C *= A 等价于 C = C * A

5

/= (除法赋值)

它将左侧操作数除以右侧操作数,并将结果赋给左侧操作数。

例如:C /= A 等价于 C = C / A

6

%= (模赋值)

它使用两个操作数进行取模运算,并将结果赋给左侧操作数。

例如:C %= A 等价于 C = C % A

注意 - 位运算符也适用相同的逻辑,因此它们将变为 <<=、>>=、>>=、&=、|= 和 ^=。

条件运算符 (? :)

条件运算符首先评估表达式的真假值,然后根据评估结果执行两个给定语句之一。

显示示例

序号 运算符和描述
1

? : (条件)

如果条件为真?则值为 X:否则为 Y

Solidity - 循环

在编写合约时,您可能会遇到需要反复执行某个操作的情况。在这种情况下,您需要编写循环语句以减少代码行数。

Solidity 支持所有必要的循环来减轻编程压力。

序号 循环和描述
1

While 循环

Solidity 中最基本的循环是 while 循环,将在本章中讨论。

2

do...while 循环

do...while 循环与 while 循环类似,只是条件检查发生在循环的末尾。

3

For 循环

for 循环是循环最紧凑的形式。它包括以下三个重要部分。

4

循环控制

Solidity 提供了完全的控制来处理循环和 switch 语句。

Solidity - 条件语句

在编写程序时,可能会出现需要从给定的一组路径中选择一条的情况。在这种情况下,您需要使用条件语句,使您的程序能够做出正确的决策并执行正确的操作。

Solidity 支持条件语句,用于根据不同的条件执行不同的操作。这里我们将解释if..else 语句。

if-else 流程图

以下流程图显示了 if-else 语句的工作原理。

Decision Making

Solidity 支持以下形式的if..else 语句:

序号 语句及描述
1

if 语句

if 语句是基本的控制语句,允许 Solidity 进行决策并有条件地执行语句。

2

if...else 语句

“if...else”语句是下一种控制语句形式,允许 Solidity 以更受控的方式执行语句。

3

if...else if... 语句。

if...else if... 语句是 if...else 的高级形式,允许 Solidity 从多个条件中做出正确的决策。

Solidity - 字符串

Solidity 使用双引号(")和单引号(')都支持字符串字面量。它提供字符串作为数据类型来声明字符串类型的变量。

pragma solidity ^0.5.0;

contract SolidityTest {
   string data = "test";
}

在上面的例子中,“test”是一个字符串字面量,data 是一个字符串变量。更推荐的方式是使用字节类型而不是字符串,因为字符串操作比字节操作需要更多的 gas。Solidity 提供了字节到字符串和反向的内置转换。在 Solidity 中,我们可以轻松地将字符串字面量赋值给 byte32 类型的变量。Solidity 将其视为 byte32 字面量。

pragma solidity ^0.5.0;

contract SolidityTest {
   bytes32 data = "test";
}

转义字符

序号 字符及描述
1

\n

开始新行。

2

\\

反斜杠

3

\'

单引号

4

\"

双引号

5

\b

退格

6

\f

换页

7

\r

回车

8

\t

制表符

9

\v

垂直制表符

10

\xNN

表示十六进制值并插入相应的字节。

11

\uNNNN

表示 Unicode 值并插入 UTF-8 序列。

字节到字符串转换

可以使用 string() 构造函数将字节转换为字符串。

bytes memory bstr = new bytes(10);
string message = string(bstr);   

示例

尝试以下代码以了解字符串在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

输出

0: string: 3

Solidity - 数组

数组是一种数据结构,它存储相同类型元素的固定大小的顺序集合。数组用于存储数据集合,但通常将其视为相同类型变量的集合更有用。

与其声明单独的变量,例如 number0、number1、... 和 number99,不如声明一个数组变量,例如 numbers,并使用 numbers[0]、numbers[1] 和 ...、numbers[99] 来表示各个变量。数组中的特定元素通过索引访问。

在 Solidity 中,数组可以是编译时固定大小的,也可以是动态大小的。对于存储数组,它也可以具有不同类型的元素。对于内存数组,元素类型不能是映射,如果要将其用作函数参数,则元素类型应为 ABI 类型。

所有数组都由连续的内存位置组成。最低地址对应于第一个元素,最高地址对应于最后一个元素。

声明数组

要在 Solidity 中声明固定大小的数组,程序员需要指定元素的类型以及数组所需的元素数量,如下所示:

type arrayName [ arraySize ];

这称为一维数组。arraySize 必须是大于零的整数常量,type 可以是任何有效的 Solidity 数据类型。例如,要声明一个名为 balance 的 10 个元素的 uint 类型数组,请使用以下语句:

uint balance[10];

要在 Solidity 中声明动态大小的数组,程序员需要指定元素的类型,如下所示:

type[] arrayName;

初始化数组

您可以逐个初始化 Solidity 数组元素,或者使用单个语句,如下所示:

uint balance[3] = [1, 2, 3];

花括号 [ ] 之间的值数量不能大于我们在方括号 [ ] 之间为数组声明的元素数量。以下是如何赋值数组单个元素的示例:

如果省略数组的大小,则会创建一个足够容纳初始化的数组。因此,如果您编写:

uint balance[] = [1, 2, 3];

您将创建与上一个示例中完全相同的数组。

balance[2] = 5;

以上语句将数组中第 3 个元素的值赋为 5。

创建动态内存数组

动态内存数组是使用 new 关键字创建的。

uint size = 3;
uint balance[] = new uint[](size);

访问数组元素

通过索引数组名称来访问元素。这是通过在数组名称后面方括号内放置元素的索引来完成的。例如:

uint salary = balance[2];

以上语句将从数组中获取第 3 个元素并将值赋给 salary 变量。以下是一个示例,它将使用上述所有三个概念,即声明、赋值和访问数组:

成员

  • length - length 返回数组的大小。length 可用于通过设置它来更改动态数组的大小。

  • push - push 允许在末尾将元素附加到动态存储数组。它返回数组的新长度。

示例

尝试以下代码以了解数组在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract test {
   function testArray() public pure{
      uint len = 7; 
      
      //dynamic array
      uint[] memory a = new uint[](7);
      
      //bytes is same as byte[]
      bytes memory b = new bytes(len);
      
      assert(a.length == 7);
      assert(b.length == len);
      
      //access array variable
      a[6] = 8;
      
      //test array variable
      assert(a[6] == 8);
      
      //static array
      uint[3] memory c = [uint(1) , 2, 3];
      assert(c.length == 3);
   }
}

Solidity - 枚举

枚举将变量限制为仅具有几个预定义值中的一个。此枚举列表中的值称为枚举。

通过使用枚举,可以减少代码中的错误数量。

例如,如果我们考虑一个鲜榨果汁店的应用程序,则可以将杯子尺寸限制为小、中和大。这将确保不允许任何人订购除小、中或大以外的任何尺寸。

示例

尝试以下代码以了解枚举在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract test {
   enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }
   FreshJuiceSize choice;
   FreshJuiceSize constant defaultChoice = FreshJuiceSize.MEDIUM;

   function setLarge() public {
      choice = FreshJuiceSize.LARGE;
   }
   function getChoice() public view returns (FreshJuiceSize) {
      return choice;
   }
   function getDefaultChoice() public pure returns (uint) {
      return uint(defaultChoice);
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

首先点击setLarge按钮将值设置为 LARGE,然后点击getChoice获取所选选项。

输出

uint8: 2

点击getDefaultChoice按钮获取默认选项。

输出

uint256: 1

Solidity - 结构体

结构类型用于表示记录。假设您想跟踪图书馆中的书籍。您可能希望跟踪每本书的以下属性:

  • 标题
  • 作者
  • 主题
  • 图书 ID

定义结构

要定义结构,必须使用struct关键字。struct 关键字定义了一种新的数据类型,它具有多个成员。结构语句的格式如下:

struct struct_name { 
   type1 type_name_1;
   type2 type_name_2;
   type3 type_name_3;
}

示例

struct Book { 
   string title;
   string author;
   uint book_id;
}

访问结构及其变量

要访问结构的任何成员,我们使用成员访问运算符 (.)。成员访问运算符被编码为结构变量名称和我们希望访问的结构成员之间的句点。您将使用 struct 来定义结构类型变量。以下示例显示了如何在程序中使用结构。

示例

尝试以下代码以了解结构在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract test {
   struct Book { 
      string title;
      string author;
      uint book_id;
   }
   Book book;

   function setBook() public {
      book = Book('Learn Java', 'TP', 1);
   }
   function getBookId() public view returns (uint) {
      return book.book_id;
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

首先点击setBook按钮将值设置为 LARGE,然后点击getBookId获取所选图书 ID。

输出

uint256: 1

Solidity - 映射

映射与数组和结构一样是一种引用类型。以下是声明映射类型的语法。

mapping(_KeyType => _ValueType)

其中

  • _KeyType - 可以是任何内置类型以及字节和字符串。不允许使用引用类型或复杂对象。

  • _ValueType - 可以是任何类型。

注意事项

  • 映射只能具有storage类型,通常用于状态变量。

  • 映射可以标记为 public。Solidity 会自动为其创建 getter。

示例

尝试以下代码以了解映射类型在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

首先点击updateBalance按钮将值设置为 10,然后查看日志,它将显示解码输出为:

输出

{
   "0": "uint256: 10"
}

Solidity - 类型转换

Solidity 允许隐式和显式转换。Solidity 编译器允许在两种数据类型之间进行隐式转换,前提是没有可能的隐式转换并且没有信息丢失。例如,uint8 可转换为 uint16,但 int8 可转换为 uint256,因为 int8 可以包含 uint256 中不允许的负值。

显式转换

我们可以使用构造函数语法将一种数据类型显式转换为另一种数据类型。

int8 y = -3;
uint x = uint(y);
//Now x = 0xfffff..fd == two complement representation of -3 in 256 bit format.

转换为较小类型会损失高位。

uint32 a = 0x12345678;
uint16 b = uint16(a); // b = 0x5678

转换为较高类型会在左侧添加填充位。

uint16 a = 0x1234;
uint32 b = uint32(a); // b = 0x00001234 

转换为较小字节会损失高位数据。

bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b = 0x12

转换为较大字节会在右侧添加填充位。

bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b = 0x12340000

固定大小字节和 int 之间的转换仅在两者大小相同时才可能。

bytes2 a = 0x1234;
uint32 b = uint16(a); // b = 0x00001234
uint32 c = uint32(bytes4(a)); // c = 0x12340000
uint8 d = uint8(uint16(a)); // d = 0x34
uint8 e = uint8(bytes1(a)); // e = 0x12

如果不需要截断,则可以将十六进制数字赋值给任何整数类型。

uint8 a = 12; // no error
uint32 b = 1234; // no error
uint16 c = 0x123456; // error, as truncation required to 0x3456

Solidity - 以太坊单位

在 solidity 中,我们可以使用 wei、finney、szabo 或 ether 作为字面量的后缀来用于转换各种基于 ether 的面额。最小的单位是 wei,1e12 表示 1 x 1012

assert(1 wei == 1);
assert(1 szabo == 1e12);
assert(1 finney == 1e15);
assert(1 ether == 1e18);
assert(2 ether == 2000 fenny);

时间单位

与货币类似,Solidity 也有时间单位,其中最小的单位是秒,我们可以使用 seconds、minutes、hours、days 和 weeks 作为后缀来表示时间。

assert(1 seconds == 1);
assert(1 minutes == 60 seconds);
assert(1 hours == 60 minutes);
assert(1 day == 24 hours);
assert(1 week == 7 days);

Solidity - 特殊变量

特殊变量是全局可用的变量,并提供有关区块链的信息。以下是特殊变量的列表:

序号 特殊变量及描述
1

blockhash(uint blockNumber) returns (bytes32)

给定区块的哈希 - 仅适用于最近的 256 个区块(不包括当前区块)。

2

block.coinbase (address payable)

当前区块矿工的地址。

3

block.difficulty (uint)

当前区块难度。

4

block.gaslimit (uint)

当前区块 gaslimit。

5

block.number (uint)

当前区块编号。

6

block.timestamp

自 Unix 纪元以来的当前区块时间戳(以秒为单位)。

7

gasleft() returns (uint256)

剩余 gas。

8

msg.data (bytes calldata)

完整的 calldata。

9

msg.sender (address payable)

消息的发送者(当前调用)。

10

msg.sig (bytes4)

calldata 的前四个字节(即函数标识符)

11

msg.value (uint)

随消息发送的 wei 数量。

12

now (uint)

当前区块时间戳(block.timestamp 的别名)。

13

tx.gasprice (uint)

交易的 gas 价格。

14

tx.origin (address payable)

交易的发送者(完整的调用链)。

示例

尝试以下代码以查看 msg 的用法,这是一个特殊变量,用于在 Solidity 中获取发送者地址。

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

首先点击updateBalance按钮将值设置为 10,然后查看日志,它将显示解码输出为:

输出

{
   "0": "uint256: 10"
}

Solidity - 样式指南

样式指南有助于保持代码布局一致并使代码更易于阅读。以下是使用 Solidity 编写合约时应遵循的最佳实践。

代码布局

  • 缩进 - 使用 4 个空格代替制表符来维护缩进级别。避免混合空格和制表符。

  • 两空行规则 - 在两个合约定义之间使用 2 个空行。

pragma solidity ^0.5.0;

contract LedgerBalance {
   //...
}
contract Updater {
   //...
}
  • 一个空行规则 - 在两个函数之间使用 1 个空行。如果只有声明,则无需空行。

pragma solidity ^0.5.0;

contract A {
   function balance() public pure;
   function account() public pure;
}
contract B is A {
   function balance() public pure {
      // ...
   }
   function account() public pure {
      // ...
   }
}
  • 最大行长 - 单行不应超过 79 个字符,以便读者可以轻松地解析代码。

  • 换行规则 - 第一个参数应在新行中,不带开括号。每个参数使用单个缩进。结束元素 ); 应是最后一个。

function_with_a_long_name(
   longArgument1,
   longArgument2,
   longArgument3
);
variable = function_with_a_long_name(
   longArgument1,
   longArgument2,
   longArgument3
);
event multipleArguments(
   address sender,
   address recipient,
   uint256 publicKey,
   uint256 amount,
   bytes32[] options
);
MultipleArguments(
   sender,
   recipient,
   publicKey,
   amount,
   options
);
  • 源代码编码 - 最好使用 UTF-8 或 ASCII 编码。

  • 导入 - 导入语句应放在文件的顶部,紧跟在 pragma 声明之后。

  • 函数顺序 - 函数应根据其可见性进行分组。

pragma solidity ^0.5.0;

contract A {
   constructor() public {
      // ...
   }
   function() external {
      // ...
   }

   // External functions
   // ...

   // External view functions
   // ...

   // External pure functions 
   // ...

   // Public functions
   // ...

   // Internal functions
   // ...

   // Private functions
   // ...
}
  • 避免多余的空格 - 避免在括号、括号或花括号内立即使用空格。

  • 控制结构 - 花括号应与声明在同一行打开。在自己的行上关闭,保持相同的缩进。在开花括号前使用空格。

pragma solidity ^0.5.0;

contract Coin {
   struct Bank {
      address owner;
      uint balance;
   }
}
if (x < 3) {
   x += 1;
} else if (x > 7) {
   x -= 1;
} else {
   x = 5;
}
if (x < 3)
   x += 1;
else
   x -= 1;
  • 函数声明 - 对花括号使用上述规则。始终添加可见性标签。可见性标签应在任何自定义修饰符之前。

function kill() public onlyowner {
   selfdestruct(owner);
}
  • 映射 - 声明映射变量时避免使用空格。

mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
  • 变量声明 - 声明数组变量时避免使用空格。

uint[] x;  // not unit [] x;
  • 字符串声明 - 使用双引号而不是单引号来声明字符串。

str = "foo";
str = "Hamlet says, 'To be or not to be...'";

布局顺序

元素应按以下顺序布局。

  • Pragma 语句

  • 导入语句

  • 接口

  • 合约

在接口、库或合约中,顺序应为:

  • 类型声明

  • 状态变量

  • 事件

  • 函数

命名约定

  • 合约和库应使用 CapWords 样式命名。例如,SmartContract、Owner 等。

  • 合约和库名称应与其文件名匹配。

  • 如果一个文件中有多个合约/库,请使用核心合约/库的名称。

Owned.sol

pragma solidity ^0.5.0;

// Owned.sol
contract Owned {
   address public owner;
   constructor() public {
      owner = msg.sender;
   }
   modifier onlyOwner {
      //....
   }
   function transferOwnership(address newOwner) public onlyOwner {
      //...
   }
}

Congress.sol

pragma solidity ^0.5.0;

// Congress.sol
import "./Owned.sol";

contract Congress is Owned, TokenRecipient {
   //...
}
  • 结构名称

    - 使用 CapWords 样式,例如 SmartCoin。

  • 事件名称

    - 使用 CapWords 样式,例如 Deposit、AfterTransfer。

  • 函数名称

    − 使用混合大小写风格,例如 initiateSupply。

  • 局部变量和状态变量

    − 使用混合大小写风格,例如 creatorAddress、supply。

  • 常量

    − 使用全大写字母,并使用下划线分隔单词,例如 MAX_BLOCKS。

  • 修饰符名称

    − 使用混合大小写风格,例如 onlyAfter。

  • 枚举名称

    − 使用首字母大写风格,例如 TokenGroup。

Solidity - 函数

函数是一组可重用的代码,可以在程序的任何地方调用。这避免了重复编写相同代码的需要。它帮助程序员编写模块化代码。函数允许程序员将一个大型程序分解成许多小的、易于管理的函数。

与任何其他高级编程语言一样,Solidity 也支持编写使用函数的模块化代码所需的所有功能。本节说明如何在 Solidity 中编写自己的函数。

函数定义

在使用函数之前,我们需要定义它。在 Solidity 中定义函数最常见的方法是使用 **function** 关键字,后跟唯一的函数名称、参数列表(可能为空)以及用大括号括起来的一组语句。

语法

基本语法如下所示。

function function-name(parameter-list) scope returns() {
   //statements
}

示例

尝试以下示例。它定义了一个名为 getResult 的函数,该函数不接受任何参数 -

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

调用函数

要在合约中的其他地方调用函数,只需编写该函数的名称,如下面的代码所示。

尝试以下代码以了解字符串在 Solidity 中如何工作。

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);//access local variable
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

输出

0: string: 3

函数参数

到目前为止,我们已经看到了没有参数的函数。但是,在调用函数时,可以使用不同的参数。这些传递的参数可以在函数内部捕获,并且可以对这些参数进行任何操作。函数可以接收多个参数,用逗号分隔。

示例

尝试以下示例。我们在这里使用了 **uint2str** 函数。它接受一个参数。

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);//access local variable
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

输出

0: string: 3

return 语句

Solidity 函数可以有一个可选的 **return** 语句。如果要从函数返回一个值,则需要此语句。此语句应为函数中的最后一条语句。

如上例所示,我们使用 uint2str 函数返回一个字符串。

在 Solidity 中,函数也可以返回多个值。请参见下面的示例 -

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint product, uint sum){
      uint a = 1; // local variable
      uint b = 2;
      product = a * b;
      sum = a + b;
  
      //alternative return statement to return 
      //multiple values
      //return(a*b, a+b);
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

输出

0: uint256: product 2
1: uint256: sum 3

Solidity - 函数修饰符

函数修饰符用于修改函数的行为。例如,向函数添加先决条件。

首先,我们创建一个带或不带参数的修饰符。

contract Owner {
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}

函数体插入修饰符定义中出现特殊符号“_;”的地方。因此,如果在调用此函数时满足修饰符的条件,则执行该函数,否则抛出异常。

请参见下面的示例 -

pragma solidity ^0.5.0;

contract Owner {
   address owner;
   constructor() public {
      owner = msg.sender;
   }
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}
contract Register is Owner {
   mapping (address => bool) registeredAddresses;
   uint price;
   constructor(uint initialPrice) public { price = initialPrice; }
   
   function register() public payable costs(price) {
      registeredAddresses[msg.sender] = true;
   }
   function changePrice(uint _price) public onlyOwner {
      price = _price;
   }
}

Solidity - View 函数

视图函数确保它们不会修改状态。函数可以声明为 **view**。如果函数中存在以下语句,则认为它们正在修改状态,编译器将在这种情况下发出警告。

  • 修改状态变量。

  • 发出事件。

  • 创建其他合约。

  • 使用 selfdestruct。

  • 通过调用发送以太坊。

  • 调用任何未标记为 view 或 pure 的函数。

  • 使用低级调用。

  • 使用包含某些操作码的内联汇编。

Getter 方法默认是视图函数。

请参见下面使用视图函数的示例。

示例

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint product, uint sum){
      uint a = 1; // local variable
      uint b = 2;
      product = a * b;
      sum = a + b; 
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

输出

0: uint256: product 2
1: uint256: sum 3

Solidity - Pure 函数

纯函数确保它们不读取或修改状态。函数可以声明为 **pure**。如果函数中存在以下语句,则认为它们正在读取状态,编译器将在这种情况下发出警告。

  • 读取状态变量。

  • 访问 address(this).balance 或 <address>.balance。

  • 访问块、tx、msg 的任何特殊变量(可以读取 msg.sig 和 msg.data)。

  • 调用任何未标记为 pure 的函数。

  • 使用包含某些操作码的内联汇编。

纯函数可以使用 revert() 和 require() 函数在发生错误时回滚潜在的状态更改。

请参见下面使用视图函数的示例。

示例

pragma solidity ^0.5.0;

contract Test {
   function getResult() public pure returns(uint product, uint sum){
      uint a = 1; 
      uint b = 2;
      product = a * b;
      sum = a + b; 
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

输出

0: uint256: product 2
1: uint256: sum 3

Solidity - 回退函数

回退函数是合约中可用的特殊函数。它具有以下功能 -

  • 当在合约上调用不存在的函数时调用它。

  • 它需要标记为 external。

  • 它没有名称。

  • 它没有参数

  • 它不能返回任何内容。

  • 每个合约可以定义一个。

  • 如果未标记为 payable,则如果合约在没有数据的情况下接收普通以太坊,它将抛出异常。

以下示例显示了每个合约的回退函数的概念。

示例

pragma solidity ^0.5.0;

contract Test {
   uint public x ;
   function() external { x = 1; }    
}
contract Sink {
   function() external payable { }
}
contract Caller {
   function callTest(Test test) public returns (bool) {
      (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
      require(success);
      // test.x is now 1

      address payable testPayable = address(uint160(address(test)));

      // Sending ether to Test contract,
      // the transfer will fail, i.e. this returns false here.
      return (testPayable.send(2 ether));
   }
   function callSink(Sink sink) public returns (bool) {
      address payable sinkPayable = address(sink);
      return (sinkPayable.send(2 ether));
   }
}

Solidity - 函数重载

您可以在相同的范围内为同一个函数名称提供多个定义。函数的定义必须在参数列表中参数的类型和/或数量上有所不同。您不能重载仅返回值类型不同的函数声明。

以下示例显示了 Solidity 中函数重载的概念。

示例

pragma solidity ^0.5.0;

contract Test {
   function getSum(uint a, uint b) public pure returns(uint){      
      return a + b;
   }
   function getSum(uint a, uint b, uint c) public pure returns(uint){      
      return a + b + c;
   }
   function callSumWithTwoArguments() public pure returns(uint){
      return getSum(1,2);
   }
   function callSumWithThreeArguments() public pure returns(uint){
      return getSum(1,2,3);
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

先点击 callSumWithTwoArguments 按钮,然后点击 callSumWithThreeArguments 按钮查看结果。

输出

0: uint256: 3
0: uint256: 6

Solidity - 数学函数

Solidity 还提供内置的数学函数。以下是经常使用的函数 -

  • **addmod(uint x, uint y, uint k) returns (uint)** - 计算 (x + y) % k,其中加法以任意精度执行,并且不会在 2256 处环绕。

  • **mulmod(uint x, uint y, uint k) returns (uint)** - 计算 (x * y) % k,其中加法以任意精度执行,并且不会在 2256 处环绕。

以下示例显示了在 Solidity 中使用数学函数。

示例

pragma solidity ^0.5.0;

contract Test {   
   function callAddMod() public pure returns(uint){
      return addmod(4, 5, 3);
   }
   function callMulMod() public pure returns(uint){
      return mulmod(4, 5, 3);
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

先点击 callAddMod 按钮,然后点击 callMulMod 按钮查看结果。

输出

0: uint256: 0
0: uint256: 2

Solidity - 密码学函数

Solidity 还提供内置的密码学函数。以下是重要的函数 -

  • **keccak256(bytes memory) returns (bytes32)** - 计算输入的 Keccak-256 哈希值。

  • **ripemd160(bytes memory) returns (bytes20)** - 计算输入的 RIPEMD-160 哈希值。

  • **sha256(bytes memory) returns (bytes32)** - 计算输入的 SHA-256 哈希值。

  • **ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)** - 从椭圆曲线签名中恢复与公钥关联的地址,或在出错时返回零。函数参数对应于签名的 ECDSA 值:r - 签名的前 32 个字节;s:签名的第二个 32 个字节;v:签名的最后一个 1 个字节。此方法返回一个地址。

以下示例显示了在 Solidity 中使用密码学函数。

示例

pragma solidity ^0.5.0;

contract Test {   
   function callKeccak256() public pure returns(bytes32 result){
      return keccak256("ABC");
   }  
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

输出

0: bytes32: result 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8

Solidity - 提款模式

提款模式确保不会进行直接转账调用,这会带来安全威胁。以下合约显示了使用转账调用发送以太坊的不安全用法。

pragma solidity ^0.5.0;

contract Test {
   address payable public richest;
   uint public mostSent;

   constructor() public payable {
      richest = msg.sender;
      mostSent = msg.value;
   }
   function becomeRichest() public payable returns (bool) {
      if (msg.value > mostSent) {
         // Insecure practice
         richest.transfer(msg.value);
         richest = msg.sender;
         mostSent = msg.value;
         return true;
      } else {
         return false;
      }
   }
}

以上合约可以通过导致最富有者成为回退函数失败的合约来使其处于不可用状态。当回退函数失败时,becomeRichest() 函数也会失败,合约将永远卡住。为了缓解这个问题,我们可以使用提款模式。

在提款模式中,我们将在每次转账前重置挂起的金额。它将确保只有调用者合约失败。

pragma solidity ^0.5.0;

contract Test {
   address public richest;
   uint public mostSent;

   mapping (address => uint) pendingWithdrawals;

   constructor() public payable {
      richest = msg.sender;
      mostSent = msg.value;
   }
   function becomeRichest() public payable returns (bool) {
      if (msg.value > mostSent) {
         pendingWithdrawals[richest] += msg.value;
         richest = msg.sender;
         mostSent = msg.value;
         return true;
      } else {
         return false;
      }
   }
   function withdraw() public {
      uint amount = pendingWithdrawals[msg.sender];
      pendingWithdrawals[msg.sender] = 0;
      msg.sender.transfer(amount);
   }
}

Solidity - 访问限制

限制对合约的访问是一种常见做法。默认情况下,合约状态为只读,除非将其指定为 public。

我们可以使用修饰符限制谁可以修改合约的状态或调用合约的函数。我们将创建和使用多个修饰符,如下所述 -

  • **onlyBy** - 一旦在函数上使用,则只有提到的调用者才能调用此函数。

  • **onlyAfter** - 一旦在函数上使用,则只能在某个时间段后才能调用该函数。

  • **costs** - 一旦在函数上使用,则只有在提供特定值的情况下,调用者才能调用此函数。

示例

pragma solidity ^0.5.0;

contract Test {
   address public owner = msg.sender;
   uint public creationTime = now;

   modifier onlyBy(address _account) {
      require(
         msg.sender == _account,
         "Sender not authorized."
      );
      _;
   }
   function changeOwner(address _newOwner) public onlyBy(owner) {
      owner = _newOwner;
   }
   modifier onlyAfter(uint _time) {
      require(
         now >= _time,
         "Function called too early."
      );
      _;
   }
   function disown() public onlyBy(owner) onlyAfter(creationTime + 6 weeks) {
      delete owner;
   }
   modifier costs(uint _amount) {
      require(
         msg.value >= _amount,
         "Not enough Ether provided."
      );
      _;
      if (msg.value > _amount)
         msg.sender.transfer(msg.value - _amount);
   }
   function forceOwnerChange(address _newOwner) public payable costs(200 ether) {
      owner = _newOwner;
      if (uint(owner) & 0 == 1) return;        
   }
}

Solidity - 合约

Solidity 中的合约类似于 C++ 中的类。合约具有以下属性。

  • **构造函数** - 使用 constructor 关键字声明的特殊函数,每个合约将执行一次,并在创建合约时调用。

  • **状态变量** - 每个合约的变量,用于存储合约的状态。

  • **函数** - 每个合约的函数,可以修改状态变量以更改合约的状态。

可见性限定符

以下是合约函数/状态变量的各种可见性限定符。

  • **external** - 外部函数旨在由其他合约调用。它们不能用于内部调用。要在合约内调用外部函数,需要使用 this.function_name() 调用。状态变量不能标记为 external。

  • **public** - 公共函数/变量可以在外部和内部使用。对于公共状态变量,Solidity 会自动创建一个 getter 函数。

  • **internal** - 内部函数/变量只能在内部或由派生合约使用。

  • **private** - 私有函数/变量只能在内部使用,即使派生合约也不可以使用。

示例

pragma solidity ^0.5.0;

contract C {
   //private state variable
   uint private data;
   
   //public state variable
   uint public info;

   //constructor
   constructor() public {
      info = 10;
   }
   //private function
   function increment(uint a) private pure returns(uint) { return a + 1; }
   
   //public function
   function updateData(uint a) public { data = a; }
   function getData() public view returns(uint) { return data; }
   function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
//External Contract
contract D {
   function readData() public returns(uint) {
      C c = new C();
      c.updateData(7);         
      return c.getData();
   }
}
//Derived Contract
contract E is C {
   uint private result;
   C private c;
   
   constructor() public {
      c = new C();
   }  
   function getComputedResult() public {      
      result = compute(3, 5); 
   }
   function getResult() public view returns(uint) { return result; }
   function getData() public view returns(uint) { return c.info(); }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。运行合约的各种方法。对于 E.getComputedResult() 后跟 E.getResult() 显示 -

输出

0: uint256: 8

Solidity - 继承

继承是扩展合约功能的一种方法。Solidity 支持单继承和多继承。以下是关键要点。

  • 派生合约可以访问所有非私有成员,包括内部方法和状态变量。但使用 this 不允许。

  • 允许函数重写,前提是函数签名保持不变。如果输出参数不同,则编译将失败。

  • 我们可以使用 super 关键字或使用超类名称来调用超类的函数。

  • 在多重继承的情况下,使用 super 调用函数优先考虑派生程度最高的合约。

示例

pragma solidity ^0.5.0;

contract C {
   //private state variable
   uint private data;
   
   //public state variable
   uint public info;

   //constructor
   constructor() public {
      info = 10;
   }
   //private function
   function increment(uint a) private pure returns(uint) { return a + 1; }
   
   //public function
   function updateData(uint a) public { data = a; }
   function getData() public view returns(uint) { return data; }
   function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
//Derived Contract
contract E is C {
   uint private result;
   C private c;
   constructor() public {
      c = new C();
   }  
   function getComputedResult() public {      
      result = compute(3, 5); 
   }
   function getResult() public view returns(uint) { return result; }
   function getData() public view returns(uint) { return c.info(); }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。运行合约的各种方法。对于 E.getComputedResult() 后跟 E.getResult() 显示 -

输出

0: uint256: 8

Solidity - 构造函数

构造函数是使用 **constructor** 关键字声明的特殊函数。它是一个可选函数,用于初始化合约的状态变量。以下是构造函数的关键特征。

  • 合约只能有一个构造函数。

  • 创建合约时会执行一次构造函数代码,它用于初始化合约状态。

  • 构造函数代码执行后,最终代码将部署到区块链。此代码包括公共函数和可通过公共函数访问的代码。仅由构造函数使用的构造函数代码或任何内部方法都不包含在最终代码中。

  • 构造函数可以是 public 或 internal。

  • 内部构造函数将合约标记为抽象。

  • 如果未定义构造函数,则合约中存在默认构造函数。

pragma solidity ^0.5.0;

contract Test {
   constructor() public {}
}
  • 如果基类有带参数的构造函数,则每个派生类都必须传递它们。

  • 可以使用以下方法直接初始化基类构造函数 -

pragma solidity ^0.5.0;

contract Base {
   uint data;
   constructor(uint _data) public {
      data = _data;   
   }
}
contract Derived is Base (5) {
   constructor() public {}
}
  • 可以使用以下方法间接初始化基类构造函数 -

pragma solidity ^0.5.0;

contract Base {
   uint data;
   constructor(uint _data) public {
      data = _data;   
   }
}
contract Derived is Base {
   constructor(uint _info) Base(_info * _info) public {}
}
  • 不允许同时使用直接和间接方法初始化基类构造函数。

  • 如果派生合约未将参数传递给基类构造函数,则派生合约将变为抽象。

Solidity - 抽象合约

抽象合约是指至少包含一个没有实现的函数的合约。此类合约用作基类合约。通常,抽象合约包含已实现的函数和抽象函数。派生合约将实现抽象函数,并在需要时使用现有函数。

如果派生合约未实现抽象函数,则此派生合约将被标记为抽象。

示例

尝试以下代码以了解抽象合约如何在 Solidity 中工作。

pragma solidity ^0.5.0;

contract Calculator {
   function getResult() public view returns(uint);
}
contract Test is Calculator {
   function getResult() public view returns(uint) {
      uint a = 1;
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

输出

0: uint256: 3

Solidity - 接口

接口类似于抽象合约,并使用 **interface** 关键字创建。以下是接口的关键特征。

  • 接口不能有任何带实现的函数。

  • 接口的函数只能是 external 类型。

  • 接口不能有构造函数。

  • 接口不能有状态变量。

  • 接口可以有枚举、结构体,可以使用接口名称点表示法访问它们。

示例

尝试以下代码来了解Solidity中接口的工作原理。

pragma solidity ^0.5.0;

interface Calculator {
   function getResult() external view returns(uint);
}
contract Test is Calculator {
   constructor() public {}
   function getResult() external view returns(uint){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

注意 - 在点击部署按钮之前,从下拉菜单中选择测试。

输出

0: uint256: 3

Solidity - 库

库类似于合约,但主要用于重用。库包含其他合约可以调用的函数。Solidity 对库的使用有一些限制。以下是Solidity 库的关键特性。

  • 如果库函数不修改状态,则可以直接调用它们。这意味着只有纯函数或视图函数可以从库外部调用。

  • 库不能被销毁,因为它被认为是无状态的。

  • 库不能有状态变量。

  • 库不能继承任何元素。

  • 库不能被继承。

示例

尝试以下代码来了解库在Solidity中的工作原理。

pragma solidity ^0.5.0;

library Search {
   function indexOf(uint[] storage self, uint value) public view returns (uint) {
      for (uint i = 0; i < self.length; i++) if (self[i] == value) return i;
      return uint(-1);
   }
}
contract Test {
   uint[] data;
   constructor() public {
      data.push(1);
      data.push(2);
      data.push(3);
      data.push(4);
      data.push(5);
   }
   function isValuePresent() external view returns(uint){
      uint value = 4;
      
      //search if value is present in the array using Library function
      uint index = Search.indexOf(data, value);
      return index;
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

注意 - 在点击部署按钮之前,从下拉菜单中选择测试。

输出

0: uint256: 3

使用For循环

指令using A for B; 可用于将库A的库函数附加到给定的类型B。这些函数将使用调用方类型作为其第一个参数(使用self标识)。

示例

尝试以下代码来了解库在Solidity中的工作原理。

pragma solidity ^0.5.0;

library Search {
   function indexOf(uint[] storage self, uint value) public view returns (uint) {
      for (uint i = 0; i < self.length; i++)if (self[i] == value) return i;
      return uint(-1);
   }
}
contract Test {
   using Search for uint[];
   uint[] data;
   constructor() public {
      data.push(1);
      data.push(2);
      data.push(3);
      data.push(4);
      data.push(5);
   }
   function isValuePresent() external view returns(uint){
      uint value = 4;      
      
      //Now data is representing the Library
      uint index = data.indexOf(value);
      return index;
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

注意 - 在点击部署按钮之前,从下拉菜单中选择测试。

输出

0: uint256: 3

Solidity - 汇编

Solidity提供了一个选项,可以使用汇编语言在Solidity源代码中编写内联汇编。我们还可以编写独立的汇编代码,然后将其转换为字节码。独立汇编是Solidity编译器的中间语言,它将Solidity代码转换为独立汇编,然后转换为字节码。我们可以使用与内联汇编中相同的语言在独立汇编中编写代码。

内联汇编

内联汇编代码可以与Solidity代码库交织在一起,以便对EVM进行更细粒度的控制,尤其是在编写库函数时使用。

汇编代码写在assembly { ... } 代码块中。

示例

尝试以下代码来了解库在Solidity中的工作原理。

pragma solidity ^0.5.0;

library Sum {   
   function sumUsingInlineAssembly(uint[] memory _data) public pure returns (uint o_sum) {
      for (uint i = 0; i < _data.length; ++i) {
         assembly {
            o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
         }
      }
   }
}
contract Test {
   uint[] data;
   
   constructor() public {
      data.push(1);
      data.push(2);
      data.push(3);
      data.push(4);
      data.push(5);
   }
   function sum() external view returns(uint){      
      return Sum.sumUsingInlineAssembly(data);
   }
}

使用Solidity 第一个应用程序章节中提供的步骤运行上述程序。

注意 - 在点击部署按钮之前,从下拉菜单中选择测试。

输出

0: uint256: 15

Solidity - 事件

事件是合约的可继承成员。当事件被发出时,它会将传递的参数存储在交易日志中。这些日志存储在区块链上,并且可以使用合约地址访问,直到合约存在于区块链上。生成的事件在合约内部不可访问,即使是创建和发出它们的合约也不行。

可以使用event关键字声明事件。

//Declare an Event
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);

//Emit an event
emit Deposit(msg.sender, _id, msg.value);

示例

尝试以下代码来了解事件在Solidity中的工作原理。

首先创建一个合约并发出一个事件。

pragma solidity ^0.5.0;

contract Test {
   event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
   function deposit(bytes32 _id) public payable {      
      emit Deposit(msg.sender, _id, msg.value);
   }
}

然后在JavaScript代码中访问合约的事件。

var abi = /* abi as generated using compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceiptContract = ClientReceipt.at("0x1234...ab67" /* address */);

var event = clientReceiptContract.Deposit(function(error, result) {
   if (!error)console.log(result);
});

它应该打印类似于以下内容的详细信息:

输出

{
   "returnValues": {
      "_from": "0x1111...FFFFCCCC",
      "_id": "0x50...sd5adb20",
      "_value": "0x420042"
   },
   "raw": {
      "data": "0x7f...91385",
      "topics": ["0xfd4...b4ead7", "0x7f...1a91385"]
   }
}

Solidity - 错误处理

Solidity提供了各种用于错误处理的函数。通常,当发生错误时,状态会回滚到其原始状态。其他检查是为了防止未经授权的代码访问。以下是错误处理中使用的一些重要方法:

  • assert(bool condition) - 如果条件不满足,此方法调用会导致无效的操作码,并且对状态所做的任何更改都会回滚。此方法用于内部错误。

  • require(bool condition) - 如果条件不满足,此方法调用会回滚到原始状态。- 此方法用于输入或外部组件中的错误。

  • require(bool condition, string memory message) - 如果条件不满足,此方法调用会回滚到原始状态。- 此方法用于输入或外部组件中的错误。它提供了一个选项来提供自定义消息。

  • revert() - 此方法中止执行并回滚对状态所做的任何更改。

  • revert(string memory reason) - 此方法中止执行并回滚对状态所做的任何更改。它提供了一个选项来提供自定义消息。

示例

尝试以下代码来了解错误处理在Solidity中的工作原理。

pragma solidity ^0.5.0;

contract Vendor {
   address public seller;
   modifier onlySeller() {
      require(
         msg.sender == seller,
         "Only seller can call this."
      );
      _;
   }
   function sell(uint amount) public payable onlySeller { 
      if (amount > msg.value / 2 ether)
         revert("Not enough Ether provided.");
      // Perform the sell operation.
   }
}

当调用revert时,它将返回如下所示的十六进制数据。

输出

0x08c379a0                     // Function selector for Error(string)
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data
广告