-
-
Save JackBekket/a7b60054e30455cbb0e1cdf631a60681 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: | |
Contract: | |
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"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"count","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"cancel","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"seller","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"freezePeriod","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"},{"name":"_count","type":"uint16"}],"name":"buy","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"status","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"rewardPromille","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"getMoney","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"no","outputs":[],"payable":false,"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":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"kill","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalEscrows","outputs":[{"name":"","type":"uint256"}],"payable":false,"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":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"feeFunds","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"}],"name":"yes","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"buyers","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"availableCount","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"price","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"datainfo","type":"string"},{"name":"_version","type":"uint256"},{"name":"_count","type":"uint16"}],"name":"accept","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"unbuy","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"getFees","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"feePromille","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"pendingCount","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"arbiter","outputs":[{"name":"","type":"address"}],"payable":false,"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.4.2-nightly.2016.9.9 | |
*/ | |
pragma solidity ^0.4.0; | |
contract escrow_goods { | |
struct EscrowInfo { | |
address buyer; | |
uint lockedFunds; | |
uint frozenTime; | |
uint frozenFunds; | |
uint buyerNo; | |
uint sellerNo; | |
} | |
//data | |
uint constant arbitragePeriod = 30 days; | |
uint constant safeGas = 25000; | |
//seller/owner of the goods | |
address public seller; | |
//escrow related | |
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 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; | |
uint16 atomicLock; | |
//events | |
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) _; } | |
//modules | |
function escrow_goods(address _arbiter, uint _freezePeriod, uint _feePromille, uint _rewardPromille, | |
uint16 _count, uint _price) { | |
seller = msg.sender; | |
// all variables are always initialized to 0, save gas | |
//escrow related | |
arbiter = _arbiter; | |
freezePeriod = _freezePeriod; | |
feePromille = _feePromille; | |
rewardPromille = _rewardPromille; | |
//goods related | |
//status = Available | |
status = 1; | |
count = _count; | |
price = _price; | |
availableCount = count; | |
} | |
function kill() { | |
//do not allow killing contract with active escrows | |
if(totalEscrows > 0) { | |
log_event("totalEscrows > 0"); | |
return; | |
} | |
//do not allow killing contract with unclaimed escrow fees | |
if(feeFunds > 0) { | |
log_event("feeFunds > 0"); | |
return; | |
} | |
//only allow the seller to kill contract | |
if(msg.sender != seller) { | |
log_event("msg.sender != seller"); | |
return; | |
} | |
suicide(msg.sender); | |
} | |
function safeSend(address addr, uint value) internal returns (bool) { | |
if(atomicLock > 0) throw; | |
atomicLock = 1; | |
if (!(addr.call.gas(safeGas).value(value)())) { | |
atomicLock = 0; | |
return false; | |
} | |
atomicLock = 0; | |
return true; | |
} | |
//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) { | |
log_event("info.lockedFunds == 0"); | |
return; | |
} | |
if(msg.sender != info.buyer && msg.sender != seller) { | |
log_event("msg.sender != info.buyer && msg.sender != 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(seller, payment)) { | |
log_event("!safeSend(seller, payment)"); | |
return; | |
} | |
} else if(msg.sender == seller) { | |
//send funds to buyer | |
if(!safeSend(info.buyer, payment)) { | |
log_event("!safeSend(info.buyer, payment)"); | |
return; | |
} | |
} else { | |
//HACK: should not get here | |
log_event("unknown msg.sender"); | |
return; | |
} | |
//remove record from escrows | |
if(totalEscrows > 0) totalEscrows -= 1; | |
info.lockedFunds = 0; | |
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) { | |
log_event("info.lockedFunds == 0"); | |
return; | |
} | |
if(msg.sender != info.buyer && msg.sender != seller) { | |
log_event("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; | |
} | |
else if(msg.sender == 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(uint id, address who, uint payment, string datainfo, uint _version) { | |
if(msg.sender != arbiter) return; | |
EscrowInfo info = escrows[id]; | |
if(info.lockedFunds == 0) { | |
log_event("info.lockedFunds == 0"); | |
return; | |
} | |
if(info.frozenFunds == 0) { | |
log_event("info.frozenFunds == 0"); | |
return; | |
} | |
if(who != seller && who != info.buyer) { | |
log_event("who != 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 * rewardPromille) / 1000; | |
if(reward > (info.lockedFunds - payment)) { | |
log_event("reward > (info.lockedFunds - payment)"); | |
return; | |
} | |
//send funds to the winner | |
if(!safeSend(who, payment)) { | |
log_event("!safeSend(who, payment)"); | |
return; | |
} | |
//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) { | |
log_event("msg.sender != arbiter"); | |
return; | |
} | |
if(feeFunds > this.balance) { | |
//HACK: should not get here - funds cannot be unlocked in this case | |
log_event("feeFunds > this.balance"); | |
return; | |
} | |
if(!safeSend(arbiter, feeFunds)) { | |
log_event("!safeSend(arbiter, feeFunds)"); | |
return; | |
} | |
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) { | |
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 + freezePeriod)) { | |
log_event("now < (info.frozenTime + 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 + freezePeriod + arbitragePeriod)) { | |
log_event("now < (info.frozenTime + freezePeriod + arbitragePeriod)"); | |
return; | |
} | |
//arbiter was silent so redeem the funds to the buyer | |
if(!safeSend(info.buyer, payment)) { | |
log_event("!safeSend(info.buyer, payment)"); | |
return; | |
} | |
info.lockedFunds = 0; | |
return; | |
} | |
if(info.buyerNo != 0) { | |
if(!safeSend(info.buyer, payment)) { | |
log_event("!safeSend(info.buyer, payment)"); | |
return; | |
} | |
info.lockedFunds = 0; | |
return; | |
} | |
if(info.sellerNo != 0) { | |
if(!safeSend(seller, payment)) { | |
log_event("!safeSend(seller, payment)"); | |
return; | |
} | |
info.lockedFunds = 0; | |
return; | |
} | |
} | |
//goods API | |
//buy with escrow. id - escrow info id | |
function buy(uint id, string datainfo, uint _version, uint16 _count) { | |
//reject money transfers for bad item status | |
if(status != 1) throw; | |
if(msg.value < (price * _count)) throw; | |
if(_count > availableCount) throw; | |
if(feePromille > 1000) throw; | |
if(rewardPromille > 1000) throw; | |
if((feePromille + rewardPromille) > 1000) throw; | |
//create default EscrowInfo struct or access existing | |
EscrowInfo info = escrows[id]; | |
//lock only once for a given id | |
if(info.lockedFunds > 0) throw; | |
//lock funds | |
uint fee = (msg.value * feePromille) / 1000; | |
//limit fees | |
if(fee > msg.value) throw; | |
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_event("_count > availableCount"); | |
return; | |
} | |
if(_count > pendingCount) { | |
log_event("_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_event("_count > pendingCount"); | |
return; | |
} | |
pendingCount -= _count; | |
//send money back | |
yes(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(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