一、环境准备

本教程基于Mac电脑,这里进行简单说明:Ganache是本地区块链模拟器。Truffle是开发框架,帮助编译、部署和测试合约。Solidity是智能合约的编程语言。

1. 安装Node.js

1
brew install node

2. 安装Ganache

1
npm install -g truffle

3. 安装Truffle

1
npm install -g ganache

4. 安装Solidity

1
npm install -g solc

二、创建智能合约

1
2
3
4
# 1. 创建项目目录
mkdir my-contract && cd my-contract
2. 初始化Truffle项目
truffle init

目录结构说明:

1
2
3
4
├── contracts      # Solidity合约目录
├── migrations # 部署脚本目录
├── test # 测试用例目录
└── truffle-config.js # 配置文件

三、创建智能合约

在contracts 目录下创建 MultiFunctionToken.sol。以下是一个实现多功能代币的智能合约例子,基于ERC20代币标准接口。

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

// ERC20代币标准接口
interface IERC20 {
// 获取代币总供应量
function totalSupply() external view returns (uint256);
// 查询账户余额
function balanceOf(address account) external view returns (uint256);
// 转账函数
function transfer(address recipient, uint256 amount) external returns (bool);
// 查询授权额度
function allowance(address owner, address spender) external view returns (uint256);
// 授权函数
function approve(address spender, uint256 amount) external returns (bool);
// 授权转账函数
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
// 转账事件
event Transfer(address indexed from, address indexed to, uint256 value);
// 授权事件
event Approval(address indexed owner, address indexed spender, uint256 value);
}

// 多功能代币合约,实现ERC20标准
contract MultiFunctionToken is IERC20 {
// 账户余额映射
mapping(address => uint256) private _balances;
// 授权额度映射
mapping(address => mapping(address => uint256)) private _allowances;
// 代币总供应量
uint256 private _totalSupply;
// 代币名称
string private _name;
// 代币符号
string private _symbol;
// 代币小数位数,默认为18
uint8 private _decimals = 18;
// 合约所有者地址
address public owner;
// 用户数据存储映射
mapping(address => string) private userData;
// 暂停状态
bool private _paused;
// 黑名单映射
mapping(address => bool) private _blacklist;
// 代币元数据URI
string private _tokenURI;

// 数据存储事件
event DataStored(address indexed user, string data);
// 所有权转移事件
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// 暂停事件
event Paused(address account);
// 恢复事件
event Unpaused(address account);
// 黑名单事件
event Blacklisted(address indexed account);
// 移除黑名单事件
event Unblacklisted(address indexed account);
// 代币销毁事件
event Burn(address indexed from, uint256 value);
// 元数据更新事件
event TokenURIUpdated(string newURI);

// 仅所有者修饰器
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}

// 未暂停修饰器
modifier whenNotPaused() {
require(!_paused, "Contract is paused");
_;
}

// 非黑名单修饰器
modifier notBlacklisted(address account) {
require(!_blacklist[account], "Account is blacklisted");
_;
}

constructor(string memory tokenName, string memory tokenSymbol) {
owner = msg.sender;
_name = tokenName;
_symbol = tokenSymbol;
_paused = false;
_mint(msg.sender, 1000000 * 10 ** _decimals);
}

// 暂停合约功能
function pause() public onlyOwner {
_paused = true;
emit Paused(msg.sender);
}

// 恢复合约功能
function unpause() public onlyOwner {
_paused = false;
emit Unpaused(msg.sender);
}

// 添加地址到黑名单
function blacklist(address account) public onlyOwner {
_blacklist[account] = true;
emit Blacklisted(account);
}

// 从黑名单移除地址
function unblacklist(address account) public onlyOwner {
_blacklist[account] = false;
emit Unblacklisted(account);
}

// 检查地址是否在黑名单中
function isBlacklisted(address account) public view returns (bool) {
return _blacklist[account];
}

// 销毁代币
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}

// 批量转账
function batchTransfer(address[] memory recipients, uint256[] memory amounts) public whenNotPaused notBlacklisted(msg.sender) {
require(recipients.length == amounts.length, "Arrays length mismatch");
for (uint256 i = 0; i < recipients.length; i++) {
_transfer(msg.sender, recipients[i], amounts[i]);
}
}

// 设置代币元数据URI
function setTokenURI(string memory newURI) public onlyOwner {
_tokenURI = newURI;
emit TokenURIUpdated(newURI);
}

// 获取代币元数据URI
function tokenURI() public view returns (string memory) {
return _tokenURI;
}

// 存储用户数据
function storeData(string memory data) public whenNotPaused notBlacklisted(msg.sender) {
userData[msg.sender] = data;
emit DataStored(msg.sender, data);
}

