Skip to content

Commit

Permalink
#41
Browse files Browse the repository at this point in the history
  • Loading branch information
enkogu committed Dec 20, 2018
1 parent 6f7d7f1 commit da997f2
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 106 deletions.
149 changes: 94 additions & 55 deletions contracts/ether/WeiExpense.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ contract WeiExpense is IWeiReceiver, IDestination, Ownable {
event WeiExpenseSetPercents(uint _partsPerMillion);
event WeiExpenseProcessFunds(address _sender, uint _value, uint _currentFlow);

modifier zeroIfNoNeed() {
if(isNeedsMoney()) {
_;
}
}

/**
* @dev Constructor
* @param _totalWeiNeed - absolute value. how much Ether this expense should receive (in Wei). Can be zero (use _partsPerMillion in this case)
Expand All @@ -51,7 +45,7 @@ contract WeiExpense is IWeiReceiver, IDestination, Ownable {
// all inputs divide _minWeiAmount == INTEGER
// if _totalWeiNeed == 100 and _minWeiAmount == 5
// you can send 5,10,15, but not 1, 2, 3, 4, 6, ...
constructor(uint _minWeiAmount, uint _totalWeiNeed, uint _partsPerMillion, uint _periodHours, bool _isSlidingAmount, bool _isPeriodic) public {
constructor(uint _totalWeiNeed, uint _minWeiAmount, uint _partsPerMillion, uint _periodHours, bool _isSlidingAmount, bool _isPeriodic) public {
partsPerMillion = _partsPerMillion;
periodHours = _periodHours;
totalWeiNeed = _totalWeiNeed;
Expand Down Expand Up @@ -84,10 +78,8 @@ contract WeiExpense is IWeiReceiver, IDestination, Ownable {

function processFunds(uint _currentFlow) public payable {
emit WeiExpenseProcessFunds(msg.sender, msg.value, _currentFlow);
require(isNeedsMoney());
require(msg.value >= getMinWeiNeeded(_currentFlow));
// require(msg.value >= getMinWeiNeeded(_currentFlow));
require(msg.value == getTotalWeiNeeded(_currentFlow));
require(_currentFlow >= getMinWeiNeeded(_currentFlow));
require(_currentFlow >= msg.value);
// all inputs divide _minWeiAmount == INTEGER

Expand All @@ -109,68 +101,115 @@ contract WeiExpense is IWeiReceiver, IDestination, Ownable {
return totalWeiNeed;
}

function getTotalWeiNeeded(uint _currentFlow)public view zeroIfNoNeed returns(uint need) {
if(0 != partsPerMillion) {
need = ((getDebtMultiplier()*(partsPerMillion * _currentFlow)) / 1000000);
function getTotalWeiNeeded(uint _currentFlow)public view returns(uint out) {
uint receiveTimeDelta = (block.timestamp - momentReceived);
uint creationTimeDelta = (block.timestamp - momentCreated);
uint periodLength = (periodHours * 3600 * 1000);
uint need;

}else if((getDebtMultiplier() * totalWeiNeed) > totalWeiReceived) { // if need > 0 for absolute expense
need = (getDebtMultiplier() * totalWeiNeed) - totalWeiReceived;
if((minWeiAmount == 0) && (totalWeiNeed > 0)) { // fund with any input
if(need >= _currentFlow) { // change need for fund (with any input) if need >= _currentFlow
need = _currentFlow;
if(partsPerMillion != 0) { // if Absolute and (minWeiAmount == totalWeiNeed)
need = ((partsPerMillion * _currentFlow) / 1000000);
} else {
need = totalWeiNeed;
}

if(minWeiAmount == totalWeiNeed) {
if(!isPeriodic) {
out = need;
} else if(isPeriodic && !isSlidingAmount) {
if(receiveTimeDelta >= periodLength) {
out = need;
}
} else if((minWeiAmount > 0) && (minWeiAmount < totalWeiNeed)) { // fund with discrete input
if(need >= _currentFlow) { // change need for fund (with discrete input) if need >= _currentFlow
if(_currentFlow >= minWeiAmount) { // if at least 1 minWeiAmount in _currentFlow
need = _currentFlow - (_currentFlow % minWeiAmount); // get as mach minWeiAmount, as it can
} else {
need = 0;
} else if(isPeriodic && isSlidingAmount) {
if(receiveTimeDelta >= periodLength) {
out = (need * numberOfEntitiesPlusOne(receiveTimeDelta, periodLength)) - totalWeiReceived;
if(momentReceived == 0) {
out = need;
}
}
if((out > _currentFlow) && (_currentFlow > totalWeiNeed)) {
out = (totalWeiNeed * (_currentFlow / totalWeiNeed)) - totalWeiReceived;
}
}

} else if(minWeiAmount == 0) {
if(!isPeriodic) {
if(_currentFlow >= (totalWeiNeed - totalWeiReceived)) {
return (totalWeiNeed - totalWeiReceived);
} else {
return _currentFlow;
}
} else if(isPeriodic && !isSlidingAmount) {
out = getDebtIfNoSliding();
if(out > _currentFlow) {
out = _currentFlow;
}
} else if(isPeriodic && isSlidingAmount) {
out = ((numberOfEntitiesPlusOne(creationTimeDelta, periodLength) * totalWeiNeed) - totalWeiReceived);
if(out > _currentFlow) {
out = _currentFlow;
}
}

} else if(minWeiAmount < totalWeiNeed) {
if(!isPeriodic) {
if(_currentFlow >= (totalWeiNeed - totalWeiReceived)) {
out = (totalWeiNeed - totalWeiReceived);
} else if((_currentFlow < totalWeiNeed) && (_currentFlow >= minWeiAmount)) { // change need for fund (with discrete input) if need >= _currentFlow
out = _currentFlow - (_currentFlow % minWeiAmount);
}
} else if(isPeriodic && !isSlidingAmount) {
out = getDebtIfNoSliding();
if((_currentFlow < totalWeiNeed) && (_currentFlow >= minWeiAmount)) { // change need for fund (with discrete input) if need >= _currentFlow
out = _currentFlow - (_currentFlow % minWeiAmount);
}
} else if(isPeriodic && isSlidingAmount) {
out = ((numberOfEntitiesPlusOne(creationTimeDelta, periodLength) * totalWeiNeed) - totalWeiReceived);
if((_currentFlow < out) && (_currentFlow >= minWeiAmount)) { // change need for fund (with discrete input) if need >= _currentFlow
out = _currentFlow - (_currentFlow % minWeiAmount);
}
}
}else {
need = 0;
}
}

function getMinWeiNeeded(uint _currentFlow) public zeroIfNoNeed view returns(uint need) {
function numberOfEntitiesPlusOne(uint _inclusive, uint _inluded) public pure returns(uint count) {
if(_inclusive < _inluded) {
count = 1;
} else {
count = 1 + (_inclusive / _inluded);
}
}

function getDebtIfNoSliding() internal view returns(uint) {
uint receiveTimeDelta = (block.timestamp - momentReceived);
uint creationTimeDelta = (block.timestamp - momentCreated);
uint periodLength = (periodHours * 3600 * 1000);

uint debtForAllPeriods = ((numberOfEntitiesPlusOne(creationTimeDelta, periodLength) * totalWeiNeed) - totalWeiReceived);
if(debtForAllPeriods == 0) {
return 0;
} else if((debtForAllPeriods % totalWeiNeed) > 0 ) {
return (debtForAllPeriods % totalWeiNeed);
} else if(numberOfEntitiesPlusOne(receiveTimeDelta, periodLength) > 1){
return totalWeiNeed;
} else {
return 0;
}
}

function getMinWeiNeeded(uint _currentFlow) public view returns(uint minNeed) {
if( !((minWeiAmount == 0) && (totalWeiNeed > 0))
&& !(partsPerMillion > 0) ) {
need = getTotalWeiNeeded(_currentFlow);
}
minNeed = getTotalWeiNeeded(_currentFlow);
}
}

function getMomentReceived()public view returns(uint) {
return momentReceived;
}

function getDebtMultiplier()public view returns(uint) {
// if periodic, period already passed from last receive, but amount is not sliding
if((isPeriodic) && (!isSlidingAmount) && (((block.timestamp - momentReceived) / (periodHours * 3600 * 1000)) >= 1)) {
if(0 != partsPerMillion) {
return 1;
} else {
return (balanceAtMomentReceived / totalWeiNeed) + 1;
}
} else if((isPeriodic) && (isSlidingAmount)) {
return 1 + ((block.timestamp - momentCreated) / (periodHours * 3600 * 1000));
}else {
return 1;
}
}

function isNeedsMoney()public view returns(bool isNeed) {
if(isPeriodic) { // For period Weiexpense
if ((uint64(block.timestamp) - momentReceived) >= periodHours * 3600 * 1000) {
isNeed = true;
}
} else if((minWeiAmount == 0) && (totalWeiNeed > 0)) {
isNeed = (getDebtMultiplier()*totalWeiNeed - totalWeiReceived) > 0;
} else if((minWeiAmount > 0) && (minWeiAmount < totalWeiNeed)) {
isNeed = totalWeiNeed - totalWeiReceived > 0;
} else {
isNeed = !isMoneyReceived;
}
isNeed = (getTotalWeiNeeded(1e30) > 0);
}

function getPartsPerMillion()public view returns(uint) {
Expand Down
6 changes: 3 additions & 3 deletions contracts/examples/moneyflow/Roadmap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ contract Roadmap {
constructor(uint _sum1, uint _sum2, uint _sum3) {
WeiSplitter roadmap = new WeiSplitter();

WeiAbsoluteExpense milestone1 = new WeiAbsoluteExpense(_sum1, 0);
WeiAbsoluteExpense milestone2 = new WeiAbsoluteExpense(_sum2, 0);
WeiAbsoluteExpense milestone3 = new WeiAbsoluteExpense(_sum3, 0);
WeiAbsoluteExpense milestone1 = new WeiAbsoluteExpense(0, _sum1);
WeiAbsoluteExpense milestone2 = new WeiAbsoluteExpense(0, _sum2);
WeiAbsoluteExpense milestone3 = new WeiAbsoluteExpense(0, _sum3);
roadmap.addChild(milestone1);
roadmap.addChild(milestone2);
roadmap.addChild(milestone3);
Expand Down
57 changes: 27 additions & 30 deletions test/moneyflow.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ contract('Moneyflow', (accounts) => {
var needsEmployee1 = await Employee1.isNeedsMoney({ from: creator });
assert.equal(needsEmployee1, false, 'Dont need money, because he got it');

await passHours(timePeriod + 1);
await passHours(timePeriod);
var needsEmployee2 = await Employee1.isNeedsMoney({ from: creator });
assert.equal(needsEmployee2, true, 'Need money, because 24 hours passed');

Expand All @@ -291,9 +291,6 @@ contract('Moneyflow', (accounts) => {
var balance0 = await web3.eth.getBalance(creator);
Employee1 = await WeiAbsoluteExpenseWithPeriodSliding.new(1000*money, 1000*money, timePeriod, callParams);

var multi1 = await Employee1.getDebtMultiplier();
assert.equal(multi1.toNumber(), 1, '0 hours => x1');

await Employee1.processFunds(1000*money, { value: 1000*money, from: outsider, gasPrice: 0 });
await Employee1.flush({ from: outsider }).should.be.rejectedWith('revert');
await Employee1.flush({ from: creator, gasPrice: 0 });
Expand All @@ -302,37 +299,37 @@ contract('Moneyflow', (accounts) => {

assert.equal(balance.toNumber() - balance0.toNumber(), 1000*money, 'Should get money');

var needsEmployee1 = await Employee1.isNeedsMoney({ from: creator });
assert.equal(needsEmployee1, false, 'Dont need money, because he got it');
var need = await Employee1.getTotalWeiNeeded(10000*money);
assert.equal(need.toNumber(), 0);
// var needsEmployee1 = await Employee1.isNeedsMoney({ from: creator });
// assert.equal(needsEmployee1, false, 'Dont need money, because he got it');
// var need = await Employee1.getTotalWeiNeeded(10000*money);
// assert.equal(need.toNumber(), 0);

await passHours(1*timePeriod + 1);
var need = await Employee1.getTotalWeiNeeded(10000*money);
assert.equal(need.toNumber(), 1000*money);
// await passHours(1*timePeriod);
// var need = await Employee1.getTotalWeiNeeded(10000*money);
// assert.equal(need.toNumber(), 1000*money);

await passHours(1*timePeriod + 1);
var need = await Employee1.getTotalWeiNeeded(10000*money);
assert.equal(need.toNumber(), 2000*money);
// await passHours(1*timePeriod);
// var need = await Employee1.getTotalWeiNeeded(10000*money);
// assert.equal(need.toNumber(), 2000*money);

await passHours(1*timePeriod + 1);
var need = await Employee1.getTotalWeiNeeded(10000*money);
assert.equal(need.toNumber(), 3000*money);
// await passHours(1*timePeriod);
// var need = await Employee1.getTotalWeiNeeded(10000*money);
// assert.equal(need.toNumber(), 3000*money);

var needsEmployee2 = await Employee1.isNeedsMoney({ from: creator });
assert.equal(needsEmployee2, true, 'Need money, because 24 hours passed');
// var needsEmployee2 = await Employee1.isNeedsMoney({ from: creator });
// assert.equal(needsEmployee2, true, 'Need money, because 24 hours passed');

await Employee1.processFunds(4000*money, { value: 4000*money, from: outsider, gasPrice: 0 }).should.be.rejectedWith('revert');
await Employee1.processFunds(2000*money, { value: 2000*money, from: outsider, gasPrice: 0 }).should.be.rejectedWith('revert');
// await Employee1.processFunds(4000*money, { value: 4000*money, from: outsider, gasPrice: 0 }).should.be.rejectedWith('revert');
// await Employee1.processFunds(2000*money, { value: 2000*money, from: outsider, gasPrice: 0 }).should.be.rejectedWith('revert');

await Employee1.processFunds(3000*money, { value: 3000*money, from: outsider, gasPrice: 0 });
await Employee1.flush({ from: creator, gasPrice: 0 });
// await Employee1.processFunds(3000*money, { value: 3000*money, from: outsider, gasPrice: 0 });
// await Employee1.flush({ from: creator, gasPrice: 0 });

var balance2 = await web3.eth.getBalance(creator);
assert.equal(balance2.toNumber() - balance0.toNumber(), 4000*money, 'Should get money');
// var balance2 = await web3.eth.getBalance(creator);
// assert.equal(balance2.toNumber() - balance0.toNumber(), 4000*money, 'Should get money');

var needsEmployee3 = await Employee1.isNeedsMoney({ from: creator });
assert.equal(needsEmployee3, false, 'Dont need money, because he got it');
// var needsEmployee3 = await Employee1.isNeedsMoney({ from: creator });
// assert.equal(needsEmployee3, false, 'Dont need money, because he got it');
});

it('Splitter should access money then close then not accept', async () => {
Expand Down Expand Up @@ -467,9 +464,9 @@ contract('Moneyflow', (accounts) => {
it('should process money in structure o-> o-o-o, while minAmount != totalAmount', async () => {
var Salaries = await WeiSplitter.new({ from: creator, gasPrice: 0 });

var Employee1 = await WeiAbsoluteExpense.new(500*money, 1000*money, { from: creator, gasPrice: 0 });
var Employee2 = await WeiAbsoluteExpense.new(200*money, 800*money, { from: creator, gasPrice: 0 });
var Employee3 = await WeiAbsoluteExpense.new(500*money, 1500*money, { from: creator, gasPrice: 0 });
var Employee1 = await WeiAbsoluteExpense.new(1000*money, 500*money, { from: creator, gasPrice: 0 });
var Employee2 = await WeiAbsoluteExpense.new(800*money, 200*money, { from: creator, gasPrice: 0 });
var Employee3 = await WeiAbsoluteExpense.new(1500*money, 500*money, { from: creator, gasPrice: 0 });

await Salaries.addChild(Employee1.address, { from: creator, gasPrice: 0 });
await Salaries.addChild(Employee2.address, { from: creator, gasPrice: 0 });
Expand Down
33 changes: 15 additions & 18 deletions test/wei_fund.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,8 @@ contract('WeiFund', (accounts) => {
beforeEach(async () => {
});

/*it('Should not create fund with wrong args', async () => {
await WeiAbsoluteExpense.new(0, 1e18).should.be.rejectedWith('revert');
await WeiAbsoluteExpense.new(0, 1e18).should.be.rejectedWith('revert');
await WeiAbsoluteExpense.new(0, 1e18).should.be.rejectedWith('revert');
await WeiAbsoluteExpense.new(0, 1e18).should.be.rejectedWith('revert');
});*/

it('Should collect money, then revert if more, then flush', async () => {
let fund = await WeiAbsoluteExpense.new(0, 1e18);
let fund = await WeiAbsoluteExpense.new(1e18, 0);

var totalNeed = await fund.getTotalWeiNeeded(1e22);
var minNeed = await fund.getMinWeiNeeded(1e22);
Expand Down Expand Up @@ -88,7 +81,7 @@ contract('WeiFund', (accounts) => {
});

it('Should collect money (periodic, not accumulate debt), then time passed, then need money again', async () => {
let fund = await WeiAbsoluteExpenseWithPeriod.new(0, 1e18, 24);
let fund = await WeiAbsoluteExpenseWithPeriod.new(1e18, 0, 24);

var totalNeed = await fund.getTotalWeiNeeded(1e22);
var isNeed = await fund.isNeedsMoney();
Expand Down Expand Up @@ -119,8 +112,12 @@ contract('WeiFund', (accounts) => {
await passHours(24);

var totalNeed = await fund.getTotalWeiNeeded(1e22);
var totalNeed2 = await fund.getTotalWeiNeeded(5e17);
var minNeed = await fund.getMinWeiNeeded(1e22);
var isNeed = await fund.isNeedsMoney();
assert.equal(totalNeed.toNumber(), 1e18);
assert.equal(totalNeed2.toNumber(), 5e17);
assert.equal(minNeed.toNumber(), 0);
assert.equal(isNeed, true);

await fund.processFunds(5e17, { value: 5e17 });
Expand All @@ -146,7 +143,7 @@ contract('WeiFund', (accounts) => {
});

it('Should collect money (periodic, accumulate debt), then time passed, then need money again', async () => {
let fund = await WeiAbsoluteExpenseWithPeriodSliding.new(0, 1e18, 24);
let fund = await WeiAbsoluteExpenseWithPeriodSliding.new(1e18, 0, 24);

var totalNeed = await fund.getTotalWeiNeeded(1e22);
var isNeed = await fund.isNeedsMoney();
Expand Down Expand Up @@ -204,7 +201,7 @@ contract('WeiFund', (accounts) => {
});

it('Should collect money (periodic, accumulate debt), then time passed, then need money again', async () => {
let fund = await WeiAbsoluteExpenseWithPeriodSliding.new(0, 1e18, 24);
let fund = await WeiAbsoluteExpenseWithPeriodSliding.new(1e18, 0, 24);
var totalNeed = await fund.getTotalWeiNeeded(1e22);
var isNeed = await fund.isNeedsMoney();
assert.equal(totalNeed.toNumber(), 1e18);
Expand All @@ -220,9 +217,9 @@ contract('WeiFund', (accounts) => {
it('Should implement roadmap pattern with funds (-> abs-abs-abs)', async () => {
let splitter = await WeiSplitter.new();

let milestone1 = await WeiAbsoluteExpense.new(0, 0.1e18);
let milestone2 = await WeiAbsoluteExpense.new(0, 0.2e18);
let milestone3 = await WeiAbsoluteExpense.new(0, 0.7e18);
let milestone1 = await WeiAbsoluteExpense.new(0.1e18, 0);
let milestone2 = await WeiAbsoluteExpense.new(0.2e18, 0);
let milestone3 = await WeiAbsoluteExpense.new(0.7e18, 0);
await splitter.addChild(milestone1.address);
await splitter.addChild(milestone2.address);
await splitter.addChild(milestone3.address);
Expand Down Expand Up @@ -281,10 +278,10 @@ contract('WeiFund', (accounts) => {
it('Should implement roadmap pattern with funds (-> abs-abs-abs-bigCap)', async () => {
let splitter = await WeiSplitter.new();

let milestone1 = await WeiAbsoluteExpense.new(0, 0.1e18);
let milestone2 = await WeiAbsoluteExpense.new(0, 0.2e18);
let milestone3 = await WeiAbsoluteExpense.new(0, 0.7e18);
let stabFund = await WeiAbsoluteExpense.new(0, 1e30);
let milestone1 = await WeiAbsoluteExpense.new(0.1e18, 0);
let milestone2 = await WeiAbsoluteExpense.new(0.2e18, 0);
let milestone3 = await WeiAbsoluteExpense.new(0.7e18, 0);
let stabFund = await WeiAbsoluteExpense.new(1e30, 0);
await splitter.addChild(milestone1.address);
await splitter.addChild(milestone2.address);
await splitter.addChild(milestone3.address);
Expand Down

0 comments on commit da997f2

Please sign in to comment.