学校期末web作业,要实现一个简单的网上购书平台,写到一半的时候发现支付这一块可以借助智能合约来完成。虽然接触过一点区块链的知识,但还没有动手写过智能合约,于是匆忙学习了一下Solidity和Smart Contract的基础,开始编写我的第一个智能合约,顺便记录一下学习历程。

先看下官网给的一个例子 https://solidity.readthedocs.io/en/develop/solidity-by-example.html#safe-remote-purchase

pragma solidity >=0.4.22 <0.6.0;contract Purchase {uint public value;address payable public seller;address payable public buyer;enum State { Created, Locked, Inactive }State public state;// Ensure that `msg.value` is an even number.// Division will truncate if it is an odd number.// Check via multiplication that it wasn't an odd number.constructor() public payable {seller = msg.sender;value = msg.value / 2;require((2 * value) == msg.value, "Value has to be even.");}modifier condition(bool _condition) {require(_condition);_;}modifier onlyBuyer() {require(msg.sender == buyer,"Only buyer can call this.");_;}modifier onlySeller() {require(msg.sender == seller,"Only seller can call this.");_;}modifier inState(State _state) {require(state == _state,"Invalid state.");_;}event Aborted();event PurchaseConfirmed();event ItemReceived();/// Abort the purchase and reclaim the ether./// Can only be called by the seller before/// the contract is locked.function abort()publiconlySellerinState(State.Created){emit Aborted();state = State.Inactive;seller.transfer(address(this).balance);}/// Confirm the purchase as buyer./// Transaction has to include `2 * value` ether./// The ether will be locked until confirmReceived/// is called.function confirmPurchase()publicinState(State.Created)condition(msg.value == (2 * value))payable{emit PurchaseConfirmed();buyer = msg.sender;state = State.Locked;}/// Confirm that you (the buyer) received the item./// This will release the locked ether.function confirmReceived()publiconlyBuyerinState(State.Locked){emit ItemReceived();// It is important to change the state first because// otherwise, the contracts called using `send` below// can call in again here.state = State.Inactive;// NOTE: This actually allows both the buyer and the seller to// block the refund - the withdraw pattern should be used.buyer.transfer(value);seller.transfer(address(this).balance);}
}

合约存在的问题

首先卖家创建合约并交两倍原价的押金,此时合约状态为Created,卖家可以执行abort取消贩卖退回押金。买家支付两倍原价进行购买,其中一倍作押金。确认收货后,各自退回押金,商品的钱交给卖家。

合约原理很简单,但存在一个很大的问题,一份合约只能用于一次交易,如果一个商品库存有100个,卖家就要去部署100次这样的合约,不仅麻烦还难于管理。

其次,合约试图用押金来约束买家和卖家的行为,但实际上,如果买家故意不确认收货,卖家会比买家更亏,也就是说没有做到平衡。比如商品价格为p,卖家支付2p的押金,发货后等于暂时失去了3p的资金;买家支付2p的押金,得到了价格为p的商品,他等于只失去了价值为p的资金。这时买家相比卖家是赚的,他有理由恶意拒绝确认。

主要解决思路

问题的解决参考了下面两篇文章(要翻墙)
https://medium.com/coinmonks/creating-smart-contracts-with-smart-contract-d54e21d26e00
https://medium.com/coinmonks/escrow-service-as-a-smart-contract-the-business-logic-5b678ebe1955

  1. 利用合约来部署合约。一类商品可以产生多个订单,卖家只需要部署每一类商品的合约;买家通过调用商品合约来创建订单合约,订单合约的地址记录在商品合约中。
  2. 增加发货和收货期限(通过记录时间戳实现)来约束买家和卖家;

(以下的代码参考原文章的代码,修改了很大一部分)