// 查询用户数据
function getData(address user) public view returns (string memory) {
return userData[user];
}

// 转移合约所有权
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "New owner is the zero address");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}

// 铸造新代币(仅所有者)
function mint(address to, uint256 amount) public onlyOwner whenNotPaused {
_mint(to, amount);
}

// 获取代币名称
function name() public view returns (string memory) {
return _name;
}

// 获取代币符号
function symbol() public view returns (string memory) {
return _symbol;
}

// 获取代币小数位数
function decimals() public view returns (uint8) {
return _decimals;
}

// 获取代币总供应量
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}

// 查询账户余额
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}

// 转账函数
function transfer(address recipient, uint256 amount) public override whenNotPaused notBlacklisted(msg.sender) notBlacklisted(recipient) returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}

// 查询授权额度
function allowance(address tokenOwner, address spender) public view override returns (uint256) {
return _allowances[tokenOwner][spender];
}

// 授权函数
function approve(address spender, uint256 amount) public override whenNotPaused notBlacklisted(msg.sender) returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}

// 授权转账函数
function transferFrom(address sender, address recipient, uint256 amount) public override whenNotPaused notBlacklisted(sender) notBlacklisted(recipient) returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, msg.sender, _allowances[sender][msg.sender] - amount);
return true;
}

// 内部转账函数
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
require(_balances[sender] >= amount, "ERC20: transfer amount exceeds balance");

_balances[sender] -= amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}

// 内部铸造函数
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
}

// 内部销毁函数
function _burn(address account, uint256 amount) internal {
require(account != address(0), "ERC20: burn from the zero address");
require(_balances[account] >= amount, "ERC20: burn amount exceeds balance");

_balances[account] -= amount;
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
emit Burn(account, amount);
}

// 内部授权函数
function _approve(address tokenOwner, address spender, uint256 amount) internal {
require(tokenOwner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[tokenOwner][spender] = amount;
emit Approval(tokenOwner, spender, amount);
}
}

四、创建迁移脚本

在migrations 目录下创建 1_deploy_contracts.js。这里文件名前缀数字代表执行顺序。

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

module.exports = function(deployer) {
deployer.deploy(MultiFunctionToken, "MultiFunctionToken", "MFT");
};

五、编写测试

在 test 目录下创建 multiFunctionToken.test.js:

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
const MultiFunctionToken = artifacts.require("MultiFunctionToken");

