@@ -0,0 +1,316 @@
/* 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 ;
}
}