diff --git a/contracts/moneyflow/MoneflowTable.sol b/contracts/moneyflow/MoneflowTable.sol index e665c34..28b94a3 100644 --- a/contracts/moneyflow/MoneflowTable.sol +++ b/contracts/moneyflow/MoneflowTable.sol @@ -86,10 +86,9 @@ contract MoneyflowTable is Ownable{//is IWeiReceiver, function _processFunds(uint _eId, uint _currentFlow, uint _amount) internal { if(Splitters[_eId].isOpen){ return _processFundsSplitter(_eId, _currentFlow, _amount); - }else if(Expenses[_eId].isOpen){ + }else if(Expenses[_eId].isOpen&&_isNeedsMoney(_eId)){ return _processFundsExpense(_eId, _currentFlow, _amount); }else { - revert(); } } @@ -115,6 +114,7 @@ contract MoneyflowTable is Ownable{//is IWeiReceiver, require(_amount==_getTotalWeiNeeded(_eId, _currentFlow)); Expenses[_eId].momentReceived = uint(now); Expenses[_eId].balance += _amount; + Expenses[_eId].isMoneyReceived = true; } function getMinWeiNeededForElement(uint _eId)external view returns(uint) { @@ -128,7 +128,7 @@ contract MoneyflowTable is Ownable{//is IWeiReceiver, }else if((Splitters[_eId].isOpen)&&(ElementTypes.UnsortedSplitter==elementsType[_eId])){ return _getMinWeiNeededUnsortedSplitter(_eId); - }else if(Expenses[_eId].isOpen){ + }else if(Expenses[_eId].isOpen && _isNeedsMoney(_eId)){ return _getMinWeiNeededExpense(_eId); }else { @@ -239,6 +239,7 @@ contract MoneyflowTable is Ownable{//is IWeiReceiver, function processFunds(uint _currentFlow) external payable { require(_currentFlow>=_getMinWeiNeeded(0)); require(msg.value>=_getMinWeiNeeded(0)); + return _processFunds(0, _currentFlow, msg.value); } @@ -365,7 +366,7 @@ contract MoneyflowTable is Ownable{//is IWeiReceiver, } function withdrawFundsFromElement(uint _eId)external onlyOwner{ - require(_isExpense(_eId)); + // require(_isExpense(_eId)); Expenses[_eId].output.processFunds.value(Expenses[_eId].balance)(Expenses[_eId].balance); Expenses[_eId].balance = 0; } diff --git a/contracts/utils/GenericCaller.sol b/contracts/utils/GenericCaller.sol index 690e622..4b395b6 100644 --- a/contracts/utils/GenericCaller.sol +++ b/contracts/utils/GenericCaller.sol @@ -15,13 +15,6 @@ import "zeppelin-solidity/contracts/ownership/Ownable.sol"; */ contract GenericCaller is DaoClient, Ownable { using VotingLib for VotingLib.VotingType; - // enum VotingType { - // NoVoting, - // Voting1p1v, - // VotingSimpleToken, - // VotingQuadratic, - // VotingLiquid - // } event consoleAddr(string comment, address a); event consoleB32(string comment, bytes32 a); diff --git a/contracts/utils/Manageable.sol b/contracts/utils/Manageable.sol deleted file mode 100644 index e7e2f1d..0000000 --- a/contracts/utils/Manageable.sol +++ /dev/null @@ -1,146 +0,0 @@ -pragma solidity ^0.4.22; - -import "zeppelin-solidity/contracts/ownership/Ownable.sol"; - -/** - * @title Manageable - * @author https://github.com/jibrelnetwork/jibrel-contracts - * @dev Contract that allows to grant permissions to any address - * @dev In real life we are not able to perform all actions with just one Ethereum address - * @dev because risks are too high. - * @dev Instead owner delegates rights to manage an contract to the different addresses and - * @dev stay able to revoke permissions at any time. - */ -contract Manageable is Ownable { -// Fields: - mapping (address => bool) managerEnabled; // hard switch for a manager - on/off - mapping (address => mapping (string => bool)) managerPermissions; // detailed info about manager`s permissions - -//Events: - event Manageable_ManagerEnabled(address indexed _manager); - event Manageable_ManagerDisabled(address indexed _manager); - event Manageable_ManagerPermissionGranted(address indexed _manager, string _permission); - event Manageable_ManagerPermissionRevoked(address indexed _manager, string _permission); - -// Modifiers: - modifier onlyValidAddress(address _manager) { - require(_manager != address(0x0)); - _; - } - - modifier onlyValidPermissionName(string _permissionName) { - require(bytes(_permissionName).length != 0); - _; - } - - modifier onlyAllowedManager(string _permissionName) { - require(isManagerAllowed(msg.sender, _permissionName) == true); - _; - } - -// Methods: - /** - * @dev Function to add new manager - * @param _manager address New manager - */ - function enableManager(address _manager) public onlyOwner onlyValidAddress(_manager) { - require(managerEnabled[_manager] == false); - - managerEnabled[_manager] = true; - emit Manageable_ManagerEnabled(_manager); - } - - /** - * @dev Function to remove existing manager - * @param _manager address Existing manager - */ - function disableManager(address _manager) public onlyOwner onlyValidAddress(_manager) { - require(managerEnabled[_manager] == true); - - managerEnabled[_manager] = false; - emit Manageable_ManagerDisabled(_manager); - } - - /** - * @dev Function to grant new permission to the manager - * @param _manager address Existing manager - * @param _permissionName string Granted permission name - */ - function grantManagerPermission( - address _manager, string _permissionName - ) - public - onlyOwner - onlyValidAddress(_manager) - onlyValidPermissionName(_permissionName) - { - require(managerPermissions[_manager][_permissionName] == false); - - managerPermissions[_manager][_permissionName] = true; - emit Manageable_ManagerPermissionGranted(_manager, _permissionName); - } - - /** - * @dev Function to revoke permission of the manager - * @param _manager address Existing manager - * @param _permissionName string Revoked permission name - */ - function revokeManagerPermission( - address _manager, string _permissionName - ) - public - onlyOwner - onlyValidAddress(_manager) - onlyValidPermissionName(_permissionName) - { - require(managerPermissions[_manager][_permissionName] == true); - - managerPermissions[_manager][_permissionName] = false; - emit Manageable_ManagerPermissionRevoked(_manager, _permissionName); - } - - /** - * @dev Function to check manager status - * @param _manager address Manager`s address - * @return True if manager is enabled - */ - function isManagerEnabled(address _manager) constant public onlyValidAddress(_manager) returns (bool) { - return managerEnabled[_manager]; - } - - /** - * @dev Function to check permissions of a manager - * @param _manager address Manager`s address - * @param _permissionName string Permission name - * @return True if manager has been granted needed permission - */ - function isPermissionGranted( - address _manager, string _permissionName - ) - constant - public - onlyValidAddress(_manager) - onlyValidPermissionName(_permissionName) - returns (bool) - { - return managerPermissions[_manager][_permissionName]; - } - - /** - * @dev Function to check if the manager can perform the action or not - * @param _manager address Manager`s address - * @param _permissionName string Permission name - * @return True if manager is enabled and has been granted needed permission - */ - function isManagerAllowed( - address _manager, string _permissionName - ) - constant - public - onlyValidAddress(_manager) - onlyValidPermissionName(_permissionName) - returns (bool) - { - return (managerEnabled[_manager] && managerPermissions[_manager][_permissionName]); - } -} diff --git a/contracts/utils/Pausable.sol b/contracts/utils/Pausable.sol deleted file mode 100644 index 4269a76..0000000 --- a/contracts/utils/Pausable.sol +++ /dev/null @@ -1,54 +0,0 @@ -pragma solidity ^0.4.22; - -import "./Manageable.sol"; - -/** - * @title Pausable - * @author https://github.com/jibrelnetwork/jibrel-contracts - * @dev Base contract which allows children to implement an emergency stop mechanism. - * @dev Based on zeppelin's Pausable, but integrated with Manageable - * @dev Contract is in paused state by default and should be explicitly unlocked - */ -contract Pausable is Manageable { -// Fields: - bool paused = true; - -// Events: - event Pausable_Pause(); - event Pausable_Unpause(); - -// Modifiers: - modifier whenContractNotPaused() { - require(paused == false); - _; - } - - modifier whenContractPaused { - require(paused == true); - _; - } - -// Methods: - /** - * @dev called by the manager to pause, triggers stopped state - */ - function pauseContract() public onlyAllowedManager('pause_contract') whenContractNotPaused { - paused = true; - emit Pausable_Pause(); - } - - /** - * @dev called by the manager to unpause, returns to normal state - */ - function unpauseContract() public onlyAllowedManager('unpause_contract') whenContractPaused { - paused = false; - emit Pausable_Unpause(); - } - - /** - * @dev The getter for "paused" contract variable - */ - function getPaused() constant public returns (bool) { - return paused; - } -} diff --git a/test/moneyflow/moneyflow_table.tests.js b/test/moneyflow/moneyflow_table.tests.js index 3291cde..ea1d017 100644 --- a/test/moneyflow/moneyflow_table.tests.js +++ b/test/moneyflow/moneyflow_table.tests.js @@ -261,17 +261,28 @@ contract('MoneyflowTable tests', (accounts) => { // 0->•abs it('should process money with WeiTopDownSplitter + 3 WeiAbsoluteExpense',async() => { let moneyflowTable = await MoneyflowTable.new(); - + var output1 = await WeiAbsoluteExpense.new(neededAmount); + var output2 = await WeiAbsoluteExpense.new(2*neededAmount); + var output3 = await WeiAbsoluteExpense.new(3*neededAmount); let topDownSplitterId = getEId(await moneyflowTable.addTopdownSplitter()); - let AbsoluteExpense1Id = getEId(await moneyflowTable.addAbsoluteExpense(neededAmount, isPeriodic, isAccumulateDebt, periodHours, output)); - let AbsoluteExpense2Id = getEId(await moneyflowTable.addAbsoluteExpense(2*neededAmount, isPeriodic, isAccumulateDebt, periodHours, output)); - let AbsoluteExpense3Id = getEId(await moneyflowTable.addAbsoluteExpense(3*neededAmount, isPeriodic, isAccumulateDebt, periodHours, output)); + let AbsoluteExpense1Id = getEId(await moneyflowTable.addAbsoluteExpense(neededAmount, isPeriodic, isAccumulateDebt, periodHours, output1.address)); + let AbsoluteExpense2Id = getEId(await moneyflowTable.addAbsoluteExpense(2*neededAmount, isPeriodic, isAccumulateDebt, periodHours, output2.address)); + let AbsoluteExpense3Id = getEId(await moneyflowTable.addAbsoluteExpense(3*neededAmount, isPeriodic, isAccumulateDebt, periodHours, output3.address)); // add 3 WeiAbsoluteExpense outputs to the splitter await moneyflowTable.addChild(topDownSplitterId, AbsoluteExpense1Id); await moneyflowTable.addChild(topDownSplitterId, AbsoluteExpense2Id); await moneyflowTable.addChild(topDownSplitterId, AbsoluteExpense3Id); + + var id1 = await moneyflowTable.getChildId(topDownSplitterId, 0); + var id2 = await moneyflowTable.getChildId(topDownSplitterId, 1); + var id3 = await moneyflowTable.getChildId(topDownSplitterId, 2); + + assert.equal(id1, AbsoluteExpense1Id); + assert.equal(id2, AbsoluteExpense2Id); + assert.equal(id3, AbsoluteExpense3Id); + // add WeiTopDownSplitter to the moneyflow await moneyflowInstance.setRootWeiReceiver(moneyflowTable.address); @@ -279,15 +290,14 @@ contract('MoneyflowTable tests', (accounts) => { assert.equal(revenueEndpointAddress, moneyflowTable.address, 'weiTopDownSplitter.address saved in moneyflowInstance as revenueEndpointAddress'); - let totalNeed = await moneyflowTable.getTotalWeiNeeded(6*neededAmount); + var totalNeed = await moneyflowTable.getTotalWeiNeeded(6*neededAmount); assert.equal(totalNeed, 6*neededAmount); - let minNeed = await moneyflowTable.getMinWeiNeeded(); - // console.log('minNeed:', minNeed) + var minNeed = await moneyflowTable.getMinWeiNeeded(); assert.equal(minNeed, 6*neededAmount); - + var need1 = await moneyflowTable.isNeedsMoney(); // now send some money to the revenue endpoint await moneyflowTable.processFunds(6*neededAmount, {value:6*neededAmount, from:creator}); - + assert.equal(need1, true); // money should end up in the outputs var absoluteExpense1Balance = await moneyflowTable.getElementBalance(AbsoluteExpense1Id); assert.equal(absoluteExpense1Balance.toNumber(),1*neededAmount, 'resource point received money from splitter'); @@ -297,6 +307,40 @@ contract('MoneyflowTable tests', (accounts) => { var absoluteExpense3Balance = await moneyflowTable.getElementBalance(AbsoluteExpense3Id); assert.equal(absoluteExpense3Balance.toNumber(),3*neededAmount, 'resource point received money from splitter'); + + var totalNeed = await moneyflowTable.getTotalWeiNeeded(6*neededAmount); + assert.equal(totalNeed.toNumber(), 0*neededAmount); + var minNeed = await moneyflowTable.getMinWeiNeeded(); + assert.equal(minNeed.toNumber(), 0*neededAmount); + + var need2 = await moneyflowTable.isNeedsMoney(); + assert.equal(need2, false); + + var b1 = await web3.eth.getBalance(output1.address); + await moneyflowTable.withdrawFundsFromElement(AbsoluteExpense1Id,{gasPrice:0}); + var b2 = await web3.eth.getBalance(output1.address); + assert.equal(b2.toNumber()-b1.toNumber(), 1*neededAmount); + + var b1 = await web3.eth.getBalance(output2.address); + await moneyflowTable.withdrawFundsFromElement(AbsoluteExpense2Id,{gasPrice:0}); + var b2 = await web3.eth.getBalance(output2.address); + assert.equal(b2.toNumber()-b1.toNumber(), 2*neededAmount); + + var b1 = await web3.eth.getBalance(output3.address); + await moneyflowTable.withdrawFundsFromElement(AbsoluteExpense3Id,{gasPrice:0}); + var b2 = await web3.eth.getBalance(output3.address); + assert.equal(b2.toNumber()-b1.toNumber(), 3*neededAmount); + + var absoluteExpense1Balance = await moneyflowTable.getElementBalance(AbsoluteExpense1Id); + assert.equal(absoluteExpense1Balance.toNumber(),0*neededAmount, 'resource point received money from splitter'); + + var absoluteExpense2Balance = await moneyflowTable.getElementBalance(AbsoluteExpense2Id); + assert.equal(absoluteExpense2Balance.toNumber(),0*neededAmount, 'resource point received money from splitter'); + + var absoluteExpense3Balance = await moneyflowTable.getElementBalance(AbsoluteExpense3Id); + assert.equal(absoluteExpense3Balance.toNumber(),0*neededAmount, 'resource point received money from splitter'); + var need2 = await moneyflowTable.isNeedsMoney(); + }); it('should process money with WeiUnsortedSplitter + 3 WeiAbsoluteExpense',async() => { @@ -353,6 +397,10 @@ contract('MoneyflowTable tests', (accounts) => { // add WeiTopDownSplitter to the moneyflow await moneyflowInstance.setRootWeiReceiver(moneyflowTable.address); + var id1 = await moneyflowTable.getChildId(topDownSplitterId, 0); + var id2 = await moneyflowTable.getChildId(topDownSplitterId, 1); + var id3 = await moneyflowTable.getChildId(topDownSplitterId, 2); + var revenueEndpointAddress = await moneyflowInstance.getRevenueEndpoint(); assert.equal(revenueEndpointAddress, moneyflowTable.address, 'weiTopDownSplitter.address saved in moneyflowInstance as revenueEndpointAddress'); @@ -580,4 +628,80 @@ contract('MoneyflowTable tests', (accounts) => { await struct.moneyflowTable.processFunds(CURRENT_INPUT*money, {value:CURRENT_INPUT*money/100, gasPrice:0}).should.be.rejectedWith('revert'); await struct.moneyflowTable.processFunds(CURRENT_INPUT*money/100, {value:CURRENT_INPUT*money, gasPrice:0}).should.be.rejectedWith('revert'); }); + + it('should process money when opened and not process when closed with WeiTopDownSplitter + 3 WeiAbsoluteExpense',async() => { + let moneyflowTable = await MoneyflowTable.new(); + + let topDownSplitterId = getEId(await moneyflowTable.addTopdownSplitter()); + let AbsoluteExpense1Id = getEId(await moneyflowTable.addAbsoluteExpense(neededAmount, isPeriodic, isAccumulateDebt, periodHours, output)); + let AbsoluteExpense2Id = getEId(await moneyflowTable.addAbsoluteExpense(2*neededAmount, isPeriodic, isAccumulateDebt, periodHours, output)); + let AbsoluteExpense3Id = getEId(await moneyflowTable.addAbsoluteExpense(3*neededAmount, isPeriodic, isAccumulateDebt, periodHours, output)); + + // add 3 WeiAbsoluteExpense outputs to the splitter + await moneyflowTable.addChild(topDownSplitterId, AbsoluteExpense1Id); + await moneyflowTable.addChild(topDownSplitterId, AbsoluteExpense2Id); + await moneyflowTable.addChild(topDownSplitterId, AbsoluteExpense3Id); + + // add WeiTopDownSplitter to the moneyflow + await moneyflowInstance.setRootWeiReceiver(moneyflowTable.address); + + var revenueEndpointAddress = await moneyflowInstance.getRevenueEndpoint(); + + assert.equal(revenueEndpointAddress, moneyflowTable.address, 'weiTopDownSplitter.address saved in moneyflowInstance as revenueEndpointAddress'); + + var totalNeed = await moneyflowTable.getTotalWeiNeeded(6*neededAmount); + assert.equal(totalNeed, 6*neededAmount); + var minNeed = await moneyflowTable.getMinWeiNeeded(); + // console.log('minNeed:', minNeed) + assert.equal(minNeed, 6*neededAmount); + + var isOpen1 = await moneyflowTable.isOpen(AbsoluteExpense1Id); + var isOpen2 = await moneyflowTable.isOpen(AbsoluteExpense2Id); + var isOpen3 = await moneyflowTable.isOpen(AbsoluteExpense3Id); + assert.equal(isOpen1, true); + assert.equal(isOpen2, true); + assert.equal(isOpen3, true); + + await moneyflowTable.closeElement(AbsoluteExpense3Id); + + var totalNeed = await moneyflowTable.getTotalWeiNeeded(6*neededAmount); + assert.equal(totalNeed, 3*neededAmount); + var minNeed = await moneyflowTable.getMinWeiNeeded(); + assert.equal(minNeed, 3*neededAmount); + + await moneyflowTable.closeElement(AbsoluteExpense1Id); + + var totalNeed = await moneyflowTable.getTotalWeiNeeded(6*neededAmount); + assert.equal(totalNeed, 2*neededAmount); + var minNeed = await moneyflowTable.getMinWeiNeeded(); + assert.equal(minNeed, 2*neededAmount); + + var isOpen1 = await moneyflowTable.isOpen(AbsoluteExpense1Id); + var isOpen2 = await moneyflowTable.isOpen(AbsoluteExpense2Id); + var isOpen3 = await moneyflowTable.isOpen(AbsoluteExpense3Id); + assert.equal(isOpen1, false); + assert.equal(isOpen2, true); + assert.equal(isOpen3, false); + + await moneyflowTable.openElement(AbsoluteExpense3Id); + var isOpen1 = await moneyflowTable.isOpen(AbsoluteExpense1Id); + var isOpen2 = await moneyflowTable.isOpen(AbsoluteExpense2Id); + var isOpen3 = await moneyflowTable.isOpen(AbsoluteExpense3Id); + assert.equal(isOpen1, false); + assert.equal(isOpen2, true); + assert.equal(isOpen3, true); + + // now send some money to the revenue endpoint + await moneyflowTable.processFunds(5*neededAmount, {value:5*neededAmount, from:creator}); + + // money should end up in the outputs + var absoluteExpense1Balance = await moneyflowTable.getElementBalance(AbsoluteExpense1Id); + assert.equal(absoluteExpense1Balance.toNumber(),0*neededAmount, 'resource point received money from splitter'); + + var absoluteExpense2Balance = await moneyflowTable.getElementBalance(AbsoluteExpense2Id); + assert.equal(absoluteExpense2Balance.toNumber(),2*neededAmount, 'resource point received money from splitter'); + + var absoluteExpense3Balance = await moneyflowTable.getElementBalance(AbsoluteExpense3Id); + assert.equal(absoluteExpense3Balance.toNumber(),3*neededAmount, 'resource point received money from splitter'); + }); });