实验6-1 会议报名登记系统的基本功能与实现。
系统功能要求
合约参与方包含一个管理员以及其余参与者,管理员可以发起不止一个会议,并指定会议信息以及总人数。参与者首先需要进行注册,将个人基本信息与以太坊地址相关联,并存储在合约上,之后可进行报名,或委托他人为自己报名。当会议报名人满时,该会议将不再可报名。
当合约内某些数据发生变化时,应能够触发事件(event)使前端重新获取并渲染数据,例如当某个会议报名人满时,应触发相应事件使前端及时更新可报名会议列表。
1.实验步骤 & 实验代码
在delegate函数中建立受托人到其委托人的一个映射trustees,是一个不定长数组,每次被委托的人都往后push即可。代码如下:
function delegate(address addr) public {
trustees[addr].push(participants[msg.sender]);
}
enrollFor函数中实现受托人为委托人报名功能,报名流程与enroll逻辑相同,不同是需要先在trustees中找到委托人,然后再报名。代码如下:
function enrollFor(string memory username,string memory title) public returns(string memory){
uint index = 0;
for (uint i = 0; i < trustees[msg.sender].length; i++) { // 先找到委托人
if (keccak256(bytes(trustees[msg.sender][i].name)) == keccak256(bytes(username))) {
index = i;
break;
}
}
for (uint i = 0; i < conferences.length; i++){
if (keccak256(bytes(conferences[i].title)) == keccak256(bytes(title))){
require(conferences[i].current<conferences[i].max,"Enrolled full");
conferences[i].current = conferences[i].current+1;
if(conferences[i].current==conferences[i].max){
emit ConferenceExpire(title);
}
trustees[msg.sender][index].confs.push(title);
}
}
uint len = trustees[msg.sender][index].confs.length;
require(len>0,"Conference does not exist");
return trustees[msg.sender][index].confs[len-1];
}
2. 练习:
应在合约的哪个函数指定管理员身份?如何指定?
在合约的构造函数中指定,用address public admin指定admin为管理员;
在发起新会议时,如何确定发起者是否为管理员?简述 require()、assert()、revert()的区别。
使用以下判断语句:
require(msg.sender==admin,"permission denied");
如下三个语句与
if(msg.sender != owner) {
throw;
}
效果等价
if(msg.sender != owner) {
revert();
}
assert(msg.sender == owner);
require(msg.sender == owner);
revert()处理与require()同样的类型,二者在执行失败时都会将剩余gas费返还给调用者,但是assert()就算执行失败也不返还gas费。
简述合约中用 memory 和 storage 声明变量的区别。
Storage 变量是指永久存储在区块链中的变量。Memory变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。
状态变量(在函数之外声明的变量)默认为storage形式,并永久写入区块链;而在函数内部声明的变量默认是memory型的,它们函数调用结束后消失。
类型 | 性质 |
---|---|
memory | 可以跨函数共享 |
storage | 只能函数内共享 |
但是有一些情况,需要手动声明存储类型。主要用于处理函数内的结构体和数组,这两个属性主要用于优化代码来节省合约的gas消耗。
实验 6-2 学习用 Truffle 组件部署和测试合约。
1. 安装Truffle和Ganache
建议使用虚拟机,win会比linux稍微好一点(linux 相关工具版本可能会不太适配,更近过程比较繁琐)。
安装 Truffle 需要用到包管理工具 npm,以及结合后续实验需要,首先应安装nodejs(可以从这里下载:https://nodejs.org/en/)。在官网下载安装 nodejs,该操作会同时安装好包管理工具 npm。完成后可在终端输入npm -v
查看 npm 版本。
接下来进行 truffle 的安装:打开系统的终端,输入npm install truffle -g
即可。完成安装之后可输入 truffle -v
查看版本信息。
下一步安装 Ganache,Ganache 有图形界面和命令行两种版本,命令行支持数据持久化。为了操作方便,本处仅下载图形界面客户端即可,https://www.trufflesuite.com/ganache 下载安装完成后,其界面如图。
2. 为合约编写测试文件
测试合约来鉴定功能是否正常,代码如下:
pragma solidity >=0.4.25 <0.7.0;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Enrollment.sol";
contract TestEnrollment{
function testSignUp() public {
Enrollment en = new Enrollment();
(string memory name, ) = en.signUp("alice","male");
(string memory expected_name, ) = ("alice","male");
Assert.equal(name,expected_name,"signup failed");
}
function testNewConf() public{
Enrollment enroll = new Enrollment();
string memory expected = "conf1";
Assert.equal(enroll.newConference("conf1","beijing",30),expected,"new conference failed");
}
function testEnroll() public{
Enrollment en = new Enrollment();
string memory expected = "conf1";
en.newConference("conf1","beijing",30);
Assert.equal(en.enroll("conf1"), expected,"Enrolled full");
}
}
进行测试:
truffle test
结果如图:
truffle migrate
结果如图:
合约部署成功。
3. 练习 :观察合约的部署过程:请观察部署完成后 Ganache 的 Blocks、Transactions 以及 Logs 记录,完整叙述合约的部署流程以及合约调用
合约部署的主要流程包括以下4步:第一步是启动一个以太坊节点,之后第二步编写智能合约,第三步是将编写后的智能合约经以太坊虚拟机的编译,成为计算机可运行的字节码,第四步是合约发起用户将编译好的字节码文件通过发起交易的形式广播到区块链网络中,由矿工挖矿确认后即可将智能合约存入区块链中,并得到智能合约所在地址及调用合约所需接口。
合约调用:在节点触发智能合约条件需要使用合约时,区块链将调取智能合约字节码在本地运行,然后将运行结果再保存入区块链账本中。
实验 6-3 利用 Web3.js 实现合约与前端的结合
练习 6-3:请参考注册组件代码中交互代码的写法,完成另外四个表单类组件对合约的调用。
首先配置Metamask(这里指导书没有写!!注意!!不配置或者配置不好最后会出现网页不显示的情况!千万要配置!!)。
之后在前端项目文件中配置合约信息,配置src/contracts/contract.js
中的abi和合约地址
代码如下:
import Web3 from 'web3';
//在此粘贴ABI信息
const abi = [ // 粘贴你的ABI信息
];
// 在此粘贴合约地址(去掉0x)
const address = '';// 粘贴你的合约地址
// window.web3.currentProvider为当前浏览器的web3 Provider
const web3 = new Web3(window.web3.currentProvider);
// 允许连接到metamask
window.ethereum.enable();
// 导出合约实例
export default new web3.eth.Contract(abi, address);
之后完成组件代码中交互代码:
// delegate
submit(address) {
contract.methods.delegate(address)
.send({from:window.web3.eth.accounts[0]},function(err,res){console.log(res)})
.then((res)=>console.log(res));
// enrollFor
submit(username,title) {
contract.methods.enrollFor(username,title)
.send({from:window.web3.eth.accounts[0]},function(err,res){console.log(res)})
.then();
// myConf
componentDidMount(){
contract.methods.queryMyConf()
.call({from:window.web3.eth.accounts[0]},(err,res)=>{
this.setState({loading: true});
if(res != null){
for(var i=0;i<res.length;i=i+1){
data.push({'title': res[i]});
}
}
else{
data.push({'title': 'no'});
}
})
.then(()=>{
this.setState({loading: false});
});
}
之后使用npm install
和npm start
后,可以看到:
(一开始所有的框应该都是空的,这里是最后才截的图)
注册用户:
创建会议:
![在这里插入图片描述](https://img-blog.csdnimg.cn/396067edfd114a4db08a800ee67d9ca6.png
报名会议: