mirror of
https://github.com/status-im/contracts.git
synced 2025-02-23 20:18:42 +00:00
base structure of upgradable republic
This commit is contained in:
parent
31a9532084
commit
762e8bd3af
311
contracts/democracy/DelegationProxy.sol
Normal file
311
contracts/democracy/DelegationProxy.sol
Normal file
@ -0,0 +1,311 @@
|
||||
pragma solidity ^0.4.11;
|
||||
|
||||
import "../token/MiniMeToken.sol";
|
||||
|
||||
|
||||
/**
|
||||
* @title DelegationProxy
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev Creates a delegation proxy layer for MiniMeToken.
|
||||
*/
|
||||
contract DelegationProxy {
|
||||
event Delegate(address who, address to);
|
||||
//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;
|
||||
//storage of indexes of the addresses to `delegations[to].from`
|
||||
mapping (address => uint256) toIndexes;
|
||||
|
||||
struct Delegation {
|
||||
uint128 fromBlock; //when this was updated
|
||||
address to; //who recieved this delegaton
|
||||
address[] from; //list of addresses that delegated to this address
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 constant 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 constant 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 MiniMeToken.
|
||||
* @return Amount of influence of `who` have.
|
||||
*/
|
||||
function influenceOf(address _who, MiniMeToken _token) public constant 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 MiniMeToken.
|
||||
* @return Sum of delegated influence received by `_who` in block `_block` from other addresses.
|
||||
*/
|
||||
function delegatedInfluenceFrom(address _who, address _token) public constant 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 MiniMeToken.
|
||||
* @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
|
||||
constant
|
||||
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 constant returns (address addr) {
|
||||
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 && parentProxy != 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 constant returns(address) {
|
||||
address delegate = delegatedToAt(_who, _block);
|
||||
if (delegate != 0x0) { //_who is delegating?
|
||||
return delegationOfAt(delegate, _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 MiniMeToken.
|
||||
* @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
|
||||
constant
|
||||
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 += MiniMeToken(_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 MiniMeToken.
|
||||
* @param _block From what block
|
||||
* @return Amount of influence of `who` have.
|
||||
*/
|
||||
function influenceOfAt(address _who, MiniMeToken _token, uint _block) public constant returns(uint256 _total) {
|
||||
if (delegationOfAt(_who, _block) == _who) { //is endpoint of delegation?
|
||||
_total = MiniMeToken(_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 constant 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
|
||||
}
|
||||
|
||||
}
|
30
contracts/democracy/DelegationProxyFactory.sol
Normal file
30
contracts/democracy/DelegationProxyFactory.sol
Normal file
@ -0,0 +1,30 @@
|
||||
pragma solidity ^0.4.11;
|
||||
|
||||
import "./DelegationProxyModel.sol";
|
||||
import "../deploy/RecoverableSystem.sol";
|
||||
import "../deploy/KillableModel.sol";
|
||||
|
||||
/**
|
||||
* @title DelegationProxyFactory
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev Upgradable delegation proxy factory
|
||||
*/
|
||||
contract DelegationProxyFactory {
|
||||
|
||||
address public systemModel;
|
||||
address public recover;
|
||||
address public watchdog;
|
||||
|
||||
function DelegationProxyFactory(address _recover, address _watchdog) public {
|
||||
watchdog = _watchdog;
|
||||
recover = _recover;
|
||||
systemModel = new DelegationProxyModel(watchdog);
|
||||
}
|
||||
|
||||
function create(address _parent) external returns (DelegationProxy) {
|
||||
DelegationProxyModel instance = DelegationProxyModel(address(new RecoverableSystem(systemModel, recover)));
|
||||
instance.initialize(_parent);
|
||||
return DelegationProxy(address(instance));
|
||||
}
|
||||
|
||||
}
|
30
contracts/democracy/DelegationProxyModel.sol
Normal file
30
contracts/democracy/DelegationProxyModel.sol
Normal file
@ -0,0 +1,30 @@
|
||||
pragma solidity ^0.4.11;
|
||||
|
||||
import "../deploy/KillableModel.sol";
|
||||
import "./DelegationProxy.sol";
|
||||
|
||||
/**
|
||||
* @title DelegationProxyModel
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev Creates a delegation proxy killable model for cheap redeploy and upgradability.
|
||||
*/
|
||||
contract DelegationProxyModel is KillableModel, DelegationProxy {
|
||||
bool private ready = false;
|
||||
|
||||
/**
|
||||
* @notice Constructor of the model - only knows about watchdog that can trigger upgrade
|
||||
*/
|
||||
function DelegationProxyModel(address _watchdog) KillableModel(_watchdog) DelegationProxy(0x0) public {
|
||||
ready = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Creates a new DelegationProxy with `_parentProxy` as default delegation.
|
||||
*/
|
||||
function initialize(address _parentProxy) public {
|
||||
require(!ready);
|
||||
ready = true;
|
||||
parentProxy = _parentProxy;
|
||||
}
|
||||
|
||||
}
|
146
contracts/democracy/ProposalManager.sol
Normal file
146
contracts/democracy/ProposalManager.sol
Normal file
@ -0,0 +1,146 @@
|
||||
pragma solidity ^0.4.10;
|
||||
|
||||
import "./TrustNetwork.sol";
|
||||
import "./DelegationProxy.sol";
|
||||
import "../token/MiniMeToken.sol";
|
||||
import "../common/Controlled.sol";
|
||||
|
||||
/**
|
||||
* @title ProposalManager
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* Store the proposals, votes and results for other smartcontracts
|
||||
*/
|
||||
contract ProposalManager is Controlled {
|
||||
|
||||
TrustNetwork public trustNet;
|
||||
MiniMeToken public SNT;
|
||||
address public stakeBank;
|
||||
|
||||
Proposal[] proposals;
|
||||
|
||||
struct Proposal {
|
||||
address topic;
|
||||
|
||||
address destination;
|
||||
uint value;
|
||||
bytes data;
|
||||
uint stake;
|
||||
|
||||
uint blockStart;
|
||||
uint voteBlockEnd;
|
||||
uint vetoBlockEnd;
|
||||
|
||||
VoteTicket[] votes;
|
||||
mapping(address => uint) voteIndex;
|
||||
|
||||
uint tabulationPosition;
|
||||
bool tabulated;
|
||||
mapping(uint8 => uint) results;
|
||||
|
||||
bool approved;
|
||||
bool executed;
|
||||
}
|
||||
|
||||
|
||||
struct VoteTicket {
|
||||
address voter;
|
||||
Vote vote;
|
||||
}
|
||||
|
||||
enum Vote {
|
||||
Reject,
|
||||
Approve,
|
||||
Veto
|
||||
}
|
||||
|
||||
function ProposalManager(MiniMeToken _SNT, TrustNetwork _trustNet, address _stakeBank) public {
|
||||
trustNet = _trustNet;
|
||||
SNT = _SNT;
|
||||
stakeBank = _stakeBank;
|
||||
}
|
||||
|
||||
function addProposal(address topic, address destination, uint value, bytes data, uint stake) returns (uint) {
|
||||
require(stake > 1000);
|
||||
require(SNT.transferFrom(msg.sender, stakeBank, stake));
|
||||
uint pos = proposals.length++;
|
||||
Proposal storage p = proposals[pos];
|
||||
|
||||
p.topic = topic;
|
||||
p.destination = destination;
|
||||
p.value = value;
|
||||
p.data = data;
|
||||
p.stake = stake;
|
||||
|
||||
p.blockStart = block.number + 1000; //will be replaced by configurations
|
||||
p.voteBlockEnd = p.blockStart + 10000; //dummy value
|
||||
p.vetoBlockEnd = p.voteBlockEnd + 5000; //dummy value
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
function getProposal(uint id) public constant returns (address topic, address destination, uint value, uint stake, bool approved, bool executed) {
|
||||
Proposal memory p = proposals[id];
|
||||
return (p.topic, p.destination, p.value, p.stake, p.approved, p.executed);
|
||||
}
|
||||
|
||||
function getProposalData(uint id) public constant returns(bytes){
|
||||
return proposals[id].data;
|
||||
}
|
||||
|
||||
function setExecuted(uint id) public onlyController {
|
||||
proposals[id].executed = true;
|
||||
}
|
||||
|
||||
function vote(uint _proposal, Vote _vote) {
|
||||
Proposal storage proposal = proposals[_proposal];
|
||||
require(block.number >= proposal.blockStart);
|
||||
if (_vote == Vote.Veto) {
|
||||
require(block.number <= proposal.vetoBlockEnd);
|
||||
} else {
|
||||
require(block.number <= proposal.voteBlockEnd);
|
||||
}
|
||||
uint votePos = proposal.voteIndex[msg.sender];
|
||||
if (votePos == 0) {
|
||||
votePos = proposal.votes.length;
|
||||
} else {
|
||||
votePos = votePos - 1;
|
||||
}
|
||||
VoteTicket storage ticket = proposal.votes[votePos];
|
||||
assert (ticket.voter == 0x0 || ticket.voter == msg.sender);
|
||||
ticket.voter = msg.sender;
|
||||
ticket.vote = _vote;
|
||||
proposal.voteIndex[msg.sender] = votePos + 1;
|
||||
}
|
||||
|
||||
function tabulate(uint _proposal, uint loopLimit) {
|
||||
Proposal storage proposal = proposals[_proposal];
|
||||
require(block.number > proposal.vetoBlockEnd);
|
||||
require(!proposal.tabulated);
|
||||
|
||||
uint totalVoted = proposal.votes.length;
|
||||
if (loopLimit == 0) {
|
||||
loopLimit = totalVoted;
|
||||
}
|
||||
require (loopLimit <= totalVoted);
|
||||
require (loopLimit > proposal.tabulationPosition);
|
||||
|
||||
DelegationProxy voteDelegation;
|
||||
DelegationProxy vetoDelegation;
|
||||
(voteDelegation, vetoDelegation) = trustNet.getTopic(proposal.topic);
|
||||
|
||||
for (uint i = proposal.tabulationPosition; i < loopLimit; i++) {
|
||||
VoteTicket memory _vote = proposal.votes[i];
|
||||
if (_vote.vote == Vote.Reject || _vote.vote == Vote.Approve) {
|
||||
proposal.results[uint8(_vote.vote)] += voteDelegation.influenceOfAt(_vote.voter, SNT, proposal.voteBlockEnd);
|
||||
} else {
|
||||
proposal.results[uint8(_vote.vote)] += vetoDelegation.influenceOfAt(_vote.voter, SNT, proposal.vetoBlockEnd);
|
||||
}
|
||||
}
|
||||
|
||||
proposal.tabulationPosition = i;
|
||||
if (proposal.tabulationPosition == totalVoted) {
|
||||
proposal.tabulated = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
59
contracts/democracy/TrustNetwork.sol
Normal file
59
contracts/democracy/TrustNetwork.sol
Normal file
@ -0,0 +1,59 @@
|
||||
pragma solidity ^0.4.10;
|
||||
|
||||
import "../common/Controlled.sol";
|
||||
import "./DelegationProxyFactory.sol";
|
||||
import "./DelegationProxy.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 Controlled {
|
||||
mapping (address => Topic) topics;
|
||||
DelegationProxyFactory delegationFactory;
|
||||
|
||||
struct Topic {
|
||||
DelegationProxy voteProxy;
|
||||
DelegationProxy vetoProxy;
|
||||
}
|
||||
|
||||
function TrustNetwork(address _delegationFactory) public {
|
||||
delegationFactory = DelegationProxyFactory(_delegationFactory);
|
||||
topics[0x0] = newTopic(0x0, 0x0);
|
||||
}
|
||||
|
||||
function addTopic(address topicId, address parentTopic) onlyController {
|
||||
|
||||
Topic memory parent = topics[parentTopic];
|
||||
address vote = address(parent.voteProxy);
|
||||
address veto = address(parent.vetoProxy);
|
||||
require(vote != 0x0);
|
||||
require(veto != 0x0);
|
||||
|
||||
Topic storage topic = topics[topicId];
|
||||
require(address(topic.voteProxy) == 0x0);
|
||||
require(address(topic.vetoProxy) == 0x0);
|
||||
|
||||
|
||||
topics[topicId] = newTopic(vote, veto);
|
||||
}
|
||||
|
||||
function getTopic(address _topicId) public constant returns (DelegationProxy vote, DelegationProxy veto) {
|
||||
Topic memory topic = topics[_topicId];
|
||||
vote = topic.voteProxy;
|
||||
veto = topic.vetoProxy;
|
||||
}
|
||||
|
||||
function newTopic(address _vote, address _veto) internal returns (Topic topic) {
|
||||
topic = Topic ({
|
||||
voteProxy: delegationFactory.create(_vote),
|
||||
vetoProxy: delegationFactory.create(_veto)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
26
contracts/democracy/TrustNetworkModel.sol
Normal file
26
contracts/democracy/TrustNetworkModel.sol
Normal file
@ -0,0 +1,26 @@
|
||||
pragma solidity ^0.4.10;
|
||||
|
||||
import "./TrustNetwork.sol";
|
||||
import "../deploy/KillableModel.sol";
|
||||
|
||||
/**
|
||||
* @title TrustNetworkModel
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* Model for TrustNetwork
|
||||
*/
|
||||
contract TrustNetworkModel is KillableModel, TrustNetwork {
|
||||
|
||||
|
||||
function TrustNetworkModel(address _watchdog) KillableModel(_watchdog) TrustNetwork(0x0) public {
|
||||
|
||||
}
|
||||
|
||||
function create(address _delegationFactory) public {
|
||||
require(topics[0x0].voteProxy == address(0x0));
|
||||
delegationFactory = DelegationProxyFactory(_delegationFactory);
|
||||
topics[0x0] = newTopic(0x0, 0x0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
29
contracts/deploy/AbstractRecoverer.sol
Normal file
29
contracts/deploy/AbstractRecoverer.sol
Normal file
@ -0,0 +1,29 @@
|
||||
pragma solidity ^0.4.17;
|
||||
|
||||
import "./BasicSystemStorage.sol";
|
||||
|
||||
|
||||
/**
|
||||
* @title AbstractRecoverer
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev Abstract recoverer contract that should be crafted to alter `address system` storage
|
||||
* in delegated logic contracts.
|
||||
*/
|
||||
contract AnstractRecoverer is BasicSystemStorage {
|
||||
|
||||
/**
|
||||
* @dev will be callable in emergency state of RecorverableSystem
|
||||
*/
|
||||
function recoverSystem(address newSystem) public {
|
||||
require(msg.sender == consensusContract());
|
||||
system = newSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev returns the consesus contract, can be a multisig or other DAO
|
||||
* should be implemented by a child contract
|
||||
*/
|
||||
function consensusContract() public constant returns(address);
|
||||
|
||||
|
||||
}
|
16
contracts/deploy/BasicSystemStorage.sol
Normal file
16
contracts/deploy/BasicSystemStorage.sol
Normal file
@ -0,0 +1,16 @@
|
||||
pragma solidity ^0.4.17;
|
||||
|
||||
|
||||
/**
|
||||
* @title BasicSystemStorage
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev Defines system vars in a shared library among Stub and SystemLibraries to
|
||||
* avoid overwriting wrong storage pointers
|
||||
*/
|
||||
contract BasicSystemStorage {
|
||||
/// protected zone start (RecorverableSystem vars)
|
||||
address system;
|
||||
address recover;
|
||||
address watchdog;
|
||||
/// protected zone end
|
||||
}
|
33
contracts/deploy/DelegatedCall.sol
Normal file
33
contracts/deploy/DelegatedCall.sol
Normal file
@ -0,0 +1,33 @@
|
||||
pragma solidity ^0.4.17;
|
||||
|
||||
|
||||
/**
|
||||
* @title DelegatedCall
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev Abstract contract that delegates calls by `delegated` modifier to result of `_target()`
|
||||
*/
|
||||
contract DelegatedCall {
|
||||
/**
|
||||
* @dev delegates the call of this function
|
||||
*/
|
||||
modifier delegated {
|
||||
require(_target().delegatecall(msg.data)); //require successfull delegate call to remote `_target()`
|
||||
assembly {
|
||||
let outSize := returndatasize
|
||||
let outDataPtr := mload(0x40) //load memory
|
||||
returndatacopy(outDataPtr, 0, outSize) //copy last return into pointer
|
||||
return(outDataPtr, outSize)
|
||||
}
|
||||
assert(false); //should never reach here
|
||||
_; //never will execute local logic
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev defines the address for delegation of calls
|
||||
*/
|
||||
function _target()
|
||||
internal
|
||||
constant
|
||||
returns(address);
|
||||
|
||||
}
|
33
contracts/deploy/KillableModel.sol
Normal file
33
contracts/deploy/KillableModel.sol
Normal file
@ -0,0 +1,33 @@
|
||||
pragma solidity ^0.4.17;
|
||||
|
||||
import "./BasicSystemStorage.sol";
|
||||
|
||||
|
||||
/**
|
||||
* @title KillableModel
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev A contract model that can be killed by a watchdog
|
||||
*/
|
||||
contract KillableModel is BasicSystemStorage {
|
||||
|
||||
/**
|
||||
* @dev Library contract constructor initialize watchdog, able to kill the Library in case of
|
||||
*/
|
||||
function KillableModel(address _watchdog) public {
|
||||
watchdog = _watchdog;
|
||||
}
|
||||
|
||||
function changeWatchdog(address newWatchDog) public {
|
||||
require(msg.sender == watchdog);
|
||||
watchdog = newWatchDog;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Should be only possible to call this at Model. Never set watchdog var at instances.
|
||||
*/
|
||||
function emergencyStop() public {
|
||||
require(msg.sender == watchdog);
|
||||
selfdestruct(watchdog);
|
||||
}
|
||||
|
||||
}
|
24
contracts/deploy/Migrations.sol
Normal file
24
contracts/deploy/Migrations.sol
Normal file
@ -0,0 +1,24 @@
|
||||
pragma solidity ^0.4.4;
|
||||
|
||||
|
||||
contract Migrations {
|
||||
address public owner;
|
||||
uint public last_completed_migration;
|
||||
|
||||
modifier restricted() {
|
||||
if (msg.sender == owner) _;
|
||||
}
|
||||
|
||||
function Migrations() public {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
function setCompleted(uint _completed) public restricted {
|
||||
last_completed_migration = _completed;
|
||||
}
|
||||
|
||||
function upgrade(address _newAddress) public restricted {
|
||||
Migrations upgraded = Migrations(_newAddress);
|
||||
upgraded.setCompleted(last_completed_migration);
|
||||
}
|
||||
}
|
56
contracts/deploy/RecoverableSystem.sol
Normal file
56
contracts/deploy/RecoverableSystem.sol
Normal file
@ -0,0 +1,56 @@
|
||||
pragma solidity ^0.4.17;
|
||||
|
||||
import "./BasicSystemStorage.sol";
|
||||
import "./DelegatedCall.sol";
|
||||
|
||||
|
||||
/**
|
||||
* @title RecoverableSystem
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev Contract that recovers from dead system to recoverer.
|
||||
*/
|
||||
contract RecoverableSystem is BasicSystemStorage, DelegatedCall {
|
||||
|
||||
function RecoverableSystem(address _system, address _recover) public {
|
||||
require(isOk(_recover));
|
||||
system = _system;
|
||||
recover = _recover;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev delegatecall everything (but declared functions) to `_target()`
|
||||
*/
|
||||
function () public delegated {
|
||||
//all goes to system (or recover)
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev checks if system contains code
|
||||
*/
|
||||
function isOk() public constant returns(bool a) {
|
||||
return isOk(system);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev checks if `_a` contains code
|
||||
*/
|
||||
function isOk(address _a) internal constant returns(bool r) {
|
||||
assembly{
|
||||
r := gt(extcodesize(_a), 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev returns system if system has code, otherwise return recover
|
||||
*/
|
||||
function _target()
|
||||
internal
|
||||
constant
|
||||
returns(address)
|
||||
{
|
||||
|
||||
return isOk() ? system : recover;
|
||||
}
|
||||
|
||||
|
||||
}
|
332
contracts/registry/TokenRegistry.sol
Normal file
332
contracts/registry/TokenRegistry.sol
Normal file
@ -0,0 +1,332 @@
|
||||
/*
|
||||
|
||||
Copyright 2017 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.4.11;
|
||||
|
||||
/*
|
||||
* Ownable
|
||||
*
|
||||
* Base contract with an owner.
|
||||
* Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
|
||||
*/
|
||||
contract Ownable {
|
||||
address public owner;
|
||||
|
||||
function Ownable() {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner);
|
||||
_;
|
||||
}
|
||||
|
||||
function transferOwnership(address newOwner) onlyOwner {
|
||||
if (newOwner != address(0)) {
|
||||
owner = newOwner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @title Token Registry - Stores metadata associated with ERC20 tokens. See ERC22 https://github.com/ethereum/EIPs/issues/22
|
||||
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
|
||||
contract TokenRegistry is Ownable {
|
||||
|
||||
event LogAddToken(
|
||||
address indexed token,
|
||||
string name,
|
||||
string symbol,
|
||||
uint8 decimals,
|
||||
bytes ipfsHash,
|
||||
bytes swarmHash
|
||||
);
|
||||
|
||||
event LogRemoveToken(
|
||||
address indexed token,
|
||||
string name,
|
||||
string symbol,
|
||||
uint8 decimals,
|
||||
bytes ipfsHash,
|
||||
bytes swarmHash
|
||||
);
|
||||
|
||||
event LogTokenNameChange(address indexed token, string oldName, string newName);
|
||||
event LogTokenSymbolChange(address indexed token, string oldSymbol, string newSymbol);
|
||||
event LogTokenIpfsHashChange(address indexed token, bytes oldIpfsHash, bytes newIpfsHash);
|
||||
event LogTokenSwarmHashChange(address indexed token, bytes oldSwarmHash, bytes newSwarmHash);
|
||||
|
||||
mapping (address => TokenMetadata) public tokens;
|
||||
mapping (string => address) tokenBySymbol;
|
||||
mapping (string => address) tokenByName;
|
||||
|
||||
address[] public tokenAddresses;
|
||||
|
||||
struct TokenMetadata {
|
||||
address token;
|
||||
string name;
|
||||
string symbol;
|
||||
uint8 decimals;
|
||||
bytes ipfsHash;
|
||||
bytes swarmHash;
|
||||
}
|
||||
|
||||
modifier tokenExists(address _token) {
|
||||
require(tokens[_token].token != address(0));
|
||||
_;
|
||||
}
|
||||
|
||||
modifier tokenDoesNotExist(address _token) {
|
||||
require(tokens[_token].token == address(0));
|
||||
_;
|
||||
}
|
||||
|
||||
modifier nameDoesNotExist(string _name) {
|
||||
require(tokenByName[_name] == address(0));
|
||||
_;
|
||||
}
|
||||
|
||||
modifier symbolDoesNotExist(string _symbol) {
|
||||
require(tokenBySymbol[_symbol] == address(0));
|
||||
_;
|
||||
}
|
||||
|
||||
modifier addressNotNull(address _address) {
|
||||
require(_address != address(0));
|
||||
_;
|
||||
}
|
||||
|
||||
|
||||
/// @dev Allows owner to add a new token to the registry.
|
||||
/// @param _token Address of new token.
|
||||
/// @param _name Name of new token.
|
||||
/// @param _symbol Symbol for new token.
|
||||
/// @param _decimals Number of decimals, divisibility of new token.
|
||||
/// @param _ipfsHash IPFS hash of token icon.
|
||||
/// @param _swarmHash Swarm hash of token icon.
|
||||
function addToken(
|
||||
address _token,
|
||||
string _name,
|
||||
string _symbol,
|
||||
uint8 _decimals,
|
||||
bytes _ipfsHash,
|
||||
bytes _swarmHash)
|
||||
public
|
||||
onlyOwner
|
||||
tokenDoesNotExist(_token)
|
||||
addressNotNull(_token)
|
||||
symbolDoesNotExist(_symbol)
|
||||
nameDoesNotExist(_name)
|
||||
{
|
||||
tokens[_token] = TokenMetadata({
|
||||
token: _token,
|
||||
name: _name,
|
||||
symbol: _symbol,
|
||||
decimals: _decimals,
|
||||
ipfsHash: _ipfsHash,
|
||||
swarmHash: _swarmHash
|
||||
});
|
||||
tokenAddresses.push(_token);
|
||||
tokenBySymbol[_symbol] = _token;
|
||||
tokenByName[_name] = _token;
|
||||
LogAddToken(
|
||||
_token,
|
||||
_name,
|
||||
_symbol,
|
||||
_decimals,
|
||||
_ipfsHash,
|
||||
_swarmHash
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Allows owner to remove an existing token from the registry.
|
||||
/// @param _token Address of existing token.
|
||||
function removeToken(address _token, uint _index)
|
||||
public
|
||||
onlyOwner
|
||||
tokenExists(_token)
|
||||
{
|
||||
require(tokenAddresses[_index] == _token);
|
||||
|
||||
tokenAddresses[_index] = tokenAddresses[tokenAddresses.length - 1];
|
||||
tokenAddresses.length -= 1;
|
||||
|
||||
TokenMetadata storage token = tokens[_token];
|
||||
LogRemoveToken(
|
||||
token.token,
|
||||
token.name,
|
||||
token.symbol,
|
||||
token.decimals,
|
||||
token.ipfsHash,
|
||||
token.swarmHash
|
||||
);
|
||||
delete tokenBySymbol[token.symbol];
|
||||
delete tokenByName[token.name];
|
||||
delete tokens[_token];
|
||||
}
|
||||
|
||||
/// @dev Allows owner to modify an existing token's name.
|
||||
/// @param _token Address of existing token.
|
||||
/// @param _name New name.
|
||||
function setTokenName(address _token, string _name)
|
||||
public
|
||||
onlyOwner
|
||||
tokenExists(_token)
|
||||
nameDoesNotExist(_name)
|
||||
{
|
||||
TokenMetadata storage token = tokens[_token];
|
||||
LogTokenNameChange(_token, token.name, _name);
|
||||
delete tokenByName[token.name];
|
||||
tokenByName[_name] = _token;
|
||||
token.name = _name;
|
||||
}
|
||||
|
||||
/// @dev Allows owner to modify an existing token's symbol.
|
||||
/// @param _token Address of existing token.
|
||||
/// @param _symbol New symbol.
|
||||
function setTokenSymbol(address _token, string _symbol)
|
||||
public
|
||||
onlyOwner
|
||||
tokenExists(_token)
|
||||
symbolDoesNotExist(_symbol)
|
||||
{
|
||||
TokenMetadata storage token = tokens[_token];
|
||||
LogTokenSymbolChange(_token, token.symbol, _symbol);
|
||||
delete tokenBySymbol[token.symbol];
|
||||
tokenBySymbol[_symbol] = _token;
|
||||
token.symbol = _symbol;
|
||||
}
|
||||
|
||||
/// @dev Allows owner to modify an existing token's IPFS hash.
|
||||
/// @param _token Address of existing token.
|
||||
/// @param _ipfsHash New IPFS hash.
|
||||
function setTokenIpfsHash(address _token, bytes _ipfsHash)
|
||||
public
|
||||
onlyOwner
|
||||
tokenExists(_token)
|
||||
{
|
||||
TokenMetadata storage token = tokens[_token];
|
||||
LogTokenIpfsHashChange(_token, token.ipfsHash, _ipfsHash);
|
||||
token.ipfsHash = _ipfsHash;
|
||||
}
|
||||
|
||||
/// @dev Allows owner to modify an existing token's Swarm hash.
|
||||
/// @param _token Address of existing token.
|
||||
/// @param _swarmHash New Swarm hash.
|
||||
function setTokenSwarmHash(address _token, bytes _swarmHash)
|
||||
public
|
||||
onlyOwner
|
||||
tokenExists(_token)
|
||||
{
|
||||
TokenMetadata storage token = tokens[_token];
|
||||
LogTokenSwarmHashChange(_token, token.swarmHash, _swarmHash);
|
||||
token.swarmHash = _swarmHash;
|
||||
}
|
||||
|
||||
/*
|
||||
* Web3 call functions
|
||||
*/
|
||||
|
||||
/// @dev Provides a registered token's address when given the token symbol.
|
||||
/// @param _symbol Symbol of registered token.
|
||||
/// @return Token's address.
|
||||
function getTokenAddressBySymbol(string _symbol) constant returns (address) {
|
||||
return tokenBySymbol[_symbol];
|
||||
}
|
||||
|
||||
/// @dev Provides a registered token's address when given the token name.
|
||||
/// @param _name Name of registered token.
|
||||
/// @return Token's address.
|
||||
function getTokenAddressByName(string _name) constant returns (address) {
|
||||
return tokenByName[_name];
|
||||
}
|
||||
|
||||
/// @dev Provides a registered token's metadata, looked up by address.
|
||||
/// @param _token Address of registered token.
|
||||
/// @return Token metadata.
|
||||
function getTokenMetaData(address _token)
|
||||
public
|
||||
constant
|
||||
returns (
|
||||
address, //tokenAddress
|
||||
string, //name
|
||||
string, //symbol
|
||||
uint8, //decimals
|
||||
bytes, //ipfsHash
|
||||
bytes //swarmHash
|
||||
)
|
||||
{
|
||||
TokenMetadata memory token = tokens[_token];
|
||||
return (
|
||||
token.token,
|
||||
token.name,
|
||||
token.symbol,
|
||||
token.decimals,
|
||||
token.ipfsHash,
|
||||
token.swarmHash
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Provides a registered token's metadata, looked up by name.
|
||||
/// @param _name Name of registered token.
|
||||
/// @return Token metadata.
|
||||
function getTokenByName(string _name)
|
||||
public
|
||||
constant
|
||||
returns (
|
||||
address, //tokenAddress
|
||||
string, //name
|
||||
string, //symbol
|
||||
uint8, //decimals
|
||||
bytes, //ipfsHash
|
||||
bytes //swarmHash
|
||||
)
|
||||
{
|
||||
address _token = tokenByName[_name];
|
||||
return getTokenMetaData(_token);
|
||||
}
|
||||
|
||||
/// @dev Provides a registered token's metadata, looked up by symbol.
|
||||
/// @param _symbol Symbol of registered token.
|
||||
/// @return Token metadata.
|
||||
function getTokenBySymbol(string _symbol)
|
||||
public
|
||||
constant
|
||||
returns (
|
||||
address, //tokenAddress
|
||||
string, //name
|
||||
string, //symbol
|
||||
uint8, //decimals
|
||||
bytes, //ipfsHash
|
||||
bytes //swarmHash
|
||||
)
|
||||
{
|
||||
address _token = tokenBySymbol[_symbol];
|
||||
return getTokenMetaData(_token);
|
||||
}
|
||||
|
||||
/// @dev Returns an array containing all token addresses.
|
||||
/// @return Array of token addresses.
|
||||
function getTokenAddresses()
|
||||
public
|
||||
constant
|
||||
returns (address[])
|
||||
{
|
||||
return tokenAddresses;
|
||||
}
|
||||
}
|
43
contracts/status/StatusConstitution.sol
Normal file
43
contracts/status/StatusConstitution.sol
Normal file
@ -0,0 +1,43 @@
|
||||
pragma solidity ^0.4.17;
|
||||
|
||||
import "../deploy/BasicSystemStorage.sol";
|
||||
import "../token/MiniMeToken.sol";
|
||||
import "../democracy/TrustNetworkModel.sol";
|
||||
import "../democracy/ProposalManager.sol";
|
||||
import "../registry/TokenRegistry.sol";
|
||||
|
||||
/**
|
||||
* @title StatusConstitution
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev
|
||||
*/
|
||||
contract StatusConstitution is BasicSystemStorage {
|
||||
|
||||
MiniMeToken public SNT ;
|
||||
TrustNetworkModel public trustNet = TrustNetworkModel(0x0);
|
||||
ProposalManager proposalManager;
|
||||
address public stakeBank;
|
||||
|
||||
function StatusConstitution(address _SNT, address _trustNet, address _stakeBank) public {
|
||||
SNT = MiniMeToken(_SNT);
|
||||
trustNet = TrustNetworkModel(_trustNet);
|
||||
stakeBank = _stakeBank;
|
||||
proposalManager = new ProposalManager(SNT, trustNet, stakeBank);
|
||||
TokenRegistry tokenReg = new TokenRegistry();
|
||||
trustNet.addTopic(address(tokenReg), 0x0);
|
||||
}
|
||||
|
||||
function executeProposal(uint proposalId) external returns (bool success) {
|
||||
address topic;
|
||||
address destination;
|
||||
uint value;
|
||||
bytes memory data;
|
||||
uint stake;
|
||||
bool approved;
|
||||
bool executed;
|
||||
(topic, destination, value, stake, approved, executed) = proposalManager.getProposal(proposalId);
|
||||
//data = proposalManager.getProposalData(proposalId);
|
||||
topic.call.value(value)(data);
|
||||
}
|
||||
|
||||
}
|
57
contracts/status/StatusRecoverer.sol
Normal file
57
contracts/status/StatusRecoverer.sol
Normal file
@ -0,0 +1,57 @@
|
||||
pragma solidity ^0.4.17;
|
||||
|
||||
import "../deploy/AbstractRecoverer.sol";
|
||||
|
||||
contract ENS {
|
||||
function owner(bytes32 node) constant returns (address);
|
||||
function resolver(bytes32 node) constant returns (Resolver);
|
||||
function ttl(bytes32 node) constant returns (uint64);
|
||||
function setOwner(bytes32 node, address owner);
|
||||
function setSubnodeOwner(bytes32 node, bytes32 label, address owner);
|
||||
function setResolver(bytes32 node, address resolver);
|
||||
function setTTL(bytes32 node, uint64 ttl);
|
||||
}
|
||||
|
||||
contract Resolver {
|
||||
function addr(bytes32 node) constant returns (address);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @title GitPivotRecoverer
|
||||
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||
* @dev Common Recoverer for GitPivot.
|
||||
* address resolved from ens recover.gitpivot.eth can set new system.
|
||||
*/
|
||||
contract StatusRecoverer is BasicSystemStorage {
|
||||
|
||||
/**
|
||||
* @dev will be callable in emergency state of RecorverableSystem
|
||||
*/
|
||||
function recoverSystem(address newSystem) public {
|
||||
require(msg.sender == consensusContract());
|
||||
system = newSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev resolves recoverer.statusnet.eth -> this method trust that ENS is safe.
|
||||
*/
|
||||
function consensusContract() public constant returns(address) {
|
||||
bytes32 node = 0x0; //recover.statusnet.eth //bytes32 node = keccak256("recoverer", keccak256("gitpivot", keccak256("eth")));
|
||||
address ensAddress = 0x314159265dD8dbb310642f98f50C066173C1259b;
|
||||
if (codeSize(ensAddress) == 0) {
|
||||
ensAddress = 0x112234455C3a32FD11230C42E7Bccd4A84e02010;
|
||||
}
|
||||
return ENS(ensAddress).resolver(node).addr(node);
|
||||
}
|
||||
|
||||
function codeSize(address _addr) internal constant returns(uint size) {
|
||||
if (_addr == 0) {
|
||||
return 0;
|
||||
}
|
||||
assembly {
|
||||
size := extcodesize(_addr)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
560
contracts/token/MiniMeToken.sol
Normal file
560
contracts/token/MiniMeToken.sol
Normal file
@ -0,0 +1,560 @@
|
||||
pragma solidity ^0.4.6;
|
||||
|
||||
/*
|
||||
Copyright 2016, Jordi Baylina
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/// @title MiniMeToken Contract
|
||||
/// @author Jordi Baylina
|
||||
/// @dev This token contract's goal is to make it easy for anyone to clone this
|
||||
/// token using the token distribution at a given block, this will allow DAO's
|
||||
/// and DApps to upgrade their features in a decentralized manner without
|
||||
/// affecting the original token
|
||||
/// @dev It is ERC20 compliant, but still needs to under go further testing.
|
||||
|
||||
import "../common/Controlled.sol";
|
||||
import "./TokenController.sol";
|
||||
import "./ApproveAndCallFallBack.sol";
|
||||
|
||||
/// @dev The actual token contract, the default controller is the msg.sender
|
||||
/// that deploys the contract, so usually this token will be deployed by a
|
||||
/// token controller contract, which Giveth will call a "Campaign"
|
||||
contract MiniMeToken is Controlled {
|
||||
|
||||
string public name; //The Token's name: e.g. DigixDAO Tokens
|
||||
uint8 public decimals; //Number of decimals of the smallest unit
|
||||
string public symbol; //An identifier: e.g. REP
|
||||
string public version = 'MMT_0.1'; //An arbitrary versioning scheme
|
||||
|
||||
|
||||
/// @dev `Checkpoint` is the structure that attaches a block number to a
|
||||
/// given value, the block number attached is the one that last changed the
|
||||
/// value
|
||||
struct Checkpoint {
|
||||
|
||||
// `fromBlock` is the block number that the value was generated from
|
||||
uint128 fromBlock;
|
||||
|
||||
// `value` is the amount of tokens at a specific block number
|
||||
uint128 value;
|
||||
}
|
||||
|
||||
// `parentToken` is the Token address that was cloned to produce this token;
|
||||
// it will be 0x0 for a token that was not cloned
|
||||
MiniMeToken public parentToken;
|
||||
|
||||
// `parentSnapShotBlock` is the block number from the Parent Token that was
|
||||
// used to determine the initial distribution of the Clone Token
|
||||
uint public parentSnapShotBlock;
|
||||
|
||||
// `creationBlock` is the block number that the Clone Token was created
|
||||
uint public creationBlock;
|
||||
|
||||
// `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
|
||||
mapping (address => Checkpoint[]) balances;
|
||||
|
||||
// `allowed` tracks any extra transfer rights as in all ERC20 tokens
|
||||
mapping (address => mapping (address => uint256)) allowed;
|
||||
|
||||
// Tracks the history of the `totalSupply` of the token
|
||||
Checkpoint[] totalSupplyHistory;
|
||||
|
||||
// Flag that determines if the token is transferable or not.
|
||||
bool public transfersEnabled;
|
||||
|
||||
// The factory used to create new clone tokens
|
||||
MiniMeTokenFactory public tokenFactory;
|
||||
|
||||
////////////////
|
||||
// Constructor
|
||||
////////////////
|
||||
|
||||
/// @notice Constructor to create a MiniMeToken
|
||||
/// @param _tokenFactory The address of the MiniMeTokenFactory contract that
|
||||
/// will create the Clone token contracts, the token factory needs to be
|
||||
/// deployed first
|
||||
/// @param _parentToken Address of the parent token, set to 0x0 if it is a
|
||||
/// new token
|
||||
/// @param _parentSnapShotBlock Block of the parent token that will
|
||||
/// determine the initial distribution of the clone token, set to 0 if it
|
||||
/// is a new token
|
||||
/// @param _tokenName Name of the new token
|
||||
/// @param _decimalUnits Number of decimals of the new token
|
||||
/// @param _tokenSymbol Token Symbol for the new token
|
||||
/// @param _transfersEnabled If true, tokens will be able to be transferred
|
||||
function MiniMeToken(
|
||||
address _tokenFactory,
|
||||
address _parentToken,
|
||||
uint _parentSnapShotBlock,
|
||||
string _tokenName,
|
||||
uint8 _decimalUnits,
|
||||
string _tokenSymbol,
|
||||
bool _transfersEnabled
|
||||
) {
|
||||
tokenFactory = MiniMeTokenFactory(_tokenFactory);
|
||||
name = _tokenName; // Set the name
|
||||
decimals = _decimalUnits; // Set the decimals
|
||||
symbol = _tokenSymbol; // Set the symbol
|
||||
parentToken = MiniMeToken(_parentToken);
|
||||
parentSnapShotBlock = _parentSnapShotBlock;
|
||||
transfersEnabled = _transfersEnabled;
|
||||
creationBlock = block.number;
|
||||
}
|
||||
|
||||
|
||||
///////////////////
|
||||
// ERC20 Methods
|
||||
///////////////////
|
||||
|
||||
/// @notice Send `_amount` tokens to `_to` from `msg.sender`
|
||||
/// @param _to The address of the recipient
|
||||
/// @param _amount The amount of tokens to be transferred
|
||||
/// @return Whether the transfer was successful or not
|
||||
function transfer(address _to, uint256 _amount) returns (bool success) {
|
||||
require(transfersEnabled);
|
||||
return doTransfer(msg.sender, _to, _amount);
|
||||
}
|
||||
|
||||
/// @notice Send `_amount` tokens to `_to` from `_from` on the condition it
|
||||
/// is approved by `_from`
|
||||
/// @param _from The address holding the tokens being transferred
|
||||
/// @param _to The address of the recipient
|
||||
/// @param _amount The amount of tokens to be transferred
|
||||
/// @return True if the transfer was successful
|
||||
function transferFrom(address _from, address _to, uint256 _amount
|
||||
) returns (bool success) {
|
||||
|
||||
// The controller of this contract can move tokens around at will,
|
||||
// this is important to recognize! Confirm that you trust the
|
||||
// controller of this contract, which in most situations should be
|
||||
// another open source smart contract or 0x0
|
||||
if (msg.sender != controller) {
|
||||
require(transfersEnabled);
|
||||
|
||||
// The standard ERC 20 transferFrom functionality
|
||||
if (allowed[_from][msg.sender] < _amount) return false;
|
||||
allowed[_from][msg.sender] -= _amount;
|
||||
}
|
||||
return doTransfer(_from, _to, _amount);
|
||||
}
|
||||
|
||||
/// @dev This is the actual transfer function in the token contract, it can
|
||||
/// only be called by other functions in this contract.
|
||||
/// @param _from The address holding the tokens being transferred
|
||||
/// @param _to The address of the recipient
|
||||
/// @param _amount The amount of tokens to be transferred
|
||||
/// @return True if the transfer was successful
|
||||
function doTransfer(address _from, address _to, uint _amount
|
||||
) internal returns(bool) {
|
||||
|
||||
if (_amount == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
require(parentSnapShotBlock < block.number);
|
||||
|
||||
// Do not allow transfer to 0x0 or the token contract itself
|
||||
require((_to != 0) && (_to != address(this)));
|
||||
|
||||
// If the amount being transfered is more than the balance of the
|
||||
// account the transfer returns false
|
||||
var previousBalanceFrom = balanceOfAt(_from, block.number);
|
||||
if (previousBalanceFrom < _amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Alerts the token controller of the transfer
|
||||
if (isContract(controller)) {
|
||||
require(TokenController(controller).onTransfer(_from, _to, _amount));
|
||||
}
|
||||
|
||||
// First update the balance array with the new value for the address
|
||||
// sending the tokens
|
||||
updateValueAtNow(balances[_from], previousBalanceFrom - _amount);
|
||||
|
||||
// Then update the balance array with the new value for the address
|
||||
// receiving the tokens
|
||||
var previousBalanceTo = balanceOfAt(_to, block.number);
|
||||
require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow
|
||||
updateValueAtNow(balances[_to], previousBalanceTo + _amount);
|
||||
|
||||
// An event to make the transfer easy to find on the blockchain
|
||||
Transfer(_from, _to, _amount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @param _owner The address that's balance is being requested
|
||||
/// @return The balance of `_owner` at the current block
|
||||
function balanceOf(address _owner) constant returns (uint256 balance) {
|
||||
return balanceOfAt(_owner, block.number);
|
||||
}
|
||||
|
||||
/// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on
|
||||
/// its behalf. This is a modified version of the ERC20 approve function
|
||||
/// to be a little bit safer
|
||||
/// @param _spender The address of the account able to transfer the tokens
|
||||
/// @param _amount The amount of tokens to be approved for transfer
|
||||
/// @return True if the approval was successful
|
||||
function approve(address _spender, uint256 _amount) returns (bool success) {
|
||||
require(transfersEnabled);
|
||||
|
||||
// To change the approve amount you first have to reduce the addresses`
|
||||
// allowance to zero by calling `approve(_spender,0)` if it is not
|
||||
// already 0 to mitigate the race condition described here:
|
||||
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
|
||||
require((_amount == 0) || (allowed[msg.sender][_spender] == 0));
|
||||
|
||||
// Alerts the token controller of the approve function call
|
||||
if (isContract(controller)) {
|
||||
require(TokenController(controller).onApprove(msg.sender, _spender, _amount));
|
||||
}
|
||||
|
||||
allowed[msg.sender][_spender] = _amount;
|
||||
Approval(msg.sender, _spender, _amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @dev This function makes it easy to read the `allowed[]` map
|
||||
/// @param _owner The address of the account that owns the token
|
||||
/// @param _spender The address of the account able to transfer the tokens
|
||||
/// @return Amount of remaining tokens of _owner that _spender is allowed
|
||||
/// to spend
|
||||
function allowance(address _owner, address _spender
|
||||
) constant returns (uint256 remaining) {
|
||||
return allowed[_owner][_spender];
|
||||
}
|
||||
|
||||
/// @notice `msg.sender` approves `_spender` to send `_amount` tokens on
|
||||
/// its behalf, and then a function is triggered in the contract that is
|
||||
/// being approved, `_spender`. This allows users to use their tokens to
|
||||
/// interact with contracts in one function call instead of two
|
||||
/// @param _spender The address of the contract able to transfer the tokens
|
||||
/// @param _amount The amount of tokens to be approved for transfer
|
||||
/// @return True if the function call was successful
|
||||
function approveAndCall(address _spender, uint256 _amount, bytes _extraData
|
||||
) returns (bool success) {
|
||||
require(approve(_spender, _amount));
|
||||
|
||||
ApproveAndCallFallBack(_spender).receiveApproval(
|
||||
msg.sender,
|
||||
_amount,
|
||||
this,
|
||||
_extraData
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @dev This function makes it easy to get the total number of tokens
|
||||
/// @return The total number of tokens
|
||||
function totalSupply() constant returns (uint) {
|
||||
return totalSupplyAt(block.number);
|
||||
}
|
||||
|
||||
|
||||
////////////////
|
||||
// Query balance and totalSupply in History
|
||||
////////////////
|
||||
|
||||
/// @dev Queries the balance of `_owner` at a specific `_blockNumber`
|
||||
/// @param _owner The address from which the balance will be retrieved
|
||||
/// @param _blockNumber The block number when the balance is queried
|
||||
/// @return The balance at `_blockNumber`
|
||||
function balanceOfAt(address _owner, uint _blockNumber) constant
|
||||
returns (uint) {
|
||||
|
||||
// These next few lines are used when the balance of the token is
|
||||
// requested before a check point was ever created for this token, it
|
||||
// requires that the `parentToken.balanceOfAt` be queried at the
|
||||
// genesis block for that token as this contains initial balance of
|
||||
// this token
|
||||
if ((balances[_owner].length == 0)
|
||||
|| (balances[_owner][0].fromBlock > _blockNumber)) {
|
||||
if (address(parentToken) != 0) {
|
||||
return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock));
|
||||
} else {
|
||||
// Has no parent
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This will return the expected balance during normal situations
|
||||
} else {
|
||||
return getValueAt(balances[_owner], _blockNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Total amount of tokens at a specific `_blockNumber`.
|
||||
/// @param _blockNumber The block number when the totalSupply is queried
|
||||
/// @return The total amount of tokens at `_blockNumber`
|
||||
function totalSupplyAt(uint _blockNumber) constant returns(uint) {
|
||||
|
||||
// These next few lines are used when the totalSupply of the token is
|
||||
// requested before a check point was ever created for this token, it
|
||||
// requires that the `parentToken.totalSupplyAt` be queried at the
|
||||
// genesis block for this token as that contains totalSupply of this
|
||||
// token at this block number.
|
||||
if ((totalSupplyHistory.length == 0)
|
||||
|| (totalSupplyHistory[0].fromBlock > _blockNumber)) {
|
||||
if (address(parentToken) != 0) {
|
||||
return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This will return the expected totalSupply during normal situations
|
||||
} else {
|
||||
return getValueAt(totalSupplyHistory, _blockNumber);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Clone Token Method
|
||||
////////////////
|
||||
|
||||
/// @notice Creates a new clone token with the initial distribution being
|
||||
/// this token at `_snapshotBlock`
|
||||
/// @param _cloneTokenName Name of the clone token
|
||||
/// @param _cloneDecimalUnits Number of decimals of the smallest unit
|
||||
/// @param _cloneTokenSymbol Symbol of the clone token
|
||||
/// @param _snapshotBlock Block when the distribution of the parent token is
|
||||
/// copied to set the initial distribution of the new clone token;
|
||||
/// if the block is zero than the actual block, the current block is used
|
||||
/// @param _transfersEnabled True if transfers are allowed in the clone
|
||||
/// @return The address of the new MiniMeToken Contract
|
||||
function createCloneToken(
|
||||
string _cloneTokenName,
|
||||
uint8 _cloneDecimalUnits,
|
||||
string _cloneTokenSymbol,
|
||||
uint _snapshotBlock,
|
||||
bool _transfersEnabled
|
||||
) returns(address) {
|
||||
if (_snapshotBlock == 0) _snapshotBlock = block.number;
|
||||
MiniMeToken cloneToken = tokenFactory.createCloneToken(
|
||||
this,
|
||||
_snapshotBlock,
|
||||
_cloneTokenName,
|
||||
_cloneDecimalUnits,
|
||||
_cloneTokenSymbol,
|
||||
_transfersEnabled
|
||||
);
|
||||
|
||||
cloneToken.changeController(msg.sender);
|
||||
|
||||
// An event to make the token easy to find on the blockchain
|
||||
NewCloneToken(address(cloneToken), _snapshotBlock);
|
||||
return address(cloneToken);
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Generate and destroy tokens
|
||||
////////////////
|
||||
|
||||
/// @notice Generates `_amount` tokens that are assigned to `_owner`
|
||||
/// @param _owner The address that will be assigned the new tokens
|
||||
/// @param _amount The quantity of tokens generated
|
||||
/// @return True if the tokens are generated correctly
|
||||
function generateTokens(address _owner, uint _amount
|
||||
) onlyController returns (bool) {
|
||||
uint curTotalSupply = totalSupply();
|
||||
require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow
|
||||
uint previousBalanceTo = balanceOf(_owner);
|
||||
require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow
|
||||
updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount);
|
||||
updateValueAtNow(balances[_owner], previousBalanceTo + _amount);
|
||||
Transfer(0, _owner, _amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// @notice Burns `_amount` tokens from `_owner`
|
||||
/// @param _owner The address that will lose the tokens
|
||||
/// @param _amount The quantity of tokens to burn
|
||||
/// @return True if the tokens are burned correctly
|
||||
function destroyTokens(address _owner, uint _amount
|
||||
) onlyController returns (bool) {
|
||||
uint curTotalSupply = totalSupply();
|
||||
require(curTotalSupply >= _amount);
|
||||
uint previousBalanceFrom = balanceOf(_owner);
|
||||
require(previousBalanceFrom >= _amount);
|
||||
updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount);
|
||||
updateValueAtNow(balances[_owner], previousBalanceFrom - _amount);
|
||||
Transfer(_owner, 0, _amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Enable tokens transfers
|
||||
////////////////
|
||||
|
||||
|
||||
/// @notice Enables token holders to transfer their tokens freely if true
|
||||
/// @param _transfersEnabled True if transfers are allowed in the clone
|
||||
function enableTransfers(bool _transfersEnabled) onlyController {
|
||||
transfersEnabled = _transfersEnabled;
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Internal helper functions to query and set a value in a snapshot array
|
||||
////////////////
|
||||
|
||||
/// @dev `getValueAt` retrieves the number of tokens at a given block number
|
||||
/// @param checkpoints The history of values being queried
|
||||
/// @param _block The block number to retrieve the value at
|
||||
/// @return The number of tokens being queried
|
||||
function getValueAt(Checkpoint[] storage checkpoints, uint _block
|
||||
) constant internal returns (uint) {
|
||||
if (checkpoints.length == 0) return 0;
|
||||
|
||||
// Shortcut for the actual value
|
||||
if (_block >= checkpoints[checkpoints.length-1].fromBlock)
|
||||
return checkpoints[checkpoints.length-1].value;
|
||||
if (_block < checkpoints[0].fromBlock) return 0;
|
||||
|
||||
// Binary search of the value in the 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;
|
||||
}
|
||||
}
|
||||
return checkpoints[min].value;
|
||||
}
|
||||
|
||||
/// @dev `updateValueAtNow` used to update the `balances` map and the
|
||||
/// `totalSupplyHistory`
|
||||
/// @param checkpoints The history of data being updated
|
||||
/// @param _value The new number of tokens
|
||||
function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value
|
||||
) internal {
|
||||
if ((checkpoints.length == 0)
|
||||
|| (checkpoints[checkpoints.length -1].fromBlock < block.number)) {
|
||||
Checkpoint storage newCheckPoint = checkpoints[ checkpoints.length++ ];
|
||||
newCheckPoint.fromBlock = uint128(block.number);
|
||||
newCheckPoint.value = uint128(_value);
|
||||
} else {
|
||||
Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1];
|
||||
oldCheckPoint.value = uint128(_value);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Internal function to determine if an address is a contract
|
||||
/// @param _addr The address being queried
|
||||
/// @return True if `_addr` is a contract
|
||||
function isContract(address _addr) constant internal returns(bool) {
|
||||
uint size;
|
||||
if (_addr == 0) return false;
|
||||
assembly {
|
||||
size := extcodesize(_addr)
|
||||
}
|
||||
return size>0;
|
||||
}
|
||||
|
||||
/// @dev Helper function to return a min betwen the two uints
|
||||
function min(uint a, uint b) internal returns (uint) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
/// @notice The fallback function: If the contract's controller has not been
|
||||
/// set to 0, then the `proxyPayment` method is called which relays the
|
||||
/// ether and creates tokens as described in the token controller contract
|
||||
function () payable {
|
||||
require(isContract(controller));
|
||||
require(TokenController(controller).proxyPayment.value(msg.value)(msg.sender));
|
||||
}
|
||||
|
||||
//////////
|
||||
// Safety Methods
|
||||
//////////
|
||||
|
||||
/// @notice This method can be used by the controller to extract mistakenly
|
||||
/// sent tokens to this contract.
|
||||
/// @param _token The address of the token contract that you want to recover
|
||||
/// set to 0 in case you want to extract ether.
|
||||
function claimTokens(address _token) onlyController {
|
||||
if (_token == 0x0) {
|
||||
controller.transfer(this.balance);
|
||||
return;
|
||||
}
|
||||
|
||||
MiniMeToken token = MiniMeToken(_token);
|
||||
uint balance = token.balanceOf(this);
|
||||
token.transfer(controller, balance);
|
||||
ClaimedTokens(_token, controller, balance);
|
||||
}
|
||||
|
||||
////////////////
|
||||
// Events
|
||||
////////////////
|
||||
event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount);
|
||||
event Transfer(address indexed _from, address indexed _to, uint256 _amount);
|
||||
event NewCloneToken(address indexed _cloneToken, uint _snapshotBlock);
|
||||
event Approval(
|
||||
address indexed _owner,
|
||||
address indexed _spender,
|
||||
uint256 _amount
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
////////////////
|
||||
// MiniMeTokenFactory
|
||||
////////////////
|
||||
|
||||
/// @dev This contract is used to generate clone contracts from a contract.
|
||||
/// In solidity this is the way to create a contract from a contract of the
|
||||
/// same class
|
||||
contract MiniMeTokenFactory {
|
||||
|
||||
/// @notice Update the DApp by creating a new token with new functionalities
|
||||
/// the msg.sender becomes the controller of this clone token
|
||||
/// @param _parentToken Address of the token being cloned
|
||||
/// @param _snapshotBlock Block of the parent token that will
|
||||
/// determine the initial distribution of the clone token
|
||||
/// @param _tokenName Name of the new token
|
||||
/// @param _decimalUnits Number of decimals of the new token
|
||||
/// @param _tokenSymbol Token Symbol for the new token
|
||||
/// @param _transfersEnabled If true, tokens will be able to be transferred
|
||||
/// @return The address of the new token contract
|
||||
function createCloneToken(
|
||||
address _parentToken,
|
||||
uint _snapshotBlock,
|
||||
string _tokenName,
|
||||
uint8 _decimalUnits,
|
||||
string _tokenSymbol,
|
||||
bool _transfersEnabled
|
||||
) returns (MiniMeToken) {
|
||||
MiniMeToken newToken = new MiniMeToken(
|
||||
this,
|
||||
_parentToken,
|
||||
_snapshotBlock,
|
||||
_tokenName,
|
||||
_decimalUnits,
|
||||
_tokenSymbol,
|
||||
_transfersEnabled
|
||||
);
|
||||
|
||||
newToken.changeController(msg.sender);
|
||||
return newToken;
|
||||
}
|
||||
}
|
26
contracts/token/TokenController.sol
Normal file
26
contracts/token/TokenController.sol
Normal file
@ -0,0 +1,26 @@
|
||||
pragma solidity ^0.4.14;
|
||||
|
||||
/// @dev The token controller contract must implement these functions
|
||||
contract TokenController {
|
||||
/// @notice Called when `_owner` sends ether to the MiniMe Token contract
|
||||
/// @param _owner The address that sent the ether to create tokens
|
||||
/// @return True if the ether is accepted, false if it throws
|
||||
function proxyPayment(address _owner) payable returns(bool);
|
||||
|
||||
/// @notice Notifies the controller about a token transfer allowing the
|
||||
/// controller to react if desired
|
||||
/// @param _from The origin of the transfer
|
||||
/// @param _to The destination of the transfer
|
||||
/// @param _amount The amount of the transfer
|
||||
/// @return False if the controller does not authorize the transfer
|
||||
function onTransfer(address _from, address _to, uint _amount) returns(bool);
|
||||
|
||||
/// @notice Notifies the controller about an approval allowing the
|
||||
/// controller to react if desired
|
||||
/// @param _owner The address that calls `approve()`
|
||||
/// @param _spender The spender in the `approve()` call
|
||||
/// @param _amount The amount in the `approve()` call
|
||||
/// @return False if the controller does not authorize the approval
|
||||
function onApprove(address _owner, address _spender, uint _amount)
|
||||
returns(bool);
|
||||
}
|
5
migrations/1_initial_migration.js
Normal file
5
migrations/1_initial_migration.js
Normal file
@ -0,0 +1,5 @@
|
||||
var Migrations = artifacts.require("./Migrations.sol");
|
||||
|
||||
module.exports = function(deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
312
test/DelegationProxyTest.js
Normal file
312
test/DelegationProxyTest.js
Normal 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")
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
})
|
34
test/TestUtils.js
Normal file
34
test/TestUtils.js
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
// This has been tested with the real Ethereum network and Testrpc.
|
||||
// Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9
|
||||
exports.assertReverts = (contractMethodCall, maxGasAvailable) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(contractMethodCall())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
.then(tx => {
|
||||
assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed")
|
||||
})
|
||||
.catch(error => {
|
||||
if ((error + "").indexOf("invalid opcode") < 0 && (error + "").indexOf("out of gas") < 0) {
|
||||
// Checks if the error is from TestRpc. If it is then ignore it.
|
||||
// Otherwise relay/throw the error produced by the above assertion.
|
||||
// Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.listenForEvent = event => new Promise((resolve, reject) => {
|
||||
event.watch((error, response) => {
|
||||
if (!error) {
|
||||
resolve(response.args)
|
||||
} else {
|
||||
reject(error)
|
||||
}
|
||||
event.stopWatching()
|
||||
})
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user