diff --git a/contracts/ether/WeiExpense.sol b/contracts/ether/WeiExpense.sol index 219c603..76680b6 100644 --- a/contracts/ether/WeiExpense.sol +++ b/contracts/ether/WeiExpense.sol @@ -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) @@ -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; @@ -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 @@ -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) { diff --git a/contracts/examples/moneyflow/Roadmap.sol b/contracts/examples/moneyflow/Roadmap.sol index b218b01..c1d4077 100644 --- a/contracts/examples/moneyflow/Roadmap.sol +++ b/contracts/examples/moneyflow/Roadmap.sol @@ -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); diff --git a/test/moneyflow.tests.js b/test/moneyflow.tests.js index 1fbc947..040a0d3 100644 --- a/test/moneyflow.tests.js +++ b/test/moneyflow.tests.js @@ -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'); @@ -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 }); @@ -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 () => { @@ -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 }); diff --git a/test/wei_fund.tests.js b/test/wei_fund.tests.js index 60b79b5..40dd66b 100644 --- a/test/wei_fund.tests.js +++ b/test/wei_fund.tests.js @@ -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); @@ -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(); @@ -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 }); @@ -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(); @@ -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); @@ -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); @@ -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);