mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			947 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			Solidity
		
	
	
	
	
	
			
		
		
	
	
			947 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			Solidity
		
	
	
	
	
	
| /*
 | |
| 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/>.
 | |
| */
 | |
| 
 | |
| 
 | |
| /*
 | |
| Standard smart contract for a Decentralized Autonomous Organization (DAO)
 | |
| to automate organizational governance and decision-making.
 | |
| */
 | |
| 
 | |
| import "./TokenCreation.sol";
 | |
| import "./ManagedAccount.sol";
 | |
| 
 | |
| abstract 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 payable 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 payable 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 payable 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;
 | |
|     }
 | |
| 
 | |
|     /// @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
 | |
|     /// @param _tokenName The name that the DAO's token will have
 | |
|     /// @param _tokenSymbol The ticker symbol that this DAO token should have
 | |
|     /// @param _decimalPlaces The number of decimal places that the token is
 | |
|     ///        counted from.
 | |
|     // This is the constructor: it can not be overloaded so it is commented out
 | |
|     //  function DAO(
 | |
|         //  address payable _curator,
 | |
|         //  DAO_Creator _daoCreator,
 | |
|         //  uint _proposalDeposit,
 | |
|         //  uint _minTokensToCreate,
 | |
|         //  uint _closingTime,
 | |
|         //  address _privateCreation
 | |
|         //  string _tokenName,
 | |
|         //  string _tokenSymbol,
 | |
|         //  uint8 _decimalPlaces
 | |
|     //  );
 | |
| 
 | |
|     /// @notice Create Token with `msg.sender` as the beneficiary
 | |
|     receive() virtual external payable;
 | |
| 
 | |
| 
 | |
|     /// @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() public virtual 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 _proposalID The proposal ID. Needed for voting on the proposal
 | |
|     function newProposal(
 | |
|         address payable _recipient,
 | |
|         uint _amount,
 | |
|         string memory _description,
 | |
|         bytes memory _transactionData,
 | |
|         uint _debatingPeriod,
 | |
|         bool _newCurator
 | |
|     ) public virtual payable 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 _codeChecksOut Whether the proposal ID matches the transaction data or not
 | |
|     function checkProposalCode(
 | |
|         uint _proposalID,
 | |
|         address payable _recipient,
 | |
|         uint _amount,
 | |
|         bytes memory _transactionData
 | |
|     ) public virtual view returns (bool _codeChecksOut);
 | |
| 
 | |
|     /// @notice Vote on proposal `_proposalID` with `_supportsProposal`
 | |
|     /// @param _proposalID The proposal ID
 | |
|     /// @param _supportsProposal Yes/No - support of the proposal
 | |
|     /// @return _voteID The vote ID.
 | |
|     function vote(
 | |
|         uint _proposalID,
 | |
|         bool _supportsProposal
 | |
|     ) public virtual 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 _success Whether the proposed transaction has been executed or not
 | |
|     function executeProposal(
 | |
|         uint _proposalID,
 | |
|         bytes memory _transactionData
 | |
|     ) public virtual 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 payable _newCurator
 | |
|     ) public virtual 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 payable _newContract) virtual public;
 | |
| 
 | |
| 
 | |
|     /// @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 _success Whether successful or not
 | |
|     function changeAllowedRecipients(address _recipient, bool _allowed) virtual 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) virtual 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 _success Whether the call was successful
 | |
|     function retrieveDAOReward(bool _toMembers) virtual external returns (bool _success);
 | |
| 
 | |
|     /// @notice Get my portion of the reward that was sent to `rewardAccount`
 | |
|     /// @return _success Whether the call was successful
 | |
|     function getMyReward() public virtual returns (bool _success);
 | |
| 
 | |
|     /// @notice Withdraw `_account`'s portion of the reward from `rewardAccount`
 | |
|     /// to `_account`'s balance
 | |
|     /// @return _success Whether the call was successful
 | |
|     function withdrawRewardFor(address payable _account) internal virtual 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 transferred
 | |
|     /// @return success Whether the transfer was successful or not
 | |
|     function transferWithoutReward(address _to, uint256 _amount) public virtual 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 transferred
 | |
|     /// @return success Whether the transfer was successful or not
 | |
|     function transferFromWithoutReward(
 | |
|         address payable _from,
 | |
|         address _to,
 | |
|         uint256 _amount
 | |
|     ) public virtual returns (bool success);
 | |
| 
 | |
|     /// @notice Doubles the 'minQuorumDivisor' in the case quorum has not been
 | |
|     /// achieved in 52 weeks
 | |
|     /// @return _success Whether the change was successful or not
 | |
