This commit is contained in:
Ricardo Guilherme Schmidt 2018-04-23 02:12:55 -03:00
commit 3ee96f0144
15 changed files with 1582 additions and 0 deletions

View File

@ -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
}
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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)
});
}
}

View File

@ -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);
}

312
test/DelegationProxyTest.js Normal file
View File

@ -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")
}
}
})
})
})