天天看點

拍得到-智能合約

拍得到智能合約流程

先把邏輯捋順再看代碼

拍得到-智能合約
拍得到-智能合約

競标規則

  1. 僅可在競标時間段内競标,競标結束後⽆法再次競标,競标⻚⾯關閉
  2. 每個⼈都可以參與競标。(賣家⾃⼰也可以參與競标)
  3. 每個⼈可以多次對同⼀産品競标。(我可以看到别⼈⽐我⾼的時候,再次出價;可以使⽤戰略, 多次出價迷惑敵⼈)
  4. 流程:

    每個⼈競标時輸⼊兩個數字和⼀個密碼:

迷惑價格:30元(打到合約的⾦額,後⾯揭标時會退還差價,公開)

理想出價:10元(隐秘,不會在競标函數中作為顯示參數傳⼊)

密碼字元串(隐秘)

系統記錄相關資訊

迷惑價格必須⼤于理想價格時,否則視為⽆效競标,⽆效競标可以送出,⽤于迷惑競争者,揭标 時⾃動退款。

競标成功的⼈最終會以 次⾼價 購買商品(買家花費的錢⽐⼼理預期少,賣家⾄少會得到起拍 價)。

  1. 揭标規則
  2. 僅限于 競标結束 和 終結拍賣 之前這段時間内完成
  3. 流程: i. 每個⼈輸⼊⼀個數字和⼀個密碼: i. 理想出價(同競标時的數字) ii. 密碼(同競标時的字元串) ii. 揭标時會⼀直動态更新産品的最⾼的出價⼈、最⾼價、次⾼價格。 iii. 淘汰的⼈得到退款(當初轉⼊的 迷惑價格 )。 iv. 最⾼價的⼈得到迷惑價格與理想出價的差額。
  4. 如果參與競标但是未及時揭标,⽆法⾃動退款
  5. 終結競拍規則
  6. 由任意⼀個第三⽅的⼈來執⾏終結。
  7. 流程: i. 建立⼀個 第三⽅合約 ,将次⾼價⾦額、買家、賣家、仲裁⼈資訊轉⼊到第三⽅合約。 ii. 轉差額(最⾼價-次⾼價的)給買家。
  8. 對第三⽅合約投票
  9. 合約中含有買家,賣家,仲裁⼈,次⾼價⾦額。
  10. 僅這三個⼈可以進⾏投票,且每⼈⼀票。
  11. 票數達到兩票的⼈獲得合約中的⾦額,拍賣結束

1.定義商品結構

struct Product {
        //
        uint id; //商品唯一id
        string name;//商品名稱
        string category;//商品類别
        string imageLink;//ipfs-->圖檔連結
        string descLink;//ipfs-->商品描述資訊連結

        uint startPrice;//競拍起始價格
        uint auctionStartTime;//競拍開始時間
        uint auctionEndTime;//競拍結束時間

        ProductStatus status; 
        ProductCondition condition;
     
             //競标資訊
        uint highestBid;   //最高出價, 50,理想價格
        address highestBidder; //最高出價人
        uint secondHighestBid; //次高價,40
        uint totalBids; //所有的競标數量  
    }
	enum ProductStatus {Open, Sold, Unsold} //競标中,賣了,沒賣出
    enum ProductCondition {Used, New} 新的,舊的
           

2.存儲商品結構

mapping(賣家位址address => mapping(産品id => 産品Product)) stores
mapping(産品id => 賣家位址address) productToOwner
           

3.添加商品函數

function addProductToStore(string _name, string _category, string _imageLink, string _descLink, uint _startTime, uint _endTime, uint _startPrice, uint condition) public {
        productIndex++; //全局唯一
        
        Product memory product = Product({
            id : productIndex,
            name : _name,
            category : _category,
            imageLink : _imageLink,
            descLink : _descLink,
            
            startPrice : _startPrice,
            auctionStartTime : _startTime,
            auctionEndTime : _endTime,
    
            status : ProductStatus.Open,
            condition : ProductCondition(condition)
            
            highestBid: 0,
            highestBidder : 0,
            secondHighestBid : 0,
            totalBids : 0   
            });
            
        //把建立好的商品添加到我們的商店結構中
        stores[msg.sender][productIndex] = product;
        productIdToOwner[productIndex] = msg.sender;
    }
}
           

