使用solidity开发以太坊智能合约

一些开发合约相关资料

环境搭建(MAC)

安装solidity

1
brew install solidity

安装truffle

truffle是集成安装、部署、测试合约为一体的开发框架;如果没有node环境需要先安装node:brew install node

1
npm install truffle -g

合约开发

初始化项目路径

1
truffle init

初始化npm环境

在初始化的过程中会让你输入一些参数的值,可以一路按回车全部使用默认值即可

1
npm init

安装npm依赖库

1
npm install dotenv truffle-wallet-provider ethereumjs-wallet

配置网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = {
networks: {
localnode: {
network_id: "*",
host: "localhost",
port: "8545"
}
},

mocha: {
// timeout: 100000
},

compilers: {
solc: {
version: "0.8.0+commit.c7dfd78e", // Fetch exact version from solc-bin (default: truffle's version)
}
},

db: {
enabled: false
}
};

开发合约

按照《精通以太坊》书本的例子写了一个“水龙头”的合约,只有两个函数,一个提取以太币(withdraw),一个接收以太币(receive)。合约的代码放在项目的contracts目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.8.0;

// my first contract
contract Faucet {

// Give out ether to anyone who asks
function withdraw(uint withdraw_amount) public {
// limit withdrawal amount
require(withdraw_amount <= 1 * 1000 * 1000 * 1000 * 1000);

msg.sender.transfer(withdraw_amount);
}

// Accept any incoming amount 一个默认的函数
// fallback () external payable {}

// 推荐使用receive()函数来接收以太币
receive() external payable{}

}

编译合约

1
truffle compile

部署合约

参考migrations/1_initial_migration.js文件,新建一个``migrations/2_initial_migration.js;(*注意:2_initial_migration.js这个名字不能以0`开头,否则部署的时候会忽略。。。*)修改文件内容如下:

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

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

然后部署到本地的测试网络(要在本地启动truffle的ethereum测试网络):

1
truffle migrate --network localnode

执行完成之后的日志如下:

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
68
69
70
71
72
(base) w:Faucet apple$ truffle migrate --network localnode

Compiling your contracts...
===========================
> Compiling ./contracts/Faucet.sol
> Artifacts written to /Users/apple/code/open-source/my-projects/etherum/Faucet/build/contracts
> Compiled successfully using:
- solc: 0.8.0+commit.c7dfd78e.Emscripten.clang



Starting migrations...
======================
> Network name: 'localnode'
> Network id: 1337
> Block gas limit: 6721975 (0x6691b7)


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

Replacing 'Migrations'
----------------------
> transaction hash: 0xaf813946c7470d83c7fb1e8b535efebcfdd90540c72ee9092ae9e04775bf56bc
> Blocks: 0 Seconds: 0
> contract address: 0x4445151f84Fd5E71aB93f0d9A1AC93fd7a454c10
> block number: 1
> block timestamp: 1615445677
> account: 0xc0F680767D4Ae17C7adaF8C6d0b4805Bc207805e
> balance: 99.99511424
> gas used: 244288 (0x3ba40)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00488576 ETH


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


2_initial_migration.js
======================

Replacing 'Faucet'
------------------
> transaction hash: 0xcb451bc9f9347fa9983e5182ccc4453f1a3c7ac74b5c7785679913a7325f7715
> Blocks: 0 Seconds: 0
> contract address: 0x6B7d6480BC95EF2C51d2Ae247bDd2aC3bBA5690c
> block number: 3
> block timestamp: 1615445677
> account: 0xc0F680767D4Ae17C7adaF8C6d0b4805Bc207805e
> balance: 99.9888339
> gas used: 271504 (0x42490)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00543008 ETH


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


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


使用truffle部署智能合约到ropsten测试网络

可以在https://infura.io/网站建立账号,可以把它看成一个geth节点,然后他提供了rpc接口。

首次执行部署,报错说HDWalletProvider没有定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ truffle migrate --network ropsten

Compiling your contracts...
===========================
> Compiling ./contracts/GZToken.sol
> Compiling ./contracts/Token2.sol
> Compiling ./contracts/Token3.sol
> Artifacts written to /Users/apple/code/open-source/my-projects/etherum/Faucet/build/contracts
> Compiled successfully using:
- solc: 0.8.0+commit.c7dfd78e.Emscripten.clang

ReferenceError: HDWalletProvider is not defined
at Object.provider (/Users/apple/code/open-source/my-projects/etherum/Faucet/truffle-config.js:62:21)
at Object.getProvider (/usr/local/lib/node_modules/truffle/build/webpack:/packages/provider/index.js:20:1)
at Object.create (/usr/local/lib/node_modules/truffle/build/webpack:/packages/provider/index.js:13:1)
at TruffleConfig.get [as provider] (/usr/local/lib/node_modules/truffle/build/webpack:/packages/config/dist/configDefaults.js:204:1)
at Object.detect (/usr/local/lib/node_modules/truffle/build/webpack:/packages/environment/environment.js:19:1)
at Object.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/commands/migrate.js:206:1)
at Command.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/command.js:136:1)
Truffle v5.2.3 (core: 5.2.3)
Node v14.16.0

解决方案:

添加依赖:
1
2
npm install truffle-hdwallet-provider
npm install dotenv
在与truffle-config.js相同的目录下添加.evn文件,然后写入你的助记词,如:
1
mnemonic=topic foster find apple famous have bonus month remain middle funny smart

注意在gitignore文件中把这个文件(.evn)忽略,避免上传到github

修改truffle-config.js:在最上面添加:
1
2
3
4
5
6
7
8
// 在使用infura作为rpc-api的提供方时,因为infura不管理我们的私钥所以要在本地有一个钱包用于签名
var HDWalletProvider = require("truffle-hdwallet-provider");
// 默认读取项目根目录下的.env文件,用process.env.调用
const result = require('dotenv').config();
if (result.error) {
throw result.error;
}

修改ropsten的配置如下:
1
2
3
4
5
6
7
8
ropsten: {
provider: () => new HDWalletProvider(process.env.mnemonic, `https://ropsten.infura.io/v3/your-id`),
network_id: 3, // Ropsten's id
gas: 5500000, // Ropsten has a lower block limit than mainnet
confirmations: 2, // # of confs to wait between deployments. (default: 0)
timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
},