// 主测试套件:测试多功能代币合约
contract("MultiFunctionToken", accounts => {
// 初始化测试账户和参数
let token;
const owner = accounts[0]; // 合约所有者账户
const user1 = accounts[1]; // 测试用户1
const user2 = accounts[2]; // 测试用户2
const user3 = accounts[3]; // 测试用户3
const initialSupply = 1000000 * 10 ** 18; // 初始供应量(带18位小数)

// 每个测试用例前的初始化操作
beforeEach(async () => {
// 部署新的合约实例
token = await MultiFunctionToken.new("MultiFunctionToken", "MFT");
});

// 测试用例:验证初始供应量
it("should have correct initial supply", async () => {
// 获取总供应量
const totalSupply = await token.totalSupply();
// 验证供应量是否正确(注意处理大数类型)
assert.equal(totalSupply.toString(), "1000000000000000000000000", "Initial supply is incorrect");
});

// 测试用例:数据存储和检索功能
it("should allow data storage and retrieval", async () => {
const testData = "test data";
// 用户1存储测试数据
await token.storeData(testData, {from: user1});
// 从用户1地址检索数据
const retrievedData = await token.getData(user1);
// 验证存储和检索的数据一致性
assert.equal(retrievedData, testData, "Data storage/retrieval failed");
});

// 测试用例:代币转账功能
it("should transfer tokens correctly", async () => {
const amount = 1000;
// 所有者向用户1转账
await token.transfer(user1, amount, {from: owner});
// 查询用户1余额
const balance = await token.balanceOf(user1);
// 验证余额是否正确
assert.equal(balance.toString(), amount.toString(), "Token transfer failed");
});

// 测试用例:铸造功能(仅所有者)
it("should allow owner to mint new tokens", async () => {
const amount = 5000;
// 所有者给用户2铸造代币
await token.mint(user2, amount, {from: owner});
// 查询用户2余额
const balance = await token.balanceOf(user2);
// 验证铸造是否成功
assert.equal(balance.toString(), amount.toString(), "Minting failed");
});

// 测试用例:非所有者禁止铸造
it("should prevent non-owner from minting", async () => {
try {
// 用户1尝试非法铸造
await token.mint(user1, 1000, {from: user1});
// 如果未抛出错误则测试失败
assert.fail("Non-owner should not be able to mint");
} catch (error) {
// 验证错误信息包含权限控制提示
assert.include(error.message, "Only owner can call this function", "Expected owner restriction");
}
});

// 测试用例:所有权转移功能
it("should transfer ownership correctly", async () => {
// 所有者将权限转移给用户1
await token.transferOwnership(user1, {from: owner});
// 查询新所有者地址
const newOwner = await token.owner();
// 验证所有权转移是否成功
assert.equal(newOwner, user1, "Ownership transfer failed");
});

// 测试用例:合约暂停功能
it("should pause and unpause contract", async () => {
// 所有者暂停合约
await token.pause({from: owner});
let paused = true;
try {
// 尝试在暂停状态下转账
await token.transfer(user1, 100, {from: owner});
} catch (error) {
paused = false; // 成功捕获到暂停状态下的错误
}
// 验证合约确实被暂停
assert.equal(paused, false, "Contract should be paused");

// 所有者解除暂停
await token.unpause({from: owner});
// 再次尝试转账
await token.transfer(user1, 100, {from: owner});
const balance = await token.balanceOf(user1);
// 验证转账成功
assert.equal(balance.toString(), "100", "Contract should be unpaused");
});

// 测试用例:黑名单管理功能
it("should manage blacklist correctly", async () => {
// 将用户2加入黑名单
await token.blacklist(user2, {from: owner});
let isBlacklisted = await token.isBlacklisted(user2);
// 验证黑名单状态
assert.equal(isBlacklisted, true, "User should be blacklisted");

let transferFailed = false;
try {
// 尝试向黑名单用户转账
await token.transfer(user2, 100, {from: owner});
} catch (error) {
transferFailed = true; // 成功拦截黑名单转账
}
assert.equal(transferFailed, true, "Blacklisted user should not receive tokens");

// 将用户2移出黑名单
await token.unblacklist(user2, {from: owner});
isBlacklisted = await token.isBlacklisted(user2);
// 验证黑名单状态已解除
assert.equal(isBlacklisted, false, "User should be unblacklisted");
});

// 测试用例:代币销毁功能
it("should burn tokens correctly", async () => {
// 获取所有者初始余额
const initialBalance = await token.balanceOf(owner);
const burnAmount = 1000;
// 执行销毁操作
await token.burn(burnAmount, {from: owner});
// 获取销毁后余额
const newBalance = await token.balanceOf(owner);
// 验证余额是否正确减少
assert.equal(newBalance.toString(), "999999999999999999999000", "Token burn failed");
});

// 测试用例:批量转账功能
it("should perform batch transfer", async () => {
// 设置接收地址和金额数组
const recipients = [user1, user2, user3];
const amounts = [100, 200, 300];
// 执行批量转账
await token.batchTransfer(recipients, amounts, {from: owner});

// 分别查询三个用户的余额
const balance1 = await token.balanceOf(user1);
const balance2 = await token.balanceOf(user2);
const balance3 = await token.balanceOf(user3);

// 验证每个用户的到账金额
assert.equal(balance1.toString(), "100", "Batch transfer failed for user1");
assert.equal(balance2.toString(), "200", "Batch transfer failed for user2");
assert.equal(balance3.toString(), "300", "Batch transfer failed for user3");
});

// 测试用例:Token元数据管理
it("should manage token URI", async () => {
const testURI = "https://example.com/token-metadata.json";
// 设置元数据URI
await token.setTokenURI(testURI, {from: owner});
// 获取当前URI
const currentURI = await token.tokenURI();
// 验证URI设置是否正确
assert.equal(currentURI, testURI, "Token URI management failed");
});
});

六、后续合约操作

1
2
3
4
5
6
7
8
9
10
# 启动本地测试网络行后,Ganache会启动一个本地以太坊节点
# 创建 10 个预设的测试账户,每个账户都有 100 ETH
# 提供 RPC 端点(通常是 http://127.0.0.1:8545)
npx ganache
# 编译合约
npx truffle compile
# 部署合约
npx truffle migrate
# 运行测试
npx truffle test

七、计算合约gas消耗

1.创建一个专门的测试脚本来测量 gas 消耗

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
# 前提条件
# 1、确保 Ganache 正在运行
# 2、部署合约
# 2、测试不同功能的 gas 消耗
const MultiFunctionToken = artifacts.require("MultiFunctionToken");

