开发智能合约
网页打开 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);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论