|
/* |
|
This file is part of the DAO. |
|
|
|
The DAO is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU lesser General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
The DAO 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 lesser General Public License for more details. |
|
|
|
You should have received a copy of the GNU lesser General Public License |
|
along with the DAO. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
|
|
/* |
|
Basic account, used by the DAO contract to separately manage both the rewards |
|
and the extraBalance accounts. |
|
*/ |
|
|
|
contract ManagedAccountInterface { |
|
// The only address with permission to withdraw from this account |
|
address public owner; |
|
// If true, only the owner of the account can receive ether from it |
|
bool public payOwnerOnly; |
|
// The sum of ether (in wei) which has been sent to this contract |
|
uint public accumulatedInput; |
|
|
|
/// @notice Sends `_amount` of wei to _recipient |
|
/// @param _amount The amount of wei to send to `_recipient` |
|
/// @param _recipient The address to receive `_amount` of wei |
|
/// @return True if the send completed |
|
function payOut(address _recipient, uint _amount) returns (bool); |
|
|
|
event PayOut(address indexed _recipient, uint _amount); |
|
} |
|
|
|
|
|
contract ManagedAccount is ManagedAccountInterface{ |
|
|
|
// The constructor sets the owner of the account |
|
function ManagedAccount(address _owner, bool _payOwnerOnly) { |
|
owner = _owner; |
|
payOwnerOnly = _payOwnerOnly; |
|
} |
|
|
|
// When the contract receives a transaction without data this is called. |
|
// It counts the amount of ether it receives and stores it in |
|
// accumulatedInput. |
|
function() { |
|
accumulatedInput += msg.value; |
|
} |
|
|
|
function payOut(address _recipient, uint _amount) returns (bool) { |
|
if (msg.sender != owner || msg.value > 0 || (payOwnerOnly && _recipient != owner)) |
|
throw; |
|
if (_recipient.call.value(_amount)()) { |
|
PayOut(_recipient, _amount); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
/* |
|
This file is part of the DAO. |
|
|
|
The DAO is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU lesser General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
The DAO 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 lesser General Public License for more details. |
|
|
|
You should have received a copy of the GNU lesser General Public License |
|
along with the DAO. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
|
|
/* |
|
Basic, standardized Token contract with no "premine". Defines the functions to |
|
check token balances, send tokens, send tokens on behalf of a 3rd party and the |
|
corresponding approval process. Tokens need to be created by a derived |
|
contract (e.g. TokenCreation.sol). |
|
|
|
Thank you ConsenSys, this contract originated from: |
|
https://github.com/ConsenSys/Tokens/blob/master/Token_Contracts/contracts/Standard_Token.sol |
|
Which is itself based on the Ethereum standardized contract APIs: |
|
https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs |
|
*/ |
|
|
|
/// @title Standard Token Contract. |
|
|
|
contract TokenInterface { |
|
mapping (address => uint256) balances; |
|
mapping (address => mapping (address => uint256)) allowed; |
|
|
|
/// Total amount of tokens |
|
uint256 public totalSupply; |
|
|
|
/// @param _owner The address from which the balance will be retrieved |
|
/// @return The balance |
|
function balanceOf(address _owner) constant returns (uint256 balance); |
|
|
|
/// @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); |
|
|
|
/// @notice Send `_amount` tokens to `_to` from `_from` on the condition it |
|
/// is approved by `_from` |
|
/// @param _from The address of the origin of the transfer |
|
/// @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 transferFrom(address _from, address _to, uint256 _amount) returns (bool success); |
|
|
|
/// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on |
|
/// its behalf |
|
/// @param _spender The address of the account able to transfer the tokens |
|
/// @param _amount The amount of tokens to be approved for transfer |
|
/// @return Whether the approval was successful or not |
|
function approve(address _spender, uint256 _amount) returns (bool success); |
|
|
|
/// @param _owner The address of the account owning tokens |
|
/// @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); |
|
|
|
event Transfer(address indexed _from, address indexed _to, uint256 _amount); |
|
event Approval( |
|
address indexed _owner, |
|
address indexed _spender, |
|
uint256 _amount |
|
); |
|
} |
|
|
|
|
|
contract Token is TokenInterface { |
|
// Protects users by preventing the execution of method calls that |
|
// inadvertently also transferred ether |
|
modifier noEther() {if (msg.value > 0) throw; _;} |
|
|
|
function balanceOf(address _owner) constant returns (uint256 balance) { |
|
return balances[_owner]; |
|
} |
|
|
|
function transfer(address _to, uint256 _amount) noEther returns (bool success) { |
|
if (balances[msg.sender] >= _amount && _amount > 0) { |
|
balances[msg.sender] -= _amount; |
|
balances[_to] += _amount; |
|
Transfer(msg.sender, _to, _amount); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
function transferFrom( |
|
address _from, |
|
address _to, |
|
uint256 _amount |
|
) noEther returns (bool success) { |
|
|
|
if (balances[_from] >= _amount |
|
&& allowed[_from][msg.sender] >= _amount |
|
&& _amount > 0) { |
|
|
|
balances[_to] += _amount; |
|
balances[_from] -= _amount; |
|
allowed[_from][msg.sender] -= _amount; |
|
Transfer(_from, _to, _amount); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
function approve(address _spender, uint256 _amount) returns (bool success) { |
|
allowed[msg.sender][_spender] = _amount; |
|
Approval(msg.sender, _spender, _amount); |
|
return true; |
|
} |
|
|
|
function allowance(address _owner, address _spender) constant returns (uint256 remaining) { |
|
return allowed[_owner][_spender]; |
|
} |
|
} |
|
|
|
contract TokenCreationInterface { |
|
|
|
// End of token creation, in Unix time |
|
uint public closingTime; |
|
// Minimum fueling goal of the token creation, denominated in tokens to |
|
// be created |
|
uint public minTokensToCreate; |
|
// True if the DAO reached its minimum fueling goal, false otherwise |
|
bool public isFueled; |
|
// For DAO splits - if privateCreation is 0, then it is a public token |
|
// creation, otherwise only the address stored in privateCreation is |
|
// allowed to create tokens |
|
address public privateCreation; |
|
// hold extra ether which has been sent after the DAO token |
|
// creation rate has increased |
|
ManagedAccount public extraBalance; |
|
// tracks the amount of wei given from each contributor (used for refund) |
|
mapping (address => uint256) weiGiven; |
|
|
|
/// @dev Constructor setting the minimum fueling goal and the |
|
/// end of the Token Creation |
|
/// @param _minTokensToCreate Minimum fueling goal in number of |
|
/// Tokens to be created |
|
/// @param _closingTime Date (in Unix time) of the end of the Token Creation |
|
/// @param _privateCreation Zero means that the creation is public. A |
|
/// non-zero address represents the only address that can create Tokens |
|
/// (the address can also create Tokens on behalf of other accounts) |
|
// This is the constructor: it can not be overloaded so it is commented out |
|
// function TokenCreation( |
|
// uint _minTokensTocreate, |
|
// uint _closingTime, |
|
// address _privateCreation |
|
// ); |
|
|
|
/// @notice Create Token with `_tokenHolder` as the initial owner of the Token |
|
/// @param _tokenHolder The address of the Tokens's recipient |
|
/// @return Whether the token creation was successful |
|
function createTokenProxy(address _tokenHolder) returns (bool success); |
|
|
|
/// @notice Refund `msg.sender` in the case the Token Creation did |
|
/// not reach its minimum fueling goal |
|
function refund(); |
|
|
|
/// @return The divisor used to calculate the token creation rate during |
|
/// the creation phase |
|
function divisor() constant returns (uint divisor); |
|
|
|
event FuelingToDate(uint value); |
|
event CreatedToken(address indexed to, uint amount); |
|
event Refund(address indexed to, uint value); |
|
} |
|
|
|
|
|
contract TokenCreation is TokenCreationInterface, Token { |
|
function TokenCreation( |
|
uint _minTokensToCreate, |
|
uint _closingTime, |
|
address _privateCreation) { |
|
|
|
closingTime = _closingTime; |
|
minTokensToCreate = _minTokensToCreate; |
|
privateCreation = _privateCreation; |
|
extraBalance = new ManagedAccount(address(this), true); |
|
} |
|
|
|
function createTokenProxy(address _tokenHolder) returns (bool success) { |
|
if (now < closingTime && msg.value > 0 |
|
&& (privateCreation == 0 || privateCreation == msg.sender)) { |
|
|
|
uint token = (msg.value * 20) / divisor(); |
|
extraBalance.call.value(msg.value - token)(); |
|
balances[_tokenHolder] += token; |
|
totalSupply += token; |
|
weiGiven[_tokenHolder] += msg.value; |
|
CreatedToken(_tokenHolder, token); |
|
if (totalSupply >= minTokensToCreate && !isFueled) { |
|
isFueled = true; |
|
FuelingToDate(totalSupply); |
|
} |
|
return true; |
|
} |
|
throw; |
|
} |
|
|
|
function refund() noEther { |
|
if (now > closingTime && !isFueled) { |
|
// Get extraBalance - will only succeed when called for the first time |
|
if (extraBalance.balance >= extraBalance.accumulatedInput()) |
|
extraBalance.payOut(address(this), extraBalance.accumulatedInput()); |
|
|
|
// Execute refund |
|
if (msg.sender.call.value(weiGiven[msg.sender])()) { |
|
Refund(msg.sender, weiGiven[msg.sender]); |
|
totalSupply -= balances[msg.sender]; |
|
balances[msg.sender] = 0; |
|
weiGiven[msg.sender] = 0; |
|
} |
|
} |
|
} |
|
|
|
function divisor() constant returns (uint divisor) { |
|
// The number of (base unit) tokens per wei is calculated |
|
// as `msg.value` * 20 / `divisor` |
|
// The fueling period starts with a 1:1 ratio |
|
if (closingTime - 2 weeks > now) { |
|
return 20; |
|
// Followed by 10 days with a daily creation rate increase of 5% |
|
} else if (closingTime - 4 days > now) { |
|
return (20 + (now - (closingTime - 2 weeks)) / (1 days)); |
|
// The last 4 days there is a constant creation rate ratio of 1:1.5 |
|
} else { |
|
return 30; |
|
} |
|
} |
|
} |
|
|
|
|
|
contract DAOInterface { |
|
|
|
// The amount of days for which people who try to participate in the |
|
// creation by calling the fallback function will still get their ether back |
|
uint constant creationGracePeriod = 40 days; |
|
// The minimum debate period that a generic proposal can have |
|
uint constant minProposalDebatePeriod = 2 weeks; |
|
// The minimum debate period that a split proposal can have |
|
uint constant minSplitDebatePeriod = 1 weeks; |
|
// Period of days inside which it's possible to execute a DAO split |
|
uint constant splitExecutionPeriod = 27 days; |
|
// Period of time after which the minimum Quorum is halved |
|
uint constant quorumHalvingPeriod = 25 weeks; |
|
// Period after which a proposal is closed |
|
// (used in the case `executeProposal` fails because it throws) |
|
uint constant executeProposalPeriod = 10 days; |
|
// Denotes the maximum proposal deposit that can be given. It is given as |
|
// a fraction of total Ether spent plus balance of the DAO |
|
uint constant maxDepositDivisor = 100; |
|
|
|
// Proposals to spend the DAO's ether or to choose a new Curator |
|
Proposal[] public proposals; |
|
// The quorum needed for each proposal is partially calculated by |
|
// totalSupply / minQuorumDivisor |
|
uint public minQuorumDivisor; |
|
// The unix time of the last time quorum was reached on a proposal |
|
uint public lastTimeMinQuorumMet; |
|
|
|
// Address of the curator |
|
address public curator; |
|
// The whitelist: List of addresses the DAO is allowed to send ether to |
|
mapping (address => bool) public allowedRecipients; |
|
|
|
// Tracks the addresses that own Reward Tokens. Those addresses can only be |
|
// DAOs that have split from the original DAO. Conceptually, Reward Tokens |
|
// represent the proportion of the rewards that the DAO has the right to |
|
// receive. These Reward Tokens are generated when the DAO spends ether. |
|
mapping (address => uint) public rewardToken; |
|
// Total supply of rewardToken |
|
uint public totalRewardToken; |
|
|
|
// The account used to manage the rewards which are to be distributed to the |
|
// DAO Token Holders of this DAO |
|
ManagedAccount public rewardAccount; |
|
|
|
// The account used to manage the rewards which are to be distributed to |
|
// any DAO that holds Reward Tokens |
|
ManagedAccount public DAOrewardAccount; |
|
|
|
// Amount of rewards (in wei) already paid out to a certain DAO |
|
mapping (address => uint) public DAOpaidOut; |
|
|
|
// Amount of rewards (in wei) already paid out to a certain address |
|
mapping (address => uint) public paidOut; |
|
// Map of addresses blocked during a vote (not allowed to transfer DAO |
|
// tokens). The address points to the proposal ID. |
|
mapping (address => uint) public blocked; |
|
|
|
// The minimum deposit (in wei) required to submit any proposal that is not |
|
// requesting a new Curator (no deposit is required for splits) |
|
uint public proposalDeposit; |
|
|
|
// the accumulated sum of all current proposal deposits |
|
uint sumOfProposalDeposits; |
|
|
|
// Contract that is able to create a new DAO (with the same code as |
|
// this one), used for splits |
|
DAO_Creator public daoCreator; |
|
|
|
// A proposal with `newCurator == false` represents a transaction |
|
// to be issued by this DAO |
|
// A proposal with `newCurator == true` represents a DAO split |
|
struct Proposal { |
|
// The address where the `amount` will go to if the proposal is accepted |
|
// or if `newCurator` is true, the proposed Curator of |
|
// the new DAO). |
|
address recipient; |
|
// The amount to transfer to `recipient` if the proposal is accepted. |
|
uint amount; |
|
// A plain text description of the proposal |
|
string description; |
|
// A unix timestamp, denoting the end of the voting period |
|
uint votingDeadline; |
|
// True if the proposal's votes have yet to be counted, otherwise False |
|
bool open; |
|
// True if quorum has been reached, the votes have been counted, and |
|
// the majority said yes |
|
bool proposalPassed; |
|
// A hash to check validity of a proposal |
|
bytes32 proposalHash; |
|
// Deposit in wei the creator added when submitting their proposal. It |
|
// is taken from the msg.value of a newProposal call. |
|
uint proposalDeposit; |
|
// True if this proposal is to assign a new Curator |
|
bool newCurator; |
|
// Data needed for splitting the DAO |
|
SplitData[] splitData; |
|
// Number of Tokens in favor of the proposal |
|
uint yea; |
|
// Number of Tokens opposed to the proposal |
|
uint nay; |
|
// Simple mapping to check if a shareholder has voted for it |
|
mapping (address => bool) votedYes; |
|
// Simple mapping to check if a shareholder has voted against it |
|
mapping (address => bool) votedNo; |
|
// Address of the shareholder who created the proposal |
|
address creator; |
|
} |
|
|
|
// Used only in the case of a newCurator proposal. |
|
struct SplitData { |
|
// The balance of the current DAO minus the deposit at the time of split |
|
uint splitBalance; |
|
// The total amount of DAO Tokens in existence at the time of split. |
|
uint totalSupply; |
|
// Amount of Reward Tokens owned by the DAO at the time of split. |
|
uint rewardToken; |
|
// The new DAO contract created at the time of split. |
|
DAO newDAO; |
|
} |
|
|
|
// Used to restrict access to certain functions to only DAO Token Holders |
|
modifier onlyTokenholders {_;} |
|
|
|
/// @dev Constructor setting the Curator and the address |
|
/// for the contract able to create another DAO as well as the parameters |
|
/// for the DAO Token Creation |
|
/// @param _curator The Curator |
|
/// @param _daoCreator The contract able to (re)create this DAO |
|
/// @param _proposalDeposit The deposit to be paid for a regular proposal |
|
/// @param _minTokensToCreate Minimum required wei-equivalent tokens |
|
/// to be created for a successful DAO Token Creation |
|
/// @param _closingTime Date (in Unix time) of the end of the DAO Token Creation |
|
/// @param _privateCreation If zero the DAO Token Creation is open to public, a |
|
/// non-zero address means that the DAO Token Creation is only for the address |
|
// This is the constructor: it can not be overloaded so it is commented out |
|
// function DAO( |
|
// address _curator, |
|
// DAO_Creator _daoCreator, |
|
// uint _proposalDeposit, |
|
// uint _minTokensToCreate, |
|
// uint _closingTime, |
|
// address _privateCreation |
|
// ); |
|
|
|
/// @notice Create Token with `msg.sender` as the beneficiary |
|
/// @return Whether the token creation was successful |
|
function (); |
|
|
|
|
|
/// @dev This function is used to send ether back |
|
/// to the DAO, it can also be used to receive payments that should not be |
|
/// counted as rewards (donations, grants, etc.) |
|
/// @return Whether the DAO received the ether successfully |
|
function receiveEther() returns(bool); |
|
|
|
/// @notice `msg.sender` creates a proposal to send `_amount` Wei to |
|
/// `_recipient` with the transaction data `_transactionData`. If |
|
/// `_newCurator` is true, then this is a proposal that splits the |
|
/// DAO and sets `_recipient` as the new DAO's Curator. |
|
/// @param _recipient Address of the recipient of the proposed transaction |
|
/// @param _amount Amount of wei to be sent with the proposed transaction |
|
/// @param _description String describing the proposal |
|
/// @param _transactionData Data of the proposed transaction |
|
/// @param _debatingPeriod Time used for debating a proposal, at least 2 |
|
/// weeks for a regular proposal, 10 days for new Curator proposal |
|
/// @param _newCurator Bool defining whether this proposal is about |
|
/// a new Curator or not |
|
/// @return The proposal ID. Needed for voting on the proposal |
|
function newProposal( |
|
address _recipient, |
|
uint _amount, |
|
string _description, |
|
bytes _transactionData, |
|
uint _debatingPeriod, |
|
bool _newCurator |
|
) onlyTokenholders returns (uint _proposalID); |
|
|
|
/// @notice Check that the proposal with the ID `_proposalID` matches the |
|
/// transaction which sends `_amount` with data `_transactionData` |
|
/// to `_recipient` |
|
/// @param _proposalID The proposal ID |
|
/// @param _recipient The recipient of the proposed transaction |
|
/// @param _amount The amount of wei to be sent in the proposed transaction |
|
/// @param _transactionData The data of the proposed transaction |
|
/// @return Whether the proposal ID matches the transaction data or not |
|
function checkProposalCode( |
|
uint _proposalID, |
|
address _recipient, |
|
uint _amount, |
|
bytes _transactionData |
|
) constant returns (bool _codeChecksOut); |
|
|
|
/// @notice Vote on proposal `_proposalID` with `_supportsProposal` |
|
/// @param _proposalID The proposal ID |
|
/// @param _supportsProposal Yes/No - support of the proposal |
|
/// @return The vote ID. |
|
function vote( |
|
uint _proposalID, |
|
bool _supportsProposal |
|
) onlyTokenholders returns (uint _voteID); |
|
|
|
/// @notice Checks whether proposal `_proposalID` with transaction data |
|
/// `_transactionData` has been voted for or rejected, and executes the |
|
/// transaction in the case it has been voted for. |
|
/// @param _proposalID The proposal ID |
|
/// @param _transactionData The data of the proposed transaction |
|
/// @return Whether the proposed transaction has been executed or not |
|
function executeProposal( |
|
uint _proposalID, |
|
bytes _transactionData |
|
) returns (bool _success); |
|
|
|
/// @notice ATTENTION! I confirm to move my remaining ether to a new DAO |
|
/// with `_newCurator` as the new Curator, as has been |
|
/// proposed in proposal `_proposalID`. This will burn my tokens. This can |
|
/// not be undone and will split the DAO into two DAO's, with two |
|
/// different underlying tokens. |
|
/// @param _proposalID The proposal ID |
|
/// @param _newCurator The new Curator of the new DAO |
|
/// @dev This function, when called for the first time for this proposal, |
|
/// will create a new DAO and send the sender's portion of the remaining |
|
/// ether and Reward Tokens to the new DAO. It will also burn the DAO Tokens |
|
/// of the sender. |
|
function splitDAO( |
|
uint _proposalID, |
|
address _newCurator |
|
) returns (bool _success); |
|
|
|
/// @dev can only be called by the DAO itself through a proposal |
|
/// updates the contract of the DAO by sending all ether and rewardTokens |
|
/// to the new DAO. The new DAO needs to be approved by the Curator |
|
/// @param _newContract the address of the new contract |
|
function newContract(address _newContract); |
|
|
|
|
|
/// @notice Add a new possible recipient `_recipient` to the whitelist so |
|
/// that the DAO can send transactions to them (using proposals) |
|
/// @param _recipient New recipient address |
|
/// @dev Can only be called by the current Curator |
|
/// @return Whether successful or not |
|
function changeAllowedRecipients(address _recipient, bool _allowed) external returns (bool _success); |
|
|
|
|
|
/// @notice Change the minimum deposit required to submit a proposal |
|
/// @param _proposalDeposit The new proposal deposit |
|
/// @dev Can only be called by this DAO (through proposals with the |
|
/// recipient being this DAO itself) |
|
function changeProposalDeposit(uint _proposalDeposit) external; |
|
|
|
/// @notice Move rewards from the DAORewards managed account |
|
/// @param _toMembers If true rewards are moved to the actual reward account |
|
/// for the DAO. If not then it's moved to the DAO itself |
|
/// @return Whether the call was successful |
|
function retrieveDAOReward(bool _toMembers) external returns (bool _success); |
|
|
|
/// @notice Get my portion of the reward that was sent to `rewardAccount` |
|
/// @return Whether the call was successful |
|
function getMyReward() returns(bool _success); |
|
|
|
/// @notice Withdraw `_account`'s portion of the reward from `rewardAccount` |
|
/// to `_account`'s balance |
|
/// @return Whether the call was successful |
|
function withdrawRewardFor(address _account) internal returns (bool _success); |
|
|
|
/// @notice Send `_amount` tokens to `_to` from `msg.sender`. Prior to this |
|
/// getMyReward() is called. |
|
/// @param _to The address of the recipient |
|
/// @param _amount The amount of tokens to be transfered |
|
/// @return Whether the transfer was successful or not |
|
function transferWithoutReward(address _to, uint256 _amount) returns (bool success); |
|
|
|
/// @notice Send `_amount` tokens to `_to` from `_from` on the condition it |
|
/// is approved by `_from`. Prior to this getMyReward() is called. |
|
/// @param _from The address of the sender |
|
/// @param _to The address of the recipient |
|
/// @param _amount The amount of tokens to be transfered |
|
/// @return Whether the transfer was successful or not |
|
function transferFromWithoutReward( |
|
address _from, |
|
address _to, |
|
uint256 _amount |
|
) returns (bool success); |
|
|
|
/// @notice Doubles the 'minQuorumDivisor' in the case quorum has not been |
|
/// achieved in 52 weeks |
|
/// @return Whether the change was successful or not |
|
function halveMinQuorum() returns (bool _success); |
|
|
|
/// @return total number of proposals ever created |
|
function numberOfProposals() constant returns (uint _numberOfProposals); |
|
|
|
/// @param _proposalID Id of the new curator proposal |
|
/// @return Address of the new DAO |
|
function getNewDAOAddress(uint _proposalID) constant returns (address _newDAO); |
|
|
|
/// @param _account The address of the account which is checked. |
|
/// @return Whether the account is blocked (not allowed to transfer tokens) or not. |
|
function isBlocked(address _account) internal returns (bool); |
|
|
|
/// @notice If the caller is blocked by a proposal whose voting deadline |
|
/// has exprired then unblock him. |
|
/// @return Whether the account is blocked (not allowed to transfer tokens) or not. |
|
function unblockMe() returns (bool); |
|
|
|
event ProposalAdded( |
|
uint indexed proposalID, |
|
address recipient, |
|
uint amount, |
|
bool newCurator, |
|
string description |
|
); |
|
event Voted(uint indexed proposalID, bool position, address indexed voter); |
|
event ProposalTallied(uint indexed proposalID, bool result, uint quorum); |
|
event NewCurator(address indexed _newCurator); |
|
event AllowedRecipientChanged(address indexed _recipient, bool _allowed); |
|
} |
|
|
|
// The DAO contract itself |
|
contract DAO is DAOInterface, Token, TokenCreation { |
|
|
|
// Modifier that allows only shareholders to vote and create new proposals |
|
modifier onlyTokenholders { |
|
if (balanceOf(msg.sender) == 0) throw; |
|
_; |
|
} |
|
|
|
function DAO( |
|
address _curator, |
|
DAO_Creator _daoCreator, |
|
uint _proposalDeposit, |
|
uint _minTokensToCreate, |
|
uint _closingTime, |
|
address _privateCreation |
|
) TokenCreation(_minTokensToCreate, _closingTime, _privateCreation) { |
|
|
|
curator = _curator; |
|
daoCreator = _daoCreator; |
|
proposalDeposit = _proposalDeposit; |
|
rewardAccount = new ManagedAccount(address(this), false); |
|
DAOrewardAccount = new ManagedAccount(address(this), false); |
|
if (address(rewardAccount) == 0) |
|
throw; |
|
if (address(DAOrewardAccount) == 0) |
|
throw; |
|
lastTimeMinQuorumMet = now; |
|
minQuorumDivisor = 5; // sets the minimal quorum to 20% |
|
proposals.length = 1; // avoids a proposal with ID 0 because it is used |
|
|
|
allowedRecipients[address(this)] = true; |
|
allowedRecipients[curator] = true; |
|
} |
|
|
|
function () { |
|
if (now < closingTime + creationGracePeriod && msg.sender != address(extraBalance)) |
|
createTokenProxy(msg.sender); |
|
else |
|
receiveEther(); |
|
} |
|
|
|
|
|
function receiveEther() returns (bool) { |
|
return true; |
|
} |
|
|
|
|
|
function newProposal( |
|
address _recipient, |
|
uint _amount, |
|
string _description, |
|
bytes _transactionData, |
|
uint _debatingPeriod, |
|
bool _newCurator |
|
) onlyTokenholders returns (uint _proposalID) { |
|
|
|
// Sanity check |
|
if (_newCurator && ( |
|
_amount != 0 |
|
|| _transactionData.length != 0 |
|
|| _recipient == curator |
|
|| msg.value > 0 |
|
|| _debatingPeriod < minSplitDebatePeriod)) { |
|
throw; |
|
} else if ( |
|
!_newCurator |
|
&& (!isRecipientAllowed(_recipient) || (_debatingPeriod < minProposalDebatePeriod)) |
|
) { |
|
throw; |
|
} |
|
|
|
if (_debatingPeriod > 8 weeks) |
|
throw; |
|
|
|
if (!isFueled |
|
|| now < closingTime |
|
|| (msg.value < proposalDeposit && !_newCurator)) { |
|
|
|
throw; |
|
} |
|
|
|
if (now + _debatingPeriod < now) // prevents overflow |
|
throw; |
|
|
|
// to prevent a 51% attacker to convert the ether into deposit |
|
if (msg.sender == address(this)) |
|
throw; |
|
|
|
_proposalID = proposals.length++; |
|
Proposal p = proposals[_proposalID]; |
|
p.recipient = _recipient; |
|
p.amount = _amount; |
|
p.description = _description; |
|
p.proposalHash = sha3(_recipient, _amount, _transactionData); |
|
p.votingDeadline = now + _debatingPeriod; |
|
p.open = true; |
|
//p.proposalPassed = False; // that's default |
|
p.newCurator = _newCurator; |
|
if (_newCurator) |
|
p.splitData.length++; |
|
p.creator = msg.sender; |
|
p.proposalDeposit = msg.value; |
|
|
|
sumOfProposalDeposits += msg.value; |
|
|
|
ProposalAdded( |
|
_proposalID, |
|
_recipient, |
|
_amount, |
|
_newCurator, |
|
_description |
|
); |
|
} |
|
|
|
|
|
function checkProposalCode( |
|
uint _proposalID, |
|
address _recipient, |
|
uint _amount, |
|
bytes _transactionData |
|
) noEther constant returns (bool _codeChecksOut) { |
|
Proposal p = proposals[_proposalID]; |
|
return p.proposalHash == sha3(_recipient, _amount, _transactionData); |
|
} |
|
|
|
|
|
function vote( |
|
uint _proposalID, |
|
bool _supportsProposal |
|
) onlyTokenholders noEther returns (uint _voteID) { |
|
|
|
Proposal p = proposals[_proposalID]; |
|
if (p.votedYes[msg.sender] |
|
|| p.votedNo[msg.sender] |
|
|| now >= p.votingDeadline) { |
|
|
|
throw; |
|
} |
|
|
|
if (_supportsProposal) { |
|
p.yea += balances[msg.sender]; |
|
p.votedYes[msg.sender] = true; |
|
} else { |
|
p.nay += balances[msg.sender]; |
|
p.votedNo[msg.sender] = true; |
|
} |
|
|
|
if (blocked[msg.sender] == 0) { |
|
blocked[msg.sender] = _proposalID; |
|
} else if (p.votingDeadline > proposals[blocked[msg.sender]].votingDeadline) { |
|
// this proposal's voting deadline is further into the future than |
|
// the proposal that blocks the sender so make it the blocker |
|
blocked[msg.sender] = _proposalID; |
|
} |
|
|
|
Voted(_proposalID, _supportsProposal, msg.sender); |
|
} |
|
|
|
|
|
function executeProposal( |
|
uint _proposalID, |
|
bytes _transactionData |
|
) noEther returns (bool _success) { |
|
|
|
Proposal p = proposals[_proposalID]; |
|
|
|
uint waitPeriod = p.newCurator |
|
? splitExecutionPeriod |
|
: executeProposalPeriod; |
|
// If we are over deadline and waiting period, assert proposal is closed |
|
if (p.open && now > p.votingDeadline + waitPeriod) { |
|
closeProposal(_proposalID); |
|
return; |
|
} |
|
|
|
// Check if the proposal can be executed |
|
if (now < p.votingDeadline // has the voting deadline arrived? |
|
// Have the votes been counted? |
|
|| !p.open |
|
// Does the transaction code match the proposal? |
|
|| p.proposalHash != sha3(p.recipient, p.amount, _transactionData)) { |
|
|
|
throw; |
|
} |
|
|
|
// If the curator removed the recipient from the whitelist, close the proposal |
|
// in order to free the deposit and allow unblocking of voters |
|
if (!isRecipientAllowed(p.recipient)) { |
|
closeProposal(_proposalID); |
|
p.creator.send(p.proposalDeposit); |
|
return; |
|
} |
|
|
|
bool proposalCheck = true; |
|
|
|
if (p.amount > actualBalance()) |
|
proposalCheck = false; |
|
|
|
uint quorum = p.yea + p.nay; |
|
|
|
// require 53% for calling newContract() |
|
if (_transactionData.length >= 4 && _transactionData[0] == 0x68 |
|
&& _transactionData[1] == 0x37 && _transactionData[2] == 0xff |
|
&& _transactionData[3] == 0x1e |
|
&& quorum < minQuorum(actualBalance() + rewardToken[address(this)])) { |
|
|
|
proposalCheck = false; |
|
} |
|
|
|
if (quorum >= minQuorum(p.amount)) { |
|
if (!p.creator.send(p.proposalDeposit)) |
|
throw; |
|
|
|
lastTimeMinQuorumMet = now; |
|
// set the minQuorum to 20% again, in the case it has been reached |
|
if (quorum > totalSupply / 5) |
|
minQuorumDivisor = 5; |
|
} |
|
|
|
// Execute result |
|
if (quorum >= minQuorum(p.amount) && p.yea > p.nay && proposalCheck) { |
|
if (!p.recipient.call.value(p.amount)(_transactionData)) |
|
throw; |
|
|
|
p.proposalPassed = true; |
|
_success = true; |
|
|
|
// only create reward tokens when ether is not sent to the DAO itself and |
|
// related addresses. Proxy addresses should be forbidden by the curator. |
|
if (p.recipient != address(this) && p.recipient != address(rewardAccount) |
|
&& p.recipient != address(DAOrewardAccount) |
|
&& p.recipient != address(extraBalance) |
|
&& p.recipient != address(curator)) { |
|
|
|
rewardToken[address(this)] += p.amount; |
|
totalRewardToken += p.amount; |
|
} |
|
} |
|
|
|
closeProposal(_proposalID); |
|
|
|
// Initiate event |
|
ProposalTallied(_proposalID, _success, quorum); |
|
} |
|
|
|
|
|
function closeProposal(uint _proposalID) internal { |
|
Proposal p = proposals[_proposalID]; |
|
if (p.open) |
|
sumOfProposalDeposits -= p.proposalDeposit; |
|
p.open = false; |
|
} |
|
|
|
function splitDAO( |
|
uint _proposalID, |
|
address _newCurator |
|
) noEther onlyTokenholders returns (bool _success) { |
|
|
|
Proposal p = proposals[_proposalID]; |
|
|
|
// Sanity check |
|
|
|
if (now < p.votingDeadline // has the voting deadline arrived? |
|
//The request for a split expires XX days after the voting deadline |
|
|| now > p.votingDeadline + splitExecutionPeriod |
|
// Does the new Curator address match? |
|
|| p.recipient != _newCurator |
|
// Is it a new curator proposal? |
|
|| !p.newCurator |
|
// Have you voted for this split? |
|
|| !p.votedYes[msg.sender] |
|
// Did you already vote on another proposal? |
|
|| (blocked[msg.sender] != _proposalID && blocked[msg.sender] != 0) ) { |
|
|
|
throw; |
|
} |
|
|
|
// If the new DAO doesn't exist yet, create the new DAO and store the |
|
// current split data |
|
if (address(p.splitData[0].newDAO) == 0) { |
|
p.splitData[0].newDAO = createNewDAO(_newCurator); |
|
// Call depth limit reached, etc. |
|
if (address(p.splitData[0].newDAO) == 0) |
|
throw; |
|
// should never happen |
|
if (this.balance < sumOfProposalDeposits) |
|
throw; |
|
p.splitData[0].splitBalance = actualBalance(); |
|
p.splitData[0].rewardToken = rewardToken[address(this)]; |
|
p.splitData[0].totalSupply = totalSupply; |
|
p.proposalPassed = true; |
|
} |
|
|
|
// Move ether and assign new Tokens |
|
uint fundsToBeMoved = |
|
(balances[msg.sender] * p.splitData[0].splitBalance) / |
|
p.splitData[0].totalSupply; |
|
|
|
|
|
// Assign reward rights to new DAO |
|
uint rewardTokenToBeMoved = |
|
(balances[msg.sender] * p.splitData[0].rewardToken) / |
|
p.splitData[0].totalSupply; |
|
|
|
uint paidOutToBeMoved = DAOpaidOut[address(this)] * rewardTokenToBeMoved / |
|
rewardToken[address(this)]; |
|
|
|
rewardToken[address(p.splitData[0].newDAO)] += rewardTokenToBeMoved; |
|
if (rewardToken[address(this)] < rewardTokenToBeMoved) |
|
throw; |
|
rewardToken[address(this)] -= rewardTokenToBeMoved; |
|
|
|
DAOpaidOut[address(p.splitData[0].newDAO)] += paidOutToBeMoved; |
|
if (DAOpaidOut[address(this)] < paidOutToBeMoved) |
|
throw; |
|
DAOpaidOut[address(this)] -= paidOutToBeMoved; |
|
|
|
// Burn DAO Tokens |
|
Transfer(msg.sender, 0, balances[msg.sender]); |
|
withdrawRewardFor(msg.sender); // be nice, and get his rewards |
|
totalSupply -= balances[msg.sender]; |
|
balances[msg.sender] = 0; |
|
paidOut[msg.sender] = 0; |
|
return true; |
|
} |
|
|
|
function newContract(address _newContract){ |
|
if (msg.sender != address(this) || !allowedRecipients[_newContract]) return; |
|
// move all ether |
|
if (!_newContract.call.value(address(this).balance)()) { |
|
throw; |
|
} |
|
|
|
//move all reward tokens |
|
rewardToken[_newContract] += rewardToken[address(this)]; |
|
rewardToken[address(this)] = 0; |
|
DAOpaidOut[_newContract] += DAOpaidOut[address(this)]; |
|
DAOpaidOut[address(this)] = 0; |
|
} |
|
|
|
|
|
function retrieveDAOReward(bool _toMembers) external noEther returns (bool _success) { |
|
DAO dao = DAO(msg.sender); |
|
|
|
if ((rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) / |
|
totalRewardToken < DAOpaidOut[msg.sender]) |
|
throw; |
|
|
|
uint reward = |
|
(rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) / |
|
totalRewardToken - DAOpaidOut[msg.sender]; |
|
if(_toMembers) { |
|
if (!DAOrewardAccount.payOut(dao.rewardAccount(), reward)) |
|
throw; |
|
} |
|
else { |
|
if (!DAOrewardAccount.payOut(dao, reward)) |
|
throw; |
|
} |
|
DAOpaidOut[msg.sender] += reward; |
|
return true; |
|
} |
|
|
|
function getMyReward() noEther returns (bool _success) { |
|
return withdrawRewardFor(msg.sender); |
|
} |
|
|
|
|
|
function withdrawRewardFor(address _account) noEther internal returns (bool _success) { |
|
if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account]) |
|
throw; |
|
|
|
uint reward = |
|
(balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account]; |
|
if (!rewardAccount.payOut(_account, reward)) |
|
throw; |
|
paidOut[_account] += reward; |
|
return true; |
|
} |
|
|
|
|
|
function transfer(address _to, uint256 _value) returns (bool success) { |
|
if (isFueled |
|
&& now > closingTime |
|
&& !isBlocked(msg.sender) |
|
&& transferPaidOut(msg.sender, _to, _value) |
|
&& super.transfer(_to, _value)) { |
|
|
|
return true; |
|
} else { |
|
throw; |
|
} |
|
} |
|
|
|
|
|
function transferWithoutReward(address _to, uint256 _value) returns (bool success) { |
|
if (!getMyReward()) |
|
throw; |
|
return transfer(_to, _value); |
|
} |
|
|
|
|
|
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { |
|
if (isFueled |
|
&& now > closingTime |
|
&& !isBlocked(_from) |
|
&& transferPaidOut(_from, _to, _value) |
|
&& super.transferFrom(_from, _to, _value)) { |
|
|
|
return true; |
|
} else { |
|
throw; |
|
} |
|
} |
|
|
|
|
|
function transferFromWithoutReward( |
|
address _from, |
|
address _to, |
|
uint256 _value |
|
) returns (bool success) { |
|
|
|
if (!withdrawRewardFor(_from)) |
|
throw; |
|
return transferFrom(_from, _to, _value); |
|
} |
|
|
|
|
|
function transferPaidOut( |
|
address _from, |
|
address _to, |
|
uint256 _value |
|
) internal returns (bool success) { |
|
|
|
uint transferPaidOut = paidOut[_from] * _value / balanceOf(_from); |
|
if (transferPaidOut > paidOut[_from]) |
|
throw; |
|
paidOut[_from] -= transferPaidOut; |
|
paidOut[_to] += transferPaidOut; |
|
return true; |
|
} |
|
|
|
|
|
function changeProposalDeposit(uint _proposalDeposit) noEther external { |
|
if (msg.sender != address(this) || _proposalDeposit > (actualBalance() + rewardToken[address(this)]) |
|
/ maxDepositDivisor) { |
|
|
|
throw; |
|
} |
|
proposalDeposit = _proposalDeposit; |
|
} |
|
|
|
|
|
function changeAllowedRecipients(address _recipient, bool _allowed) noEther external returns (bool _success) { |
|
if (msg.sender != curator) |
|
throw; |
|
allowedRecipients[_recipient] = _allowed; |
|
AllowedRecipientChanged(_recipient, _allowed); |
|
return true; |
|
} |
|
|
|
|
|
function isRecipientAllowed(address _recipient) internal returns (bool _isAllowed) { |
|
if (allowedRecipients[_recipient] |
|
|| (_recipient == address(extraBalance) |
|
// only allowed when at least the amount held in the |
|
// extraBalance account has been spent from the DAO |
|
&& totalRewardToken > extraBalance.accumulatedInput())) |
|
return true; |
|
else |
|
return false; |
|
} |
|
|
|
function actualBalance() constant returns (uint _actualBalance) { |
|
return this.balance - sumOfProposalDeposits; |
|
} |
|
|
|
|
|
function minQuorum(uint _value) internal constant returns (uint _minQuorum) { |
|
// minimum of 20% and maximum of 53.33% |
|
return totalSupply / minQuorumDivisor + |
|
(_value * totalSupply) / (3 * (actualBalance() + rewardToken[address(this)])); |
|
} |
|
|
|
|
|
function halveMinQuorum() returns (bool _success) { |
|
// this can only be called after `quorumHalvingPeriod` has passed or at anytime |
|
// by the curator with a delay of at least `minProposalDebatePeriod` between the calls |
|
if ((lastTimeMinQuorumMet < (now - quorumHalvingPeriod) || msg.sender == curator) |
|
&& lastTimeMinQuorumMet < (now - minProposalDebatePeriod)) { |
|
lastTimeMinQuorumMet = now; |
|
minQuorumDivisor *= 2; |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
function createNewDAO(address _newCurator) internal returns (DAO _newDAO) { |
|
NewCurator(_newCurator); |
|
return daoCreator.createDAO(_newCurator, 0, 0, now + splitExecutionPeriod); |
|
} |
|
|
|
function numberOfProposals() constant returns (uint _numberOfProposals) { |
|
// Don't count index 0. It's used by isBlocked() and exists from start |
|
return proposals.length - 1; |
|
} |
|
|
|
function getNewDAOAddress(uint _proposalID) constant returns (address _newDAO) { |
|
return proposals[_proposalID].splitData[0].newDAO; |
|
} |
|
|
|
function isBlocked(address _account) internal returns (bool) { |
|
if (blocked[_account] == 0) |
|
return false; |
|
Proposal p = proposals[blocked[_account]]; |
|
if (now > p.votingDeadline) { |
|
blocked[_account] = 0; |
|
return false; |
|
} else { |
|
return true; |
|
} |
|
} |
|
|
|
function unblockMe() returns (bool) { |
|
return isBlocked(msg.sender); |
|
} |
|
} |
|
|
|
contract DAO_Creator { |
|
function createDAO( |
|
address _curator, |
|
uint _proposalDeposit, |
|
uint _minTokensToCreate, |
|
uint _closingTime |
|
) returns (DAO _newDAO) { |
|
|
|
return new DAO( |
|
_curator, |
|
DAO_Creator(this), |
|
_proposalDeposit, |
|
_minTokensToCreate, |
|
_closingTime, |
|
msg.sender |
|
); |
|
} |
|
} |
|
|