Created
July 5, 2016 10:43
-
-
Save blackyblack/2c5532b3a71e960674c53d4fcd6da8cc 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 | |
ABI: [{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"escrows","outputs":[{"name":"buyer","type":"address"},{"name":"lockedFunds","type":"uint256"},{"name":"frozenTime","type":"uint256"},{"name":"frozenFunds","type":"uint256"},{"name":"buyerNo","type":"uint256"},{"name":"sellerNo","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"count","outputs":[{"name":"","type":"uint16"}],"type":"function"},{"constant":false,"inputs":[{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"cancel","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"seller","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"freezePeriod","outputs":[{"name":"","type":"uint256"}],"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":true,"inputs":[],"name":"status","outputs":[{"name":"","type":"uint16"}],"type":"function"},{"constant":true,"inputs":[],"name":"rewardPromille","outputs":[{"name":"","type":"uint256"}],"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":true,"inputs":[],"name":"totalEscrows","outputs":[{"name":"","type":"uint256"}],"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":"feeFunds","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"yes","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"buyers","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"availableCount","outputs":[{"name":"","type":"uint16"}],"type":"function"},{"constant":true,"inputs":[],"name":"price","outputs":[{"name":"","type":"uint256"}],"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"},{"constant":true,"inputs":[],"name":"feePromille","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"activityTime","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"pendingCount","outputs":[{"name":"","type":"uint16"}],"type":"function"},{"constant":true,"inputs":[],"name":"arbiter","outputs":[{"name":"","type":"address"}],"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"},{"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"}] | |
Optimized: yes | |
Solidity version: v0.3.5-2016-06-14-371690f | |
*/ | |
contract escrow_goods { | |
//seller/owner of the goods | |
address public seller; | |
//escrow related | |
struct EscrowInfo { | |
address buyer; | |
uint lockedFunds; | |
uint frozenTime; | |
uint frozenFunds; | |
uint buyerNo; | |
uint sellerNo; | |
} | |
address public arbiter; | |
uint public freezePeriod; | |
//each lock fee in promilles. | |
uint public feePromille; | |
//reward in promilles. promille = percent * 10, eg 1,5% reward = 15 rewardPromille | |
uint public rewardPromille; | |
uint constant arbitragePeriod = 30 days; | |
uint constant stalePeriod = 180 days; | |
uint public activityTime; | |
uint public feeFunds; | |
uint public totalEscrows; | |
mapping (uint => EscrowInfo) public escrows; | |
//goods related | |
//status of the goods: Available, Pending, Sold, Canceled | |
uint16 public status; | |
//how many for sale | |
uint16 public count; | |
//price per item | |
uint public price; | |
uint16 public availableCount; | |
uint16 public pendingCount; | |
mapping (address => uint) public 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 { if (msg.sender == seller) _ } | |
function escrow_goods(address _arbiter, uint _freezePeriod, uint _feePromille, uint _rewardPromille, | |
uint16 _count, uint _price) { | |
seller = msg.sender; | |
//escrow related | |
arbiter = _arbiter; | |
freezePeriod = _freezePeriod; | |
feePromille = _feePromille; | |
rewardPromille = _rewardPromille; | |
activityTime = now; | |
feeFunds = 0; | |
totalEscrows = 0; | |
//goods related | |
//status = Available | |
status = 1; | |
count = _count; | |
price = _price; | |
availableCount = count; | |
pendingCount = 0; | |
} | |
function kill() { | |
//allow anyone to claim dead contract funds | |
if(now > (activityTime + stalePeriod)) { | |
suicide(msg.sender); | |
return; | |
} | |
//do not allow killing contract with active escrows | |
if(totalEscrows > 0) return; | |
//do not allow killing contract with unclaimed escrow fees | |
if(feeFunds > 0) return; | |
if(msg.sender != seller) return; | |
suicide(seller); | |
} | |
function log(string message) private { | |
log_event(message); | |
} | |
//escrow API | |
//vote YES - immediately sends funds to the peer | |
function yes(uint id, string datainfo, uint _version) { | |
EscrowInfo info = escrows[id]; | |
if(info.lockedFunds == 0) return; | |
if(msg.sender != info.buyer && msg.sender != seller) return; | |
uint payment = info.lockedFunds; | |
if(payment > this.balance) payment = this.balance; | |
if(totalEscrows > 0) totalEscrows -= 1; | |
info.lockedFunds = 0; | |
if(msg.sender == info.buyer) { | |
//send funds to seller | |
seller.send(payment); | |
} | |
if(msg.sender == 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(uint id, string datainfo, uint _version) { | |
EscrowInfo info = escrows[id]; | |
if(info.lockedFunds == 0) return; | |
if(msg.sender != info.buyer && msg.sender != 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 == 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(uint id, address who, uint payment, string datainfo, uint _version) { | |
if(msg.sender != arbiter) return; | |
EscrowInfo info = escrows[id]; | |
if(info.lockedFunds == 0) return; | |
if(info.frozenFunds == 0) return; | |
if(who != 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 * 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; | |
feeFunds += info.lockedFunds; | |
info.lockedFunds = 0; | |
content(id, datainfo, _version, 13, msg.sender, 0, payment); | |
} | |
//allow arbiter to get his collected fees | |
function getFees() { | |
if(msg.sender != arbiter) return; | |
if(feeFunds > this.balance) feeFunds = this.balance; | |
arbiter.send(feeFunds); | |
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(uint id) { | |
EscrowInfo info = 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 + 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 + 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) { | |
seller.send(payment); | |
info.lockedFunds = 0; | |
return; | |
} | |
} | |
//goods API | |
//buy with escrow. id - escrow info id | |
function buy(uint id, string datainfo, uint _version, uint16 _count) { | |
if(status != 1) { log("status != 1"); throw; } | |
if(msg.value < (price * _count)) { log("msg.value < (price * _count)"); throw; } | |
if(_count > availableCount) { log("_count > availableCount"); throw; } | |
//create default EscrowInfo struct or access existing | |
EscrowInfo info = escrows[id]; | |
//lock only once for a given id | |
if(info.lockedFunds > 0) { | |
log("Already locked"); | |
throw; | |
} | |
//lock funds | |
//refresh watchdog timer | |
activityTime = now; | |
uint fee = (msg.value * feePromille) / 1000; | |
uint funds = (msg.value - fee); | |
feeFunds += fee; | |
totalEscrows += 1; | |
info.buyer = msg.sender; | |
info.lockedFunds = funds; | |
info.frozenFunds = 0; | |
info.buyerNo = 0; | |
info.sellerNo = 0; | |
pendingCount += _count; | |
buyers[msg.sender] = 1; | |
//Buy order to event log | |
content(id, datainfo, _version, 1, msg.sender, _count, msg.value); | |
} | |
function accept(uint id, string datainfo, uint _version, uint16 _count) onlyowner { | |
if(_count > availableCount) { log("_count > availableCount"); return; } | |
if(_count > pendingCount) { log("_count > pendingCount"); return; } | |
pendingCount -= _count; | |
availableCount -= _count; | |
//Accept order to event log | |
content(id, datainfo, _version, 2, msg.sender, _count, 0); | |
} | |
function reject(uint id, string datainfo, uint _version, uint16 _count, address recipient, uint amount) onlyowner { | |
if(_count > pendingCount) { log("_count > pendingCount"); return; } | |
pendingCount -= _count; | |
//send money back | |
yes(id, datainfo, _version); | |
//Reject order to event log | |
content(id, datainfo, _version, 3, msg.sender, _count, amount); | |
} | |
function cancel(string datainfo, uint _version) onlyowner { | |
//Canceled status | |
status = 2; | |
//Cancel order to event log | |
content(0, datainfo, _version, 4, msg.sender, availableCount, 0); | |
} | |
//remove buyer from the watchlist | |
function unbuy() { | |
buyers[msg.sender] = 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment