Solidity区块链应用新手教程

参考文档:https://archive.trufflesuite.com/guides/pet-shop/#background

Web3开发流程

背景

该项目是truffle开发工具官方的新手教学,项目背景是宠物店开发一个领养宠物的区块链应用

应用架构

应用架构

编写智能合约

编写智能合约需要学习Solidity语言

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
//第一行指定编译器版本
pragma solidity ^0.5.0;

/**
合约简介:该合约是复制的truffle官网的宠物店教学指南
参考资料:https://archive.trufflesuite.com/guides/pet-shop/
*/
contract Adoption {
//这里的16代表合约应用场景中宠物店16个宠物的领养人选
//在区块链中一个用户使用一个20字节的地址代表
//其中换算关系如下:
//一个字节由 8 位组成,而每个十六进制字符表示 4 位,因此一个字节可以用两个十六进制字符表示
//所以一个20字节的地址可以用长度为40的16进制字符串表示
address[16] public adopters;
// Adopting a pet
function adopt(uint petId) public returns (uint) {
require(petId >= 0 && petId <= 15);

adopters[petId] = msg.sender;

return petId;
}
// Retrieving the adopters
// 在应用场景中就是返回全部的领养人的地址
// 函数声明中的 view 关键字表示该函数不会修改合约的状态
function getAdopters() public view returns (address[16] memory) {
return adopters;
}
}

编译和迁移智能合约

编译

执行下面的命令编译合约

1
truffle compile

迁移

运行本地区块链

  1. 下载安装ganache运行一个本地区块链用于部署智能合约
  2. truffle-config.js配置内容如下
1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*" // Match any network id
},
develop: {
port: 8545
}
}
};

执行迁移命令

1
truffle migrate

测试智能合约

编写Solidity代码测试

在test目录中新建TestAdoption.sol,文件内容如下:

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
pragma solidity ^0.5.0;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

contract TestAdoption {
// The address of the adoption contract to be tested
Adoption adoption = Adoption(DeployedAddresses.Adoption());

// The id of the pet that will be used for testing
uint expectedPetId = 8;

//The expected owner of adopted pet is this contract
address expectedAdopter = address(this);

// Testing the adopt() function
function testUserCanAdoptPet() public {
uint returnedId = adoption.adopt(expectedPetId);
Assert.equal(
returnedId,
expectedPetId,
"Adoption of the expected pet should match what is returned."
);
}

// Testing retrieval of a single pet's owner
function testGetAdopterAddressByPetId() public {
address adopter = adoption.adopters(expectedPetId);

Assert.equal(
adopter,
expectedAdopter,
"Owner of the expected pet should be this contract"
);
}

// Testing retrieval of all pet owners
function testGetAdopterAddressByPetIdInArray() public {
// Store adopters in memory rather than contract's storage
address[16] memory adopters = adoption.getAdopters();

Assert.equal(
adopters[expectedPetId],
expectedAdopter,
"Owner of the expected pet should be this contract"
);
}
}

编写JavaScript代码测试

参考使用JavaScript测试智能合约

创建前端与链端交互