重新部署:

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
$truffle migrate --network ropsten

Compiling your contracts...
===========================
> Compiling ./contracts/GZToken.sol
> Compiling ./contracts/Token3.sol
> Artifacts written to /Users/apple/code/open-source/my-projects/etherum/Faucet/build/contracts
> Compiled successfully using:
- solc: 0.8.0+commit.c7dfd78e.Emscripten.clang



Starting migrations...
======================
> Network name: 'ropsten'
> Network id: 3
> Block gas limit: 8000000 (0x7a1200)


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

Deploying 'Migrations'
----------------------

Error: *** Deployment Failed ***

"Migrations" -- insufficient funds for gas * price + value.

at /usr/local/lib/node_modules/truffle/build/webpack:/packages/deployer/src/deployment.js:365:1
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at Migration._deploy (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/Migration.js:74:1)
at Migration._load (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/Migration.js:61:1)
at Migration.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/Migration.js:212:1)
at Object.runMigrations (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/index.js:150:1)
at Object.runFrom (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/index.js:110:1)
at Object.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/migrate/index.js:87:1)
at runMigrations (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/commands/migrate.js:263:1)
at Object.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/commands/migrate.js:228:1)
at Command.run (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/command.js:136:1)
Truffle v5.2.3 (core: 5.2.3)
Node v14.16.0

原因是没有足够的汽油费,去https://faucet.ropsten.be/获取即可。

在此重新部署:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
$ truffle migrate --network ropsten

Compiling your contracts...
===========================
> Compiling ./contracts/Token3.sol
> Artifacts written to /Users/apple/code/open-source/my-projects/etherum/Faucet/build/contracts
> Compiled successfully using:
- solc: 0.8.0+commit.c7dfd78e.Emscripten.clang



Starting migrations...
======================
> Network name: 'ropsten'
> Network id: 3
> Block gas limit: 8000000 (0x7a1200)


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

Deploying 'Migrations'
----------------------
> transaction hash: 0x212e4eee81d5fc3024db8d6787b36a51f070f832b94961605de18675d8f597d3
> Blocks: 2 Seconds: 13
> contract address: 0x0d4f7ff83b66e1E3377aacE5EFA70d9E036890c2
> block number: 9941219
> block timestamp: 1617088554
> account: 0x2560be5793F9AA00963e163A1287807Feb897e2F
> balance: 0.99508824
> gas used: 245588 (0x3bf54)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00491176 ETH

Pausing for 2 confirmations...
------------------------------
> confirmation number: 2 (block: 9941221)

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


