基础入门智能合约简单的智能合约区块链基础以太坊虚拟机安装Solidity编译器版本Remixnpm / Node.jsDocker根据例子学习Solidity投票合约秘密竞价(盲拍)合约安全的远程购买合约SOLIDITY 详解Solidity 源文件结构版权许可(SPDX License Identifier)Pragmas版本标识ABI Coder PragmaSMTChecker导入其他源文件注释合约结构状态变量函数函数修改器(modifier)事件(Event)
基础
入门智能合约
简单的智能合约
存储合约
把一个数据保存到链上
货币合约(Subcurrency)
下面的合约实现了一个最简单的加密货币。
上面代码中minter字段由public关键字自动创建的getter函数如下:
虽然不会显式存在,但是你不能再定义一个和上面完全一样的函数了。
而balances由public关键字自动创建的getter函数如下:
声明的event在send最后一行被触发。用户界面(当然也包括服务器应用程序)可以监听区块链上正在发送的事件,而不会花费太多成本。一旦它被发出,监听该事件的listener都将收到通知。为了监听这个事件,你可以使用如下JavaScript代码(假设 Coin 是已经通过 web3.js 创建好的合约对象 ):
区块链基础
交易/事务
区块链是全球共享的事务性数据库。必须创建一个被所有其他人所接受的事务才能改变数据库中的东西。事务一词意味着你的操作要么一点没做,要么全部完成。
区块
比特币中的交易会打包到区块中,然后分发给所有参与节点。这些块按时间形成了一个线性序列,这正是“区块链”这个词的来源。
区块以一定的时间间隔添加到链上 —— 对于以太坊,这间隔大约是17秒。
如果两笔交易互相矛盾,那么最终被确认为后发生的交易将被拒绝,不会被包含到区块中。这即是“双花攻击 (double-spend attack)”。
以太坊虚拟机
概述
以太坊虚拟机 EVM 是智能合约的运行环境。它不仅是沙盒封装的,而且是完全隔离的,也就是说在 EVM 中运行代码是无法访问网络、文件系统和其他进程的。甚至智能合约之间的访问也是受限的。
账户
以太坊中有两类账户(它们共用同一个地址空间):
- 外部账户:由公钥-私钥对(也就是人)控制,地址由公钥决定的
- 合约账户:由和账户一起存储的代码控制,地址是在创建该合约时确定的(这个地址通过合约创建者的地址和从该地址发出过的交易数量计算得到的,也就是所谓的“nonce”)
交易
交易可以看作是从一个帐户发送到另一个帐户的消息。
如果目标账户是零账户(账户地址为
0
),此交易将创建一个 新合约。在合约创建的过程中,它的代码还是空的。所以直到构造函数执行结束,你都不应该在其中调用合约自己函数。
Gas
发送者账户需要预付的手续费=
gas_price * gas
。一旦 gas 被耗尽(比如降为负值),将会触发一个 out-of-gas 异常。当前调用帧(call frame)所做的所有状态修改都将被回滚。
存储,内存和栈
每个账户有一块持久化内存区称为 存储 。 存储是将256位字映射到256位字的键值存储区。 在合约中枚举存储是不可能的,且读存储的相对开销很高,修改存储的开销甚至更高。合约只能读写存储区内属于自己的部分。
第二个内存区称为 内存 ,合约会试图为每一次消息调用获取一块被重新擦拭干净的内存实例。 内存是线性的,可按字节级寻址,但读的长度被限制为256位,而写的长度可以是8位或256位。当访问(无论是读还是写)之前从未访问过的内存字(word)时(无论是偏移到该字内的任何位置),内存将按字进行扩展(每个字是256位)。扩容也将消耗一定的gas。 随着内存使用量的增长,其费用也会增高(以平方级别)。
EVM 不是基于寄存器的,而是基于栈的,因此所有的计算都在一个被称为 栈(stack) 的区域执行。 栈最大有1024个元素,每个元素长度是一个字(256位)。对栈的访问只限于其顶端,限制方式为:允许拷贝最顶端的16个元素中的一个到栈顶,或者是交换栈顶元素和下面16个元素中的一个。所有其他操作都只能取最顶的两个(或一个,或更多,取决于具体的操作)元素,运算后,把结果压入栈顶。当然可以把栈上的元素放到存储或内存中。但是无法只访问栈上指定深度的那个元素,除非先从栈顶移除其他元素。
指令集
EVM的指令集量应尽量少,以最大限度地避免可能导致共识问题的错误实现。所有的指令都是针对”256位的字(word)”这个基本的数据类型来进行操作。具备常用的算术、位、逻辑和比较操作。也可以做到有条件和无条件跳转。此外,合约可以访问当前区块的相关属性,比如它的编号和时间戳。
消息调用
合约可以通过消息调用的方式来调用其它合约或者发送以太币到非合约账户。
委托调用(delegatecall)
目标地址的代码将在发起调用的合约的上下文中执行,并且
msg.sender
和 msg.value
不变。 日志
一路映射到区块层级的存储数据,可从区块链外高效访问。Solidity用它来实现事件(events)。
合约创建
负载的代码被执行并存储为合约代码,调用者/创建者在栈上得到新合约的地址。
失效和自毁
合约可以调用
selfdestruct
执行自毁操作。安装Solidity编译器
版本
版本迭代变更较大,推荐使用最新版本。
Remix
如果处理大型合约或者需要更多的编译选项,推荐使用命令行编译器solc。
npm / Node.js
可以使用
npm
便捷地安装Solidity编译器solcjs,但它没有solc那么全的功能。安装命令如下:Docker
使用docker构建编译器
目前,docker 镜像只含有 solc 的可执行程序,因此你需要额外的工作去把源代码和输出目录连接起来。
根据例子学习Solidity
投票合约
秘密竞价(盲拍)合约
简单的公开拍卖
秘密竞拍(盲拍)
安全的远程购买合约
SOLIDITY 详解
Solidity 源文件结构
版权许可(SPDX License Identifier)
版权指定为MIT写法如下:
如果不想明确指定版权,可以按下面这样写:
Pragmas
关键字
pragma
用来启用某些编译器检查。版本标识
表示源文件适用版本不允许低于0.5.2,也不允许高于0.6.0。
ABI Coder Pragma
到目前为止,ABI Coder一共有两个版本,ABI coder (v1) 和 ABI coder (v2)。
Solidity 0.7.4 以前默认就是v1
Solidity 0.7.4 之后可以使用
pragma experimental ABIEncoderV2
选用v2Solidity 0.7.4 之后默认就是v2,当然也可以使用
pragma abicoder v1;
选用v1SMTChecker
SMT(Satisfiability modulo theories)可以进行代码安全检查。使用
pragma experimental SMTChecker;
, 就可以获得 SMT solver 额外的安全检查。但是这个模块目前不支持 Solidity 的全部语法特性,因此有可能输出一些警告信息。导入其他源文件
语法与语义
一般情况写法如下:
如果要修改符号名则写法如下:
如果修改某个别符号,写法如下:
路径
一般使用相对路径,如果要引入当前源文件同目录下的文件,则写法如下:
通常,目录层次不必严格映射到本地文件系统, 它也可以映射到能通过诸如 ipfs,http 或者 git 发现的资源。
在实际的编译器中使用
通过将指定路径前缀重映射到另一个路径。
例如,
github.com/ethereum/dapp-bin/library
会被重映射到 /usr/local/dapp-bin/library
, 此时编译器将从重映射位置读取文件。如果重映射到多个路径,优先尝试重映射路径最长的一个。注释
单行注释://
多行注释:/*...*/
natspec 注释:/// 或 /**...*/,一般直接用在函数声明或语句使用上。
合约结构
状态变量
函数
函数是代码的可执行单元。函数通常在合约内部定义,但也可以在合约外定义。
函数修改器(modifier)
事件(Event)
事件是能方便地调用以太坊虚拟机日志功能的接口。