Skip to content

Instantly share code, notes, and snippets.

@mpolci
Forked from blackyblack/escrow_goods.sol
Created July 10, 2016 07:29
Show Gist options
  • Save mpolci/15acd19a8e5863f8335bd2133cf76f28 to your computer and use it in GitHub Desktop.
Save mpolci/15acd19a8e5863f8335bd2133cf76f28 to your computer and use it in GitHub Desktop.
/* 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