2_initial_migration.js
======================

Deploying 'Faucet'
------------------
> transaction hash: 0x40ead4c9b0fa1efb90d551e43587c10457e0c99deeaf4f804222645395435090
> Blocks: 2 Seconds: 21
> contract address: 0xE18C40cEf01bEedA140033892b4638CFBc464BbD
> block number: 9941227
> block timestamp: 1617088603
> account: 0x2560be5793F9AA00963e163A1287807Feb897e2F
> balance: 0.9887139
> gas used: 272804 (0x429a4)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00545608 ETH

Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 9941229)
> confirmation number: 2 (block: 9941230)

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


3_initial_migration.js
======================

Deploying 'GZToken'
-------------------
> transaction hash: 0xf28b9b32e6cbf0b12a44249dd4c6eb914706820daafc671e43cabb4541d5396b
> Blocks: 2 Seconds: 25
> contract address: 0xB8DfEe0D9aC703E75EE3D031148227B3BbB26524
> block number: 9941234
> block timestamp: 1617088662
> account: 0x2560be5793F9AA00963e163A1287807Feb897e2F
> balance: 0.9713052
> gas used: 841622 (0xcd796)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.01683244 ETH

Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 9941235)
> confirmation number: 2 (block: 9941236)

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


Summary
=======
> Total deployments: 3
> Final cost: 0.02720028 ETH

调用合约的报错汇总:

1
2
Received unexpected error:
out of gas
1
intrinsic gas too low: have 0, want 21420 (supplied gas 0)

上面这些全部都是因为gas费设置的太少导致的。

与合约交互

remix中调用合约

remix网页中部署完成合约之后,在下面可以看到withdraw函数,输入提现的金额然后点击transact按钮,就可以调起MetaMask调用智能合约。

image-20210309173249673

使用truffle的命令调用合约

启动truffle的console控制台:
1
truffle console --network localnode
打开之后双击两次tab键会有命令提示,我们查看一下部署的Faucet合约的地址:
1
2
truffle(localnode)> Faucet.address
'0x6B7d6480BC95EF2C51d2Ae247bDd2aC3bBA5690c'
查看我们合约的账户余额:
1
2
truffle(localnode)> web3.eth.getBalance(Faucet.address)
'0'

truffle develop

develop Open a console with a local development blockchain

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
truffle(develop)> Faucet.deployed().then(i => {FaucetDeployed = i})
// 发送给合约1个eth
truffle(develop)> FaucetDeployed.send(web3.utils.toWei("1", "ether")).then(res => { console.log(res.logs[0].event, res.logs[0].args) })
Deposit Result {
'0': '0xAC3509D2d2746B6fEC873087AAB9394d03472131',
'1': BN {
negative: 0,
words: [ 56885248, 2993385, 222, <1 empty item> ],
length: 3,
red: null
},
__length__: 2,
to: '0xAC3509D2d2746B6fEC873087AAB9394d03472131',
amount: BN {
negative: 0,
words: [ 56885248, 2993385, 222, <1 empty item> ],
length: 3,
red: null
}
}
// 从合约提取0.1eth
truffle(develop)> FaucetDeployed.withdraw(web3.utils.toWei("0.1", "ether")).then(res => { console.log(res.logs[0].event, res.logs[0].args) })
Withdrawal Result {
'0': '0xAC3509D2d2746B6fEC873087AAB9394d03472131',
'1': BN {
negative: 0,
words: [ 25821184, 13721111, 22, <1 empty item> ],
length: 3,
red: null
},
__length__: 2,
to: '0xAC3509D2d2746B6fEC873087AAB9394d03472131',
amount: BN {
negative: 0,
words: [ 25821184, 13721111, 22, <1 empty item> ],
length: 3,
red: null
}
}

与Gas费相关的注意事项

如果在执行过程中gas费耗尽,会触发如下一系列事件:

  • 抛出“out of gas”异常
  • 状态被恢复到执行开始之前
  • 所有在这次执行过程中的gas开销都会被作为交易费用,以太坊不会因为交易中止而退回gas或以太币。

如何把合约函数调用的gas费消耗最小化

避免动态尺寸的数组

例如对数组的每个元素进行操作,或者通过遍历的方式查找数组中的某个值。

避免调用其他合约

调用其他合约,特别是那些gas消耗未知的合约,可能会产生高昂的gas开销。

估计gas开销