|     function halveMinQuorum() public virtual returns (bool _success);
 | |
| 
 | |
|     /// @return _numberOfProposals total number of proposals ever created
 | |
|     function numberOfProposals() public virtual view returns (uint _numberOfProposals);
 | |
| 
 | |
|     /// @param _proposalID Id of the new curator proposal
 | |
|     /// @return _newDAO Address of the new DAO
 | |
|     function getNewDAOAddress(uint _proposalID) public virtual view 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 virtual 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() public virtual 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) revert();
 | |
|             _;
 | |
|     }
 | |
| 
 | |
|     constructor(
 | |
|         address payable _curator,
 | |
|         DAO_Creator _daoCreator,
 | |
|         uint _proposalDeposit,
 | |
|         uint _minTokensToCreate,
 | |
|         uint _closingTime,
 | |
|         address _privateCreation,
 | |
|         string memory _tokenName,
 | |
|         string memory _tokenSymbol,
 | |
|         uint8 _decimalPlaces
 | |
|     ) TokenCreation(
 | |
|         _minTokensToCreate,
 | |
|         _closingTime,
 | |
|         _privateCreation,
 | |
|         _tokenName,
 | |
|         _tokenSymbol,
 | |
|         _decimalPlaces) public {
 | |
| 
 | |
|         curator = _curator;
 | |
|         daoCreator = _daoCreator;
 | |
|         proposalDeposit = _proposalDeposit;
 | |
|         rewardAccount = new ManagedAccount(address(this), false);
 | |
|         DAOrewardAccount = new ManagedAccount(address(this), false);
 | |
|         if (address(rewardAccount) == 0x0000000000000000000000000000000000000000)
 | |
|             revert();
 | |
|         if (address(DAOrewardAccount) == 0x0000000000000000000000000000000000000000)
 | |
|             revert();
 | |
|         lastTimeMinQuorumMet = block.timestamp;
 | |
|         minQuorumDivisor = 5; // sets the minimal quorum to 20%
 | |
|         proposals.push(); // avoids a proposal with ID 0 because it is used
 | |
| 
 | |
|         allowedRecipients[address(this)] = true;
 | |
|         allowedRecipients[curator] = true;
 | |
|     }
 | |
| 
 | |
