Skip to content

Instantly share code, notes, and snippets.

@blackyblack
Created July 5, 2016 10:42
Show Gist options
  • Save blackyblack/b7c3e7a5cec5a3dd834792797a61d7c3 to your computer and use it in GitHub Desktop.
Save blackyblack/b7c3e7a5cec5a3dd834792797a61d7c3 to your computer and use it in GitHub Desktop.
/* A contract to store only messages approved by owner */
/* Deployment:
Owner: 0xeb5fa6cbf2aca03a0df228f2df67229e2d3bd01e
Last address: 0x0318179601a70085aeb488f178b081295b65ecc9
ABI: [{"constant":false,"inputs":[{"name":"datainfo","type":"string"},{"name":"version","type":"uint256"}],"name":"add","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"flush","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"contentCount","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"datainfo","type":"string"},{"indexed":true,"name":"version","type":"uint256"}],"name":"content","type":"event"}]
Optimized: yes
Solidity version: 0.3.2-9e36bdda
*/
contract self_store {
address owner;
uint public contentCount = 0;
event content(string datainfo, uint indexed version);
modifier onlyowner { if (msg.sender == owner) _ }
function self_store() public { owner = msg.sender; }
///TODO: remove in release
function kill() onlyowner { suicide(owner); }
function flush() onlyowner {
owner.send(this.balance);
}
function add(string datainfo, uint version) onlyowner {
contentCount++;
content(datainfo, version);
}
}
@mpolci
Copy link

mpolci commented Jul 12, 2016

This is a trivial refactoring in order to check deployment gas cost using a library

// Deployment original escrow_goods: 2376873 gas
// Deployment EscrowLib: 1999050 gas
// Deployment escrow_goods: 1120790 gas
// Deployment escrow_goods: 1101878 gas (removed initializations to 0)

