坚实度 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 运算符,如算术运算符、赋值运算符、字符串运算符、比较运算符、逻辑运算符、类型运算符和按位运算符。根据允许的用法,这些运算符可以与各种值类型一起使用。
整数
该值类型分配整数。整数有两个子类型,即int
和uint
,分别是有符号整数和无符号整数类型。内存大小是在编译时分配的;使用int8
或int256
指定,其中数字代表内存中分配的大小。仅使用int
或unit
分配内存,默认分配最大的内存大小。
地址
该值类型包含一个 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);
当我们使用transfer
或send
成员时,消耗的气体量几乎相同。transfer
是从 Solidity 0.4.13 引入的,因为 send 不发送任何气体,也不传播异常。Transfer
被认为是将以太网从一个地址发送到另一个地址的安全方式,因为它会引发错误并允许某人传播错误。
call
、callcode
、delegatecall
用于与没有应用二进制接口 ( ABI )的函数进行交互。call
返回一个布尔值,表明函数是成功运行还是在 EVM 中终止。
当a
调用b
时,代码在b
的上下文中运行,使用b
的存储。另一方面,当a
在b
上做callcode
时,代码在a
的上下文中运行,使用a
的存储,但是使用a
的与存储的代码。
delegatecall
函数用于根据需要委托一个合同使用另一个合同的存储。
所有这些成员:call
、delegatecall
和callcode
不建议使用,除非真的有必要,因为它们会破坏坚固性的类型安全。有可能callcode
在不久的将来会被弃用。
数组值类型
Solidity 有一个固定和动态的数组值类型。在固定大小的字节数组中,关键字的范围从bytes1
到bytes32
。另一方面,在动态大小的字节数组中,关键字可以包含字节或字符串。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
被弃用,为pure
和view
功能让路。view
的作用与constant
一样,它的功能不能以任何方式改变存储。payable
允许函数在被调用时接收以太。
通过用空格分隔来指定每个修饰符,可以在一个函数中使用多个修饰符;它们按照书写的顺序进行评估。
参考类型
这些是通过引用传递的;由于它们所构成的内存分配,它们非常占用内存。
结构
结构是在逻辑组下声明的复合数据类型。结构用于定义新类型。尽管结构可以是映射成员的值类型,但结构不可能包含其自身类型的成员。下面是一个结构示例:
struct Gotham {
address Batcave;
uint cars;
uint batcomputer;
uint enemies;
string gordon;
address twoface;
}
数据单元
这指定了特定数据类型的存储位置。它适用于数组和结构。使用storage
或memory
关键字指定数据位置。还有第三个数据位置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 文件的布局、契约的结构以及值和引用的类型。我们还学习了映射。
在下一章中,我们将应用本章中的新知识来开发一个实际的合同,并在测试网络上部署该合同。