开发智能合约
网页打开Remix,http://remix.ethereum.org/。点击左上角加号,新建智能合约,取名 market.sol。
新建 struct :
- 商品Commodity
pragma solidity ^0.4.14;
contract Market{
struct Commodity{
string name;
string url;
uint price;
address seller;
address buyer;
uint time;
bool isFinish;
}
}
主角有了,下面该定义他的行为了:
- Seller 发布物品,需要有个容器来保存 谁发布了什么,是不是mapping比较好?
- 还有发布物品的方法。
- Buyer 决定购买要往合约里打钱,还应该定义 收钱方法,正如前文所述,是带payable关键字的方法。
mapping(address => Commodity) warehouse;
function addCommodity(string name, string url, uint price){
require(bytes(name).length > 0);
require(bytes(url).length > 0);
warehouse[msg.sender] = Commodity(name, url, price/(1 ether), msg.sender, 0x0, now, false);
}
我们增加了warehouse(仓库)这个mapping容器,以seller地址作为索引。addCommodity方法中,检验输入参数string长度大于0,Solidity中必须先把string转换为bytes形式才能计算长度。require 检验条件通常放在方法一开头。
price 这里除以 1 ether的意思是 把单位1换算成ether,不然后面的transfer转账操作会出现问题。
msg.sender为调用该合约的地址,在这个方法中就是发布物品的人,因此这样完成mapping 地址与新物品的映射。新Commodity的构造参数顺序必须按照struct定义时顺序,最后的0x0表示地址为空,因为现在刚刚发布还不知道买家是谁。
time参数是 发布时间的意思,简简单单一个now就代表了当前时间戳。
isFinish 表示这件物品是否卖出去了,判断交易是否结束。
等一等。如果卖家以后想要修改之前发布的信息呢?比如根据市场动态,决定提价,或者过了一段时间之后无人问津,决定降价,或者后来卖给其他朋友了呢。因此还需要 删改方法。
function removeCommodity(){
var commodity = warehouse[msg.sender];
require(commodity.seller != 0x0);
delete warehouse[msg.sender];
}
function updateCommodity(string name, uint price){
var commodity = warehouse[msg.sender];
require(commodity.seller != 0x0);
warehouse[msg.sender].name = name;
warehouse[msg.sender].price = price;
warehouse[msg.sender].time = now;
}
delete关键字出现了。正如前文所述,要删除一个元素必须通过它实现。
或许会有疑问:
var commodity = warehouse[msg.sender];
这句已经获取到了commodity对象,直接删改它不就好了么?
之前讲过memory和storage的区别,这里commodity实际上是memory变量,warehouse[msg.sender]获取的是storage变量,当storage变量赋值给memory变量时,实际上是拷贝了一份数据给memory使用,因此修改commodity实际上改的并不是warehouse[msg.sender]这个storage,因此我们这样删改。
还要注意:这里warehouse的索引都是msg.sender,意思是必须是卖家自己才能操作。如不做检查,谁都能改数据岂不乱套了?
下面是 打钱function:
function buy(address seller) payable returns (uint) {
var commodity = warehouse[seller];
require(commodity.seller != 0x0);
warehouse[seller].buyer = msg.sender;
return this.balance;
}
此次实例,我们以卖家address作为索引,来获取对应的物品。在方法一开始还是保留一个良好的习惯:上来先检查有无这个卖家对应的物品。然后记录下买家地址。
最后,就是激动人心的买家确认收货,放款环节了。
function confirmFinish(address seller){
var commodity = warehouse[seller];
require(commodity.seller != 0x0);
require(commodity.buyer == msg.sender);
warehouse[seller].isFinish = true;
commodity.seller.transfer(commodity.price);
}
最重要的是要确定当前调用该方法的地址是买家地址。然后再修改状态,transfer执行向seller地址打钱操作。
转钱操作有两个:
transfer:
发生异常throws。
默认2300gas,不可调整,这样防止可重入攻击(因为2300gas太少了以至于除了转账不能做多余的操作)
send:
- 发送异常返回false。
默认2300gas,不可调整,这样防止可重入攻击。
如果你想处理合约中的失败情况,应该少用。
目前完整代码:
pragma solidity ^0.4.14;
contract Market{
struct Commodity{
string name;
string url;
uint price;
address seller;
address buyer;
uint time;
bool isFinish;
}
mapping(address => Commodity) warehouse;
function buy(address seller) payable returns (uint) {
var commodity = warehouse[seller];
require(commodity.seller != 0x0);
warehouse[seller].buyer = msg.sender;
return this.balance;
}
function addCommodity(string name, string url, uint price){
require(bytes(name).length > 0);
require(bytes(url).length > 0);
warehouse[msg.sender] = Commodity(name, url, price/(1 ether), msg.sender, 0x0, now, false);
}
function removeCommodity(){
var commodity = warehouse[msg.sender];
require(commodity.seller != 0x0);
delete warehouse[msg.sender];
}
function updateCommodity(string name, uint price){
var commodity = warehouse[msg.sender];
require(commodity.seller != 0x0);
warehouse[msg.sender].name = name;
warehouse[msg.sender].price = price/(1 ether);
warehouse[msg.sender].time = now;
}
function confirmFinish(address seller){
var commodity = warehouse[seller];
require(commodity.seller != 0x0);
require(commodity.buyer == msg.sender);
warehouse[seller].isFinish = true;
commodity.seller.transfer(commodity.price);
}
}