pragma solidity^0.4.22;// 商品合约
contract Product {bool public enable;         // 当前商品是否有效address public sellerAddr;  // 卖家的地址uint public price;          // 商品的单价uint public commitTime;     // 卖家承诺发货时间address[] public orders;    // 所有订单的地址address public lastOrder;   // 最新订单的地址modifier onlySeller() {require(sellerAddr == msg.sender,"卖家账号地址错误!");_;}constructor(bool _enable, uint _price, uint _commitTime)public{require(_price != 0 && _commitTime != 0,"参数信息不完整");sellerAddr = msg.sender;enable = _enable;price = _price;commitTime = _commitTime;}function setEnable(bool _enable)publiconlySeller{enable = _enable;}function setPrice(uint _price)publiconlySeller{price = _price;}function setCommitTime(uint _commitTime)publiconlySeller{commitTime = _commitTime;}function getOrderCount()publicviewreturns(uint _count){return orders.length;}// 买家创建订单,输入购买数量和两倍原价的msg.valueevent NewOrder(address _orderAddr);function newOrder(uint amount)publicpayable{require(enable == true,"产品当前无法下单");require(amount != 0,"购买数量不能为0");Order order = (new Order).value(msg.value)(msg.sender, sellerAddr, price * amount, commitTime);orders.push(order);lastOrder = order;emit NewOrder(order);}
}// 订单合约
contract Order {// 已支付,已接单,已发货,已签收,已收货,已失效enum Status {Paid, Taken, Shipped, Signed, Received, Inactive}address public buyerAddr;      // 买家的地址address public sellerAddr;     // 卖家的地址uint public price;             // 商品的总价uint public commitTime;        // 卖家承诺发货时间Status public status;          // 订单的状态uint public createTime;        // 订单的创建(支付)时间uint public signTime;          // 订单的签收时间string public trackingNumber;  // 订单的物流号uint8 public score;            // 订单的评分string public assession;       // 订单的评价modifier inStatus(Status _status) {require(status == _status,"订单的状态不可操作");_;}modifier onlyBuyer() {require(buyerAddr == msg.sender,"买家账号地址错误!");_;}modifier onlySeller() {require(sellerAddr == msg.sender,"卖家账号地址错误!");_;}function getBalance()publicviewreturns(uint _balance){return address(this).balance;}constructor(address _buyerAddr, address _sellerAddr, uint _price, uint _commitTime)publicpayable{require(msg.value == _price * 2,"买家需要额外支付与商品价格等价的金额作为押金");buyerAddr = _buyerAddr;sellerAddr = _sellerAddr;price = _price;commitTime = _commitTime;status = Status.Paid;createTime = now;}// 卖家接单之前用户可以取消购买function abort()publiconlyBuyerpayableinStatus(Status.Paid){status = Status.Inactive;buyerAddr.transfer(price * 2);}// 卖家选择接单或拒绝接单function take(bool _takeOrder)publiconlySellerpayableinStatus(Status.Paid){if (_takeOrder) {require(msg.value == price,"卖家需要支付与商品价格等价的金额作为押金");status = Status.Taken;}else {status = Status.Inactive;buyerAddr.transfer(price * 2);}}// 买家检查卖家是否没按时发货,如果是则退还买家钱,同时作为惩罚,卖家的押金被锁定在合约里了function checkLate()publiconlyBuyerinStatus(Status.Taken){require(now - createTime > commitTime,"尚未到卖家发货截止期限");status = Status.Inactive;buyerAddr.transfer(price * 2);}// 卖家输入运单号,确认发货function ship(string _trackingNumber)publiconlySellerpayableinStatus(Status.Taken){// todo: 检查运单号是否真实存在status = Status.Shipped;trackingNumber = _trackingNumber;sellerAddr.transfer(price); // 卖家发货后退回押金}// 确认签收function sign()publicinStatus(Status.Shipped){// todo: 通过运单号查询物流信息,判断是否签收并获取签收时间status = Status.Signed;// signTime = getSignTime();signTime = now; // 测试用}// 买家确认收货function receive()publiconlyBuyerpayableinStatus(Status.Signed){status = Status.Received;buyerAddr.transfer(price);sellerAddr.transfer(price);}// 过了10天买家没确认收货,则卖家可以自己确认function confirmBySeller()publiconlySellerpayableinStatus(Status.Signed){require(now - signTime > 10 days,"卖家超过10天才可以确认");status = Status.Received;buyerAddr.transfer(price);sellerAddr.transfer(price);}// 买家对订单进行评价function assess(uint8 _score, string _assession)publiconlyBuyerinStatus(Status.Received){require(_score >= 1 && _score <= 5,"评分只能是1~5之间的整数");score = _score;assession = _assession;status = Status.Inactive;}
}

合约整体流程

  1. 首先卖家部署商品合约,声明自己的账户地址、商品的价格、承诺的发货时间。
  2. 买家通过商品合约部署订单合约(注意订单合约的msg.sender是商品合约的地址)支付商品价格p+押金p,买家等待卖家接单,期间可以取消购买,退还2p。
  3. 卖家如果接单,需要交付价值为p的押金,同时要在自己承诺的发货时间内发货。如果拒绝接单,则合约将钱退还买家。
  4. 如果卖家没能在承诺时间发货,买家可以调用checkLate,退回押金,同时作为惩罚,卖家的押金将无法取回(可能有方法取回?)。
  5. 卖家确认发货时要填写运单(物流)号,为了验证这个订单号,需要物流公司接入以太坊并提供接口,这里就不模拟了,直接跳过验证。同样,签收的过程也偷懒省略了。
  6. 接着买家确认收货,为了防止恶意不确认或买家忘记确认,签收10天后卖家可以自己确认。
  7. 最后买家对订单进行评价。合约没有实现退货等功能。

代码在Remix上可以测试,在前端调用合约时用web3.js。