library EscrowLib {
    struct EscrowInfo {
        address buyer;
        uint lockedFunds;
        uint frozenTime;
        uint frozenFunds;
        uint buyerNo;
        uint sellerNo;
    }
    uint constant arbitragePeriod = 30 days;
    uint constant stalePeriod = 180 days;

    struct Context {

          //seller/owner of the goods
          address seller;

          //escrow related

          address arbiter;

          uint freezePeriod;
          //each lock fee in promilles.
          uint feePromille;
          //reward in promilles. promille = percent * 10, eg 1,5% reward = 15 rewardPromille
          uint rewardPromille;

          uint activityTime;
          uint feeFunds;
          uint totalEscrows;

          mapping (uint => EscrowInfo) escrows;

          //goods related

          //status of the goods: Available, Pending, Sold, Canceled
          uint16 status;
          //how many for sale
          uint16 count;
          //price per item
          uint price;

          uint16 availableCount;
          uint16 pendingCount;

          mapping (address => uint) buyers;


    }
    event log_event(string message);
    event content(uint indexed lockid, string datainfo, uint indexed version, uint datatype, address indexed sender, uint count, uint payment);
    modifier onlyowner(Context storage data) { if (msg.sender == data.seller) _ }

    function kill(Context storage data) {
        //allow anyone to claim dead contract funds
        if(now > (data.activityTime + stalePeriod)) {
           suicide(msg.sender);
           return;
        }

        //do not allow killing contract with active escrows
        if(data.totalEscrows > 0) return;
        //do not allow killing contract with unclaimed escrow fees
        if(data.feeFunds > 0) return;
        if(msg.sender != data.seller) return;
        suicide(data.seller);
    }

    function log(string message) private {
        log_event(message);
    }

    //escrow API

    //vote YES - immediately sends funds to the peer
    function yes(Context storage data, uint id, string datainfo, uint _version) {

        EscrowInfo info = data.escrows[id];

        if(info.lockedFunds == 0) return;
        if(msg.sender != info.buyer && msg.sender != data.seller) return;

        uint payment = info.lockedFunds;
        if(payment > this.balance) payment = this.balance;
        if(data.totalEscrows > 0) data.totalEscrows -= 1;
        info.lockedFunds = 0;

        if(msg.sender == info.buyer) {
            //send funds to seller
            data.seller.send(payment);
        }

        if(msg.sender == data.seller) {
            //send funds to buyer
            info.buyer.send(payment);
        }

        content(id, datainfo, _version, 11, msg.sender, 0, payment);
    }

    //vote NO - freeze funds for arbitrage
    function no(Context storage data, uint id, string datainfo, uint _version) {

        EscrowInfo info = data.escrows[id];

        if(info.lockedFunds == 0) return;
        if(msg.sender != info.buyer && msg.sender != data.seller) return;

        //freeze funds
        //only allow one time freeze
        if(info.frozenFunds == 0) {
            info.frozenFunds = info.lockedFunds;
            info.frozenTime = now;
        }

        if(msg.sender == info.buyer) {
            info.buyerNo = 1;
        }

        if(msg.sender == data.seller) {
            info.sellerNo = 1;
        }

        content(id, datainfo, _version, 12, msg.sender, 0, info.lockedFunds);
    }

    //arbiter's decision on the case.
    //arbiter can only decide when both buyer and seller voted NO
    //arbiter decides on his own reward but not bigger than announced percentage (rewardPromille)
    function arbYes(Context storage data, uint id, address who, uint payment, string datainfo, uint _version) {
        if(msg.sender != data.arbiter) return;

        EscrowInfo info = data.escrows[id];

        if(info.lockedFunds == 0) return;
        if(info.frozenFunds == 0) return;

        if(who != data.seller && who != info.buyer) return;
        //requires both NO to arbitrage
        if(info.buyerNo == 0 || info.sellerNo == 0) return;

        if(info.lockedFunds > this.balance) info.lockedFunds = this.balance;
        if(payment > info.lockedFunds) payment = info.lockedFunds;

        //limit payment
        uint reward = (info.lockedFunds * data.rewardPromille) / 1000;
        if(reward > (info.lockedFunds - payment)) {
            log("Reward exceeds reward limit");
            return;
        }

        //send funds to the winner
        who.send(payment);

        //send the rest as reward
        info.lockedFunds -= payment;
        data.feeFunds += info.lockedFunds;

        info.lockedFunds = 0;
        content(id, datainfo, _version, 13, msg.sender, 0, payment);
    }

    //allow arbiter to get his collected fees
    function getFees(Context storage data) {
        if(msg.sender != data.arbiter) return;
        if(data.feeFunds > this.balance) data.feeFunds = this.balance;
        data.arbiter.send(data.feeFunds);
        data.feeFunds = 0;
    }

    //allow buyer or seller take timeouted funds.
    //buyer can get funds if seller is silent and seller can get funds if buyer is silent (after freezePeriod)
    //buyer can get back funds under arbitrage if arbiter is silent (after arbitragePeriod)
    function getMoney(Context storage data, uint id) {

        EscrowInfo info = data.escrows[id];

        if(info.lockedFunds == 0) return;
        //HACK: this check is necessary since frozenTime == 0 at escrow creation
        if(info.frozenFunds == 0) return;

        //timout for voting not over yet
        if(now < (info.frozenTime + data.freezePeriod)) return;

        uint payment = info.lockedFunds;
        if(payment > this.balance) payment = this.balance;
        //both has voted - money is under arbitrage
        if(info.buyerNo != 0 && info.sellerNo != 0) {
            //arbitrage timeout is not over yet
            if(now < (info.frozenTime + data.freezePeriod + arbitragePeriod)) return;

            //arbiter was silent so redeem the funds to the buyer
            info.buyer.send(payment);
            info.lockedFunds = 0;
            return;
        }

        if(info.buyerNo != 0) {
            info.buyer.send(payment);
            info.lockedFunds = 0;
            return;
        }
        if(info.sellerNo != 0) {
            data.seller.send(payment);
            info.lockedFunds = 0;
            return;
        }
    }

    //goods API

    //buy with escrow. id - escrow info id
    function buy(Context storage data, uint id, string datainfo, uint _version, uint16 _count) {
        if(data.status != 1) { log("status != 1"); throw; }
        if(msg.value < (data.price * _count)) { log("msg.value < (data.price * _count)"); throw; }
        if(_count > data.availableCount) { log("_count > availableCount"); throw; }

        //create default EscrowInfo struct or access existing
        EscrowInfo info = data.escrows[id];

        //lock only once for a given id
        if(info.lockedFunds > 0) {
            log("Already locked");
            throw;
        }

        //lock funds
        //refresh watchdog timer
        data.activityTime = now;

        uint fee = (msg.value * data.feePromille) / 1000;
        uint funds = (msg.value - fee);
        data.feeFunds += fee;
        data.totalEscrows += 1;

        info.buyer = msg.sender;
        info.lockedFunds = funds;
        info.frozenFunds = 0;
        info.buyerNo = 0;
        info.sellerNo = 0;

        data.pendingCount += _count;
        data.buyers[msg.sender] = 1;

        //Buy order to event log
        content(id, datainfo, _version, 1, msg.sender, _count, msg.value);
    }

    function accept(Context storage data, uint id, string datainfo, uint _version, uint16 _count) onlyowner(data) {
        if(_count > data.availableCount) { log("_count > availableCount"); return; }
        if(_count > data.pendingCount) { log("_count > pendingCount"); return; }

        data.pendingCount -= _count;
        data.availableCount -= _count;

        //Accept order to event log
        content(id, datainfo, _version, 2, msg.sender, _count, 0);
    }

    function reject(Context storage data, uint id, string datainfo, uint _version, uint16 _count, address recipient, uint amount) onlyowner(data) {
        if(_count > data.pendingCount) { log("_count > pendingCount"); return; }

        data.pendingCount -= _count;

        //send money back
        yes(data, id, datainfo, _version);

        //Reject order to event log
        content(id, datainfo, _version, 3, msg.sender, _count, amount);
    }

    function cancel(Context storage data, string datainfo, uint _version) onlyowner(data) {
        //Canceled data.status
        data.status = 2;

        //Cancel order to event log
        content(0, datainfo, _version, 4, msg.sender, data.availableCount, 0);
    }

    //remove buyer from the watchlist
    function unbuy(Context storage data) {
        data.buyers[msg.sender] = 0;
    }
}

