作业背景

刚上大学,得知有我们学校跳骚市场(二手交易市场)这个东西的时候,我是十分惊喜的,因为居然还有这么方便的东西存在,但同时,既然是民间组织,肯定还是有不规范性和一定的风险性。直到了解到了区块链,我突然回想起了这件事,是不是可以把区块链应用到跳骚市场上,大大提升安全性和可靠性,还有交易的透明可溯源也得到了保证,于是我开始利用这次编写智能合约的机会探索区块链与交易的结合。

作业思路

有了想法,最初我是写了一个操作的框架,对于二手交易市场而言,不同于普通的商品交易,更应该注重的是二手商品的描述,也就是几成新,哪里有些破损,可能会有些部位不灵敏之类的,所以,考虑到这些,我编写以下内容:

首先商品属性有:id,name,description,price,purchase(是否已经被购买)

1
2
3
4
5
6
7
8
9
10
struct Product{

uint id;
string name;
string description;
uint price;
address payable owner;
bool purchased;

}

1).上架商品createProduct

需要输入三个参数,分别是商品的name,description,price

如果输入错误的话会相应报错提示,name,description的长度不能为0,price价格也不可能为负数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createProduct(string memory _name , string memory _description,uint _price) public {

//确保合法
if(bytes(_name).length <= 0)
revert("Product's name's length should be positive!");
if(bytes(_description).length <= 0)
revert("Product's description's length should be positive!");
if(_price <= 0)
revert("Product's price should be positive!");
productCount++;
//创建一个产品
products[productCount] = Product(productCount,_name,_description,_price,msg.sender,false);
emit ProductCreated(productCount,_name,_description,_price,msg.sender,false);
}

2).购买商品purchaseProduct

按商品的id进行购买(id号是和商品捆绑在一起的,每上架一个商品都会和一个id进行mapping)

这个也设计了鲁棒性,商品的id不能输入为负值,不然会报错“Product id的问题”,钱要足够,不然会报错“Fee not enough!”,而且自己不能购买自己的商品,不能购买已经被购买的商品。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function purchaseProduct(uint _id) public payable {

Product memory _product = products[_id];
address payable _seller = _product.owner;
//make sure that...
if(_product.id < 0 || _product.id > productCount)
revert("Product's id should bewteen 1~productCount!");
if(msg.value < _product.price)
revert("Fee not enough!");
if(_product.purchased)
revert("This Product has been purchased!");
if(_seller == msg.sender)
revert("You can't buy your own products!");

_product.owner = msg.sender;
_product.purchased = true;

//update the imformation of products
products[_id]= _product;
address(_seller).transfer(msg.value);
emit ProductPurchased(productCount,_product.name,_product.description,_product.price,msg.sender,true);
}

3).查询商品

我将查询分为了五种:

  1. 单个商品查询,这样查询会获得包括卖家地址的一切信息

  2. 详细的所有商品展示,包括商品的id,name,description,price,purchase信息

  3. 简要的所有商品展示,包括商品的id,name,price,purchase信息

  4. 未购买的所有商品展示,包括商品的id,name,price信息

  5. 已购买的所有商品展示,包括商品的id,name,price信息

1
2
3
4
5
6
7
8
9
function getAllProductDes() view public returns(string memory){

string memory res = "-----HITSZ Flea Market";

for(uint i=1;i<=productCount;i++){
res=string(abi.encodePacked (res,"-----NO",uint2str(i),":","[Name]:",products[i].name,", Description]:",products[i].description,",[Price]:",uint2str(products[i].price),", [State]:",boolstr(products[i].purchased)));
}
return res;
}

1
2
3
4
5
6
7
8
9
10
function getAllProductBrief() view public returns(string memory){

string memory res = "-----HITSZ Flea Market";

for(uint i=1;i<=productCount;i++){
res=string(abi.encodePacked (res,"-----NO",uint2str(i),":","[Name]:",products[i].name,", [Price]:",uint2str(products[i].price),", [State]:",boolstr(products[i].purchased)));
}
return res;

} //No description

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
function getUnpurchasedProduct() view public returns(string memory){
string memory res = "---*Unpurchased Products:*";
uint i =1;
uint j=1;
while(j<=productCount){
if(!products[j].purchased){
res=string(abi.encodePacked (res,"-----NO",uint2str(j),":","[Name]:",products[j].name,", [Price]:",uint2str(products[j].price) ));
i++;
}
j++;
}
if(i == 1)
return "No Unpurchased Products";
return res;
}



function getPurchasedProduct()view public returns(string memory){
string memory res = "---*Purchased Products:*";
uint i =1;
uint j=1;
while(j<=productCount){
if(products[j].purchased){
res=string(abi.encodePacked (res,"-----NO",uint2str(j),":","[Name]:",products[j].name,", [Price]:",uint2str(products[j].price) ));
i++;
}
j++;
}
if(i == 1)
return "No Purchased Products";
return res;
}

4).下架商品