contract("MultiFunctionToken", (accounts) => {
let token;
const owner = accounts[0];
const user1 = accounts[1];
const user2 = accounts[2];

before(async () => {
token = await MultiFunctionToken.new("TestToken", "TST");
});

describe("Gas Consumption Tests", () => {
it("Deployment gas cost", async () => {
const receipt = await token.transactionReceipt;
console.log("Deployment gas used:", receipt.gasUsed);
});

it("Transfer gas cost", async () => {
const receipt = await token.transfer(user1, 100, { from: owner });
console.log("Transfer gas used:", receipt.gasUsed);
});

it("Approve gas cost", async () => {
const receipt = await token.approve(user2, 100, { from: user1 });
console.log("Approve gas used:", receipt.gasUsed);
});

it("TransferFrom gas cost", async () => {
const receipt = await token.transferFrom(user1, user2, 100, { from: user2 });
console.log("TransferFrom gas used:", receipt.gasUsed);
});

it("Pause/Unpause gas cost", async () => {
const pauseReceipt = await token.pause({ from: owner });
console.log("Pause gas used:", pauseReceipt.gasUsed);

const unpauseReceipt = await token.unpause({ from: owner });
console.log("Unpause gas used:", unpauseReceipt.gasUsed);
});

it("Add/Remove blacklist gas cost", async () => {
const blacklistReceipt = await token.addToBlacklist(user1, { from: owner });
console.log("Add to blacklist gas used:", blacklistReceipt.gasUsed);

const unblacklistReceipt = await token.removeFromBlacklist(user1, { from: owner });
console.log("Remove from blacklist gas used:", unblacklistReceipt.gasUsed);
});
});
});

2.运行测试,打印消耗

1
2
3
npx truffle test
# 查询实时Gas价格
curl -s https://ethgasstation.info/api/ethgasAPI.json | jq '.fastest/10'

3.交易状态监控

1
2
3
4
5
const receipt = await web3.eth.getTransactionReceipt(txHash);
if(receipt.status) {
console.log("交易成功");
console.log("消耗Gas:", receipt.gasUsed);
}

八、调用合约

1.交互式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 进入交互式控制台
truffle console
# 在truffle控制台中可执行以下命令
// 获取合约实例
const instance = await MultiFunctionToken.deployed()

// 调用合约方法示例
// 1. 查询总供应量
const totalSupply = await instance.totalSupply()
console.log("Total Supply:", totalSupply.toString())

// 2. 执行代币转账(需替换实际地址)
const recipient = "0x..." // 目标地址
await instance.transfer(recipient, 1000, {from: accounts[0]})

// 3. 铸造新代币(仅限owner)
await instance.mint("0x...", 5000, {from: accounts[0]})

// 4. 暂停合约
await instance.pause({from: accounts[0]})

2.web3交易调用

1
2
3
4
5
6
7
8
9
10
11
12
const Web3 = require('web3');
const web3 = new Web3('https://goerli.infura.io/v3/YOUR_PROJECT_ID');
const contract = new web3.eth.Contract(abi, contractAddress);

// 执行黑名单操作
contract.methods.blacklist("0x...").send({
from: "0x...",
gas: 300000
})
.on('transactionHash', hash => {
console.log('Tx Hash:', hash);
});

九、部署至主网

1.使用 Infura 或 Alchemy 获取主网 RPC URL

2.修改 truffle-config.js,添加主网配置

1
2
3
4
5
6
7
mainnet: {
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, process.env.MAINNET_RPC_URL),
network_id: 1,
gas: 5500000,
confirmations: 2,
timeoutBlocks: 200
}

3.部署合约

1
npx truffle migrate --network mainnet

4.验证部署

1
2
const instance = await MultiFunctionToken.deployed()
console.log("Contract deployed at:", instance.address)

5.查询余额

1
2
const balance = await instance.balanceOf(owner)
console.log("Owner balance:", balance.toString())

6.查询代币信息

1
2
3
4
5
6
const name = await instance.name()
const symbol = await instance.symbol()
const decimals = await instance.decimals()
console.log("Name:", name)
console.log("Symbol:", symbol)
console.log("Decimals:", decimals)

7.查询代币余额

1
2
const balance = await instance.balanceOf(owner)
console.log("Owner balance:", balance.toString())

8.查询代币总供应量

1
2
const totalSupply = await instance.totalSupply()
console.log("Total supply:", totalSupply.toString())

9.查询代币暂停状态

1
2
const paused = await instance.paused()
console.log("Paused:", paused)

10.查询代币黑名单状态

1
2
const blacklisted = await instance.blacklisted(user1)
console.log("Blacklisted:", blacklisted)

11.查询代币代币地址

1
2
const tokenAddress = instance.address
console.log("Token address:", tokenAddress)

12.查询代币代币名称

1
2
const name = await instance.name()
console.log("Name:", name)

13.查询代币代币符号

1
2
const symbol = await instance.symbol()
console.log("Symbol:", symbol)

14.查询代币代币精度

1
2
const decimals = await instance.decimals()
console.log("Decimals:", decimals)

十、特别说明

本教程只用于区块链的学习,不得转载和用于商业用途。