contract escrow_goods {
    using EscrowLib for EscrowLib.Context;
    EscrowLib.Context public data;

    function escrow_goods(address _arbiter, uint _freezePeriod, uint _feePromille, uint _rewardPromille,
                          uint16 _count, uint _price) {

        data.seller = msg.sender;

        //escrow related
        data.arbiter = _arbiter;
        data.freezePeriod = _freezePeriod;
        data.feePromille = _feePromille;
        data.rewardPromille = _rewardPromille;

        data.activityTime = now;
        // all variables are always initialized to 0, save gas
        //data.feeFunds = 0;
        //data.totalEscrows = 0;

        //goods related
        //status = Available
        data.status = 1;
        data.count = _count;
        data.price = _price;

        data.availableCount = data.count;
        //data.pendingCount = 0;
    }

    function kill() {
        data.kill();
    }

    //escrow API

    //vote YES - immediately sends funds to the peer
    function yes(uint id, string datainfo, uint _version) {
        data.yes(id, datainfo, _version);
    }

    //vote NO - freeze funds for arbitrage
    function no(uint id, string datainfo, uint _version) {
        data.no(id, datainfo, _version);
    }

    //arbiter's decision on the case.
    //arbiter can only decide when both buyer and seller voted NO
    //arbiter decides on his own reward but not bigger than announced percentage (rewardPromille)
    function arbYes(uint id, address who, uint payment, string datainfo, uint _version) {
      data.arbYes(id, who, payment, datainfo, _version);
    }

    //allow arbiter to get his collected fees
    function getFees() {
        data.getFees();
    }

    //allow buyer or seller take timeouted funds.
    //buyer can get funds if seller is silent and seller can get funds if buyer is silent (after freezePeriod)
    //buyer can get back funds under arbitrage if arbiter is silent (after arbitragePeriod)
    function getMoney(uint id) {
        data.getMoney(id);
    }

    //goods API

    //buy with escrow. id - escrow info id
    function buy(uint id, string datainfo, uint _version, uint16 _count) {
        data.buy(id, datainfo, _version, _count);
    }

    function accept(uint id, string datainfo, uint _version, uint16 _count) {
        data.accept(id, datainfo, _version, _count);
    }

    function reject(uint id, string datainfo, uint _version, uint16 _count, address recipient, uint amount) {
        data.reject(id, datainfo, _version, _count, recipient, amount);
    }

    function cancel(string datainfo, uint _version) {
        data.cancel(datainfo, _version);
    }

    //remove buyer from the watchlist
    function unbuy() {
        data.unbuy();
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment