This commit is contained in:
Ricardo Guilherme Schmidt 2018-05-22 07:46:29 -03:00
parent f021b36058
commit aa93e03035
15 changed files with 298 additions and 373 deletions

View File

@ -32,7 +32,48 @@
},
"UpdatableInstance": {
"deploy": false
}
},
"SNT": {
"instanceOf": "MiniMeToken",
"deploy": true,
"args": [
"$MiniMeTokenFactory",
0,
0,
"TestMiniMeToken",
18,
"TST",
true
]
},
"DelegationProxyFactory": {
"deploy": true
},
"DelegationProxy": {
"deploy": false
},
"DelegationProxyView": {
"deploy": false
},
"DelegationProxyKernel": {
"deploy": false
},
"TrustNetwork": {
"deploy" : false
},
"ProposalCuration": {
"deploy": false
},
"ProposalManager": {
"deploy": false
},
"Democracy": {
"deploy": true,
"args": [
"$SNT",
"$DelegationProxyFactory"
]
}
}
}
}

View File

@ -17,7 +17,7 @@ contract DelegationProxy is DelegationProxyInterface {
/**
* @notice Calls Constructor
*/
function DelegationProxy(address _parentProxy) public {
constructor(address _parentProxy) public {
parentProxy = _parentProxy;
}
@ -258,7 +258,7 @@ contract DelegationProxy is DelegationProxyInterface {
*/
function _updateDelegate(address _from, address _to) internal {
require(delegationOfAt(_to, block.number) != msg.sender); //block impossible circular delegation
Delegate(_from, _to);
emit Delegate(_from, _to);
Delegation memory _newFrom; //allocate memory
Delegation[] storage fromHistory = delegations[_from];
if (fromHistory.length > 0) { //have old config?

View File

@ -12,7 +12,7 @@ import "../deploy/Instance.sol";
*/
contract DelegationProxyFactory is Factory {
function DelegationProxyFactory()
constructor()
Factory(new DelegationProxyKernel())
public
{ }

View File

@ -14,7 +14,7 @@ contract DelegationProxyKernel is InstanceStorage, DelegationProxyView {
/**
* @notice Constructor of the model - only knows about watchdog that can trigger upgrade
*/
function DelegationProxyKernel() DelegationProxy(0x0) public {
constructor() DelegationProxyView(0x0) public {
ready = true;
}

View File

@ -12,12 +12,16 @@ contract DelegationProxyView is DelegationProxy {
//storage of preprocessed view of FinalDelegate
mapping(bytes32 => FinalDelegate) public delegationView;
struct FinalDelegate {
address delegate;
bool found;
}
constructor(address _parentTopic) DelegationProxy(0x0) public {
}
/**
* @notice Reads the final delegate of `_who` at block number `_block`.
* @param _who Address to lookup.

View File

@ -1,12 +1,18 @@
pragma solidity ^0.4.21;
import "./DemocracyInterface.sol";
import "../token/MiniMeTokenInterface.sol";
import "./DelegationProxyFactory.sol";
import "./TrustNetwork.sol";
import "./ProposalCuration.sol";
import "./ProposalManager.sol";
import "./FeeRecycler.sol";
contract Democracy is DemocracyInterface {
contract Democracy {
MiniMeTokenInterface public token;
TrustNetwork public trustNet;
ProposalManager public proposalManager;
mapping (bytes32 => Allowance) topicAllowance;
mapping (uint256 => bool) executedProposals;
@ -15,11 +21,10 @@ contract Democracy is DemocracyInterface {
mapping(bytes32 => bool) calls;
}
function Democracy(MiniMeTokenInterface _token, TrustNetworkInterface _trustNetwork) public {
constructor(MiniMeTokenInterface _token, DelegationProxyFactory _delegationProxyFactory) public {
token = _token;
trustNet = _trustNetwork;
feeCollector = new FeeRecycler(_token);
proposalManager = new ProposalManager(_token, _trustNetwork, feeCollector);
trustNet = new TrustNetwork(_delegationProxyFactory);
proposalManager = new ProposalCuration(_token, trustNet).proposalManager();
}
function allowTopicSpecific(bytes32 _topic, address _destination, bytes4 _allowedCall, bool allowance)

View File

@ -1,21 +0,0 @@
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

@ -1,23 +0,0 @@
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

@ -1,26 +0,0 @@
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

@ -1,110 +0,0 @@
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,125 @@
pragma solidity ^0.4.21;
import "../common/Controlled.sol";
import "../token/MiniMeTokenInterface.sol";
import "./ProposalManager.sol";
contract ProposalCuration is Controlled {
uint256 public constant RESULT_NULL = 0;
uint256 public constant RESULT_REJECT = 1;
uint256 public constant RESULT_APPROVE = 2;
uint256 public constant RESULT_VETO = 3;
mapping (uint256 => ProposalData) public proposals;
ProposalManager public proposalManager;
uint256 public approvalTimeLimit;
MiniMeTokenInterface token;
mapping (address => SubmitPrice) submitAllowances;
struct SubmitPrice {
bool allowedSubmitter;
uint256 stakePrice;
}
struct ProposalData {
address proposer;
address to;
uint256 value;
bytes data;
bytes description;
uint256 stakedAmount;
}
constructor(
MiniMeTokenInterface _token,
TrustNetworkInterface _trustNet
)
public
{
token = _token;
proposalManager = new ProposalManager(_token, _trustNet);
}
function submitProposal(
bytes32 _topic,
address _to,
uint256 _value,
bytes _data,
bytes _description
)
external
returns (uint256 proposalId)
{
uint256 submitPrice = getSubmitPrice(msg.sender);
require(token.allowance(msg.sender, address(this)) >= submitPrice);
require(token.transferFrom(msg.sender, address(this), submitPrice));
proposalId = proposalManager.addProposal(_topic,keccak256(_to,_value,_data));
proposals[proposalId] = ProposalData(
msg.sender,
_to,
_value,
_data,
_description,
submitPrice
);
}
function withdrawStake(
uint256 _proposalId
)
external
{
require(proposalManager.getProposalFinalResult(_proposalId) == RESULT_APPROVE);
uint256 refundValue = proposals[_proposalId].stakedAmount;
address refundAddress = proposals[_proposalId].proposer;
delete proposals[_proposalId];
if (refundValue > 0) {
require(token.transfer(refundAddress, refundValue));
}
}
function slashStake(
uint256 _proposalId
)
external
{
uint8 result = proposalManager.getProposalFinalResult(_proposalId);
require(result == RESULT_REJECT || result == RESULT_VETO);
uint256 refundValue = proposals[_proposalId].stakedAmount;
delete proposals[_proposalId];
if (refundValue > 0) {
require(token.transfer(controller, refundValue));
}
}
function setSubmitPrice(address _who, bool _allowedSubmitter, uint256 _stakeValue)
external
onlyController
{
if (_allowedSubmitter) {
submitAllowances[_who] = SubmitPrice(_allowedSubmitter, _stakeValue);
} else {
delete submitAllowances[_who];
}
}
function getSubmitPrice(address _who)
public
view
returns (uint256 price)
{
SubmitPrice memory allowance = submitAllowances[_who];
if(allowance.allowedSubmitter){
return allowance.stakePrice;
} else {
allowance = submitAllowances[_who];
require(allowance.allowedSubmitter);
return allowance.stakePrice;
}
}
}

View File

@ -2,83 +2,136 @@ pragma solidity ^0.4.21;
import "../common/Controlled.sol";
import "../token/MiniMeTokenInterface.sol";
import "./ProposalManagerInterface.sol";
import "./TrustNetworkInterface.sol";
import "./DelegationProxyInterface.sol";
import "./TrustNetworkInterface.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 {
contract ProposalManager is Controlled {
event ProposalSet(bytes32 indexed topic, uint256 proposalId, bytes32 txHash);
event ProposalResult(uint256 proposalId, uint8 finalResult);
function ProposalManager(
MiniMeTokenInterface _SNT,
TrustNetworkInterface _trustNet,
FeeCollector _feeCollector
MiniMeTokenInterface public token;
TrustNetworkInterface public trustNet;
uint256 public tabulationBlockDelay;
Proposal[] public proposals;
struct Proposal {
bytes32 topic;
bytes32 txHash;
uint blockStart;
uint voteBlockEnd;
address[] voters;
mapping(address => Vote) voteMap;
mapping(address => bool) tabulated;
mapping(uint8 => uint256) results;
Vote result;
uint256 lastTabulationTimestamp;
}
enum Vote {
Null,
Reject,
Approve
}
constructor(
MiniMeTokenInterface _token,
TrustNetworkInterface _trustNet
)
public
{
trustNet = _trustNet;
token = _SNT;
feeCollector = _feeCollector;
token = _token;
}
function addProposal(
bytes32 _topic,
bytes32 _txHash,
uint _visibilityFee
bytes32 _txHash
)
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);
emit ProposalSet(_topic, proposalId, _txHash);
}
function increaseProposalVisibility(
uint _proposalId,
uint256 _visibilityFee
)
external
function voteProposal(uint _proposalId, Vote _vote)
public
{
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);
Proposal storage proposal = proposals[_proposalId];
require(block.number >= proposal.blockStart);
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]);
proposal.tabulated[_delegator] = true;
Vote _vote = proposal.voteMap[_delegator];
if(_vote == Vote.Null) {
address delegate = trustNet.getVoteDelegation(proposal.topic).delegationOfAt(_delegator, proposal.voteBlockEnd);
_vote = proposal.voteMap[delegate];
}
if (_vote == Vote.Reject || _vote == Vote.Approve) {
proposal.results[uint8(_vote)] += token.balanceOfAt(_delegator, proposal.voteBlockEnd);
}
proposal.lastTabulationTimestamp = block.timestamp;
}
function finalResult(uint _proposalId)
public
{
Proposal storage proposal = proposals[_proposalId];
require(proposal.lastTabulationTimestamp + tabulationBlockDelay > block.number);
require(proposal.result == Vote.Null);
uint256 totalTokens = token.totalSupplyAt(proposal.voteBlockEnd);
uint256 approvals = proposal.results[uint8(Vote.Approve)];
uint256 approvalQuorum = (totalTokens / 2);
if(approvals >= approvalQuorum) {
proposal.result = Vote.Approve;
} else {
proposal.result = Vote.Reject;
}
emit ProposalResult(_proposalId, uint8(proposal.result));
}
function getProposalFinalResult(
uint256 _proposalId
)
external
view
returns (uint8)
{
return uint8(proposals[_proposalId].result);
}
function getProposal(uint _proposalId)
public
external
view
returns (
bytes32 topic,
@ -89,86 +142,21 @@ contract ProposalManager is ProposalManagerInterface, Controlled {
Proposal memory p = proposals[_proposalId];
return (p.topic, p.txHash, p.result == Vote.Approve);
}
function getProposalTxHash(uint _proposalId)
public
function offchainTabulateVoteResult(uint256 _proposalId)
external
view
returns(bytes32)
returns (uint256[] votes)
{
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);
Proposal memory proposal = proposals[_proposalId];
address[] memory voters = proposal.voters;
uint256 len = proposal.voters.length;
votes = new uint256[](4);
for(uint256 i = 0; i < len; i++) {
address voter = proposal.voters[i];
uint8 voteIndex = uint8(proposals[_proposalId].voteMap[voter]);
votes[voteIndex] += trustNet.getVoteDelegation(proposal.topic).influenceOfAt(proposal.voters[i], token, proposal.voteBlockEnd);
}
}
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

@ -1,58 +0,0 @@
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

@ -21,7 +21,7 @@ contract TrustNetwork is TrustNetworkInterface, Controlled {
DelegationProxyInterface vetoDelegation;
}
function TrustNetwork(address _delegationFactory) public {
constructor(address _delegationFactory) public {
delegationFactory = DelegationProxyFactory(_delegationFactory);
topics[0x0] = newTopic(0x0, 0x0);
}

View File

@ -71,7 +71,7 @@ contract MiniMeToken is MiniMeTokenInterface, Controlled {
// `balances` is the map that tracks the balance of each address, in this
// contract when the balance changes the block number that the change
// occurred is also included in the map
// occurred is also included in the map
mapping (address => Checkpoint[]) balances;
// `allowed` tracks any extra transfer rights as in all ERC20 tokens