Skip to content

Commit

Permalink
#49
Browse files Browse the repository at this point in the history
  • Loading branch information
enkogu committed Dec 26, 2018
1 parent f86605a commit 4fe71c8
Show file tree
Hide file tree
Showing 18 changed files with 859 additions and 817 deletions.
245 changes: 59 additions & 186 deletions contracts/ExpenseBase.sol
Original file line number Diff line number Diff line change
@@ -1,212 +1,85 @@
pragma solidity ^0.4.24;

import "./ExpenseLib.sol";

import "./interfaces/IDestination.sol";
import "./interfaces/IReceiver.sol";

import "zeppelin-solidity/contracts/math/SafeMath.sol";
import "zeppelin-solidity/contracts/ownership/Ownable.sol";


/**
* @title ExpenseBase
* @dev library-like contract for WeiExpense, WeiTable, ERC20Expense, ERC20Table
* @title WeiExpense
* @dev Something that needs money (task, salary, bonus, etc)
* Should be used in the Moneyflow so will automatically receive Wei.
*/
contract ExpenseBase {
event ExpenseFlush(address _owner, uint _balance);
event ExpenseSetNeeded(uint _totalNeeded);
event ExpenseSetPercents(uint _partsPerMillion);
event ExpenseProcessFunds(address _sender, uint _value, uint _currentFlow);

struct Expense {
uint128 totalNeeded;
uint128 minAmount;
uint32 partsPerMillion;

uint32 periodHours;

PeriodType periodType;

uint32 momentReceived;
uint128 balance;

uint128 totalReceived;
uint32 momentCreated;

bool isOpen;
contract ExpenseBase is ExpenseLib, IReceiver, IDestination, Ownable {
Expense public expense;

function getReceiverType() public view returns(Type) {
if(0 != expense.partsPerMillion) {
return Type.Relative;
} else {
return Type.Absolute;
}
}

enum PeriodType {
NonPeriodic,
Periodic,
PeriodicSliding
function processFunds(uint _currentFlow) public payable {
emit ExpenseProcessFunds(msg.sender, msg.value, _currentFlow);
expense = _processFunds(expense, _currentFlow, msg.value);
}
/**
* @dev constructExpense
* @param _totalNeeded - absolute value. how much Ether this e should receive (in Wei). Can be zero (use _partsPerMillion in this case)
* @param _partsPerMillion - if need to get % out of the input flow -> specify this parameter (1% is 10000 units)
* @param _periodHours - if not isPeriodic and periodHours>0 ->no sense. if isPeriodic and periodHours == 0 -> needs money everytime. if isPeriodic and periodHours>0 -> needs money every period.
* @param _isSlidingAmount - if you don't pay in the current period -> will accumulate the needed amount (only for _totalNeeded!)
* @param _isPeriodic - if isPeriodic and periodHours>0 -> needs money every period. if isPeriodic and periodHours == 0 -> needs money everytime.
*/

// _totalNeeded divide _minAmount == INTEGER
// all inputs divide _minAmount == INTEGER
// if _totalNeeded == 100 and _minAmount == 5
// you can send 5,10,15, but not 1, 2, 3, 4, 6, ...
function constructExpense(uint128 _totalNeeded, uint128 _minAmount, uint32 _partsPerMillion, uint32 _periodHours, bool _isSlidingAmount, bool _isPeriodic) internal view returns(Expense e) {
require(!((_isSlidingAmount) && (_periodHours == 0)));
require(!(!(_isPeriodic) && (_periodHours != 0)));
require(!((_isSlidingAmount) && (!_isPeriodic)));
require(!((_totalNeeded == 0) && (_minAmount != 0)));
require(!((_partsPerMillion != 0) && (_minAmount != 0)));
require(!((_partsPerMillion != 0) && (_totalNeeded != 0)));
require(_minAmount <= _totalNeeded);
if(_minAmount != 0) {
require((_totalNeeded % _minAmount) == 0);
}

e.partsPerMillion = _partsPerMillion;
e.periodHours = _periodHours;
e.totalNeeded = _totalNeeded;
e.minAmount = _minAmount;
e.momentCreated = uint32(block.timestamp);
e.isOpen = true;

if(!_isPeriodic) {
e.periodType = PeriodType.NonPeriodic;
} else if(_isPeriodic && !_isSlidingAmount) {
e.periodType = PeriodType.Periodic;
} else {
e.periodType = PeriodType.PeriodicSliding;
}
function getIsMoneyReceived() public view returns(bool) {
return expense.totalReceived > 0;
}

function processWeiExpenseFunds(Expense _e, uint _currentFlow, uint _value) internal view returns(Expense e) {
e = _e;
require(_value == getExpenseTotalNeeded(e, _currentFlow));
require(_currentFlow >= _value);
// all inputs divide _minAmount == INTEGER
function getNeededWei() public view returns(uint) {
return expense.totalNeeded;
}

e.totalReceived += uint128(_value);
e.balance += uint128(_value);
function getTotalNeeded(uint _currentFlow)public view returns(uint) {
return _getTotalNeeded(expense, _currentFlow);
}

if((getExpenseTotalNeeded(_e, _value) == 0) || (_e.periodType == PeriodType.Periodic)) {
e.momentReceived = uint32(block.timestamp);
}
function getMinNeeded(uint _currentFlow) public view returns(uint) {
return _getMinNeeded(expense, _currentFlow);
}

function openExpense(Expense storage _e) internal {
_e.isOpen = true;
function getMomentReceived()public view returns(uint) {
return expense.momentReceived;
}

function closeExpense(Expense storage _e) internal {
_e.isOpen = false;
}

function getExpenseTotalNeeded(Expense _e, uint _currentFlow) internal view returns(uint need) {
uint receiveTimeDelta = (block.timestamp - _e.momentReceived);
uint creationTimeDelta = (block.timestamp - _e.momentCreated);
uint periodLength = (_e.periodHours * 3600 * 1000);
uint baseNeed;

if(_e.partsPerMillion != 0) { // if Absolute and (_e.minAmount == _e.totalNeeded)
baseNeed = ((_e.partsPerMillion * _currentFlow) / 1000000);
} else {
baseNeed = _e.totalNeeded;
}
function isNeeds()public view returns(bool) {
return _isNeeds(expense);
}

if(!_e.isOpen) {
need = 0;

} else if(_e.minAmount == _e.totalNeeded) {
if(_e.periodType == PeriodType.NonPeriodic) {
if(_e.balance == 0) {
need = baseNeed;
}
} else if(_e.periodType == PeriodType.Periodic) {
if(receiveTimeDelta >= periodLength) {
need = baseNeed;
}
} else if(_e.periodType == PeriodType.PeriodicSliding) {
if(receiveTimeDelta >= periodLength) {
need = (baseNeed * numberOfEntitiesPlusOne(receiveTimeDelta, periodLength)) - _e.totalReceived;
if(_e.momentReceived == 0) {
need = baseNeed;
}
}
if((need > _currentFlow) && (_currentFlow > _e.totalNeeded)) {
need = (_e.totalNeeded * (_currentFlow / _e.totalNeeded)) - _e.totalReceived;
}
}

} else if(_e.minAmount == 0) {
if(_e.periodType == PeriodType.NonPeriodic) {
if(_currentFlow >= (_e.totalNeeded - _e.totalReceived)) {
need = (_e.totalNeeded - _e.totalReceived);
} else {
need = _currentFlow;
}
} else if(_e.periodType == PeriodType.Periodic) {
need = getDebtIfNoSliding(_e);
if(need > _currentFlow) {
need = _currentFlow;
}
} else if(_e.periodType == PeriodType.PeriodicSliding) {
need = ((numberOfEntitiesPlusOne(creationTimeDelta, periodLength) * _e.totalNeeded) - _e.totalReceived);
if(need > _currentFlow) {
need = _currentFlow;
}
}

} else if(_e.minAmount < _e.totalNeeded) {
if(_e.periodType == PeriodType.NonPeriodic) {
if(_currentFlow >= (_e.totalNeeded - _e.totalReceived)) {
need = (_e.totalNeeded - _e.totalReceived);
} else if((_currentFlow < _e.totalNeeded) && (_currentFlow >= _e.minAmount)) { // change need for fund (with discrete input) if baseNeed >= _currentFlow
need = _currentFlow - (_currentFlow % _e.minAmount);
}
} else if(_e.periodType == PeriodType.Periodic) {
need = getDebtIfNoSliding(_e);
if((_currentFlow < _e.totalNeeded) && (_currentFlow >= _e.minAmount)) { // change need for fund (with discrete input) if baseNeed >= _currentFlow
need = _currentFlow - (_currentFlow % _e.minAmount);
}
} else if(_e.periodType == PeriodType.PeriodicSliding) {
need = ((numberOfEntitiesPlusOne(creationTimeDelta, periodLength) * _e.totalNeeded) - _e.totalReceived);
if((_currentFlow < need) && (_currentFlow >= _e.minAmount)) { // change need for fund (with discrete input) if baseNeed >= _currentFlow
need = _currentFlow - (_currentFlow % _e.minAmount);
}
}
}
function getPartsPerMillion()public view returns(uint) {
return expense.partsPerMillion;
}

function getExpenseMinNeeded(Expense _e, uint _currentFlow) internal view returns(uint minNeed) {
if( !((_e.minAmount == 0) && (_e.totalNeeded > 0))
&& !(_e.partsPerMillion > 0) ) {
minNeed = getExpenseTotalNeeded(_e, _currentFlow);
}
function flush()public onlyOwner {
emit ExpenseFlush(owner, address(this).balance);
owner.transfer(address(this).balance);
expense.balance = 0;
}

function isExpenseNeeds(Expense _e)internal view returns(bool isNeed) {
isNeed = (getExpenseTotalNeeded(_e, 1e30) > 0);
}
function flushTo(address _to) public onlyOwner {
emit ExpenseFlush(_to, address(this).balance);
_to.transfer(address(this).balance);
expense.balance = 0;
}

// -------------------- INTERNAL FUNCTIONS
function numberOfEntitiesPlusOne(uint _inclusive, uint _inluded) internal view returns(uint count) {
if(_inclusive < _inluded) {
count = 1;
} else {
count = 1 + (_inclusive / _inluded);
}
function setNeededWei(uint _totalNeeded) public onlyOwner {
emit ExpenseSetNeeded(_totalNeeded);
expense.totalNeeded = uint128(_totalNeeded);
}

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

uint debtForAllPeriods = ((numberOfEntitiesPlusOne(creationTimeDelta, periodLength) * _e.totalNeeded) - _e.totalReceived);
if(debtForAllPeriods == 0) {
return 0;
} else if((debtForAllPeriods % _e.totalNeeded) > 0 ) {
return (debtForAllPeriods % _e.totalNeeded);
} else if(numberOfEntitiesPlusOne(receiveTimeDelta, periodLength) > 1) {
return _e.totalNeeded;
} else {
return 0;
}
function setPercents(uint _partsPerMillion) public onlyOwner {
emit ExpenseSetPercents(_partsPerMillion);
expense.partsPerMillion = uint32(_partsPerMillion);
}
}

function() public {}

}
Loading

0 comments on commit 4fe71c8

Please sign in to comment.