aboutsummaryrefslogtreecommitdiffstats
path: root/test/compilationTests/milestonetracker/MilestoneTracker.sol
diff options
context:
space:
mode:
Diffstat (limited to 'test/compilationTests/milestonetracker/MilestoneTracker.sol')
-rw-r--r--test/compilationTests/milestonetracker/MilestoneTracker.sol367
1 files changed, 367 insertions, 0 deletions
diff --git a/test/compilationTests/milestonetracker/MilestoneTracker.sol b/test/compilationTests/milestonetracker/MilestoneTracker.sol
new file mode 100644
index 00000000..318330df
--- /dev/null
+++ b/test/compilationTests/milestonetracker/MilestoneTracker.sol
@@ -0,0 +1,367 @@
+pragma solidity ^0.4.6;
+
+/*
+ Copyright 2016, Jordi Baylina
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// @title MilestoneTracker Contract
+/// @author Jordi Baylina
+/// @dev This contract tracks the
+
+
+/// is rules the relation betwen a donor and a recipient
+/// in order to guaranty to the donor that the job will be done and to guaranty
+/// to the recipient that he will be paid
+
+
+/// @dev We use the RLP library to decode RLP so that the donor can approve one
+/// set of milestone changes at a time.
+/// https://github.com/androlo/standard-contracts/blob/master/contracts/src/codec/RLP.sol
+import "RLP.sol";
+
+
+
+/// @dev This contract allows for `recipient` to set and modify milestones
+contract MilestoneTracker {
+ using RLP for RLP.RLPItem;
+ using RLP for RLP.Iterator;
+ using RLP for bytes;
+
+ struct Milestone {
+ string description; // Description of this milestone
+ string url; // A link to more information (swarm gateway)
+ uint minCompletionDate; // Earliest UNIX time the milestone can be paid
+ uint maxCompletionDate; // Latest UNIX time the milestone can be paid
+ address milestoneLeadLink;
+ // Similar to `recipient`but for this milestone
+ address reviewer; // Can reject the completion of this milestone
+ uint reviewTime; // How many seconds the reviewer has to review
+ address paymentSource; // Where the milestone payment is sent from
+ bytes payData; // Data defining how much ether is sent where
+
+ MilestoneStatus status; // Current status of the milestone
+ // (Completed, AuthorizedForPayment...)
+ uint doneTime; // UNIX time when the milestone was marked DONE
+ }
+
+ // The list of all the milestones.
+ Milestone[] public milestones;
+
+ address public recipient; // Calls functions in the name of the recipient
+ address public donor; // Calls functions in the name of the donor
+ address public arbitrator; // Calls functions in the name of the arbitrator
+
+ enum MilestoneStatus {
+ AcceptedAndInProgress,
+ Completed,
+ AuthorizedForPayment,
+ Canceled
+ }
+
+ // True if the campaign has been canceled
+ bool public campaignCanceled;
+
+ // True if an approval on a change to `milestones` is a pending
+ bool public changingMilestones;
+
+ // The pending change to `milestones` encoded in RLP
+ bytes public proposedMilestones;
+
+
+ /// @dev The following modifiers only allow specific roles to call functions
+ /// with these modifiers
+ modifier onlyRecipient { if (msg.sender != recipient) throw; _; }
+ modifier onlyArbitrator { if (msg.sender != arbitrator) throw; _; }
+ modifier onlyDonor { if (msg.sender != donor) throw; _; }
+
+ /// @dev The following modifiers prevent functions from being called if the
+ /// campaign has been canceled or if new milestones are being proposed
+ modifier campaignNotCanceled { if (campaignCanceled) throw; _; }
+ modifier notChanging { if (changingMilestones) throw; _; }
+
+ // @dev Events to make the payment movements easy to find on the blockchain
+ event NewMilestoneListProposed();
+ event NewMilestoneListUnproposed();
+ event NewMilestoneListAccepted();
+ event ProposalStatusChanged(uint idProposal, MilestoneStatus newProposal);
+ event CampaignCanceled();
+
+
+///////////
+// Constructor
+///////////
+
+ /// @notice The Constructor creates the Milestone contract on the blockchain
+ /// @param _arbitrator Address assigned to be the arbitrator
+ /// @param _donor Address assigned to be the donor
+ /// @param _recipient Address assigned to be the recipient
+ function MilestoneTracker (
+ address _arbitrator,
+ address _donor,
+ address _recipient
+ ) {
+ arbitrator = _arbitrator;
+ donor = _donor;
+ recipient = _recipient;
+ }
+
+
+/////////
+// Helper functions
+/////////
+
+ /// @return The number of milestones ever created even if they were canceled
+ function numberOfMilestones() constant returns (uint) {
+ return milestones.length;
+ }
+
+
+////////
+// Change players
+////////
+
+ /// @notice `onlyArbitrator` Reassigns the arbitrator to a new address
+ /// @param _newArbitrator The new arbitrator
+ function changeArbitrator(address _newArbitrator) onlyArbitrator {
+ arbitrator = _newArbitrator;
+ }
+
+ /// @notice `onlyDonor` Reassigns the `donor` to a new address
+ /// @param _newDonor The new donor
+ function changeDonor(address _newDonor) onlyDonor {
+ donor = _newDonor;
+ }
+
+ /// @notice `onlyRecipient` Reassigns the `recipient` to a new address
+ /// @param _newRecipient The new recipient
+ function changeRecipient(address _newRecipient) onlyRecipient {
+ recipient = _newRecipient;
+ }
+
+
+////////////
+// Creation and modification of Milestones
+////////////
+
+ /// @notice `onlyRecipient` Proposes new milestones or changes old
+ /// milestones, this will require a user interface to be built up to
+ /// support this functionality as asks for RLP encoded bytecode to be
+ /// generated, until this interface is built you can use this script:
+ /// https://github.com/Giveth/milestonetracker/blob/master/js/milestonetracker_helper.js
+ /// the functions milestones2bytes and bytes2milestones will enable the
+ /// recipient to encode and decode a list of milestones, also see
+ /// https://github.com/Giveth/milestonetracker/blob/master/README.md
+ /// @param _newMilestones The RLP encoded list of milestones; each milestone
+ /// has these fields:
+ /// string description,
+ /// string url,
+ /// uint minCompletionDate, // seconds since 1/1/1970 (UNIX time)
+ /// uint maxCompletionDate, // seconds since 1/1/1970 (UNIX time)
+ /// address milestoneLeadLink,
+ /// address reviewer,
+ /// uint reviewTime
+ /// address paymentSource,
+ /// bytes payData,
+ function proposeMilestones(bytes _newMilestones
+ ) onlyRecipient campaignNotCanceled {
+ proposedMilestones = _newMilestones;
+ changingMilestones = true;
+ NewMilestoneListProposed();
+ }
+
+
+////////////
+// Normal actions that will change the state of the milestones
+////////////
+
+ /// @notice `onlyRecipient` Cancels the proposed milestones and reactivates
+ /// the previous set of milestones
+ function unproposeMilestones() onlyRecipient campaignNotCanceled {
+ delete proposedMilestones;
+ changingMilestones = false;
+ NewMilestoneListUnproposed();
+ }
+
+ /// @notice `onlyDonor` Approves the proposed milestone list
+ /// @param _hashProposals The sha3() of the proposed milestone list's
+ /// bytecode; this confirms that the `donor` knows the set of milestones
+ /// they are approving
+ function acceptProposedMilestones(bytes32 _hashProposals
+ ) onlyDonor campaignNotCanceled {
+
+ uint i;
+
+ if (!changingMilestones) throw;
+ if (sha3(proposedMilestones) != _hashProposals) throw;
+
+ // Cancel all the unfinished milestones
+ for (i=0; i<milestones.length; i++) {
+ if (milestones[i].status != MilestoneStatus.AuthorizedForPayment) {
+ milestones[i].status = MilestoneStatus.Canceled;
+ }
+ }
+ // Decode the RLP encoded milestones and add them to the milestones list
+ bytes memory mProposedMilestones = proposedMilestones;
+
+ var itmProposals = mProposedMilestones.toRLPItem(true);
+
+ if (!itmProposals.isList()) throw;
+
+ var itrProposals = itmProposals.iterator();
+
+ while(itrProposals.hasNext()) {
+
+
+ var itmProposal = itrProposals.next();
+
+ Milestone milestone = milestones[milestones.length ++];
+
+ if (!itmProposal.isList()) throw;
+
+ var itrProposal = itmProposal.iterator();
+
+ milestone.description = itrProposal.next().toAscii();
+ milestone.url = itrProposal.next().toAscii();
+ milestone.minCompletionDate = itrProposal.next().toUint();
+ milestone.maxCompletionDate = itrProposal.next().toUint();
+ milestone.milestoneLeadLink = itrProposal.next().toAddress();
+ milestone.reviewer = itrProposal.next().toAddress();
+ milestone.reviewTime = itrProposal.next().toUint();
+ milestone.paymentSource = itrProposal.next().toAddress();
+ milestone.payData = itrProposal.next().toData();
+
+ milestone.status = MilestoneStatus.AcceptedAndInProgress;
+
+ }
+
+ delete proposedMilestones;
+ changingMilestones = false;
+ NewMilestoneListAccepted();
+ }
+
+ /// @notice `onlyRecipientOrLeadLink`Marks a milestone as DONE and
+ /// ready for review
+ /// @param _idMilestone ID of the milestone that has been completed
+ function markMilestoneComplete(uint _idMilestone)
+ campaignNotCanceled notChanging
+ {
+ if (_idMilestone >= milestones.length) throw;
+ Milestone milestone = milestones[_idMilestone];
+ if ( (msg.sender != milestone.milestoneLeadLink)
+ &&(msg.sender != recipient))
+ throw;
+ if (milestone.status != MilestoneStatus.AcceptedAndInProgress) throw;
+ if (now < milestone.minCompletionDate) throw;
+ if (now > milestone.maxCompletionDate) throw;
+ milestone.status = MilestoneStatus.Completed;
+ milestone.doneTime = now;
+ ProposalStatusChanged(_idMilestone, milestone.status);
+ }
+
+ /// @notice `onlyReviewer` Approves a specific milestone
+ /// @param _idMilestone ID of the milestone that is approved
+ function approveCompletedMilestone(uint _idMilestone)
+ campaignNotCanceled notChanging
+ {
+ if (_idMilestone >= milestones.length) throw;
+ Milestone milestone = milestones[_idMilestone];
+ if ((msg.sender != milestone.reviewer) ||
+ (milestone.status != MilestoneStatus.Completed)) throw;
+
+ authorizePayment(_idMilestone);
+ }
+
+ /// @notice `onlyReviewer` Rejects a specific milestone's completion and
+ /// reverts the `milestone.status` back to the `AcceptedAndInProgress`
+ /// state
+ /// @param _idMilestone ID of the milestone that is being rejected
+ function rejectMilestone(uint _idMilestone)
+ campaignNotCanceled notChanging
+ {
+ if (_idMilestone >= milestones.length) throw;
+ Milestone milestone = milestones[_idMilestone];
+ if ((msg.sender != milestone.reviewer) ||
+ (milestone.status != MilestoneStatus.Completed)) throw;
+
+ milestone.status = MilestoneStatus.AcceptedAndInProgress;
+ ProposalStatusChanged(_idMilestone, milestone.status);
+ }
+
+ /// @notice `onlyRecipientOrLeadLink` Sends the milestone payment as
+ /// specified in `payData`; the recipient can only call this after the
+ /// `reviewTime` has elapsed
+ /// @param _idMilestone ID of the milestone to be paid out
+ function requestMilestonePayment(uint _idMilestone
+ ) campaignNotCanceled notChanging {
+ if (_idMilestone >= milestones.length) throw;
+ Milestone milestone = milestones[_idMilestone];
+ if ( (msg.sender != milestone.milestoneLeadLink)
+ &&(msg.sender != recipient))
+ throw;
+ if ((milestone.status != MilestoneStatus.Completed) ||
+ (now < milestone.doneTime + milestone.reviewTime))
+ throw;
+
+ authorizePayment(_idMilestone);
+ }
+
+ /// @notice `onlyRecipient` Cancels a previously accepted milestone
+ /// @param _idMilestone ID of the milestone to be canceled
+ function cancelMilestone(uint _idMilestone)
+ onlyRecipient campaignNotCanceled notChanging
+ {
+ if (_idMilestone >= milestones.length) throw;
+ Milestone milestone = milestones[_idMilestone];
+ if ((milestone.status != MilestoneStatus.AcceptedAndInProgress) &&
+ (milestone.status != MilestoneStatus.Completed))
+ throw;
+
+ milestone.status = MilestoneStatus.Canceled;
+ ProposalStatusChanged(_idMilestone, milestone.status);
+ }
+
+ /// @notice `onlyArbitrator` Forces a milestone to be paid out as long as it
+ /// has not been paid or canceled
+ /// @param _idMilestone ID of the milestone to be paid out
+ function arbitrateApproveMilestone(uint _idMilestone
+ ) onlyArbitrator campaignNotCanceled notChanging {
+ if (_idMilestone >= milestones.length) throw;
+ Milestone milestone = milestones[_idMilestone];
+ if ((milestone.status != MilestoneStatus.AcceptedAndInProgress) &&
+ (milestone.status != MilestoneStatus.Completed))
+ throw;
+ authorizePayment(_idMilestone);
+ }
+
+ /// @notice `onlyArbitrator` Cancels the entire campaign voiding all
+ /// milestones.
+ function arbitrateCancelCampaign() onlyArbitrator campaignNotCanceled {
+ campaignCanceled = true;
+ CampaignCanceled();
+ }
+
+ // @dev This internal function is executed when the milestone is paid out
+ function authorizePayment(uint _idMilestone) internal {
+ if (_idMilestone >= milestones.length) throw;
+ Milestone milestone = milestones[_idMilestone];
+ // Recheck again to not pay twice
+ if (milestone.status == MilestoneStatus.AuthorizedForPayment) throw;
+ milestone.status = MilestoneStatus.AuthorizedForPayment;
+ if (!milestone.paymentSource.call.value(0)(milestone.payData))
+ throw;
+ ProposalStatusChanged(_idMilestone, milestone.status);
+ }
+}