Hike News
Hike News

智能合约入门

参考 Solidity 0.5.9 中文文档

Environment

  • Ubuntu 16.04.6

Intro

Solidity 是一种智能合约的高级语言,运行在 Ethereum 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。但作为一种真正意义上运行在网络上的去中心合约,它又有很多的不同,下面列举一些:

  • 以太坊底层是基于账户,而非 UTXO 的,所以有一个特殊的 Address 的类型。用于定位用户,定位合约,定位合约的代码(合约本身也是一个账户)。
  • 由于语言内嵌框架是支持支付的,所以提供了一些关键字,如 payable,可以在语言层面直接支持支付,而且超级简单。
  • 存储是使用网络上的区块链,数据的每一个状态都可以永久存储,所以需要确定变量使用内存,还是区块链。
  • 运行环境是在去中心化的网络上,会比较强调合约或函数执行的调用的方式。因为原来一个简单的函数调用变为了一个网络上的节点中的代码执行,分布式的感觉。
  • 最后一个非常大的不同则是它的异常机制,一旦出现异常,所有的执行都将会被回撤,这主要是为了保证合约执行的原子性,以避免中间状态出现的数据不一致。

Hello World

下面尝试编写 Solidity 下的 Hello World。

第一步是安装 nodejs 和 npm,然后才能安装 truffle:

1
2
3
4
5
6
sudo apt update
sudo apt install npm nodejs nodejs-legacy
sudo npm install npm -g
sudo npm install n -g
sudo n stable # 下载稳定版 node
sudo npm install -g truffle truffle-contract web3 --unsafe-perm=true --allow-root

安装完 truffle 后,下载 Ganache 以方便搭建本地区块链环境(Ganache 在内存中模拟了一个区块链,因此每次 Ganache 关闭之后,区块链会丢失)。下载完后双击打开软件并启动本地环境。

各部分都成功安装之后,新建一个目录,并在目录下运行 truffle init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  HelloWorld truffle init

✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
✔ Setting up box

Unbox successful. Sweet!

Commands:

Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test

简单说明一下生成的几个文件:

  • /contracts:存放智能合约原始代码的地方。
  • /migrations:这是 Truffle 用来部署智能合约的功能。
  • /test:测试智能合约的代码放在这里,支持 jssol 测试。
  • truffle-config.js:Truffle 的设置文档。

Solidity 中合约的含义就是一组代码(函数)和数据(状态),它们位于以太坊区块链的一个特定地址上。关键字 pragma 告知编译器源代码所适用的 Solidity 版本为 >=0.4.0 及 <0.7.0,为了确保合约不会在新的编译器版本中突然行为异常。

关键字 contract 即为指定合约,关键字 function 指定函数。public 指定函数的公开权限,view 用来标识那些不改变存储状态的方法(相比之下而 pure 更加严格,它修饰的方法不但不改变存贮状态,也不读取存储的变量值),returns 指定返回值的数据类型。

注意:所有的标识符(合约名称,函数名称和变量名称)都只能使用 ASCII 字符集。UTF-8 编码的数据可以用字符串变量的形式存储;小心使用 Unicode 文本,因为有些字符虽然长得相像(甚至一样),但其字符码是不同的,其编码后的字符数组也会不一样。

以下为一个简单的返回 Hello World 的函数:

1
2
3
4
5
6
7
pragma solidity >=0.4.0 <0.7.0;

contract HelloWorld {
function sayHello() public view returns (string memory) {
return "Hello World";
}
}

然后输入 truffle compile 进行编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  HelloWorld truffle compile

Compiling your contracts...
===========================
> Compiling ./contracts/HelloWorld.sol
> Compiling ./contracts/Migrations.sol

> compilation warnings encountered:

