Created
August 15, 2016 15:56
-
-
Save blackyblack/4bfb4266057610a2b63153d288ef0051 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* A contract to store goods with escrowed funds. */ | |
/* Deployment: | |
Owner: seller | |
Last address: dynamic | |
Lib ABI: [{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"},{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"},{"name":"_count","type":"uint16"}],"name":"buy","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"}],"name":"getFees","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"},{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"yes","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"cancel","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"},{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"},{"name":"_count","type":"uint16"},{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"}],"name":"reject","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"},{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"},{"name":"_count","type":"uint16"}],"name":"accept","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"},{"name":"id","type":"uint256"},{"name":"who","type":"address"},{"name":"payment","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"arbYes","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"},{"name":"id","type":"uint256"}],"name":"getMoney","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"}],"name":"unbuy","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"EscrowLib.Context storage"},{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"no","outputs":[],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"message","type":"string"}],"name":"log_event","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"lockid","type":"uint256"},{"indexed":false,"name":"datainfo","type":"string"},{"indexed":true,"name":"version","type":"uint256"},{"indexed":false,"name":"datatype","type":"uint256"},{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"count","type":"uint256"},{"indexed":false,"name":"payment","type":"uint256"}],"name":"content","type":"event"}] | |
ABI: [{"constant":false,"inputs":[{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"cancel","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"},{"name":"_count","type":"uint16"}],"name":"buy","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"getMoney","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"no","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"},{"name":"_count","type":"uint16"},{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"}],"name":"reject","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"who","type":"address"},{"name":"payment","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"arbYes","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"data","outputs":[{"name":"seller","type":"address"},{"name":"arbiter","type":"address"},{"name":"freezePeriod","type":"uint256"},{"name":"feePromille","type":"uint256"},{"name":"rewardPromille","type":"uint256"},{"name":"activityTime","type":"uint256"},{"name":"feeFunds","type":"uint256"},{"name":"totalEscrows","type":"uint256"},{"name":"status","type":"uint16"},{"name":"count","type":"uint16"},{"name":"price","type":"uint256"},{"name":"availableCount","type":"uint16"},{"name":"pendingCount","type":"uint16"},{"name":"atomicLock","type":"uint16"}],"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"yes","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"},{"name":"_count","type":"uint16"}],"name":"accept","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"unbuy","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"getFees","outputs":[],"type":"function"},{"inputs":[{"name":"_arbiter","type":"address"},{"name":"_freezePeriod","type":"uint256"},{"name":"_feePromille","type":"uint256"},{"name":"_rewardPromille","type":"uint256"},{"name":"_count","type":"uint16"},{"name":"_price","type":"uint256"}],"type":"constructor"}] | |
Optimized: yes | |
Solidity version: v0.3.6-2016-08-12-9e03bda | |
*/ | |
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; | |
uint constant safeGas = 25000; | |
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; | |
uint16 atomicLock; | |
} | |
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) { | |
//do not allow killing contract with active escrows | |
if(data.totalEscrows > 0) { | |
log_event("totalEscrows > 0"); | |
return; | |
} | |
//do not allow killing contract with unclaimed escrow fees | |
if(data.feeFunds > 0) { | |
log_event("feeFunds > 0"); | |
return; | |
} | |
//only allow the seller to kill contract | |
if(msg.sender != data.seller) { | |
log_event("msg.sender != data.seller"); | |
return; | |
} | |
suicide(msg.sender); | |
} | |
function safeSend(Context storage data, address addr, uint value) internal returns (bool) { | |
if(data.atomicLock > 0) throw; | |
data.atomicLock = 1; | |
if (!(addr.call.gas(safeGas).value(value)())) { | |
data.atomicLock = 0; | |
return false; | |
} | |
data.atomicLock = 0; | |
return true; | |
} | |
//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) { | |
log_event("info.lockedFunds == 0"); | |
return; | |
} | |
if(msg.sender != info.buyer && msg.sender != data.seller) { | |
log_event("msg.sender != info.buyer && msg.sender != data.seller"); | |
return; | |
} | |
uint payment = info.lockedFunds; | |
if(payment > this.balance) { | |
//HACK: should not get here - funds cannot be unlocked in this case | |
log_event("payment > this.balance"); | |
return; | |
} | |
if(msg.sender == info.buyer) { | |
//send funds to seller | |
if(!safeSend(data, data.seller, payment)) { | |
log_event("!safeSend(data, data.seller, payment)"); | |
return; | |
} | |
} else if(msg.sender == data.seller) { | |
//send funds to buyer | |
if(!safeSend(data, info.buyer, payment)) { | |
log_event("!safeSend(data, info.buyer, payment)"); | |
return; | |
} | |
} else { | |
//HACK: should not get here | |
log_event("unknown msg.sender"); | |
return; | |
} | |
//remove record from escrows | |
if(data.totalEscrows > 0) data.totalEscrows -= 1; | |
info.lockedFunds = 0; | |
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) { | |
log_event("info.lockedFunds == 0"); | |
return; | |
} | |
if(msg.sender != info.buyer && msg.sender != data.seller) { | |
log_event("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; | |
} | |
else if(msg.sender == data.seller) { | |
info.sellerNo = 1; | |
} else { | |
//HACK: should not get here | |
log_event("unknown msg.sender"); | |
return; | |
} | |
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) { | |
log_event("info.lockedFunds == 0"); | |
return; | |
} | |
if(info.frozenFunds == 0) { | |
log_event("info.frozenFunds == 0"); | |
return; | |
} | |
if(who != data.seller && who != info.buyer) { | |
log_event("who != data.seller && who != info.buyer"); | |
return; | |
} | |
//requires both NO to arbitrage | |
if(info.buyerNo == 0 || info.sellerNo == 0) { | |
log_event("info.buyerNo == 0 || info.sellerNo == 0"); | |
return; | |
} | |
if(payment > info.lockedFunds) { | |
log_event("payment > info.lockedFunds"); | |
return; | |
} | |
if(payment > this.balance) { | |
//HACK: should not get here - funds cannot be unlocked in this case | |
log_event("payment > this.balance"); | |
return; | |
} | |
//limit payment | |
uint reward = (info.lockedFunds * data.rewardPromille) / 1000; | |
if(reward > (info.lockedFunds - payment)) { | |
log_event("reward > (info.lockedFunds - payment)"); | |
return; | |
} | |
//send funds to the winner | |
if(!safeSend(data, who, payment)) { | |
log_event("!safeSend(data, who, payment)"); | |
return; | |
} | |
//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) { | |
log_event("msg.sender != data.arbiter"); | |
return; | |
} | |
if(data.feeFunds > this.balance) { | |
//HACK: should not get here - funds cannot be unlocked in this case | |
log_event("data.feeFunds > this.balance"); | |
return; | |
} | |
if(!safeSend(data, data.arbiter, data.feeFunds)) { | |
log_event("!safeSend(data, data.arbiter, data.feeFunds)"); | |
return; | |
} | |
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) { | |
log_event("info.lockedFunds == 0"); | |
return; | |
} | |
//HACK: this check is necessary since frozenTime == 0 at escrow creation | |
if(info.frozenFunds == 0) { | |
log_event("info.frozenFunds == 0"); | |
return; | |
} | |
//timout for voting not over yet | |
if(now < (info.frozenTime + data.freezePeriod)) { | |
log_event("now < (info.frozenTime + data.freezePeriod)"); | |
return; | |
} | |
uint payment = info.lockedFunds; | |
if(payment > this.balance) { | |
//HACK: should not get here - funds cannot be unlocked in this case | |
log_event("payment > this.balance"); | |
return; | |
} | |
//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)) { | |
log_event("now < (info.frozenTime + data.freezePeriod + arbitragePeriod)"); | |
return; | |
} | |
//arbiter was silent so redeem the funds to the buyer | |
if(!safeSend(data, info.buyer, payment)) { | |
log_event("!safeSend(data, info.buyer, payment)"); | |
return; | |
} | |
info.lockedFunds = 0; | |
return; | |
} | |
if(info.buyerNo != 0) { | |
if(!safeSend(data, info.buyer, payment)) { | |
log_event("!safeSend(data, info.buyer, payment)"); | |
return; | |
} | |
info.lockedFunds = 0; | |
return; | |
} | |
if(info.sellerNo != 0) { | |
if(!safeSend(data, data.seller, payment)) { | |
log_event("!safeSend(data, data.seller, payment)"); | |
return; | |
} | |
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) { | |
//reject money transfers for bad item status | |
if(data.status != 1) throw; | |
if(msg.value < (data.price * _count)) throw; | |
if(_count > data.availableCount) throw; | |
if(data.feePromille > 1000) throw; | |
if(data.rewardPromille > 1000) throw; | |
if((data.feePromille + data.rewardPromille) > 1000) throw; | |
//create default EscrowInfo struct or access existing | |
EscrowInfo info = data.escrows[id]; | |
//lock only once for a given id | |
if(info.lockedFunds > 0) throw; | |
//lock funds | |
//refresh watchdog timer | |
data.activityTime = now; | |
uint fee = (msg.value * data.feePromille) / 1000; | |
//limit fees | |
if(fee > msg.value) throw; | |
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_event("_count > availableCount"); | |
return; | |
} | |
if(_count > data.pendingCount) { | |
log_event("_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_event("_count > pendingCount"); | |
return; | |
} | |
data.pendingCount -= _count; | |
//send money back | |
yes(data, id, datainfo, _version); | |
//Reject order to event log | |
//HACK: "yes" call above may fail and this event will be non-relevant. Do not rely on it. | |
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; | |
// all variables are always initialized to 0, save gas | |
//escrow related | |
data.arbiter = _arbiter; | |
data.freezePeriod = _freezePeriod; | |
data.feePromille = _feePromille; | |
data.rewardPromille = _rewardPromille; | |
data.activityTime = now; | |
//goods related | |
//status = Available | |
data.status = 1; | |
data.count = _count; | |
data.price = _price; | |
data.availableCount = data.count; | |
} | |
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