Created
July 5, 2016 10:42
-
-
Save blackyblack/b7c3e7a5cec5a3dd834792797a61d7c3 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 only messages approved by owner */ | |
/* Deployment: | |
Owner: 0xeb5fa6cbf2aca03a0df228f2df67229e2d3bd01e | |
Last address: 0x0318179601a70085aeb488f178b081295b65ecc9 | |
ABI: [{"constant":false,"inputs":[{"name":"datainfo","type":"string"},{"name":"version","type":"uint256"}],"name":"add","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"flush","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"contentCount","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"datainfo","type":"string"},{"indexed":true,"name":"version","type":"uint256"}],"name":"content","type":"event"}] | |
Optimized: yes | |
Solidity version: 0.3.2-9e36bdda | |
*/ | |
contract self_store { | |
address owner; | |
uint public contentCount = 0; | |
event content(string datainfo, uint indexed version); | |
modifier onlyowner { if (msg.sender == owner) _ } | |
function self_store() public { owner = msg.sender; } | |
///TODO: remove in release | |
function kill() onlyowner { suicide(owner); } | |
function flush() onlyowner { | |
owner.send(this.balance); | |
} | |
function add(string datainfo, uint version) onlyowner { | |
contentCount++; | |
content(datainfo, version); | |
} | |
} |
This is a trivial refactoring in order to check deployment gas cost using a library
// Deployment original escrow_goods: 2376873 gas
// Deployment EscrowLib: 1999050 gas
// Deployment escrow_goods: 1120790 gas
// Deployment escrow_goods: 1101878 gas (removed initializations to 0)
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;
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;
}
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) {
//allow anyone to claim dead contract funds
if(now > (data.activityTime + stalePeriod)) {
suicide(msg.sender);
return;
}
//do not allow killing contract with active escrows
if(data.totalEscrows > 0) return;
//do not allow killing contract with unclaimed escrow fees
if(data.feeFunds > 0) return;
if(msg.sender != data.seller) return;
suicide(data.seller);
}
function log(string message) private {
log_event(message);
}
//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) return;
if(msg.sender != info.buyer && msg.sender != data.seller) return;
uint payment = info.lockedFunds;
if(payment > this.balance) payment = this.balance;
if(data.totalEscrows > 0) data.totalEscrows -= 1;
info.lockedFunds = 0;
if(msg.sender == info.buyer) {
//send funds to seller
data.seller.send(payment);
}
if(msg.sender == data.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(Context storage data, uint id, string datainfo, uint _version) {
EscrowInfo info = data.escrows[id];
if(info.lockedFunds == 0) return;
if(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;
}
if(msg.sender == data.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(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) return;
if(info.frozenFunds == 0) return;
if(who != data.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 * data.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;
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) return;
if(data.feeFunds > this.balance) data.feeFunds = this.balance;
data.arbiter.send(data.feeFunds);
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) 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 + data.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 + data.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) {
data.seller.send(payment);
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) {
if(data.status != 1) { log("status != 1"); throw; }
if(msg.value < (data.price * _count)) { log("msg.value < (data.price * _count)"); throw; }
if(_count > data.availableCount) { log("_count > availableCount"); throw; }
//create default EscrowInfo struct or access existing
EscrowInfo info = data.escrows[id];
//lock only once for a given id
if(info.lockedFunds > 0) {
log("Already locked");
throw;
}
//lock funds
//refresh watchdog timer
data.activityTime = now;
uint fee = (msg.value * data.feePromille) / 1000;
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("_count > availableCount"); return; }
if(_count > data.pendingCount) { log("_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("_count > pendingCount"); return; }
data.pendingCount -= _count;
//send money back
yes(data, id, datainfo, _version);
//Reject order to event log
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;
//escrow related
data.arbiter = _arbiter;
data.freezePeriod = _freezePeriod;
data.feePromille = _feePromille;
data.rewardPromille = _rewardPromille;
data.activityTime = now;
// all variables are always initialized to 0, save gas
//data.feeFunds = 0;
//data.totalEscrows = 0;
//goods related
//status = Available
data.status = 1;
data.count = _count;
data.price = _price;
data.availableCount = data.count;
//data.pendingCount = 0;
}
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
Always verify .send() returned value. A send can fail, in this case the value is not transfered but the contract state is not rolled back. Instructions sequence like this are error prone:
A general best practices are:
So an approach could be:
Deployment of the contract escrow_goods costs a minimum of 0.0475 eth, about 0.5$. If it is too much, the gas
consumption could be lowered creating a Solidity library. Remember that when the eth price goes up quickly, the miners don't adjust the minimum gas price so quickly.