用Solidity写一个网上购物智能合约

学校期末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()
        public
        onlySeller
        inState(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()
        public
        inState(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()
        public
        onlyBuyer
        inState(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)
		public
		onlySeller
	{
		enable = _enable;
	}

	function setPrice(uint _price)
		public
		onlySeller
	{
		price = _price;
	}

	function setCommitTime(uint _commitTime)
		public
		onlySeller
	{
		commitTime = _commitTime;
	}

	function getOrderCount()
		public
		view
		returns(uint _count)
	{
		return orders.length;
	}

	// 买家创建订单,输入购买数量和两倍原价的msg.value
	event NewOrder(address _orderAddr);
	function newOrder(uint amount)
		public
		payable
	{
		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()
		public
		view
		returns(uint _balance)
	{
		return address(this).balance;
	}

	constructor(address _buyerAddr, address _sellerAddr, uint _price, uint _commitTime)
		public
		payable
	{
		require(
			msg.value == _price * 2,
			"买家需要额外支付与商品价格等价的金额作为押金"
		);
		buyerAddr = _buyerAddr;
		sellerAddr = _sellerAddr;
		price = _price;
		commitTime = _commitTime;
		status = Status.Paid;
		createTime = now;
	}

	// 卖家接单之前用户可以取消购买
	function abort()
		public
		onlyBuyer
		payable
		inStatus(Status.Paid)
	{
		status = Status.Inactive;
		buyerAddr.transfer(price * 2);
	}

	// 卖家选择接单或拒绝接单
	function take(bool _takeOrder)
		public
		onlySeller
		payable
		inStatus(Status.Paid)
	{
		if (_takeOrder) {
			require(
				msg.value == price,
				"卖家需要支付与商品价格等价的金额作为押金"
			);
			status = Status.Taken;
		}
		else {
			status = Status.Inactive;
			buyerAddr.transfer(price * 2);
		}
	}

	// 买家检查卖家是否没按时发货,如果是则退还买家钱,同时作为惩罚,卖家的押金被锁定在合约里了
	function checkLate()
		public
		onlyBuyer
		inStatus(Status.Taken)
	{
		require(
			now - createTime > commitTime,
			"尚未到卖家发货截止期限"
		);
		status = Status.Inactive;
		buyerAddr.transfer(price * 2);
	}

	// 卖家输入运单号,确认发货
	function ship(string _trackingNumber)
		public
		onlySeller
		payable
		inStatus(Status.Taken)
	{
		// todo: 检查运单号是否真实存在
		status = Status.Shipped;
		trackingNumber = _trackingNumber;
		sellerAddr.transfer(price); // 卖家发货后退回押金
	}

	// 确认签收
	function sign()
		public
		inStatus(Status.Shipped)
	{
		// todo: 通过运单号查询物流信息,判断是否签收并获取签收时间
		status = Status.Signed;
		// signTime = getSignTime();
		signTime = now; // 测试用
	}

	// 买家确认收货
	function receive()
		public
		onlyBuyer
		payable
		inStatus(Status.Signed)
	{
		status = Status.Received;
		buyerAddr.transfer(price);
		sellerAddr.transfer(price);
	}

	// 过了10天买家没确认收货,则卖家可以自己确认
	function confirmBySeller()
		public
		onlySeller
		payable
		inStatus(Status.Signed)
	{
		require(
			now - signTime > 10 days,
			"卖家超过10天才可以确认"
		);
		status = Status.Received;
		buyerAddr.transfer(price);
		sellerAddr.transfer(price);
	}

	// 买家对订单进行评价
	function assess(uint8 _score, string _assession)
		public
		onlyBuyer
		inStatus(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。

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

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值