按照商品的id进行下架操作,但是前提是调用合约的人一定是产品的owner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function undercarriage(uint _id) public returns (string memory){
if(products[_id].owner == msg.sender)
{
for(uint i = _id;i <productCount;i++){
products[i]=products[i+1];
}
productCount--;
return "Product has been undercarriaged";
}
else
return "You are not the owner!";


}

Xchain上的实操:

0). 预备知识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*-----合约实战-----*/
编译合约
solc --bin --abi market.sol -o .
部署合约
./xchain-cli evm deploy --account XC1111111111111111@xuper --cname fleamarketevm --fee 520000 ../../xuperchain/core/contractsdk/Xchain-HITSZ-flea/TiaoSao.bin --abi ../../xuperchain/core/contractsdk/Xchain-HITSZ-flea/TiaoSao.abi -H:39101

/*-------
||调用合约||
---------*/

//第一种是不需要fee的
无参数
./xchain-cli evm query --method getName -a '{"0":""}' Helloworldevm --abi Helloworld.abi -H:39101 --name xuper

有参数:"形参":"实参" 的格式
./xchain-cli evm query --method testPure -a '{"a":"5","b":"7"}' Helloworldevm --abi Helloworld.abi -H:39101 --name xuper
./xchain-cli evm query --method candidates -a '{"":"0"}' movievoteevm --abi MovieBallot.abi -H:39101 --name xuper

//第二种是需要fee的,method就是函数,然后传参就是形参+实参
./xchain-cli evm invoke --method changeName -a '{"_name":"TsingHua"}' Helloworldevm --fee 222 --abi Helloworld.abi -H:39101 --name xuper
./xchain-cli evm invoke --method voteCandidates -a '{"index":"0"}' movievoteevm --fee 52000 --abi MovieBallot.abi -H:39101 --name xuper


1).出售商品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//出售商品,如果输入错误会进行不同的报错
./xchain-cli evm invoke --method createProduct -a '{"_name":"iPhone 12 purple","_description":"9.5New with a little damage on the back","_price":"5300"}' fleamarketevm --fee 52000 --abi TiaoSao.abi -H:39101 --name xuper

./xchain-cli evm invoke --method createProduct -a '{"_name":"iPad Air 4","_description":"9New,used for 10 months,Just used for noting and studying","_price":"4000"}' fleamarketevm --fee 52000 --abi TiaoSao.abi -H:39101 --name xuper

./xchain-cli evm invoke --method createProduct -a '{"_name":"AirPods Pro","_description":"9.9New,just open for checking,never uses","_price":"1500"}' fleamarketevm --fee 52000 --abi TiaoSao.abi -H:39101 --name xuper

//报错演示
./xchain-cli evm invoke --method createProduct -a '{"_name":"","_description":"9.5New with a little damage on the back","_price":"5300"}' fleamarketevm --fee 52000 --abi TiaoSao.abi -H:39101 --name xuper



if(bytes(_name).length <= 0)
revert("Product's name's length should be positive!");
if(bytes(_description).length <= 0)
revert("Product's description's length should be positive!");
if(_price <= 0)
revert("Product's price should be positive!");