|     receive() external payable override(DAOInterface, TokenCreation) {
 | |
|         if (block.timestamp < closingTime + creationGracePeriod && msg.sender != address(extraBalance))
 | |
|             createTokenProxy(payable(msg.sender));
 | |
|         else
 | |
|             receiveEther();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function receiveEther() public override returns (bool) {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function newProposal(
 | |
|         address payable _recipient,
 | |
|         uint _amount,
 | |
|         string memory _description,
 | |
|         bytes memory _transactionData,
 | |
|         uint _debatingPeriod,
 | |
|         bool _newCurator
 | |
|     ) onlyTokenholders public override payable returns (uint _proposalID) {
 | |
| 
 | |
|         // Sanity check
 | |
|         if (_newCurator && (
 | |
|             _amount != 0
 | |
|             || _transactionData.length != 0
 | |
|             || _recipient == curator
 | |
|             || msg.value > 0
 | |
|             || _debatingPeriod < minSplitDebatePeriod)) {
 | |
|             revert();
 | |
|         } else if (
 | |
|             !_newCurator
 | |
|             && (!isRecipientAllowed(_recipient) || (_debatingPeriod <  minProposalDebatePeriod))
 | |
|         ) {
 | |
|             revert();
 | |
|         }
 | |
| 
 | |
|         if (_debatingPeriod > 8 weeks)
 | |
|             revert();
 | |
| 
 | |
|         if (!isFueled
 | |
|             || block.timestamp < closingTime
 | |
|             || (msg.value < proposalDeposit && !_newCurator)) {
 | |
| 
 | |
|             revert();
 | |
|         }
 | |
| 
 | |
|         if (block.timestamp + _debatingPeriod < block.timestamp) // prevents overflow
 | |
|             revert();
 | |
| 
 | |
|         // to prevent a 51% attacker to convert the ether into deposit
 | |
|         if (msg.sender == address(this))
 | |
|             revert();
 | |
| 
 | |
|         // to prevent curator from halving quorum before first proposal
 | |
|         if (proposals.length == 1) // initial length is 1 (see constructor)
 | |
|             lastTimeMinQuorumMet = block.timestamp;
 | |
| 
 | |
|         Proposal storage p = proposals.push();
 | |
|          _proposalID = proposals.length - 1;
 | |
|         p.recipient = _recipient;
 | |
|         p.amount = _amount;
 | |
|         p.description = _description;
 | |
|         p.proposalHash = keccak256(abi.encodePacked(_recipient, _amount, _transactionData));
 | |
|         p.votingDeadline = block.timestamp + _debatingPeriod;
 | |
|         p.open = true;
 | |
|         //p.proposalPassed = False; // that's default
 | |
|         p.newCurator = _newCurator;
 | |
|         if (_newCurator)
 | |
|             p.splitData.push();
 | |
|         p.creator = payable(msg.sender);
 | |
|         p.proposalDeposit = msg.value;
 | |
| 
 | |
|         sumOfProposalDeposits += msg.value;
 | |
| 
 | |
|         emit ProposalAdded(
 | |
|             _proposalID,
 | |
|             _recipient,
 | |
|             _amount,
 | |
|             _newCurator,
 | |
|             _description
 | |
|         );
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function checkProposalCode(
 | |
|         uint _proposalID,
 | |
|         address payable _recipient,
 | |
|         uint _amount,
 | |
|         bytes memory _transactionData
 | |
|     ) public override view returns (bool _codeChecksOut) {
 | |
|         Proposal storage p = proposals[_proposalID];
 | |
|         return p.proposalHash == keccak256(abi.encodePacked(_recipient, _amount, _transactionData));
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function vote(
 | |
|         uint _proposalID,
 | |
|         bool _supportsProposal
 | |
|     ) onlyTokenholders public override returns (uint _voteID) {
 | |
| 
 | |
|         Proposal storage p = proposals[_proposalID];
 | |
|         if (p.votedYes[msg.sender]
 | |
|             || p.votedNo[msg.sender]
 | |
|             || block.timestamp >= p.votingDeadline) {
 | |
| 
 | |
|             revert();
 | |
|         }
 | |
| 
 | |
|         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;
 | |
|         }
 | |
| 
 | |
|         emit Voted(_proposalID, _supportsProposal, msg.sender);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function executeProposal(
 | |
|         uint _proposalID,
 | |
|         bytes memory _transactionData
 | |
|     ) public override returns (bool _success) {
 | |
| 
 | |
|         Proposal storage p = proposals[_proposalID];
 | |
| 
 | |
|         uint waitPeriod = p.newCurator
 | |
|             ? splitExecutionPeriod
 | |
|             : executeProposalPeriod;
 | |
|         // If we are over deadline and waiting period, assert proposal is closed
 | |
|         if (p.open && block.timestamp > p.votingDeadline + waitPeriod) {
 | |
|             closeProposal(_proposalID);
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // Check if the proposal can be executed
 | |
|         if (block.timestamp < p.votingDeadline  // has the voting deadline arrived?
 | |
|             // Have the votes been counted?
 | |
|             || !p.open
 | |
|             || p.proposalPassed // anyone trying to call us recursively?
 | |
|             // Does the transaction code match the proposal?
 | |
|             || p.proposalHash != keccak256(abi.encodePacked(p.recipient, p.amount, _transactionData))) {
 | |
| 
 | |
|             revert();
 | |
|         }
 | |
| 
 | |
|         // 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 false;
 | |
|         }
 | |
| 
 | |
|         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))
 | |
|                 revert();
 | |
| 
 | |
|             lastTimeMinQuorumMet = block.timestamp;
 | |
|             // 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) {
 | |
|             // we are setting this here before the CALL() value transfer to
 | |
|             // assure that in the case of a malicious recipient contract trying
 | |
|             // to call executeProposal() recursively money can't be transferred
 | |
|             // multiple times out of the DAO
 | |
|             p.proposalPassed = true;
 | |
| 
 | |
|             (bool success,) = p.recipient.call{value: p.amount}(_transactionData);
 | |
|             if (!success)
 | |
|                 revert();
 | |
| 
 | |
|             _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
 | |
|         emit ProposalTallied(_proposalID, _success, quorum);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function closeProposal(uint _proposalID) internal {
 | |
|         Proposal storage p = proposals[_proposalID];
 | |
|         if (p.open)
 | |
|             sumOfProposalDeposits -= p.proposalDeposit;
 | |
|         p.open = false;
 | |
|     }
 | |
| 
 | |
|     function splitDAO(
 | |
|         uint _proposalID,
 | |
|         address payable _newCurator
 | |
|     ) onlyTokenholders public override returns (bool _success) {
 | |
| 
 | |
|         Proposal storage p = proposals[_proposalID];
 | |
| 
 | |
|         // Sanity check
 | |
| 
 | |
|         if (block.timestamp < p.votingDeadline  // has the voting deadline arrived?
 | |
|             //The request for a split expires XX days after the voting deadline
 | |
|             || block.timestamp > 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) )  {
 | |
| 
 | |
|             revert();
 | |
|         }
 | |
| 
 | |
|         // If the new DAO doesn't exist yet, create the new DAO and store the
 | |
|         // current split data
 | |
|         if (address(p.splitData[0].newDAO) == 0x0000000000000000000000000000000000000000) {
 | |
|             p.splitData[0].newDAO = createNewDAO(_newCurator);
 | |
|             // Call depth limit reached, etc.
 | |
|             if (address(p.splitData[0].newDAO) == 0x0000000000000000000000000000000000000000)
 | |
|                 revert();
 | |
|             // should never happen
 | |
|             if (address(this).balance < sumOfProposalDeposits)
 | |
|                 revert();
 | |
|             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;
 | |
|         if (p.splitData[0].newDAO.createTokenProxy{value: fundsToBeMoved}(payable(msg.sender)) == false)
 | |
|             revert();
 | |
| 
 | |
| 
 | |
|         // 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)
 | |
|             revert();
 | |
|         rewardToken[address(this)] -= rewardTokenToBeMoved;
 | |
| 
 | |
|         DAOpaidOut[address(p.splitData[0].newDAO)] += paidOutToBeMoved;
 | |
|         if (DAOpaidOut[address(this)] < paidOutToBeMoved)
 | |
|             revert();
 | |
|         DAOpaidOut[address(this)] -= paidOutToBeMoved;
 | |
| 
 | |
|         // Burn DAO Tokens
 | |
|         emit Transfer(msg.sender, 0x0000000000000000000000000000000000000000, balances[msg.sender]);
 | |
|         withdrawRewardFor(payable(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 payable _newContract) public override {
 | |
|         if (msg.sender != address(this) || !allowedRecipients[_newContract]) return;
 | |
|         // move all ether
 | |
|         (bool success,) = _newContract.call{value: address(this).balance}("");
 | |
|         if (!success) {
 | |
|             revert();
 | |
|         }
 | |
| 
 | |
|         //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 override returns (bool _success) {
 | |
|         DAO dao = DAO(payable(msg.sender));
 | |
| 
 | |
|         if ((rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) /
 | |
|             totalRewardToken < DAOpaidOut[msg.sender])
 | |
|             revert();
 | |
| 
 | |
|         uint reward =
 | |
|             (rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) /
 | |
|             totalRewardToken - DAOpaidOut[msg.sender];
 | |
| 
 | |
|         reward = address(DAOrewardAccount).balance < reward ? address(DAOrewardAccount).balance : reward;
 | |
| 
 | |
|         if(_toMembers) {
 | |
|             if (!DAOrewardAccount.payOut(payable(dao.rewardAccount()), reward))
 | |
|                 revert();
 | |
|             }
 | |
|         else {
 | |
|             if (!DAOrewardAccount.payOut(payable(dao), reward))
 | |
|                 revert();
 | |
|         }
 | |
|         DAOpaidOut[msg.sender] += reward;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function getMyReward() public override returns (bool _success) {
 | |
|         return withdrawRewardFor(payable(msg.sender));
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function withdrawRewardFor(address payable _account) noEther internal
 | |
| override returns (bool _success) {
 | |
|         if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account])
 | |
|             revert();
 | |
| 
 | |
|         uint reward =
 | |
|             (balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account];
 | |
| 
 | |
|         reward = address(rewardAccount).balance < reward ? address(rewardAccount).balance : reward;
 | |
| 
 | |
|         if (!rewardAccount.payOut(_account, reward))
 | |
|             revert();
 | |
|         paidOut[_account] += reward;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function transfer(address _to, uint256 _value) public override returns (bool success) {
 | |
|         if (isFueled
 | |
|             && block.timestamp > closingTime
 | |
|             && !isBlocked(msg.sender)
 | |
|             && _to != address(this)
 | |
|             && transferPaidOut(msg.sender, _to, _value)
 | |
|             && super.transfer(_to, _value)) {
 | |
| 
 | |
|             return true;
 | |
|         } else {
 | |
|             revert();
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function transferWithoutReward(address _to, uint256 _value) public override returns (bool success) {
 | |
|         if (!getMyReward())
 | |
|             revert();
 | |
|         return transfer(_to, _value);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function transferFrom(address _from, address _to, uint256 _value) public
 | |
| override returns (bool success) {
 | |
|         if (isFueled
 | |
|             && block.timestamp > closingTime
 | |
|             && !isBlocked(_from)
 | |
|             && _to != address(this)
 | |
|             && transferPaidOut(_from, _to, _value)
 | |
|             && super.transferFrom(_from, _to, _value)) {
 | |
| 
 | |
|             return true;
 | |
|         } else {
 | |
|             revert();
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function transferFromWithoutReward(
 | |
|         address payable _from,
 | |
|         address _to,
 | |
|         uint256 _value
 | |
|     ) public override returns (bool success) {
 | |
| 
 | |
|         if (!withdrawRewardFor(_from))
 | |
|             revert();
 | |
|         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])
 | |
|             revert();
 | |
|         paidOut[_from] -= transferPaidOut;
 | |
|         paidOut[_to] += transferPaidOut;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function changeProposalDeposit(uint _proposalDeposit) external override {
 | |
|         if (msg.sender != address(this) || _proposalDeposit > (actualBalance() + rewardToken[address(this)])
 | |
|             / maxDepositDivisor) {
 | |
| 
 | |
|             revert();
 | |
|         }
 | |
|         proposalDeposit = _proposalDeposit;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function changeAllowedRecipients(address _recipient, bool _allowed) external
 | |
| override returns (bool _success) {
 | |
|         if (msg.sender != curator)
 | |
|             revert();
 | |
|         allowedRecipients[_recipient] = _allowed;
 | |
|         emit 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() public view returns (uint _actualBalance) {
 | |
|         return address(this).balance - sumOfProposalDeposits;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function minQuorum(uint _value) internal view returns (uint _minQuorum) {
 | |
|         // minimum of 20% and maximum of 53.33%
 | |
|         return totalSupply / minQuorumDivisor +
 | |
|             (_value * totalSupply) / (3 * (actualBalance() + rewardToken[address(this)]));
 | |
|     }
 | |
| 
 | |
| 
 | |
|     function halveMinQuorum() public override returns (bool _success) {
 | |
|         // this can only be called after `quorumHalvingPeriod` has passed or at anytime after
 | |
|         // fueling by the curator with a delay of at least `minProposalDebatePeriod`
 | |
|         // between the calls
 | |
|         if ((lastTimeMinQuorumMet < (block.timestamp - quorumHalvingPeriod) || msg.sender == curator)
 | |
|             && lastTimeMinQuorumMet < (block.timestamp - minProposalDebatePeriod)
 | |
|             && block.timestamp >= closingTime
 | |
|             && proposals.length > 1) {
 | |
|             lastTimeMinQuorumMet = block.timestamp;
 | |
|             minQuorumDivisor *= 2;
 | |
|             return true;
 | |
|         } else {
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function createNewDAO(address payable _newCurator) internal returns (DAO _newDAO) {
 | |
|         emit NewCurator(_newCurator);
 | |
|         return daoCreator.createDAO(
 | |
|             _newCurator,
 | |
|             0,
 | |
|             0,
 | |
|             block.timestamp + splitExecutionPeriod,
 | |
|             name,
 | |
|             symbol,
 | |
|             decimals
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     function numberOfProposals() public view override 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) public override view returns (address _newDAO) {
 | |
|         return address(proposals[_proposalID].splitData[0].newDAO);
 | |
|     }
 | |
| 
 | |
|     function isBlocked(address _account) internal override returns (bool) {
 | |
|         if (blocked[_account] == 0)
 | |
|             return false;
 | |
|         Proposal storage p = proposals[blocked[_account]];
 | |
|         if (block.timestamp > p.votingDeadline) {
 | |
|             blocked[_account] = 0;
 | |
|             return false;
 | |
|         } else {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function unblockMe() public override returns (bool) {
 | |
|         return isBlocked(msg.sender);
 | |
|     }
 | |
| }
 | |
| 
 | |
| contract DAO_Creator {
 | |
|     function createDAO(
 | |
|         address payable _curator,
 | |
|         uint _proposalDeposit,
 | |
|         uint _minTokensToCreate,
 | |
|         uint _closingTime,
 | |
|         string memory _tokenName,
 | |
|         string memory _tokenSymbol,
 | |
|         uint8 _decimalPlaces
 | |
|     ) public returns (DAO _newDAO) {
 | |
| 
 | |
|         return new DAO(
 | |
|             _curator,
 | |
|             DAO_Creator(this),
 | |
|             _proposalDeposit,
 | |
|             _minTokensToCreate,
 | |
|             _closingTime,
 | |
|             msg.sender,
 | |
|             _tokenName,
 | |
|             _tokenSymbol,
 | |
|             _decimalPlaces
 | |
|         );
 | |
|     }
 | |
| }
 |