最后,我自己也是刚入门智能合约,这个代码的逻辑上和功能上肯定还存在许多漏洞,所以仅供参考。

用Solidity写一个网上购物智能合约相关推荐

  1. 开发一个简单的智能合约

    一.环境搭建 搭建Truffle框架 简介:这是一个流行的以太坊开发框架,内置了智能合约编译,连接,部署等功能 Truffle框架依赖Node,需要使用npm来安装,首先需要安装node,npm会同时 ...

  2. 动手编写一个以太坊智能合约

    如何部署.调用智能合约 RPC 之前的章节中讲到了怎么写.部署合约以及与合约互动.现在该讲讲与以太坊网络和智能合约沟通的细节了. 一个以太坊节点提供一个RPC界面.这个界面给Ðapp(去中心化应用)访 ...

  3. 不同步节点在线使用Remix开发以太坊Dapp及solidity学习入门 ( 一 ):智能合约HelloWorld

    有问题可以点击–>加群互相学习 本人本来想自己写公链,结果发现任重道远: 遂,开始写Dapp,顺便写的时候搞个教程吧... 通过系列教程学习将会: 1.基本使用solidity 语言开发智能合约 ...

  4. 用solidity语言开发代币智能合约

    智能合约开发是以太坊编程的核心之一,而代币是区块链应用的关键环节,下面我们来用solidity语言开发一个代币合约的实例,希望对大家有帮助. 以太坊的应用被称为去中心化应用(DApp),DApp的开发 ...

  5. Solidity入门级别|用智能合约实现房屋贷款系统

    Home Loans Based on Smart Contract for Banks 代码目的: 创造一个自运行的房屋贷款系统,减轻银行许多传统金融合约操作流程的负担 执行摘要: 贷款申请人在线登 ...

  6. UBTC主网上线智能合约以及实现混合共识机制

    经过前一阶段广泛深入的测试,UnitedBitcoin("UB"比特联储)很荣幸地宣布将支持智能合约以及工作量证明("POW")和权益证明("POS& ...

  7. solidity采坑日记之智能合约返回事件内容解析

    最近在用java调用solidity智能合约方法的时候,遇到了方法Log解析的问题. 正常在调用合约后,如果该合约有event时间,那么执行结果会返回对应的log日志,但是返回的log日志是0x开头的 ...

  8. solidity modifier函数修改器 智能合约开发知识浅学(三)

    环境说明: Ide:在线remix Solidity IDE 语言:solidity solidity 版本号:0.4.20 Tip:如果一点都不懂的建议从头开始看 运行结果截图我不赘述,所有合约代码 ...

  9. solidity不同sol文件的智能合约调用 A调用B

    直接进入正题 上代码 DemoSimple.sol 这是B合约 // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0 ...

最新文章

  1. Linux系统下配置Java环境
  2. python使用matplotlib绘图sigmoid_使用matplotlib库绘制函数图
  3. 使Java具有响应性的框架和工具包:RxJava,Spring Reactor,Akka和Vert.x概述
  4. 629. K个逆序对数组
  5. Linux读写缓存Page Cache
  6. Perl程序设计中常用的函数
  7. html5离线缓存使用
  8. minmaxloc matlab,opencv minmaxloc 最大最小匹配值是什么意思
  9. OTSU大津法对图像二值化
  10. linux系统时间分区的设置方法分享
  11. 0-1背包问题(需要输出具体背包序号)
  12. STM32软件IIC速度
  13. delphi android 打印机,delphi中如何检测打印机状态?(在线等) ( 积分: 100 )
  14. 2003系统服务器不设置密码,服务器2003怎么设置密码
  15. 对校招生培养工作的建议_贵单位对我校学生培养工作有何建议
  16. 使用fiddler实现手机抓包
  17. Big Faceless Java Pdf报表生成器
  18. Latex排版[1]:输入矩阵(latex如何输入矩阵、对角阵、方程组)
  19. python读取二维数组的行列数_Python获取二维数组的行列数的2种方法
  20. 新视野|大数据时代的信息安全

热门文章

  1. 滚球控制系统(B 题 本科组)-- 2017 年全国大学生电子设计竞赛试题
  2. 什么是3G-SDI?它与HD-SDI有什么区别?
  3. 《深入理解Scala》——第1章,第1.2节当函数式编程遇见面向对象
  4. h61支持服务器内存吗,H61主板支持什么CPU、内存条、显卡,最佳升级方案
  5. 【Himi转载推荐之一】如何优化cocos2d/x程序的内存使用和程序大小
  6. 基于esp32的简易宿舍开门神器
  7. 一个无经验的大学生毕业后两只脚踏入游戏测试的坑——我的回忆杀
  8. 计算机组成原理(6)总线 带宽 波特率 总线仲裁
  9. Flutter折叠展开列表的使用
  10. 一套详细的安卓软件反编译教程