/home/assassinq/SmartContract/HelloWorld/contracts/HelloWorld.sol:4:3: Warning: Function state mutability can be restricted to pure
function sayHello() public view returns (string memory) {
^ (Relevant source part starts here and spans across multiple lines).

> Artifacts written to /home/assassinq/SmartContract/HelloWorld/build/contracts
> Compiled successfully using:
- solc: 0.5.8+commit.23d335f2.Emscripten.clang

接下来我们需要在 migrations 下添加一个部署文件 2_deploy_contracts.js,用来待会儿对 HelloWorld.sol 的部署。部署文件的文件名要求以数字为前缀,后缀为描述。编号前缀是必需的,以便记录迁移是否成功运行,后缀纯粹是为了方便理解。

注意:编号还有记录运行迁移文件顺序的作用。

使用 artifacts.require 语句来取得准备部署的合约。使用 deployer.deploy 语句将合约部署到区块链上。这边 HelloWorld 是 contract 的名称而不是文件名。因此可以用此语法读入任一 .sol 文件中的任一合约。

1
2
3
4
5
const HelloWorld = artifacts.require("HelloWorld");

module.exports = function(deployer) {
deployer.deploy(HelloWorld);
};

在确保之前的 Ganache 已经启动并生成了十个账户后,输入 truffle migrate 将合约部署到 Ganache 上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
➜  HelloWorld truffle migrate

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.



Starting migrations...
======================
> Network name: 'ganache'
> Network id: 5777
> Block gas limit: 0x6691b7


1_initial_migration.js
======================

Deploying 'Migrations'
----------------------
> transaction hash: 0xc3b75999936e57ba192b2053a581762c1f235aad3090caea5572c3deb1d98802
> Blocks: 0 Seconds: 0
> contract address: 0x55a333d4f932a737E0b97af4E3F8F4E971600D43
> block number: 5
> block timestamp: 1569581737
> account: 0x6e5707f027eF99beF9Aa6f5c03Ac7678757E0bA0
> balance: 99.98561934
> gas used: 261393
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00522786 ETH


> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00522786 ETH


2_deploy_contracts.js
=====================

Deploying 'HelloWorld'
----------------------
> transaction hash: 0x9c05c079cbdbb90a3d53fdf5e66e3a79fce1a0e5f633815068c9a82aaaf912b0
> Blocks: 0 Seconds: 0
> contract address: 0x4752C4f381D9e492e10daCCf9213d916bd1f8caF
> block number: 7
> block timestamp: 1569581739
> account: 0x6e5707f027eF99beF9Aa6f5c03Ac7678757E0bA0
> balance: 99.98223486
> gas used: 127201
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00254402 ETH


> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00254402 ETH


Summary
=======
> Total deployments: 2
> Final cost: 0.00777188 ETH

最后执行 truffle console,可以使用 js 与刚才部署的合约进行交互:

1
2
3
4
truffle(ganache)> let x = await HelloWorld.deployed()
undefined
truffle(ganache)> x.sayHello()
'Hello World'

我们还可以尝试添加一个函数 echo,可以输出我们传入的字符串:

1
2
3
4
5
6
7
8
9
10
11
pragma solidity >=0.4.0 <0.7.0;

contract HelloWorld {
function sayHello() public view returns (string memory) {
return "Hello World";
}

function echo(string memory name) public view returns (string memory) {
return name;
}
}

要重新编译 Truffle 项目里的合约,请切换到项目工程所在根目录。后续运行中,Truffle 将仅编译自上次编译以来有更改的合约,如果想覆盖可以使用 --all 选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
➜  HelloWorld truffle compile

Compiling your contracts...
===========================
> Compiling ./contracts/HelloWorld.sol

> compilation warnings encountered:

/home/assassinq/SmartContract/HelloWorld/contracts/HelloWorld.sol:4:3: Warning: Function state mutability can be restricted to pure
function sayHello() public view returns (string memory) {
^ (Relevant source part starts here and spans across multiple lines).
,/home/assassinq/SmartContract/HelloWorld/contracts/HelloWorld.sol:8:3: Warning: Function state mutability can be restricted to pure
function echo(string memory name) public view returns (string memory) {
^ (Relevant source part starts here and spans across multiple lines).

> Artifacts written to /home/assassinq/SmartContract/HelloWorld/build/contracts
> Compiled successfully using:
- solc: 0.5.8+commit.23d335f2.Emscripten.clang

部署时需要加上 --reset 参数进行重新部署。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
➜  HelloWorld truffle migrate --reset

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.



Starting migrations...
======================
> Network name: 'ganache'
> Network id: 5777
> Block gas limit: 0x6691b7


1_initial_migration.js
======================

Replacing 'Migrations'
----------------------
> transaction hash: 0x73d288f20ed2d68fe565fb01e8a15ba1a591c0cfd642028b45056fc057c194e6
> Blocks: 0 Seconds: 0
> contract address: 0x48245c079FA8558a35629BC9b8A94b00c91eD9A9
> block number: 9
> block timestamp: 1569581861
> account: 0x6e5707f027eF99beF9Aa6f5c03Ac7678757E0bA0
> balance: 99.97646654
> gas used: 261393
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00522786 ETH


> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00522786 ETH


2_deploy_contracts.js
=====================

Replacing 'HelloWorld'
----------------------
> transaction hash: 0x8a7ca29c50f2e2f5645a9b4c33e5ca0eb6ab50def348c2ebcfe2f6fd38d9851e
> Blocks: 0 Seconds: 0
> contract address: 0x0d12C183e366AE74393346ae33d4b662bfB5492F
> block number: 11
> block timestamp: 1569581862
> account: 0x6e5707f027eF99beF9Aa6f5c03Ac7678757E0bA0
> balance: 99.97130642
> gas used: 215983
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00431966 ETH


> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00431966 ETH


Summary
=======
> Total deployments: 2
> Final cost: 0.00954752 ETH

最后进行交互:

1
2
3
4
5
6
truffle(ganache)> let x = await HelloWorld.deployed()
undefined
truffle(ganache)> x.sayHello()
'Hello World'
truffle(ganache)> x.echo('This is assassinq.')
'This is assassinq.'

References

https://blog.csdn.net/liyuechun520/article/details/78036363
https://blog.csdn.net/weixin_42595515
https://www.jianshu.com/p/983122b8243e
http://blog.sina.com.cn/s/blog_bad31d930102xa1l.html