4.通過商品id擷取商品資訊

function getProductById(uint _productId) public view returns (uint, string, string, string, string, uint, uint, uint, uint){
        
      //通過id向定義的兩個mapping結構中查詢并傳回
        address owner = productIdToOwner[_productId];
        Product memory product = stores[owner][_productId];
        
        return (
            product.id, product.name, product.category, product.imageLink, product.descLink,
            product.auctionStartTime, product.auctionEndTime, product.startPrice, uint(product.status)
        );
           

5.定義競标結構

struct Bid {
        uint productId;//産品id
        uint price; //迷惑價格,不是理想價格
        bool isRevealed;//是否揭标
        address bidder;//競标人
    }
           

6.在Product中增加存儲所有合約自己相關競标的結構

// 存儲所有的競标人對這個商品的競标(與stroes類似)
        mapping(address => mapping(bytes32 => Bid)) bids;
           

7.添加競标函數bid

function bid(uint _productId, uint _idealPrice, string _secret) public payable {
        //将傳入的理想價格和明文進行keccak256加密成密文
        bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
        bytes32 bytesHash = keccak256(bytesInfo);
        
        //通過id擷取商品資訊
        address owner = productIdToOwner[_productId];
        Product storage product = stores[owner][_productId];
        
        //每一個競标必須大于等于起始價格
        require(msg.value >= product.startPrice);
        
        //競标總數+1
        product.totalBids++;
        
        //存儲競标人對這個商品的競标
        Bid memory bidLocal = Bid(_productId, msg.value, false, msg.sender);
        product.bids[msg.sender][bytesHash] = bidLocal; 
    }
           

8.驗證競标

//擷取指定的bid資訊
    function getBidById(uint _productId, uint _idealPrice, string _secret) public view returns (uint, uint, bool, address) {
        
        //通過id擷取商品資訊
        address owner = productIdToOwner[_productId];
        Product storage product = stores[owner][_productId];
        
        //通過參數生成密文
        bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
        bytes32 bytesHash = keccak256(bytesInfo);
		
        //從競标mapping取出競标資訊并傳回
        Bid memory bidLocal = product.bids[msg.sender][bytesHash];
        return (bidLocal.productId, bidLocal.price, bidLocal.isRevealed, bidLocal.bidder);
    }
           

9.揭标

//揭标事件,做log日志傳回給用戶端
    event revealEvent(uint productid, bytes32 bidId, uint idealPrice, uint price, uint refund);
    
    function revealBid(uint _productId, uint _idealPrice, string _secret) public {
       
        //通過id取商品資訊
        address owner = productIdToOwner[_productId];
        Product storage product = stores[owner][_productId];
        
        //生成密文
        bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
        bytes32 bidId = keccak256(bytesInfo);
        
        //mapping(address => mapping(bytes32 => Bid)) bids;
        
        //一個人可以對同一個商品競标多次,揭标的時候也要揭标多次, storage類型
        //擷取目前id的bid資訊
        Bid storage currBid = product.bids[msg.sender][bidId];
        
        //目前時間要大于競拍時間,測試的時候可以關閉
        require(now > product.auctionStartTime);
        
       //每個标隻能揭标一次,判斷是否揭過
        require(!currBid.isRevealed);
        
        //說明找到了這個标, 找到了正确的标
        require(currBid.bidder != address(0));  

        currBid.isRevealed = true;

        //bid中的是迷惑價格,真實價格揭标時傳遞進來
        uint confusePrice = currBid.price;

        //退款金額, 程式最後,統一退款
        uint refund = 0;
        
        //理想價格
        uint idealPrice = _idealPrice;
        
        //迷惑價格小于理想價格
        if (confusePrice < idealPrice) {
            //路徑1:無效交易,退還金額
            refund = confusePrice;
        } else {
            //理想價格大于最高價
            if (idealPrice > product.highestBid) {
                if (product.highestBidder == address(0)) {
                    //目前賬戶是第一個揭标人
                    //路徑2:
                    product.highestBidder = msg.sender;
                    product.highestBid = idealPrice;
                    product.secondHighestBid = product.startPrice;
                    refund = confusePrice - idealPrice;
                } else {
                    //路徑3:不是第一個,但是出價是目前最高的,更新最高競标人,最高價格,次高價格
                    product.highestBidder.transfer(product.highestBid);
                    product.secondHighestBid = product.highestBid;
                    
                    product.highestBid = idealPrice; //wangwu 40
                    product.highestBidder = msg.sender; //wangwu
                    refund = confusePrice - idealPrice; //10
                }
            } else {
                //路徑4:價格低于最高價,但是高于次高價
                if (idealPrice > product.secondHighestBid) {
                    //路徑4:更新次高價,然後拿回自己的錢
                    product.secondHighestBid = idealPrice;
                    refund = confusePrice; //40
                
                } else {
                    //路徑5:路人甲,價格低于次高價,直接退款
                    refund = confusePrice;
                }
            }
        }
	//送出事件
        emit revealEvent(_productId, bidId, confusePrice, currBid.price, refund);
		//退款
        if (refund > 0) {
            msg.sender.transfer(refund);
        }
    }
    
           

10.終結函數

//key是産品id,value:是第三方合約
    //全局唯一, 用于投票時,找到這個第三方合約。
	mapping(uint => address) public productToEscrow;
    
    function finalaizeAuction(uint _productId) public {
        
        //擷取商品資訊
        address owner = productIdToOwner[_productId];
        Product storage product = stores[owner][_productId];
        
        address buyer = product.highestBidder; //買家
        address seller = owner;//賣家
        address arbiter = msg.sender; //仲裁人
        
        //仲裁人不允許是買家或者賣家
        require(arbiter != buyer && arbiter != seller);
        
        //限定僅在揭标之後才可以進行仲裁
        //require(now > product.auctionEndTime);

        require(product.status == ProductStatus.Open); //Open, Sold, Unsold

        //如果競标了,但是沒有揭标,那麼也是沒有賣出去(自行拓展)
        if (product.totalBids == 0) {
            product.status = ProductStatus.Unsold;
        } else {
            product.status = ProductStatus.Sold;
        }
        
		//.value()方式進行外部調用時轉錢
        //類比feed.info.value(10).gas(800)(); 
        //這是構造的時候傳錢,constructor加上payable關鍵字
        
	    //address escrow = (new Escrow).value(25)(buyer, seller, arbiter)
        address escrow = (new Escrow).value(product.secondHighestBid)(buyer, seller, arbiter);
        
        productToEscrow[_productId] = escrow;
        
        //退還差價 30- 25 = 5 , 30是理想出價,25是次高
        buyer.transfer(product.highestBid - product.secondHighestBid);
    }
           

11.三方合約(另起合約)

contract Escrow {

    // 屬性:
    // 1. 買家
    address public buyer;
    // 2. 賣家
    address public seller;
    // 3. 仲裁人
    address public arbiter;

    //構造函數
    constructor(address _buyer, address _seller, address _arbiter) public payable {
        buyer = _buyer;
        seller = _seller;
        arbiter = _arbiter;
    }
    
    function getBalance() public view returns(uint256) {
        return address(this).balance;
    }
}
           

12.在Escrow中實作兩個投票函數

// 4. 賣家獲得的票數
    uint sellerVotesCount;
    // 5. 買家獲得的票數
    uint buyerVotesCount;
    
    // 6. 标記某個位址是否已經投票
    mapping(address => bool) addressVotedMap;

    // 7. 是否已經完成付款了
    bool isSpent = false;   

	// 方法:
    //向賣家投票方法
    function giveMoneyToSeller(address caller)  callerRestrict(caller) public {
        require(!isSpent);
        
        //記錄已經投票的狀态,如果投過票,就設定為true
        //不是mapping中的成員不可以投
        require(!addressVotedMap[caller]);
        addressVotedMap[caller] = true; //address => bool
        
        //sellerVotesCount++;
        if (++sellerVotesCount == 2 ) {
            isSpent = true;
            //轉錢
            seller.transfer(address(this).balance);
        }
    }

    //向買家投票方法
    function giveMoneyToBuyer(address caller) callerRestrict(caller) public {
        require(!isSpent);
        require(!addressVotedMap[caller]);
        addressVotedMap[caller] = true;

        if (++buyerVotesCount == 2) {
            isSpent = true;
            buyer.transfer(address(this).balance);
        }
    }
    
           

13.添加修飾器

modifier callerRestrict(address caller ) {
        require(caller == seller || caller == buyer || caller == arbiter);
        _;
    }//必須是三方中一員才可以投票
           

14.擷取仲裁資訊

function escrowInfo() public view returns(address, address, address, uint, uint, uint256) {
        //擷取合約餘額
        uint256 balance = getEscrowBalance();
        return (buyer, seller, arbiter, buyerVotesCount, sellerVotesCount, balance);
    }
    
    
    function getEscrowBalance() public view returns(uint256) {
        return address(this).balance;
    }
           

15.更新EcommerceStore,對投票合約進行調用

function getEscrowInfo(uint _productId) public view returns (address, address, address, uint, uint, uint256) {
        //擷取仲裁商品對應的位址
        address escrow = productToEscrow[_productId];
        Escrow instanceContract = Escrow(escrow);
        
        return instanceContract.escrowInfo();
    }

    function giveToSeller(uint _productId) public {
        address contract1 = productToEscrow[_productId];
        Escrow(contract1).giveMoneyToSeller(msg.sender); //把調用人傳給Escrow合約
    }

    function giveToBuyer(uint _productId) public {
        Escrow(productToEscrow[_productId]).giveMoneyToBuyer(msg.sender);
    }
           

16.完整的Escrow合約

contract Escrow {

    // 屬性:
    // 1. 買家
    address  buyer;
    // 2. 賣家
    address  seller;
    // 3. 仲裁人
    address  arbiter;
    
    
    // 4. 賣家獲得的票數
    uint sellerVotesCount;
    // 5. 買家獲得的票數
    uint buyerVotesCount;
    
    // 6. 标記某個位址是否已經投票
    mapping(address => bool) addressVotedMap;

    // 7. 是否已經完成付款了
    bool isSpent = false;
    

    constructor(address _buyer, address _seller, address _arbiter) public payable {
        buyer = _buyer;
        seller = _seller;
        arbiter = _arbiter;
    }
    
        // 方法:
    //向賣家投票方法
    function giveMoneyToSeller(address caller)  callerRestrict(caller) public {
        require(!isSpent);
        
        //記錄已經投票的狀态,如果投過票,就設定為true
        require(!addressVotedMap[caller]);
        addressVotedMap[caller] = true; //address => bool
        
        //sellerVotesCount++;
        if (++sellerVotesCount == 2 ) {
            isSpent = true;
            seller.transfer(address(this).balance);
        }
    }

    //向買家投票方法
    function giveMoneyToBuyer(address caller) callerRestrict(caller) public {
        require(!isSpent);
        require(!addressVotedMap[caller]);
        addressVotedMap[caller] = true;

        if (++buyerVotesCount == 2) {
            isSpent = true;
            buyer.transfer(address(this).balance);
        }
    }
    
    
    function escrowInfo() public view returns(address, address, address, uint, uint, uint256) {
        uint256 balance = getEscrowBalance();
        return (buyer, seller, arbiter, buyerVotesCount, sellerVotesCount, balance);
    }
    
    
    
    modifier callerRestrict(address caller ) {
        require(caller == seller || caller == buyer || caller == arbiter);
        _;
    }
    
    
    function getEscrowBalance() public view returns(uint256) {
        return address(this).balance;
    }
}
           

17.完整的EcommerceStore合約

pragma solidity ^0.4.24;


contract EcommerceStore {
    
    //把商品添加到商店的業務分析:
    
    // 1. 定義商品結構
    // 2. 每個商品有一個唯一的id,數字,每添加一個商品,id++
    // 3. 需要有一個存儲所有商品的結構, 通過id可以的到對應的商品
    // 4. 添加商品的方法
    
    uint public productIndex;  //每一個産品都有自己的id
    
    struct Product {
        //基礎資訊
        uint id;
        string name;
        string category;
        string imageLink;
        string descLink;

        uint startPrice;
        uint auctionStartTime;
        uint auctionEndTime;

        ProductStatus status; 
        ProductCondition condition;
        
        
        //競标資訊
        uint highestBid;   //最高出價, 50,理想價格
        address highestBidder; //最高出價人
        uint secondHighestBid; //次高價,40
        uint totalBids; //所有的競标數量
        
        // 存儲所有的競标人對這個商品的競标(與stroes類似)
        mapping(address => mapping(bytes32 => Bid)) bids;
        
    }

    enum ProductStatus {Open, Sold, Unsold} //競标中,賣了,沒賣出
    enum ProductCondition {Used, New} 新的,舊的
    
    
    mapping(address => mapping(uint => Product)) stores;
    mapping(uint => address) productIdToOwner;
    
    
    function addProductToStore(string _name, string _category, string _imageLink, string _descLink, uint _startTime, uint _endTime, uint _startPrice, uint condition) public {
        productIndex++;
        
        Product memory product = Product({
            id : productIndex,
            name : _name,
            category : _category,
            imageLink : _imageLink,
            descLink : _descLink,
            
            startPrice : _startPrice,
            auctionStartTime : _startTime,
            auctionEndTime : _endTime,
    
            status : ProductStatus.Open,
            condition : ProductCondition(condition),
            
            //++++++++++++
            highestBid: 0,
            highestBidder : 0,
            secondHighestBid : 0,
            totalBids : 0
            
            });
            
        //把建立好的商品添加到我們的商店結構中
        stores[msg.sender][productIndex] = product;
        productIdToOwner[productIndex] = msg.sender;
    }
    
    
    function getProductById(uint _productId) public view returns (uint, string, string, string, string, uint, uint, uint, uint){
        
        address owner = productIdToOwner[_productId];
        Product memory product = stores[owner][_productId];
        
        return (
            product.id, product.name, product.category, product.imageLink, product.descLink,
            product.auctionStartTime, product.auctionEndTime, product.startPrice, uint(product.status)
        );
    }
    
    
    // 競标的結構:
    // 	1. 産品ID
    // 	2. 轉賬(迷惑)價格,注意,不是理想價格
    // 	3. 揭标與否
    // 	4. 競标人

    struct Bid {
        uint productId;
        uint price;
        bool isRevealed;
        address bidder;
    }

    
    function bid(uint _productId, uint _idealPrice, string _secret) public payable {
        
        bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
        bytes32 bytesHash = keccak256(bytesInfo);
        
        address owner = productIdToOwner[_productId];
        Product storage product = stores[owner][_productId];
        
        //每一個競标必須大于等于起始價格
        require(msg.value >= product.startPrice);
        
        product.totalBids++;
        
        Bid memory bidLocal = Bid(_productId, msg.value, false, msg.sender);
        product.bids[msg.sender][bytesHash] = bidLocal; 
    }
    
    //擷取指定的bid資訊
    function getBidById(uint _productId, uint _idealPrice, string _secret) public view returns (uint, uint, bool, address) {
       
        //Product storage product = stores[productIdToOwner[_productId]][_productId];
        address owner = productIdToOwner[_productId];
        Product storage product = stores[owner][_productId];
        
        bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
        bytes32 bytesHash = keccak256(bytesInfo);

        Bid memory bidLocal = product.bids[msg.sender][bytesHash];
        return (bidLocal.productId, bidLocal.price, bidLocal.isRevealed, bidLocal.bidder);
    }
    
    function getBalance() public view returns (uint){
        return address(this).balance;
    }
    
    
    event revealEvent(uint productid, bytes32 bidId, uint idealPrice, uint price, uint refund);
    
    function revealBid(uint _productId, uint _idealPrice, string _secret) public {
       
        address owner = productIdToOwner[_productId];
        Product storage product = stores[owner][_productId];
        
        bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
        bytes32 bidId = keccak256(bytesInfo);
        
        //mapping(address => mapping(bytes32 => Bid)) bids;
        
        //一個人可以對同一個商品競标多次,揭标的時候也要揭标多次, storage類型
        Bid storage currBid = product.bids[msg.sender][bidId];
        
       //require(now > product.auctionStartTime);
       //每個标隻能揭标一次
        require(!currBid.isRevealed);
        
        require(currBid.bidder != address(0));  //說明找到了這個标, 找到了正确的标

        currBid.isRevealed = true;

        //bid中的是迷惑價格,真實價格揭标時傳遞進來
        uint confusePrice = currBid.price;

        //退款金額, 程式最後,統一退款
        uint refund = 0;
        
        uint idealPrice = _idealPrice;
        
        if (confusePrice < idealPrice) {
            //路徑1:無效交易
            refund = confusePrice;
        } else {
            if (idealPrice > product.highestBid) {
                if (product.highestBidder == address(0)) {
                    //目前賬戶是第一個揭标人
                    //路徑2:
                    product.highestBidder = msg.sender;
                    product.highestBid = idealPrice;
                    product.secondHighestBid = product.startPrice;
                    refund = confusePrice - idealPrice;
                } else {
                    //路徑3:不是第一個,但是出價是目前最高的,更新最高競标人,最高價格,次高價格
                    product.highestBidder.transfer(product.highestBid);
                    product.secondHighestBid = product.highestBid;
                    
                    product.highestBid = idealPrice; //wangwu 40
                    product.highestBidder = msg.sender; //wangwu
                    refund = confusePrice - idealPrice; //10
                }
            } else {
                //路徑4:價格低于最高價,但是高于次高價
                if (idealPrice > product.secondHighestBid) {
                    //路徑4:更新次高價,然後拿回自己的錢
                    product.secondHighestBid = idealPrice;
                    refund = confusePrice; //40
                
                } else {
                    //路徑5:路人甲,價格低于次高價,直接退款
                    refund = confusePrice;
                }
            }
        }

        emit revealEvent(_productId, bidId, confusePrice, currBid.price, refund);

        if (refund > 0) {
            msg.sender.transfer(refund);
        }
    }
    
    
    function getHighestBidInfo(uint _productId) public view returns(address, uint, uint, uint) {
        address owner = productIdToOwner[_productId];
        Product memory product = stores[owner][_productId];
        
        return (product.highestBidder, product.highestBid, product.secondHighestBid, product.totalBids);
    }
    
    
    //key是産品id,value:是第三方合約
    //全局唯一, 用于投票時,找到這個第三方合約。
	mapping(uint => address) public productToEscrow;
    
    function finalaizeAuction(uint _productId) public {
        
        address owner = productIdToOwner[_productId];
        Product storage product = stores[owner][_productId];
        
        address buyer = product.highestBidder; //買家
        address seller = owner;//賣家
        address arbiter = msg.sender; //仲裁人
        
        //仲裁人不允許是買家或者賣家
        require(arbiter != buyer && arbiter != seller);
        
        //限定僅在揭标之後才可以進行仲裁
        //require(now > product.auctionEndTime);

        require(product.status == ProductStatus.Open); //Open, Sold, Unsold

        //如果競标了,但是沒有揭标,那麼也是沒有賣出去(自行拓展)
        if (product.totalBids == 0) {
            product.status = ProductStatus.Unsold;
        } else {
            product.status = ProductStatus.Sold;
        }
        
		//.value()方式進行外部調用時轉錢
        //類比feed.info.value(10).gas(800)(); 
        //這是構造的時候傳錢,constructor加上payable關鍵字
        
	    //address escrow = (new Escrow).value(25)(buyer, seller, arbiter)
        address escrow = (new Escrow).value(product.secondHighestBid)(buyer, seller, arbiter);
        
        productToEscrow[_productId] = escrow;
        
        //退還差價 30- 25 = 5 , 30是理想出價,25是次高
        buyer.transfer(product.highestBid - product.secondHighestBid);
    }
    
    
    function getEscrowInfo(uint _productId) public view returns (address, address, address, uint, uint, uint256) {
        address escrow = productToEscrow[_productId];
        Escrow instanceContract = Escrow(escrow);
        
        return instanceContract.escrowInfo();
    }

    function giveToSeller(uint _productId) public {
        address contract1 = productToEscrow[_productId];
        Escrow(contract1).giveMoneyToSeller(msg.sender); //把調用人傳給Escrow合約
    }

    function giveToBuyer(uint _productId) public {
        Escrow(productToEscrow[_productId]).giveMoneyToBuyer(msg.sender);
    }
}
           

繼續閱讀