diff --git a/contracts/democracy/DelegationProxy.sol b/contracts/democracy/DelegationProxy.sol new file mode 100644 index 0000000..fd7d094 --- /dev/null +++ b/contracts/democracy/DelegationProxy.sol @@ -0,0 +1,347 @@ +pragma solidity ^0.4.21; + +import "../token/MiniMeTokenInterface.sol"; +import "./DelegationProxyInterface.sol"; + + +/** + * @title DelegationProxy + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * @dev Creates a delegation proxy layer for MiniMeTokenInterface. + */ +contract DelegationProxy is DelegationProxyInterface { + + //storage of indexes of the addresses to `delegations[to].from` + mapping (address => uint256) toIndexes; + + /** + * @notice Calls Constructor + */ + function DelegationProxy(address _parentProxy) public { + parentProxy = _parentProxy; + } + + /** + * @notice Changes the delegation of `msg.sender` to `_to`. if _to 0x00: delegate to self. + * In case of having a parent proxy, if never defined, fall back to parent proxy. + * If once defined and want to delegate to parent proxy, set `_to` as parent address. + * @param _to To what address the caller address will delegate to. + */ + function delegate(address _to) external { + _updateDelegate(msg.sender, _to); + } + + /** + * @notice Reads `_who` configured delegation in this level, + * or from parent level if `_who` never defined/defined to parent address. + * @param _who What address to lookup. + * @return The address `_who` choosen delegate to. + */ + function delegatedTo(address _who) + public + view + returns (address) + { + return delegatedToAt(_who, block.number); + } + + /** + * @notice Reads the final delegate of `_who` at block number `_block`. + * @param _who Address to lookup. + * @return Final delegate address. + */ + function delegationOf(address _who) + public + view + returns(address) + { + return delegationOfAt(_who, block.number); + } + + /** + * @notice Reads the sum of votes a `_who' have at block number `_block`. + * @param _who From what address. + * @param _token Address of source MiniMeTokenInterface. + * @return Amount of influence of `who` have. + */ + function influenceOf( + address _who, + MiniMeTokenInterface _token + ) + public + view + returns(uint256 _total) + { + return influenceOfAt(_who, _token, block.number); + } + + /** + * @notice Reads amount delegated influence `_who` received from other addresses. + * @param _who What address to lookup. + * @param _token Source MiniMeTokenInterface. + * @return Sum of delegated influence received by `_who` in block `_block` from other addresses. + */ + function delegatedInfluenceFrom( + address _who, + address _token + ) + public + view + returns(uint256 _total) + { + return delegatedInfluenceFromAt(_who, _token, block.number, address(this)); + } + + /** + * @notice Reads amount delegated influence `_who` received from other addresses at block number `_block`. + * @param _who What address to lookup. + * @param _token Source MiniMeTokenInterface. + * @param _block Position in history to lookup. + * @return Sum of delegated influence received by `_who` in block `_block` from other addresses + */ + function delegatedInfluenceFromAt( + address _who, + address _token, + uint _block + ) + public + view + returns(uint256 _total) + { + return delegatedInfluenceFromAt(_who, _token, _block, address(this)); + } + + /** + * @notice Reads `_who` configured delegation at block number `_block` in this level, + * or from parent level if `_who` never defined/defined to parent address. + * @param _who What address to lookup. + * @param _block Block number of what height in history. + * @return The address `_who` choosen delegate to. + */ + function delegatedToAt( + address _who, + uint _block + ) + public + view + returns (address directDelegate) + { + Delegation[] storage checkpoints = delegations[_who]; + + //In case there is no registry + if (checkpoints.length == 0) { + if (parentProxy != 0x0) { + return DelegationProxy(parentProxy).delegatedToAt(_who, _block); + } else { + return 0x0; + } + } + Delegation memory d = _getMemoryAt(checkpoints, _block); + // Case user set delegate to parentProxy address + if (d.to == parentProxy && d.to != 0x0) { + return DelegationProxy(parentProxy).delegatedToAt(_who, _block); + } + return d.to; + } + + /** + * @notice Reads the final delegate of `_who` at block number `_block`. + * @param _who Address to lookup. + * @param _block From what block. + * @return Final delegate address. + */ + function delegationOfAt( + address _who, + uint _block + ) + public + view + returns(address finalDelegate) + { + finalDelegate = delegatedToAt(_who, _block); + if (finalDelegate != _who) { //_who is delegating? + return delegationOfAt(finalDelegate, _block); //load the delegation of _who delegation + } else { + return _who; //reached the endpoint of delegation + } + + } + + /** + * @dev Reads amount delegated influence received from other addresses. + * @param _who What address to lookup. + * @param _token Source MiniMeTokenInterface. + * @param _block Position in history to lookup. + * @param _childProxy The child DelegationProxy requesting the call to parent. + * @return Sum of delegated influence received by `_who` in block `_block` from other addresses. + */ + function delegatedInfluenceFromAt( + address _who, + address _token, + uint _block, + address _childProxy + ) + public + view + returns(uint256 _total) + { + Delegation[] storage checkpoints = delegations[_who]; + //In case there is no registry + if (checkpoints.length == 0) { + if (parentProxy != 0x0) { + return DelegationProxy(parentProxy).delegatedInfluenceFromAt( + _from, + _token, + _block, + _childProxy + ); + } else { + return 0; + } + } + + Delegation memory d = _getMemoryAt(checkpoints, _block); + // Case user set delegate to parentProxy + if (d.to == parentProxy && parentProxy != 0x0) { + return DelegationProxy(parentProxy).delegatedInfluenceFromAt( + _from, + _token, + _block, + _childProxy + ); + } + + uint _len = d.from.length; + for (uint256 i = 0; _len > i; i++) { + address _from = d.from[i]; + _total += MiniMeTokenInterface(_token).balanceOfAt(_from, _block); // source of _who votes + _total += DelegationProxy(_childProxy).delegatedInfluenceFromAt( + _from, + _token, + _block, + _childProxy + ); //sum the from delegation votes + } + + } + + /** + * @notice Reads the sum of votes a `_who' have at block number `_block`. + * @param _who From what address. + * @param _token Address of source MiniMeTokenInterface. + * @param _block From what block + * @return Amount of influence of `who` have. + */ + function influenceOfAt( + address _who, + MiniMeTokenInterface _token, + uint _block + ) + public + view + returns(uint256 _total) + { + if (delegationOfAt(_who, _block) == _who) { //is endpoint of delegation? + _total = MiniMeTokenInterface(_token).balanceOfAt(_who, _block); // source of _who votes + _total += delegatedInfluenceFromAt(_who, _token, _block, address(this)); //votes delegated to `_who` + } else { + _total = 0; //no influence because were delegated + } + } + + /** + * @dev Changes the delegation of `_from` to `_to`. if _to 0x00: delegate to self. + * In case of having a parent proxy, if never defined, fall back to parent proxy. + * If once defined and want to delegate to parent proxy, set `_to` as parent address. + * @param _from Address delegating. + * @param _to Address delegated. + */ + function _updateDelegate(address _from, address _to) internal { + require(delegationOfAt(_to, block.number) != msg.sender); //block impossible circular delegation + Delegate(_from, _to); + Delegation memory _newFrom; //allocate memory + Delegation[] storage fromHistory = delegations[_from]; + if (fromHistory.length > 0) { //have old config? + _newFrom = fromHistory[fromHistory.length - 1]; //load to memory + _newFrom.from = fromHistory[fromHistory.length - 1].from; + if (toIndexes[_from] > 0) { //was delegating? remove old link + _removeDelegated(_newFrom.to, toIndexes[_from]); + } + } + //Add the new delegation + _newFrom.fromBlock = uint128(block.number); + _newFrom.to = _to; //delegate address + + if (_to != 0x0 && _to != parentProxy) { //_to is an address? + _addDelegated(_from, _to); + } else { + toIndexes[_from] = 0; //zero index + } + fromHistory.push(_newFrom); //register `from` delegation update; + } + + /** + * @dev `_getDelegationAt` retrieves the delegation at a given block number. + * @param checkpoints The memory being queried. + * @param _block The block number to retrieve the value at. + * @return The delegation being queried. + */ + function _getMemoryAt(Delegation[] storage checkpoints, uint _block) internal view returns (Delegation d) { + // Case last checkpoint is the one; + if (_block >= checkpoints[checkpoints.length-1].fromBlock) { + d = checkpoints[checkpoints.length-1]; + } else { + // Lookup in array; + uint min = 0; + uint max = checkpoints.length-1; + while (max > min) { + uint mid = (max + min + 1) / 2; + if (checkpoints[mid].fromBlock <= _block) { + min = mid; + } else { + max = mid-1; + } + } + d = checkpoints[min]; + } + } + + /** + * @dev Removes delegation at `_toIndex` from `_to` address. + * @param _to Delegate address to remove delegation. + * @param _toIndex Index of delegation being removed. + */ + function _removeDelegated(address _to, uint _toIndex) private { + Delegation[] storage oldTo = delegations[_to]; //load delegation storage + uint _oldToLen = oldTo.length; + assert(_oldToLen > 0); //if code tried to remove from nothing this is absolutely bad + Delegation memory _newOldTo = oldTo[_oldToLen - 1];//copy to memory last result + address replacer = _newOldTo.from[_newOldTo.from.length - 1]; + _newOldTo.from[_toIndex - 1] = replacer; //replace delegated address at `_toIndex` + oldTo.push(_newOldTo); //save the value at memory + oldTo[_oldToLen].from.length--; //remove duplicated `to` + toIndexes[replacer] = _toIndex; + } + + /** + * @dev Add delegation of `_from` in delegate `_to`. + * @param _from Delegator address delegating their influence. + * @param _to Delegate address receiving the influence; + * @return _toIndex The index of `_from` in `_to` delegate. + */ + function _addDelegated(address _from, address _to) private { + Delegation memory _newTo; // allocates space in memory + Delegation[] storage toHistory = delegations[_to]; //load delegation storage + uint toHistLen = toHistory.length; + if (toHistLen > 0) { //have data, should copy 'from' array. + _newTo = toHistory[toHistLen - 1]; //copy to memory last one + } else { + _newTo.to = parentProxy; // configure delegate of `_to` because it was never defined + } + _newTo.fromBlock = uint128(block.number); //define the new block number + toHistory.push(_newTo); //register `to` delegated from + toHistory[toHistLen].from.push(_from); //add the delegated from in to list + toIndexes[_from] = toHistory[toHistLen].from.length; //link to index + } + +} \ No newline at end of file diff --git a/contracts/democracy/DelegationProxyFactory.sol b/contracts/democracy/DelegationProxyFactory.sol new file mode 100644 index 0000000..b4fd52e --- /dev/null +++ b/contracts/democracy/DelegationProxyFactory.sol @@ -0,0 +1,29 @@ +pragma solidity ^0.4.21; + +import "./DelegationProxyInterface.sol"; +import "./DelegationProxyKernel.sol"; +import "../deploy/Factory.sol"; +import "../deploy/Instance.sol"; + +/** + * @title DelegationProxyFactory + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * @dev Upgradable delegation proxy factory + */ +contract DelegationProxyFactory is Factory { + + function DelegationProxyFactory() + Factory(new DelegationProxyKernel()) + public + { } + + function createDelegationProxy(address _parent) + external + returns (DelegationProxyInterface) + { + DelegationProxyKernel instance = DelegationProxyKernel(address(new Instance(latestKernel))); + instance.initializeDelegationProxy(_parent); + return DelegationProxyInterface(address(instance)); + } + +} \ No newline at end of file diff --git a/contracts/democracy/DelegationProxyInterface.sol b/contracts/democracy/DelegationProxyInterface.sol new file mode 100644 index 0000000..2d43997 --- /dev/null +++ b/contracts/democracy/DelegationProxyInterface.sol @@ -0,0 +1,158 @@ +pragma solidity ^0.4.21; + +import "../token/MiniMeTokenInterface.sol"; + + +contract DelegationProxyInterface { + + struct Delegation { + uint128 fromBlock; //when this was updated + address to; //who recieved this delegaton + address[] from; //list of addresses that delegated to this address + } + + //default delegation proxy, being used when user didn't set any delegation at this level. + address public parentProxy; + //snapshots of changes, allow delegation changes be done at any time without compromising vote results. + mapping (address => Delegation[]) public delegations; + + event Delegate(address who, address to); + + /** + * @notice Changes the delegation of `msg.sender` to `_to`. if _to 0x00: delegate to self. + * In case of having a parent proxy, if never defined, fall back to parent proxy. + * If once defined and want to delegate to parent proxy, set `_to` as parent address. + * @param _to To what address the caller address will delegate to. + */ + function delegate(address _to) external; + + /** + * @notice Reads `_who` configured delegation in this level, + * or from parent level if `_who` never defined/defined to parent address. + * @param _who What address to lookup. + * @return The address `_who` choosen delegate to. + */ + function delegatedTo(address _who) + public + view + returns (address directDelegate); + + /** + * @notice Reads the final delegate of `_who` at block number `_block`. + * @param _who Address to lookup. + * @return Final delegate address. + */ + function delegationOf(address _who) + public + view + returns(address finalDelegate); + + /** + * @notice Reads the sum of votes a `_who' have at block number `_block`. + * @param _who From what address. + * @param _token Address of source MiniMeTokenInterface. + * @return Amount of influence of `who` have. + */ + function influenceOf( + address _who, + MiniMeTokenInterface _token + ) + public + view + returns(uint256 _total); + + /** + * @notice Reads amount delegated influence `_who` received from other addresses. + * @param _who What address to lookup. + * @param _token Source MiniMeTokenInterface. + * @return Sum of delegated influence received by `_who` in block `_block` from other addresses. + */ + function delegatedInfluenceFrom( + address _who, + address _token + ) + public + view + returns(uint256 _total); + + /** + * @notice Reads amount delegated influence `_who` received from other addresses at block number `_block`. + * @param _who What address to lookup. + * @param _token Source MiniMeTokenInterface. + * @param _block Position in history to lookup. + * @return Sum of delegated influence received by `_who` in block `_block` from other addresses + */ + function delegatedInfluenceFromAt( + address _who, + address _token, + uint _block + ) + public + view + returns(uint256 _total); + + /** + * @notice Reads `_who` configured delegation at block number `_block` in this level, + * or from parent level if `_who` never defined/defined to parent address. + * @param _who What address to lookup. + * @param _block Block number of what height in history. + * @return The address `_who` choosen delegate to. + */ + function delegatedToAt( + address _who, + uint _block + ) + public + view + returns (address directDelegate); + + /** + * @notice Reads the final delegate of `_who` at block number `_block`. + * @param _who Address to lookup. + * @param _block From what block. + * @return Final delegate address. + */ + function delegationOfAt( + address _who, + uint _block + ) + public + view + returns(address finalDelegate); + + /** + * @dev Reads amount delegated influence received from other addresses. + * @param _who What address to lookup. + * @param _token Source MiniMeTokenInterface. + * @param _block Position in history to lookup. + * @param _childProxy The child DelegationProxy requesting the call to parent. + * @return Sum of delegated influence received by `_who` in block `_block` from other addresses. + */ + function delegatedInfluenceFromAt( + address _who, + address _token, + uint _block, + address _childProxy + ) + public + view + returns(uint256 _total); + + /** + * @notice Reads the sum of votes a `_who' have at block number `_block`. + * @param _who From what address. + * @param _token Address of source MiniMeTokenInterface. + * @param _block From what block + * @return Amount of influence of `who` have. + */ + function influenceOfAt( + address _who, + MiniMeTokenInterface _token, + uint _block + ) + public + view + returns(uint256 _total); + + +} \ No newline at end of file diff --git a/contracts/democracy/DelegationProxyKernel.sol b/contracts/democracy/DelegationProxyKernel.sol new file mode 100644 index 0000000..aad7994 --- /dev/null +++ b/contracts/democracy/DelegationProxyKernel.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.4.21; + +import "../deploy/InstanceStorage.sol"; +import "./DelegationProxyView.sol"; + +/** + * @title DelegationProxyKernel + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * @dev Creates a delegation proxy killable model for cheap redeploy and upgradability. + */ +contract DelegationProxyKernel is InstanceStorage, DelegationProxyView { + bool private ready = false; //TODO: abstract initialized flag + + /** + * @notice Constructor of the model - only knows about watchdog that can trigger upgrade + */ + function DelegationProxyKernel() DelegationProxy(0x0) public { + ready = true; + } + + /** + * @notice Creates a new DelegationProxy with `_parentProxy` as default delegation. + */ + function initializeDelegationProxy(address _parentProxy) public { + require(!ready); + ready = true; + parentProxy = _parentProxy; + } + +} \ No newline at end of file diff --git a/contracts/democracy/DelegationProxyView.sol b/contracts/democracy/DelegationProxyView.sol new file mode 100644 index 0000000..20936a8 --- /dev/null +++ b/contracts/democracy/DelegationProxyView.sol @@ -0,0 +1,120 @@ +pragma solidity ^0.4.21; + +import "./DelegationProxy.sol"; + + +/** + * @title DelegationProxy + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * @dev Creates a delegation proxy layer for MiniMeTokenInterface. + */ +contract DelegationProxyView is DelegationProxy { + + //storage of preprocessed view of FinalDelegate + mapping(bytes32 => FinalDelegate) public delegationView; + + struct FinalDelegate { + address delegate; + bool found; + } + + /** + * @notice Reads the final delegate of `_who` at block number `_block`. + * @param _who Address to lookup. + * @param _block From what block. + * @return Final delegate address. + */ + function delegationOfAt( + address _who, + uint _block + ) + public + view + returns(address finalDelegate) + { + bytes32 searchIndex = keccak256(_who, _block); + FinalDelegate memory search = delegationView[searchIndex]; + if (search.found) { + return search.delegate; + } else { + return super.delegationOfAt(_who, _block); + } + + } + + /** + * @notice Dig into delegate chain to find final delegate, makes delegationOfAt cheaper to call; + * Should be used when you want to track an isolated long delegation chain FinalDelegate + * @param _delegator Address to lookup final delegate. + * @param _block From what block. + * @return True when found final delegate. + */ + function findFinalDelegate( + address _delegator, + uint256 _block, + uint256 loopLimit + ) + external + returns (bool) + { + bytes32 searchIndex = keccak256(_delegator,_block); + FinalDelegate memory search = delegationView[searchIndex]; + require(!search.found); + for (uint i = 0; i < loopLimit; i++) { + if (search.delegate == address(0)) { + search.delegate = _delegator; + } + address delegateFrom = delegatedToAt(search.delegate, _block); + if (delegateFrom == address(0)) { + // search.delegate demonsted this address didnt delegated, + search.found = true; // so its the final delegate + } else { + search.delegate = delegateFrom; + } + if (search.found) { + break; + } + } + delegationView[searchIndex] = search; //save search + return search.found; + } + + /** + * @notice Explore the chain from `_delegator`, saving FinalDelegate indexes for all delegates, makes delegationOfAt cheaper to call. + * Should be used to track a common FinalDelegates in a small delegation chain, saving gas on repetitive lookups; + * @param _delegator Address to lookup final delegate. + * @param _block From what block. + * @param _stackLimit how much deep explore to build the indexes + * @return address of delegate when found, or the last top delegate found if stacklimit reached without getting into FinalDelegate. + */ + function buildFinalDelegateChain( + address _delegator, + uint256 _block, + uint256 _stackLimit + ) + public + returns (address lastDelegate, bool found) + { + require(_block > block.number); //cannot renderize current state view ? + bytes32 searchIndex = keccak256(_delegator, _block); + FinalDelegate memory search = delegationView[searchIndex]; + if (!search.found) { + if (search.delegate == address(0)) { + lastDelegate = delegatedToAt(_delegator, _block); + if (lastDelegate == address(0)) { + //`_delegator` FinalDelegate is itself + lastDelegate = _delegator; + found = true; + } + } + + if (!found && _stackLimit > 0) { + //`_delegator` FinalDelegate is the same FinalDelegate of it's `delegate` + (lastDelegate, found) = buildFinalDelegateChain(lastDelegate, _block, _stackLimit - 1); + } + delegationView[searchIndex] = FinalDelegate(lastDelegate, found); + } + return (lastDelegate, found); + } + +} \ No newline at end of file diff --git a/contracts/democracy/Democracy.sol b/contracts/democracy/Democracy.sol new file mode 100644 index 0000000..cf7e290 --- /dev/null +++ b/contracts/democracy/Democracy.sol @@ -0,0 +1,81 @@ +pragma solidity ^0.4.21; + +import "./DemocracyInterface.sol"; +import "./ProposalManager.sol"; +import "./FeeRecycler.sol"; + + +contract Democracy is DemocracyInterface { + + mapping (bytes32 => Allowance) topicAllowance; + mapping (uint256 => bool) executedProposals; + + struct Allowance { + mapping(address => bool) anyCall; + mapping(bytes32 => bool) calls; + } + + function Democracy(MiniMeTokenInterface _token, TrustNetworkInterface _trustNetwork) public { + token = _token; + trustNet = _trustNetwork; + feeCollector = new FeeRecycler(_token); + proposalManager = new ProposalManager(_token, _trustNetwork, feeCollector); + } + + function allowTopicSpecific(bytes32 _topic, address _destination, bytes4 _allowedCall, bool allowance) + external + { + require(msg.sender == address(this)); + topicAllowance[_topic].calls[keccak256(_destination, _allowedCall)] = allowance; + } + + function allowTopicAnyCall(bytes32 _topic, address _destination, bool allowance) + external + { + require(msg.sender == address(this)); + topicAllowance[_topic].anyCall[_destination] = allowance; + } + + function executeProposal( + uint256 _proposalId, + address _destination, + uint _value, + bytes _data + ) + external + returns (bool success) + { + require(!executedProposals[_proposalId]); + executedProposals[_proposalId] = true; + + bytes32 topic; + bytes32 txHash; + bool approved; + (topic, txHash, approved) = proposalManager.getProposal(_proposalId); + require(approved); + require( + txHash == keccak256( + _destination, + _value, + _data + ) + ); + + if(topic != 0x0) { //if not root topic + Allowance storage allowed = topicAllowance[topic]; + if(!allowed.anyCall[_destination]){ //if topic not allowed any call to destination + bytes memory data = _data; + bytes4 calling; + assembly { + calling := mload(add(data, 4)) + } + delete data; + require(allowed.calls[keccak256(_destination, calling)]); //require call allowance + } + } + + //execute the call + return _destination.call.value(_value)(_data); + } + +} \ No newline at end of file diff --git a/contracts/democracy/DemocracyInterface.sol b/contracts/democracy/DemocracyInterface.sol new file mode 100644 index 0000000..0cbd457 --- /dev/null +++ b/contracts/democracy/DemocracyInterface.sol @@ -0,0 +1,21 @@ +pragma solidity ^0.4.21; + +import "./DemocracyStorage.sol"; + +/** + * @title DemocracyInterface + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + */ +contract DemocracyInterface is DemocracyStorage { + + function executeProposal( + uint256 _proposalId, + address _destination, + uint _value, + bytes _data + ) + external + returns(bool success); + + +} \ No newline at end of file diff --git a/contracts/democracy/DemocracyStorage.sol b/contracts/democracy/DemocracyStorage.sol new file mode 100644 index 0000000..9e13a8f --- /dev/null +++ b/contracts/democracy/DemocracyStorage.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.4.21; + +import "../deploy/InstanceStorage.sol"; +import "../token/MiniMeTokenInterface.sol"; +import "./TrustNetworkInterface.sol"; +import "./ProposalManagerInterface.sol"; +import "./FeeCollector.sol"; + +/** + * @title DemocracyStorage + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * @dev Defines kernel vars that Kernel contract share with Instance. + * Important to avoid overwriting wrong storage pointers is that + * InstanceStorage should be always the first contract at heritance. + */ +contract DemocracyStorage is InstanceStorage { + // protected zone start (InstanceStorage vars) + MiniMeTokenInterface public token; + TrustNetworkInterface public trustNet; + ProposalManagerInterface public proposalManager; + FeeCollector public feeCollector; + // protected zone end +} \ No newline at end of file diff --git a/contracts/democracy/FeeCollector.sol b/contracts/democracy/FeeCollector.sol new file mode 100644 index 0000000..2ae272e --- /dev/null +++ b/contracts/democracy/FeeCollector.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.4.21; + +/** @notice Interface for fee collector */ +contract FeeCollector { + + /** + * @notice Collect a fee from yourself in your address + * @param _amount to be collected + */ + function collect(uint256 _amount) external; + + /** + * @notice Collect a fee from your address in name of someone + * @param _from to which address fee will be registered to + * @param _amount to be collected + */ + function collectFor(address _from, uint256 _amount) external; + + /** + * @notice Collect a fee from someone + * @param _from who allowed collection + * @param _amount to be collected + */ + function collectFrom(address _from, uint256 _amount) external; + +} \ No newline at end of file diff --git a/contracts/democracy/FeeRecycler.sol b/contracts/democracy/FeeRecycler.sol new file mode 100644 index 0000000..7c8fafd --- /dev/null +++ b/contracts/democracy/FeeRecycler.sol @@ -0,0 +1,110 @@ +pragma solidity ^0.4.21; + +import "../common/Controlled.sol"; +import "../token/ERC20Token.sol"; +import "../token/MiniMeTokenInterface.sol"; +import "./FeeCollector.sol"; +/** + * @title FeeRecycler + * @author Ricardo Guilherme Schmidt (Status Research & Development GmBH) + * @dev Allow user selecting predefined destinations to where this fees will be invested + */ +contract FeeRecycler is Controlled, FeeCollector { + + //allowed democratically choosen destinations + mapping (address => bool) public destinations; + //balances of users + mapping (address => uint256) public balances; + //used for withdrawing lost tokens + uint256 public totalLocked; + //base token + MiniMeTokenInterface public token; + + /** + * @notice Constructor defines the unchangable (?) baseToken + * @param _token base token + */ + function FeeRecycler(MiniMeTokenInterface _token) public { + token = _token; + } + + /** + * @notice Collect a fee from yourself in your address + * @param _amount to be collected + */ + function collect(uint256 _amount) external { + require(token.transferFrom(msg.sender, address(this), _amount)); + balances[msg.sender] += _amount; + totalLocked += _amount; + } + + /** + * @notice Collect a fee from someone + * @param _from who allowed collection + * @param _amount to be collected + */ + function collectFrom(address _from, uint256 _amount) external { + require(token.transferFrom(_from, address(this), _amount)); + balances[_from] += _amount; + totalLocked += _amount; + } + + /** + * @notice Lock a fee in name of someone + * @param _from who would be able to recycle this funds + * @param _amount to be locked + */ + function collectFor(address _from, uint256 _amount) external { + require(token.transferFrom(msg.sender, address(this), _amount)); + balances[_from] += _amount; + totalLocked += _amount; + } + + /** + * @notice Unlock and approveAndCall + * @param _to Allowed destination to get tokens + * @param _amount that will be transfered + */ + function recycle(address _to, uint256 _amount) external { + require(destinations[_to]); + require(balances[msg.sender] >= _amount); + balances[msg.sender] -= _amount; + totalLocked -= _amount; + token.approveAndCall(_to, _amount, new bytes(0)); + } + + /** + * @notice Controller should enable destinations to recycle + * @param _destination that would be available to recycle + * @param _allowed users can recycle to this address? + */ + function setDestination(address _destination, bool _allowed) + external + onlyController + { + destinations[_destination] = _allowed; + } + + /** + * @notice Withdraw lost tokens in the contract + * @param _token if is base token than can only transfer unlocked amount + * @param _destination address receiving this tokens + * @param _amount the amount to be transfered + */ + function withdraw(ERC20Token _token, address _destination, uint256 _amount) + external + onlyController + { + if (address(_token) == address(token)) { + require(_amount <= _token.balanceOf(address(this)) - totalLocked); + } else if (address(_token) == address(0)) { + require(address(this).balance <= _amount); + } + if (address(_token) != address(0)) { + _token.transfer(_destination, _amount); + } else { + _destination.transfer(_amount); + } + } + +} \ No newline at end of file diff --git a/contracts/democracy/ProposalManager.sol b/contracts/democracy/ProposalManager.sol new file mode 100644 index 0000000..904f2aa --- /dev/null +++ b/contracts/democracy/ProposalManager.sol @@ -0,0 +1,174 @@ +pragma solidity ^0.4.21; + +import "../common/Controlled.sol"; +import "../token/MiniMeTokenInterface.sol"; +import "./ProposalManagerInterface.sol"; +import "./TrustNetworkInterface.sol"; +import "./DelegationProxyInterface.sol"; + +/** + * @title ProposalManager + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * Store the proposals, votes and tabulate results for Democracy + */ +contract ProposalManager is ProposalManagerInterface, Controlled { + + function ProposalManager( + MiniMeTokenInterface _SNT, + TrustNetworkInterface _trustNet, + FeeCollector _feeCollector + ) + public + { + trustNet = _trustNet; + token = _SNT; + feeCollector = _feeCollector; + } + + function addProposal( + bytes32 _topic, + bytes32 _txHash, + uint _visibilityFee + ) + public + returns (uint proposalId) + { + require(_visibilityFee > minVisibilityFee); + require( + token.transferFrom( + msg.sender, + address(this), + _visibilityFee + ) + ); + token.approve(feeCollector, _visibilityFee); + feeCollector.collectFor(msg.sender, _visibilityFee); + proposalId = proposals.length++; + Proposal storage p = proposals[proposalId]; + + p.topic = _topic; + p.txHash = _txHash; + p.visibilityFee = _visibilityFee; + + p.blockStart = block.number + 1000; //will be replaced by configurations + p.voteBlockEnd = p.blockStart + 10000; //dummy value + p.vetoBlockEnd = p.voteBlockEnd + 5000; //dummy value + emit ProposalSet(_topic, proposalId, _txHash, _visibilityFee); + } + + function increaseProposalVisibility( + uint _proposalId, + uint256 _visibilityFee + ) + external + { + require(_visibilityFee > minVisibilityFee); + require( + token.transferFrom( + msg.sender, + address(this), + _visibilityFee + ) + ); + token.approve(feeCollector, _visibilityFee); + feeCollector.collectFor(msg.sender, _visibilityFee); + proposals[_proposalId].visibilityFee += _visibilityFee; + Proposal memory p = proposals[_proposalId]; + emit ProposalSet(p.topic, _proposalId, p.txHash, p.visibilityFee); + } + + function getProposal(uint _proposalId) + public + view + returns ( + bytes32 topic, + bytes32 txHash, + bool approved + ) + { + Proposal memory p = proposals[_proposalId]; + return (p.topic, p.txHash, p.result == Vote.Approve); + } + + function getProposalTxHash(uint _proposalId) + public + view + returns(bytes32) + { + return proposals[_proposalId].txHash; + } + + function voteProposal(uint _proposalId, Vote _vote) + public + { + Proposal storage proposal = proposals[_proposalId]; + require(block.number >= proposal.blockStart); + if (_vote == Vote.Veto) { + require(block.number <= proposal.vetoBlockEnd); + } else { + require(block.number <= proposal.voteBlockEnd); + } + + proposal.voteMap[msg.sender] = _vote; + + } + + function tabulateVote(uint _proposalId, address _delegator) + public + { + Proposal storage proposal = proposals[_proposalId]; + require(block.number > proposal.voteBlockEnd); + require(!proposal.tabulated[_delegator].vote); + proposal.tabulated[_delegator].vote = true; + Vote _vote = proposal.voteMap[_delegator]; + if(_vote == Vote.Null) { + address delegate = trustNet.getVoteDelegation(proposal.topic).delegationOfAt(_delegator, proposal.vetoBlockEnd); + _vote = proposal.voteMap[delegate]; + } + + if (_vote == Vote.Reject || _vote == Vote.Approve) { + proposal.results[uint8(_vote)] += token.balanceOfAt(_delegator, proposal.voteBlockEnd); + } + } + + function tabulateVeto(uint _proposalId, address _delegator) + public + { + Proposal storage proposal = proposals[_proposalId]; + require(block.number > proposal.vetoBlockEnd); + require(!proposal.tabulated[_delegator].veto); + proposal.tabulated[_delegator].veto = true; + Vote _vote = proposal.voteMap[_delegator]; + if (_vote == Vote.Null) { + address delegate = trustNet.getVetoDelegation(proposal.topic).delegationOfAt(_delegator, proposal.vetoBlockEnd); + _vote = proposal.voteMap[delegate]; + } + + if (_vote == Vote.Veto) { + proposal.results[uint8(Vote.Veto)] += token.balanceOfAt(_delegator, proposal.vetoBlockEnd); + } + + } + + function finalResult(uint _proposalId) + public + { + Proposal storage proposal = proposals[_proposalId]; + require(proposal.vetoBlockEnd + tabulationBlockDelay > block.number); + require(proposal.result == Vote.Null); + uint256 totalTokens = token.totalSupplyAt(proposal.vetoBlockEnd); + uint256 approvals = proposal.results[uint8(Vote.Approve)]; + uint256 vetos = proposal.results[uint8(Vote.Veto)]; + uint256 approvalQuorum = (totalTokens / 2); + uint256 vetoQuorum = (totalTokens / 3); + if (vetos >= vetoQuorum) { + proposal.result = Vote.Veto; + } else if(approvals >= approvalQuorum) { + proposal.result = Vote.Approve; + } else { + proposal.result = Vote.Reject; + } + emit ProposalResult(_proposalId, proposal.result); + } + +} \ No newline at end of file diff --git a/contracts/democracy/ProposalManagerInterface.sol b/contracts/democracy/ProposalManagerInterface.sol new file mode 100644 index 0000000..4e4ff7e --- /dev/null +++ b/contracts/democracy/ProposalManagerInterface.sol @@ -0,0 +1,58 @@ +pragma solidity ^0.4.21; + +import "../token/MiniMeTokenInterface.sol"; +import "./TrustNetworkInterface.sol"; +import "./DelegationProxyInterface.sol"; +import "./FeeCollector.sol"; +/** + * @title ProposalManagerInterface + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + */ +contract ProposalManagerInterface { + + struct Proposal { + bytes32 topic; + bytes32 txHash; + + uint visibilityFee; + + uint blockStart; + uint voteBlockEnd; + uint vetoBlockEnd; + + mapping(address => Vote) voteMap; + mapping(address => Tabulations) tabulated; + mapping(uint8 => uint256) results; + + Vote result; + } + + struct Tabulations { + bool vote; + bool veto; + } + + enum Vote { + Null, + Reject, + Approve, + Veto + } + + TrustNetworkInterface public trustNet; + MiniMeTokenInterface public token; + FeeCollector public feeCollector; + uint256 public tabulationBlockDelay; + uint256 public minVisibilityFee = 1000; + Proposal[] public proposals; + + event ProposalSet(bytes32 indexed topic, uint256 _proposalId, bytes32 _txHash, uint256 _visibility); + event ProposalResult(uint256 _proposalId, Vote finalResult); + + function addProposal(bytes32 _topic, bytes32 _txHash, uint _visibilityFee) public returns (uint); + function getProposal(uint _id) public view returns (bytes32 topic, bytes32 txHash, bool approved); + function voteProposal(uint _proposal, Vote _vote) public; + function tabulateVote(uint _proposal, address _delegator) public; + function tabulateVeto(uint _proposal, address _delegator) public; + function finalResult(uint _proposalId) public; +} \ No newline at end of file diff --git a/contracts/democracy/TrustNetwork.sol b/contracts/democracy/TrustNetwork.sol new file mode 100644 index 0000000..7634e21 --- /dev/null +++ b/contracts/democracy/TrustNetwork.sol @@ -0,0 +1,82 @@ +pragma solidity ^0.4.21; + +import "../common/Controlled.sol"; +import "./TrustNetworkInterface.sol"; +import "./DelegationProxyInterface.sol"; +import "./DelegationProxyFactory.sol"; + + +/** + * @title TrustNetwork + * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) + * Defines two contolled DelegationProxy chains: vote and veto chains. + * New layers need to be defined under a unique topic address topic, and all fall back to root topic (topic 0x0) + */ +contract TrustNetwork is TrustNetworkInterface, Controlled { + mapping (bytes32 => Topic) topics; + DelegationProxyFactory delegationFactory; + + struct Topic { + DelegationProxyInterface voteDelegation; + DelegationProxyInterface vetoDelegation; + } + + function TrustNetwork(address _delegationFactory) public { + delegationFactory = DelegationProxyFactory(_delegationFactory); + topics[0x0] = newTopic(0x0, 0x0); + } + + function addTopic(bytes32 topicId, bytes32 parentTopic) public onlyController { + Topic memory parent = topics[parentTopic]; + address vote = address(parent.voteDelegation); + address veto = address(parent.vetoDelegation); + require(vote != 0x0); + require(veto != 0x0); + + Topic storage topic = topics[topicId]; + require(address(topic.voteDelegation) == 0x0); + require(address(topic.vetoDelegation) == 0x0); + + topics[topicId] = newTopic(vote, veto); + } + + function getTopic(bytes32 _topicId) public view returns (DelegationProxyInterface vote, DelegationProxyInterface veto) { + Topic memory topic = topics[_topicId]; + vote = topic.voteDelegation; + veto = topic.vetoDelegation; + } + + function getVoteDelegation( + bytes32 _topicId + ) + public + view + returns (DelegationProxyInterface voteDelegation) + { + return topics[_topicId].voteDelegation; + } + + function getVetoDelegation( + bytes32 _topicId + ) + public + view + returns (DelegationProxyInterface vetoDelegation) + { + return topics[_topicId].vetoDelegation; + } + + + function newTopic(address _vote, address _veto) internal returns (Topic topic) { + topic = Topic ({ + voteDelegation: delegationFactory.createDelegationProxy(_vote), + vetoDelegation: delegationFactory.createDelegationProxy(_veto) + }); + } + + + + + + +} \ No newline at end of file diff --git a/contracts/democracy/TrustNetworkInterface.sol b/contracts/democracy/TrustNetworkInterface.sol new file mode 100644 index 0000000..773d96b --- /dev/null +++ b/contracts/democracy/TrustNetworkInterface.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.21; + +import "./DelegationProxyInterface.sol"; + +contract TrustNetworkInterface { + + function addTopic(bytes32 topicId, bytes32 parentTopic) public; + function getTopic(bytes32 _topicId) public view returns (DelegationProxyInterface vote, DelegationProxyInterface veto); + function getVoteDelegation(bytes32 _topicId) public view returns (DelegationProxyInterface voteDelegation); + function getVetoDelegation(bytes32 _topicId) public view returns (DelegationProxyInterface vetoDelegation); +} \ No newline at end of file diff --git a/test/DelegationProxyTest.js b/test/DelegationProxyTest.js new file mode 100644 index 0000000..93a9067 --- /dev/null +++ b/test/DelegationProxyTest.js @@ -0,0 +1,312 @@ +const DelegationProxy = artifacts.require("DelegationProxy.sol") +const MiniMeTokenFactory = artifacts.require("MiniMeTokenFactory.sol") +const MiniMeToken = artifacts.require("MiniMeToken.sol") +const TestUtils = require("./TestUtils.js") + +// TODO: This a very minimal set of tests purely for understanding the contract. I think they can be used though. +contract("DelegationProxy", accounts => { + + //Initialize global/common contracts for all tests + let delegationProxy = [] + const delegateTo = [ + [ + accounts[1], + accounts[2], + 0x0 + ], + [ + 0x0, + accounts[2], + 0x0 + ] + ] + const delegationOf = [ + [ + accounts[2], + accounts[2], + accounts[2] + ], + [ + accounts[0], + accounts[2], + accounts[2] + ] + ] + + const delegatedInfluence = [ + [0, 1, 2], + [0, 0, 1] + ] + + const influence = [ + [0, 0, 3], + [1, 0, 2] + ] + + beforeEach(async () => { + delegationProxy[0] = await DelegationProxy.new(0) + delegationProxy[1] = await DelegationProxy.new(delegationProxy[0].address) + }) + + describe("delegate(address _to)", () => { + + it("creates Delegate Log event", async () => { + const i = 0 + const j = 0 + delegationProxy[i].delegate(delegateTo[i][j], {from: accounts[j]}) + const delegateArgs = await TestUtils.listenForEvent(delegationProxy[i].Delegate()) + assert.equal(delegateArgs.who, accounts[j], "["+i+","+j+"] Delegate Log shows delegating from isn't sender") + assert.equal(delegateArgs.to, delegateTo[i][j], "["+i+","+j+"]Delegate Log shows delegating to isn't passed address") + + }) + + it("updates delegations mapping with new delegate", async () => { + const i = 0 + const j = 0 + await delegationProxy[i].delegate(delegateTo[i][j], {from: accounts[j]}) + const delegations = await delegationProxy[i].delegations.call(accounts[j], 0) + assert.equal(delegations[0].c, web3.eth.blockNumber, "["+i+","+j+"] Delegations block number is incorrect") + assert.equal(delegations[1], delegateTo[i][j], "["+i+","+j+"] Delegations to account is incorrect") + }) + + it("stores delegation checkpoints correctly", async () => { + const delegateTo2 = [ + [ + 0x0, + accounts[0], + accounts[0] + ], + [ + accounts[2], + 0x0, + accounts[1] + ] + ] + + const delegationOf2 = [ + [ + accounts[0], + accounts[0], + accounts[0] + ], + [ + accounts[1], + accounts[1], + accounts[1] + ] + ] + + const delegateTo3 = [ + [ + 0x0, + 0x0, + 0x0 + ], + [ + 0x0, + 0x0, + 0x0 + ] + ] + + const delegationOf3 = [ + [ + accounts[0], + accounts[1], + accounts[2] + ], + [ + accounts[0], + accounts[1], + accounts[2] + ] + ] + + for (var i = 0; i < delegateTo.length; i++) { + for (var j = 0; j < delegateTo[i].length; j++) { + await delegationProxy[i].delegate(delegateTo[i][j], {from: accounts[j]}); + } + } + const blockn1 = web3.eth.blockNumber + + for (var i = 0; i < delegateTo2.length; i++) { + for (var j = 0; j < delegateTo2[i].length; j++) { + await delegationProxy[i].delegate(delegateTo2[i][j], {from: accounts[j]}); + } + } + const blockn2 = web3.eth.blockNumber + + for (var i = 0; i < delegateTo3.length; i++) { + for (var j = 0; j < delegateTo3[i].length; j++) { + await delegationProxy[i].delegate(delegateTo3[i][j], {from: accounts[j]}); + } + } + const blockn3 = web3.eth.blockNumber + + for (var i = 0; i < delegateTo.length; i++) { + for (var j = 0; j < delegateTo[i].length; j++) { + assert.equal( + await delegationProxy[i].delegatedToAt(accounts[j], blockn1), + delegateTo[i][j], + "["+i+","+j+"] +"+delegationProxy[i].address+".delegatedToAt("+accounts[j]+", +"+blockn1+") is incorrect") + assert.equal( + await delegationProxy[i].delegatedToAt(accounts[j], blockn2), + delegateTo2[i][j], + "["+i+","+j+"] +"+delegationProxy[i].address+".delegatedToAt("+accounts[j]+", +"+blockn2+") is incorrect") + assert.equal( + await delegationProxy[i].delegatedToAt(accounts[j], blockn3), + delegateTo3[i][j], + "["+i+","+j+"] +"+delegationProxy[i].address+".delegatedToAt("+accounts[j]+", +"+blockn3+") is incorrect") + + assert.equal( + await delegationProxy[i].delegationOfAt(accounts[j], blockn1), + delegationOf[i][j], + "["+i+","+j+"] +"+delegationProxy[i].address+".delegationOfAt("+accounts[j]+", +"+blockn1+") is incorrect") + assert.equal( + await delegationProxy[i].delegationOfAt(accounts[j], blockn2), + delegationOf2[i][j], + "["+i+","+j+"] +"+delegationProxy[i].address+".delegationOfAt("+accounts[j]+", +"+blockn2+") is incorrect") + assert.equal( + await delegationProxy[i].delegationOfAt(accounts[j], blockn3), + delegationOf3[i][j], + "["+i+","+j+"] +"+delegationProxy[i].address+".delegationOfAt("+accounts[j]+", +"+blockn3+") is incorrect") + } + } + + }) + + it("delegates back to parentProxy", async () => { + for (var i = 0; i < delegateTo.length; i++) { + for (var j = 0; j < delegateTo[i].length; j++) { + await delegationProxy[i].delegate(delegateTo[i][j], {from: accounts[j]}); + } + } + const blockn1 = web3.eth.blockNumber + + for (var j = 0; j < delegateTo[1].length; j++) { + await delegationProxy[1].delegate(delegationProxy[0].address, {from: accounts[j]}); + } + + const blockn2 = web3.eth.blockNumber + + for (var j = 0; j < delegateTo[1].length; j++) { + assert.equal( + await delegationProxy[1].delegatedToAt(accounts[j], blockn1), + delegateTo[1][j], + "["+j+"] +"+delegationProxy[1].address+".delegatedToAt("+accounts[j]+", +"+blockn1+") is incorrect") + assert.equal( + await delegationProxy[1].delegatedToAt(accounts[j], blockn2), + delegateTo[0][j], + "["+j+"] +"+delegationProxy[1].address+".delegatedToAt("+accounts[j]+", +"+blockn2+") is incorrect") + + assert.equal( + await delegationProxy[1].delegationOfAt(accounts[j], blockn1), + delegationOf[1][j], + "["+j+"] +"+delegationProxy[1].address+".delegationOfAt("+accounts[j]+", +"+blockn1+") is incorrect") + assert.equal( + await delegationProxy[1].delegationOfAt(accounts[j], blockn2), + delegationOf[0][j], + "["+j+"] +"+delegationProxy[1].address+".delegationOfAt("+accounts[j]+", +"+blockn2+") is incorrect") + + } + }) + }) + + describe("delegatedToAt(address _who, uint _block)", () => { + + it("returns correctly delegated to address", async () => { + let delegatedTo + for (var i = 0; i < delegateTo.length; i++) { + for (var j = 0; j < delegateTo[i].length; j++) { + await delegationProxy[i].delegate(delegateTo[i][j], {from: accounts[j]}); + delegatedTo = await delegationProxy[i].delegatedToAt(accounts[j], web3.eth.blockNumber) + assert.equal( + delegatedTo, + delegateTo[i][j], + "["+i+","+j+"] +"+delegationProxy[i].address+".delegatedToAt("+accounts[j]+", +"+web3.eth.blockNumber+") is incorrect") + + } + } + }) + }) + + describe("delegationOfAt(address _who, uint _block)", () => { + + it("returns correctly delegation endpoints of address", async () => { + let result = [[],[]] + for (var i = 0; i < delegateTo.length; i++) { + for (var j = 0; j < delegateTo[i].length; j++) { + await delegationProxy[i].delegate(delegateTo[i][j], {from: accounts[j]}); + } + for (j = 0; j < delegateTo[i].length; j++) { + assert.equal( + await delegationProxy[i].delegationOfAt(accounts[j], web3.eth.blockNumber), + delegationOf[i][j], + "["+i+","+j+"] +"+delegationProxy[i].address+".delegationOfAt("+accounts[j]+", +"+web3.eth.blockNumber+") is incorrect" + ) + } + } + }) + }) + + describe("delegatedInfluenceFromAt(address _who, address _token, uint _block)", () => { + + let miniMeTokenFactory + let miniMeToken + const tokensBalance = 1000 + + beforeEach(async () => { + miniMeTokenFactory = await MiniMeTokenFactory.new(); + miniMeToken = await MiniMeToken.new(miniMeTokenFactory.address, 0, 0, "TestToken", 18, "TTN", true) + for (var i = 0; i < 6; i++) { + miniMeToken.generateTokens(accounts[i], tokensBalance) + } + }) + + it("returns expected amount of influence delegated from", async () => { + + for (var i = 0; i < delegationProxy.length; i++) { + for (var j = 0; j < delegateTo[i].length; j++) { + await delegationProxy[i].delegate(delegateTo[i][j], {from: accounts[j]}); + } + for (var j = 0; j < delegatedInfluence[i].length; j++) { + assert.equal( + delegatedInfluence[i][j] * tokensBalance, + (await delegationProxy[i].delegatedInfluenceFromAt(accounts[j], miniMeToken.address, web3.eth.blockNumber)).toNumber(), + "["+i+","+j+"] +"+delegationProxy[i].address+".delegatedInfluenceFrom("+accounts[j]+", +"+web3.eth.blockNumber+") is not as expected" + ) + } + } + }) + }) + + describe("influenceOfAt(address _who, address _token, uint _block)", () => { + let miniMeTokenFactory + let miniMeToken + const tokensBalance = 1000 + + beforeEach(async () => { + miniMeTokenFactory = await MiniMeTokenFactory.new(); + miniMeToken = await MiniMeToken.new(miniMeTokenFactory.address, 0, 0, "TestToken", 18, "TTN", true) + for (var i = 0; i < 6; i++) { + miniMeToken.generateTokens(accounts[i], tokensBalance) + } + }) + + it("returns expected influence", async () => { + + for (var i = 0; i < influence.length; i++) { + for (var j = 0; j < delegateTo[i].length; j++) { + delegationProxy[i].delegate(delegateTo[i][j], {from: accounts[j]}); + } + for (var j = 0; j < influence[i].length; j++) { + assert.equal( + influence[i][j] * tokensBalance, + (await delegationProxy[i].influenceOfAt(accounts[j], miniMeToken.address, web3.eth.blockNumber)).toNumber(), + "["+i+","+j+"] +"+delegationProxy[i].address+".influenceOfAt("+accounts[j]+", +"+web3.eth.blockNumber+") is not as expected") + } + } + }) + }) + +}) \ No newline at end of file