前端与链端交互的核心是web3.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
App = {
web3Provider: null,
contracts: {},

init: async function () {
// 加载前端视图需要展示的16个模拟宠物数据
$.getJSON('../pets.json', function (data) {
var petsRow = $('#petsRow');
var petTemplate = $('#petTemplate');

for (i = 0; i < data.length; i++) {
petTemplate.find('.panel-title').text(data[i].name);
petTemplate.find('img').attr('src', data[i].picture);
petTemplate.find('.pet-breed').text(data[i].breed);
petTemplate.find('.pet-age').text(data[i].age);
petTemplate.find('.pet-location').text(data[i].location);
petTemplate.find('.btn-adopt').attr('data-id', data[i].id);

petsRow.append(petTemplate.html());
}
});

return await App.initWeb3();
},

initWeb3: async function () {
// 该方式是web3.js中用于创建区块链连接的
// 版本较新的现代浏览器,这个条件分支会调用MetaMask插件
if (window.ethereum) {
App.web3Provider = window.ethereum;
try {
// Request account access
await window.ethereum.enable();
} catch (error) {
// User denied account access...
console.error("User denied account access")
}
}
// 废弃的旧时代浏览器,没测试
else if (window.web3) {
App.web3Provider = window.web3.currentProvider;
}
// If no injected web3 instance is detected, fall back to Ganache
// 如果没有检测到MetaMask插件注入的实例,就会通过http协议与区块链通信,但是官方不建议:
// If no injected web3 instance is present, we create our web3 object based on our local provider. (This fallback is fine for development environments, but insecure and not suitable for production.)
else {
App.web3Provider = new Web3.providers.HttpProvider('http://10.10.5.197:7545');
}
web3 = new Web3(App.web3Provider);


return App.initContract();
},

initContract: function () {
//Adoption.json文件是通过执行truffle compile编译完得到的编译产物
//该文件通常包含以下几个重要部分:
//合约 ABI (Application Binary Interface):ABI 是一个 JSON 数组,描述了合约的函数、事件和状态变量。它定义了与合约交互所需的信息,包括每个函数的名称、输入参数类型、输出参数类型等。通过 ABI,前端应用或其他合约可以调用这个合约的函数。
//合约字节码 (Bytecode):这是合约的编译结果,以字节形式表示。它是部署到以太坊区块链上的代码。
//合约地址、编译版本、网络信息、合约名称
$.getJSON('Adoption.json', function (data) {

var AdoptionArtifact = data;
// 获取前端可以操作的智能合约的实例
App.contracts.Adoption = TruffleContract(AdoptionArtifact);

// 向智能合约实例设置provider
App.contracts.Adoption.setProvider(App.web3Provider);

// Use our contract to retrieve and mark the adopted pets
return App.markAdopted();
});


return App.bindEvents();
},

bindEvents: function () {
//绑定按钮的点击事件
$(document).on('click', '.btn-adopt', App.handleAdopt);
},

markAdopted: function () {
var adoptionInstance;

App.contracts.Adoption.deployed().then(function (instance) {
//获取已部署的合约的实例
adoptionInstance = instance;
//调用合约中的getAdopters方法
return adoptionInstance.getAdopters.call();
}).then(function (adopters) {
console.log(adopters,'adopters');

for (i = 0; i < adopters.length; i++) {
if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
$('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
}
}
}).catch(function (err) {
console.log(err.message);
});

},

handleAdopt: function (event) {
event.preventDefault();

var petId = parseInt($(event.target).data('id'));

var adoptionInstance;

web3.eth.getAccounts(function (error, accounts) {
if (error) {
console.log(error);
}

var account = accounts[0];

App.contracts.Adoption.deployed().then(function (instance) {
adoptionInstance = instance;

// Execute adopt as a transaction by sending account
return adoptionInstance.adopt(petId, { from: account });
}).then(function (result) {
return App.markAdopted();
}).catch(function (err) {
console.log(err.message);
});
});

}

};

$(function () {
$(window).load(function () {
//jquery的语法,意思是页面加载完毕后执行App.init方法
App.init();
});
});

拓展资源

  1. cryptozombies:通过内容有趣且系统的实战案例学习Solidity语言相关内容

  2. web3研习社:一站式免费学习Web3技术、应用与创新的交流与分享平台

  3. 登链社区:Web3开发者社区

Solidity语言基础内容

函数修饰符

可见性修饰符

  • private 只能被合约内部调用
  • internal能被合约内部和继承的合约调用
  • external只能从合约外部调用
  • public能在任何地方调用

状态修饰符

  • view指明运行函数不会写入任何数据
  • pure指明运行函数不会从区块链读取和写入数据
  • payable指明函数可以接收以太币

自定义修饰符

示例:

1
2
3
4
5
modifier aboveLevel(uint _level, uint _zombieId) {
//修饰符中定义的参数是函数中的参数的子集
require(zombies[_zombieId].level >= _level);
_; //_表示进行目标函数执行
}

合约结构

版本编译指令

合约文件的第一行定义编译器版本,使用pragma solidity ${version}; 语句

1
pragma solidity ^0.5.2;

包含上述行的源文件在版本低于 0.5.2 的编译器上无法编译,并且在版本从 0.6.0 开始的编译器上也无法工作(第二个条件是通过使用 ^ 添加的)

可以为编译器版本指定更复杂的规则,这些规则遵循 npm 使用的相同语法。

导入其他源文件

Solidity 支持导入语句,以帮助模块化代码,这些语句类似于 JavaScript 中可用的导入语句(从 ES6 开始)。 然而,Solidity 不支持 default export 的概念。

1
import "./zombiehelper.sol";

Solidity区块链应用新手教程
http://blog.jingxiang.ltd/2024/09/29/Solidity区块链应用新手教程/
作者
yemangran
发布于
2024年9月29日
许可协议