/*xchain目前不支持公链部署evm合约,所以这部分在SolidityIDE中演示

报错演示

2).购买商品

由于xchain现在公链不支持evm部署,所以我就用IDE演示一下

3).展示商品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
查询商品
//按序号查询商品:包括商品的卖家地址
./xchain-cli evm query --method products -a '{"":"1"}' fleamarketevm --abi TiaoSao.abi -H:39101 --name xuper

//详细的商品展示,包括商品描述
./xchain-cli evm query --method getAllProductDes -a '{"0":""}' fleamarketevm --abi TiaoSao.abi -H:39101 --name xuper

//简略的商品展示
./xchain-cli evm query --method getAllProductBrief -a '{"0":""}' fleamarketevm --abi TiaoSao.abi -H:39101 --name xuper

//已经出售的商品,如果没有已出售的商品,会显示无商品出售过
./xchain-cli evm query --method getPurchasedProduct -a '{"0":""}' fleamarketevm --abi TiaoSao.abi -H:39101 --name xuper

//未出售的商品,如果商品都被买了,会显示无未出售商品
./xchain-cli evm query --method getUnpurchasedProduct -a '{"0":""}' fleamarketevm --abi TiaoSao.abi -H:39101 --name xuper

4). 下架商品

1
2
//下架商品
./xchain-cli evm invoke --method undercarriage -a '{"_id":"1"}' fleamarketevm --fee 520000 --abi TiaoSao.abi -H:39101 --name xuper

Solidity编程的感想:

我选择的是用solidity编写智能合约,solidity是一门很新的语言,专门为区块链编写智能合约而设计,对我来说,这是一次陌生的机遇和挑战。困难还是挺多的,比如说国内solidity资源很少,所以我就去YouTube上自学,加上看官方文档辅助,这过程里面碰到了很多不理解的问题,但是多亏了学长学姐和baidu老师的耐心讲解,难题基本都解决了,现在还剩一个是公链部署合约的问题,但是这个属于baidu的xchain的一点小遗憾,xchain暂时没有公链部署evm的功能,也希望xchain后面可以完善。

现在简单说一下solidity的一些坑:solidity版本变化极大,也就是版本之间存在不兼容的问题,而且不同版本的编程语法居然还不一样,当时学的时候确实有点晕。

不仅如此,而且有时恰恰还需要不同版本的合约,solc版本需要频繁切换,为了解决这个问题,我在github上找到了solc-select这个软件,可以随意切换solc version

然后就是语言的特性吧,其实像我设计这种返回商品信息的操作,换做其他语言,直接一个printf函数搞定了,但是solidity并没有输出函数,只能return一个string,这样的话其实是需要一个小技巧,数字uint型,bool型要转换成对应的string才能被返回,设计了如下函数解决这些问题。

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

contract TiaoSao{

string public name;
uint public productCount = 0;
mapping (uint => Product) public products;

struct Product{

uint id;
string name;
string description;
uint price;
address payable owner;
bool purchased;

}

//原来以为Xchain会有响应的
event ProductCreated(
uint id,
string name,
string description,
uint price,
address payable owner,
bool purchased
);

event ProductPurchased(
uint id,
string name,
string description,
uint price,
address payable owner,
bool purchased
);


constructor() public {
name= "-----HITSZ Flea Market-----";
} //constructor is NO return


function createProduct(string memory _name , string memory _description,uint _price) public {

//确保合法
if(bytes(_name).length <= 0)
revert("Product's name's length should be positive!");
if(bytes(_description).length <= 0)
revert("Product's description's length should be positive!");
if(_price <= 0)
revert("Product's price should be positive!");
productCount++;
//创建一个产品
products[productCount] = Product(productCount,_name,_description,_price,msg.sender,false);
emit ProductCreated(productCount,_name,_description,_price,msg.sender,false);
}


function purchaseProduct(uint _id) public payable {

Product memory _product = products[_id];
address payable _seller = _product.owner;
//make sure that...
if(_product.id < 0 || _product.id > productCount)
revert("Product's id should bewteen 1~productCount!");
if(msg.value < _product.price)
revert("Fee not enough!");
if(_product.purchased)
revert("This Product has been purchased!");
if(_seller == msg.sender)
revert("You can't buy your own products!");

_product.owner = msg.sender;
_product.purchased = true;

//update the imformation of products
products[_id]= _product;
address(_seller).transfer(msg.value);
emit ProductPurchased(productCount,_product.name,_product.description,_product.price,msg.sender,true);
}

function getAllProductBrief() view public returns(string memory){

string memory res = "-----HITSZ Flea Market";

for(uint i=1;i<=productCount;i++){
res=string(abi.encodePacked (res,"-----NO",uint2str(i),":","[Name]:",products[i].name,", [Price]:",uint2str(products[i].price),", [State]:",boolstr(products[i].purchased)));
}
return res;

} //No description


function getAllProductDes() view public returns(string memory){

string memory res = "-----HITSZ Flea Market";

for(uint i=1;i<=productCount;i++){
res=string(abi.encodePacked (res,"-----NO",uint2str(i),":","[Name]:",products[i].name,", Description]:",products[i].description,",[Price]:",uint2str(products[i].price),", [State]:",boolstr(products[i].purchased)));
}
return res;
}



function getUnpurchasedProduct() view public returns(string memory){
string memory res = "---*Unpurchased Products:*";
uint i =1;
uint j=1;
while(j<=productCount){
if(!products[j].purchased){
res=string(abi.encodePacked (res,"-----NO",uint2str(j),":","[Name]:",products[j].name,", [Price]:",uint2str(products[j].price) ));
i++;
}
j++;
}
if(i == 1)
return "No Unpurchased Products";
return res;
}



function getPurchasedProduct()view public returns(string memory){
string memory res = "---*Purchased Products:*";
uint i =1;
uint j=1;
while(j<=productCount){
if(products[j].purchased){
res=string(abi.encodePacked (res,"-----NO",uint2str(j),":","[Name]:",products[j].name,", [Price]:",uint2str(products[j].price) ));
i++;
}
j++;
}
if(i == 1)
return "No Purchased Products";
return res;
}


function undercarriage(uint _id) public returns (string memory){
if(products[_id].owner == msg.sender)
{
for(uint i = _id;i <productCount;i++){
products[i]=products[i+1];
}
productCount--;
return "Product has been undercarriaged";
}
else
return "You are not the owner!";


}

//function getSeller()

function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
if (_i == 0) {
return "0";
}
uint j = _i;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len;
while (_i != 0) {
k = k-1;
uint8 temp = (48 + uint8(_i - _i / 10 * 10));
bytes1 b1 = bytes1(temp);
bstr[k] = b1;
_i /= 10;
}
return string(bstr);
}

function boolstr(bool _i) internal pure returns (string memory){
if (_i == false)
return "Unpurchased";
else
return "Purchased";
}


function char(bytes1 b) internal pure returns (bytes1 c) {
if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
else return bytes1(uint8(b) + 0x57);
}

}

总结:通过本次课程,收获了很多,包括区块链的一些概念,到熟悉Linux环境,以及智能合约的编写。希望以后也能多多参加这样的一些很棒的课程。