| /* | |
| The contract 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 contract 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. | |
| */ | |
| /* | |
| DAO PROPOSAL FRAMEWORK | |
| ====================== | |
| The intent of this document is to suggest a Proposal Framework that would solve | |
| most of the larger attack vectors that the DAO current has. This contract has not | |
| been tested and further analysis is required to make sure it works from both a | |
| technical standpoint but also a game-theoretical sense. | |
| The main features this implements: | |
| * Before a proposal is whitelisted to the DAO, this allows the proposal to gather | |
| informal support. This allows any token holder to show their support of the proposal | |
| an information that can be used both by the proposers, the curators or app makers. | |
| Due to gas block limits, a maximum of 256 supporters is allowed, the smallest one | |
| always being replaced by larger ones. | |
| * At most 48 hours before voting deadline, the proposal can watch its own votes. If | |
| it sees that it wasn't able to win by that window, it will return the funds immediately | |
| upon receiving them. This creates an incentive for YEA supporters to vote early, while | |
| giving NAY supporters an extra 48 hours on their voting deadline. | |
| * Immediately after the vote is over, the contract will check again its votes for quorum. | |
| If it sees that YEA has not met 75% of the minQuorum, it will return the funds immediately | |
| upon receiving them. This diminishes the fear that voting NO might help the quorum. | |
| * After the contract receives its funds, there is a 3 week period in which either the Curators | |
| or the DAO can request to cancel the contract and get the funds back. | |
| * After the contract is finally activated, only a initial payment is made. Further payments | |
| can be made only after a set of milestones are reached. The milestoneOracle is a | |
| judge that was agreed upon by both the contractor and the DAO. Milestones also have a | |
| minimum due date, so the contractor can use them to receive periodical payments by | |
| setting the milestone oracle as the same as the curator. | |
| * All payments are not set in ether, but instead in any unit desirable. The unit conversion is | |
| set by a priceFeedOracle, a contract agreed upon by both the contractor and The DAO. Oracles | |
| can be changed by mutual agreement. If the contractor wants to be paid in ether, they can | |
| do so by setting the priceFeedOracle to always return 1 ether. All ether left at the end of the | |
| contract can be sent back to the DAO. If the contract becomes insolvent due to price fall, | |
| further funding is required in order to keep the payments. | |
| * When all milestones are completed OR when the maximum duration of the contract is reached | |
| (this is final non-negotiable deadline so contractors must take these in consideration) then | |
| the contract is destroyed and all remaining funds are sent to the DAO | |
| */ | |
| // The DAO functions that the contract uses | |
| contract DAO { | |
| Proposal[] public proposals; | |
| struct Proposal { | |
| address recipient; | |
| uint amount; | |
| // string description; | |
| uint votingDeadline; | |
| bool open; | |
| bool proposalPassed; | |
| bytes32 proposalHash; | |
| uint proposalDeposit; | |
| bool newCurator; | |
| //SplitData[] splitData; | |
| uint yea; | |
| uint nay; | |
| // mapping (address => bool) votedYes; | |
| // mapping (address => bool) votedNo; | |
| address creator; | |
| } | |
| uint public minQuorumDivisor; | |
| uint public totalSupply; | |
| function balanceOf(address _owner) constant returns (uint256 balance); | |
| } | |
| // A price oracle so that the contract can be set in any currency | |
| contract PriceOracle { | |
| function convertToWei(uint units) returns (uint amountOfWei) { | |
| return units * 1 ether; | |
| } | |
| } | |
| /* | |
| An Offer from a Contractor to the DAO without any reward going back to | |
| the DAO. | |
| Feel free to use as a template for your own proposal. | |
| Actors: | |
| - Offerer: the entity that creates the Offer. Usually it is the initial | |
| Contractor. | |
| - Contractor: the entity that has rights to withdraw ether to perform | |
| its project. | |
| - Client: the DAO that gives ether to the Contractor. It signs off | |
| the Offer, can adjust daily withdraw limit or even fire the | |
| Contractor. | |
| */ | |
| contract DAOProposal { | |
| // Period of time after which money can be withdrawn from this contract | |
| uint constant payoutFreezePeriod = 3 weeks; | |
| // The address of the Contractor. | |
| DAO client = DAO(0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413); // address of DAO | |
| DAO originalClient = client; // address of DAO who signed the contract | |
| address public contractor; | |
| address public curators = 0xDa4a4626d3E16e094De3225A751aAb7128e96526; | |
| address public milestoneOracle; | |
| PriceOracle public priceFeed; | |
| bytes32 public clientOracleCandidates; | |
| bytes32 public contractorOracleCandidates; | |
| // The hash of the Proposal/Offer document. | |
| bytes32 public hashOfTheProposalDocument; | |
| uint public dateOfSignature; | |
| uint initialPayment; | |
| uint totalPayments; | |
| uint proposalId; | |
| bool wasApprovedBeforeDeadline; | |
| uint maxContractDuration; | |
| address[256] supporters; | |
| uint maxSupportGathered; | |
| uint lastSupportGathered; | |
| uint lastCheckedSupport; | |
| // creates contract states and milestones | |
| enum State { PreVote, Voting, Approved, Active, Void } | |
| State public state; | |
| struct Milestone { | |
| uint value; | |
| uint validAfter; | |
| bool completed; | |
| bool paid; | |
| } | |
| Milestone[] public milestones; | |
| uint public totalMilestones; | |
| // Some functions can only called by The DAO | |
| modifier onlyClient { | |
| if (msg.sender != address(client)) | |
| throw; | |
| _ | |
| } | |
| // Some functions can only called by The DAO | |
| modifier onlyContractor { | |
| if (msg.sender != address(contractor)) | |
| throw; | |
| _ | |
| } | |
| // Checks the state of the contract | |
| modifier inState(State _state) { | |
| if (state != _state) throw; | |
| _ | |
| } | |
| // Prevents methods from perfoming any value transfer | |
| modifier noEther() {if (msg.value > 0) throw; _} | |
| // STAGES OF A PROPOSAL | |
| /* | |
| STATE: PREVOTE | |
| ============== | |
| */ | |
| // When the proposal is created it's in a "prevote" state where it can be analyzed and | |
| // optionally public support might be gathered | |
| function DAOProposal( | |
| address _contractor, | |
| bytes32 _hashOfTheProposalDocument, | |
| uint _initialPayout, | |
| address _milestoneOracle, | |
| address _priceFeed, | |
| uint[] milestoneDates, | |
| uint[] milestoneValues, | |
| uint _maxContractDuration | |
| ) { | |
| contractor = _contractor; | |
| hashOfTheProposalDocument = _hashOfTheProposalDocument; | |
| state = State.PreVote; | |
| initialPayment = _initialPayout; | |
| totalPayments = _initialPayout; | |
| milestoneOracle = _milestoneOracle; | |
| priceFeed = PriceOracle(_priceFeed); | |
| totalMilestones = milestoneValues.length; | |
| maxContractDuration = _maxContractDuration; | |
| for (uint i=0; i< milestoneValues.length; i++) { | |
| milestones.length++; | |
| milestones[i] = Milestone({ | |
| value: milestoneValues[i], | |
| validAfter: milestoneDates[i], | |
| completed: false, | |
| paid: false | |
| }) ; | |
| totalPayments += milestoneValues[i]; | |
| } | |
| } | |
| // This allows an option where token holders can show support for a proposal before it goes live | |
| // This can be used by the contractor as a way to change the terms if there's not much support | |
| // Or can be used by curators as a way to know which proposal to prioritize on the whitelist | |
| function getSupport() inState(State.PreVote) { | |
| bool loop = true; | |
| uint currentSupport = 0; | |
| lastCheckedSupport = now; | |
| uint smallestSupporter = 256; | |
| uint smallestSupporterBalance = 10^100; // make it a googol | |
| uint thisBalance = 0; | |
| for (uint i = 0; i < 256; i++) { | |
| thisBalance = client.balanceOf(supporters[i]); | |
| currentSupport += thisBalance; | |
| if ( thisBalance < smallestSupporterBalance) { | |
| smallestSupporterBalance = thisBalance; | |
| smallestSupporter = i; | |
| } | |
| } | |
| uint senderBalance = client.balanceOf(msg.sender); | |
| if (senderBalance > smallestSupporterBalance) { | |
| supporters[smallestSupporter] = msg.sender; | |
| currentSupport = currentSupport - smallestSupporterBalance + senderBalance; | |
| } | |
| if (currentSupport > maxSupportGathered) | |
| maxSupportGathered = currentSupport; | |
| lastSupportGathered = currentSupport; | |
| } | |
| /* | |
| STATE: VOTING | |
| ============= | |
| */ | |
| // Once a proposal is submitted, the Contractor can save its proposalId so | |
| // the vote can be watched | |
| function watchProposal(uint _proposalId) inState(State.PreVote) onlyContractor { | |
| address recipient; | |
| uint votingDeadline; | |
| bool open; | |
| (recipient,,votingDeadline, open, ) = client.proposals(_proposalId); | |
| if (recipient == address(this) | |
| && votingDeadline > now | |
| && open | |
| && proposalId == 0 | |
| ) { | |
| proposalId = _proposalId; | |
| state = State.Voting; | |
| } | |
| } | |
| // The proposal will not accept the results of the vote if it wasn't able to | |
| // be sure that YEA was able to suceed 48 hours before the deadline | |
| function protectFromAmbush() { | |
| // This function can be called by anyone | |
| uint votingDeadline; | |
| uint yea; | |
| uint nay; | |
| (,,votingDeadline,,,,,,yea,nay,) = client.proposals(proposalId); | |
| // Only execute until 48 hours before the deadline | |
| if (now > votingDeadline - 48 hours) throw; | |
| // If so, then check if was approved at that moment | |
| wasApprovedBeforeDeadline = (yea > nay); | |
| } | |
| /* | |
| STATE: APPROVED | |
| =============== | |
| */ | |
| // The contract is considered "signed" when the DAO sends money to it | |
| function() { | |
| if (msg.sender == address(client) | |
| && state == State.Voting) { | |
| uint votingDeadline; | |
| uint yea; | |
| uint nay; | |
| (,,votingDeadline,,,,,,yea,nay,) = client.proposals(proposalId); | |
| uint quorum = client.totalSupply() / client.minQuorumDivisor(); | |
| // Check if Yea is at least 75% of the quorum and if it was not approved by ambush | |
| if (4 * yea < 3 * quorum | |
| || !wasApprovedBeforeDeadline) { | |
| client.send(this.balance); | |
| state = State.Void; | |
| } else { | |
| dateOfSignature = now; | |
| state = State.Approved; | |
| } | |
| } | |
| } | |
| // After a contract is approved, it can still be cancelled by the DAO or curators for 3 weeks | |
| function cancelContract() inState(State.Approved) { | |
| if (msg.sender != address(client) || msg.sender != curators) | |
| throw; | |
| state = State.Void; | |
| endContract(); | |
| } | |
| /* | |
| STATE: ACTIVE | |
| ============= | |
| */ | |
| // Once the 3 weeks are up, the contract can be activated and first payment made | |
| function activateContract() inState(State.Approved) { | |
| if (now < dateOfSignature + payoutFreezePeriod) throw; | |
| state = State.Active; | |
| if (!contractor.send( priceFeed.convertToWei( initialPayment ))) throw; | |
| } | |
| // The milestoneOracle can approve or disapprove milestones | |
| // Milestones can be used for multiple payouts by simply using | |
| // the contractor as the oracle and using "validAfter" dates. | |
| function reachMilestone(uint milestoneId, bool reached) { | |
| if (msg.sender != address(milestoneOracle)) | |
| throw; | |
| Milestone m = milestones[milestoneId]; | |
| m.completed = reached; | |
| } | |
| // Once the oracle has approved it and the minimun date is reached, then the payment can be made | |
| // Payment is in uints set by the price oracle, agreed by client and contractor | |
| // If ether price raises, the remainder can be returned to the client | |
| // If ether price falls and the contract becomes insolvent, further funding will be required | |
| function receiveMilestonePayment(uint milestoneId) { | |
| Milestone m = milestones[milestoneId]; | |
| if ( now < m.validAfter | |
| || !m.completed | |
| || !contractor.send( priceFeed.convertToWei( m.value ))) throw; | |
| m.paid = true; | |
| // Check all milestones | |
| Milestone mm = milestones[0]; | |
| uint totalMilestonesPaid = 0; | |
| for (uint i=0; i< totalMilestones; i++) { | |
| if (mm.paid) totalMilestonesPaid++; | |
| } | |
| if (totalMilestonesPaid == totalMilestones) | |
| state = State.Void; | |
| } | |
| /* | |
| STATE: VOID | |
| =========== | |
| */ | |
| // Contract can be terminated once it has reached its maximum duration, | |
| // if it has completed all milestones or during the approval period | |
| // by the DAO or Curators | |
| function endContract() { | |
| if (state != State.Void || now > dateOfSignature + maxContractDuration * 1 days) | |
| throw; | |
| client.send(this.balance); | |
| suicide(client); | |
| } | |
| /* | |
| STATE: ANY | |
| ========== | |
| */ | |
| // Change the client DAO by giving the new DAO's address | |
| // warning: The new DAO must come either from a split of the original | |
| // DAO or an update via `newContract()` so that it can claim rewards | |
| function updateClientAddress(DAO _newClient) onlyClient noEther { | |
| client = _newClient; | |
| } | |
| // Change the contractor payout address | |
| function updateContractorAddress(DAO _newClient) onlyContractor noEther { | |
| client = _newClient; | |
| } | |
| // Change the Oracles | |
| function changeOracles(address newMilestoneOracle, address newPriceFeedOracle) | |
| returns (bool changedIt) { | |
| if (msg.sender == address(client)) { | |
| clientOracleCandidates = sha3(newMilestoneOracle, newPriceFeedOracle); | |
| } else if (msg.sender == contractor) { | |
| contractorOracleCandidates = sha3(newMilestoneOracle, newPriceFeedOracle); | |
| } | |
| if (clientOracleCandidates == contractorOracleCandidates) { | |
| milestoneOracle = newMilestoneOracle; | |
| priceFeed = PriceOracle(newPriceFeedOracle); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| } | |
| contract MyProposal is DAOProposal { | |
| // Add here your own code | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment