464 lines
14 KiB
Solidity
464 lines
14 KiB
Solidity
pragma solidity ^0.4.11;
|
|
|
|
|
|
/// @title StandardBounty
|
|
/// @dev Used to pay out individuals or groups for task fulfillment through
|
|
/// stepwise work submission, acceptance, and payment
|
|
/// @author Mark Beylin <mark.beylin@consensys.net>, Gonçalo Sá <goncalo.sa@consensys.net>
|
|
contract StandardBounty {
|
|
|
|
uint constant public MAX_FULFILLMENTS = 254;
|
|
|
|
/*
|
|
* Events
|
|
*/
|
|
|
|
event BountyActivated(address issuer);
|
|
event BountyFulfilled(address indexed fulfiller, uint256 indexed _fulfillmentId, uint256 indexed _milestoneId);
|
|
event FulfillmentAccepted(address indexed fulfiller, uint256 indexed _fulfillmentId, uint256 indexed _milestoneId);
|
|
event FulfillmentPaid(address indexed fulfiller, uint256 indexed _fulfillmentId, uint256 indexed _milestoneId);
|
|
event BountyKilled();
|
|
event ContributionAdded(address indexed contributor, uint256 value);
|
|
event DeadlineExtended(uint newDeadline);
|
|
|
|
/*
|
|
* Storage
|
|
*/
|
|
|
|
address public issuer; // the creator of the bounty
|
|
string public issuerContact; // string of a contact method used to reach the issuer in case it is needed
|
|
|
|
address public arbiter; // should mediate conflicts, has the power to accept work
|
|
|
|
BountyStages public bountyStage; // the stage of the bounty
|
|
|
|
uint public deadline; // unix timestamp before which all work must be submitted
|
|
string public data; // a hash of data representing the requirements for each milestone of the bounty, along with any associated files
|
|
|
|
uint public numMilestones; // the total number of milestones
|
|
uint[] public fulfillmentAmounts; // the amount of wei to be rewarded to the user who fulfills the i'th milestone
|
|
|
|
mapping(uint=>Fulfillment[]) public fulfillments; // all submitted fulfillments for each milestone
|
|
mapping(uint=>uint) public numFulfillments; // the number of submitted fulfillments for each milestone
|
|
|
|
mapping(uint=>uint) public numAccepted; // the number of accepted fulfillments for each milestone
|
|
mapping(uint=>uint) public numPaid; // the number of paid fulfillments for each milestone
|
|
|
|
/*
|
|
* Enums
|
|
*/
|
|
|
|
enum BountyStages {
|
|
Draft,
|
|
Active,
|
|
Dead
|
|
}
|
|
|
|
/*
|
|
* Structs
|
|
*/
|
|
|
|
struct Fulfillment {
|
|
bool paid;
|
|
bool accepted;
|
|
address fulfiller;
|
|
string data;
|
|
string dataType;
|
|
}
|
|
|
|
/*
|
|
* Modifiers
|
|
*/
|
|
|
|
modifier onlyIssuer() {
|
|
require(msg.sender == issuer);
|
|
_;
|
|
}
|
|
modifier onlyIssuerOrArbiter() {
|
|
require(msg.sender == issuer || msg.sender == arbiter);
|
|
_;
|
|
}
|
|
modifier notIssuerOrArbiter() {
|
|
require(msg.sender != issuer && msg.sender != arbiter);
|
|
_;
|
|
}
|
|
|
|
modifier onlyFulfiller(uint _fulfillmentId, uint _milestoneId) {
|
|
require(msg.sender == fulfillments[_milestoneId][_fulfillmentId].fulfiller);
|
|
_;
|
|
}
|
|
modifier amountIsNotZero(uint amount) {
|
|
require(amount != 0);
|
|
_;
|
|
}
|
|
modifier amountsNotZero(uint[] amount) {
|
|
for (uint i = 0; i < amount.length; i++){
|
|
require(amount[i] != 0);
|
|
}
|
|
_;
|
|
}
|
|
|
|
modifier amountEqualsValue(uint amount) {
|
|
require((amount * 1 wei) == msg.value);
|
|
_;
|
|
}
|
|
|
|
modifier isBeforeDeadline() {
|
|
require(now < deadline);
|
|
_;
|
|
}
|
|
|
|
modifier validateDeadline(uint newDeadline) {
|
|
require(newDeadline > now);
|
|
_;
|
|
}
|
|
|
|
modifier newDeadlineIsValid(uint newDeadline) {
|
|
require(newDeadline > deadline);
|
|
_;
|
|
}
|
|
|
|
modifier isAtStage(BountyStages desiredStage) {
|
|
require(bountyStage == desiredStage);
|
|
_;
|
|
}
|
|
|
|
modifier isNotDraft () {
|
|
require(bountyStage != BountyStages.Draft);
|
|
_;
|
|
}
|
|
|
|
modifier isNotDead() {
|
|
require(bountyStage != BountyStages.Dead);
|
|
_;
|
|
}
|
|
|
|
modifier checkFulfillmentsNumber(uint _milestoneId) {
|
|
require(numFulfillments[_milestoneId] < MAX_FULFILLMENTS);
|
|
_;
|
|
}
|
|
|
|
modifier validateFulfillmentArrayIndex(uint index, uint _milestoneId) {
|
|
require(index < numFulfillments[_milestoneId]);
|
|
_;
|
|
}
|
|
|
|
modifier validateMilestoneIndex(uint _milestoneId){
|
|
require(_milestoneId < numMilestones);
|
|
_;
|
|
}
|
|
|
|
modifier checkFulfillmentIsApprovedAndUnpaid(uint _fulfillmentId, uint _milestoneId) {
|
|
require(fulfillments[_milestoneId][_fulfillmentId].accepted && !fulfillments[_milestoneId][_fulfillmentId].paid);
|
|
_;
|
|
}
|
|
|
|
|
|
modifier validateFunding() {
|
|
|
|
uint total = 0;
|
|
for (uint i = 0 ; i < numMilestones; i++){
|
|
total += fulfillmentAmounts[i];
|
|
}
|
|
|
|
require (this.balance >= (total + unpaidAmount()));
|
|
_;
|
|
}
|
|
|
|
modifier correctLengths(uint _numMilestones, uint _length) {
|
|
require(_numMilestones == _length);
|
|
_;
|
|
}
|
|
|
|
modifier unpaidAmountRemains(uint _milestoneId) {
|
|
require((unpaidAmount() + fulfillmentAmounts[_milestoneId]) < this.balance);
|
|
_;
|
|
}
|
|
|
|
|
|
/*
|
|
* Public functions
|
|
*/
|
|
|
|
/// @dev StandardBounty(): instantiates a new draft bounty
|
|
/// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
|
|
/// @param _contactInfo a string with contact info of the issuer, for them to be contacted if needed
|
|
/// @param _data the requirements of the bounty
|
|
/// @param _fulfillmentAmounts the amount of wei to be paid out for each successful fulfillment
|
|
/// @param _numMilestones the total number of milestones which can be paid out
|
|
/// @param _arbiter the address of the arbiter who can mediate claims
|
|
function StandardBounty(
|
|
uint _deadline,
|
|
string _contactInfo,
|
|
string _data,
|
|
uint[] _fulfillmentAmounts,
|
|
uint _numMilestones,
|
|
address _arbiter
|
|
)
|
|
amountsNotZero(_fulfillmentAmounts)
|
|
validateDeadline(_deadline)
|
|
correctLengths(_numMilestones, _fulfillmentAmounts.length)
|
|
{
|
|
issuer = tx.origin; //this allows for the issuance of a bounty using a factory
|
|
issuerContact = _contactInfo;
|
|
bountyStage = BountyStages.Draft;
|
|
deadline = _deadline;
|
|
data = _data;
|
|
numMilestones = _numMilestones;
|
|
arbiter = _arbiter;
|
|
|
|
fulfillmentAmounts = _fulfillmentAmounts;
|
|
}
|
|
|
|
/// @dev contribute(): a function allowing anyone to contribute ether to a
|
|
/// bounty, as long as it is still before its deadline. Shouldn't
|
|
/// keep ether by accident (hence 'value').
|
|
/// @notice Please note you funds will be at the mercy of the issuer
|
|
/// and can be drained at any moment. Be careful!
|
|
/// @param value the amount being contributed in ether to prevent
|
|
/// accidental deposits
|
|
function contribute (uint value)
|
|
payable
|
|
isBeforeDeadline
|
|
isNotDead
|
|
amountIsNotZero(value)
|
|
amountEqualsValue(value)
|
|
{
|
|
ContributionAdded(msg.sender, msg.value);
|
|
}
|
|
|
|
/// @notice Send funds to activate the bug bounty
|
|
/// @dev activateBounty(): activate a bounty so it may pay out
|
|
/// @param value the amount being contributed in ether to prevent
|
|
/// accidental deposits
|
|
function activateBounty(uint value)
|
|
payable
|
|
public
|
|
isBeforeDeadline
|
|
onlyIssuer
|
|
amountEqualsValue(value)
|
|
validateFunding
|
|
{
|
|
transitionToState(BountyStages.Active);
|
|
|
|
ContributionAdded(msg.sender, msg.value);
|
|
BountyActivated(msg.sender);
|
|
}
|
|
|
|
/// @dev fulfillBounty(): submit a fulfillment for the given bounty
|
|
/// @param _data the data artifacts representing the fulfillment of the bounty
|
|
/// @param _dataType a meaningful description of the type of data the fulfillment represents
|
|
/// @param _milestoneId the id of the milestone being fulfilled
|
|
function fulfillBounty(string _data, string _dataType, uint _milestoneId)
|
|
public
|
|
isAtStage(BountyStages.Active)
|
|
isBeforeDeadline
|
|
checkFulfillmentsNumber(_milestoneId)
|
|
notIssuerOrArbiter
|
|
{
|
|
fulfillments[_milestoneId].push(Fulfillment(false, false, msg.sender, _data, _dataType));
|
|
|
|
BountyFulfilled(msg.sender, numFulfillments[_milestoneId]++, _milestoneId);
|
|
}
|
|
|
|
/// @dev acceptFulfillment(): accept a given fulfillment
|
|
/// @param _fulfillmentId the index of the fulfillment being accepted
|
|
/// @param _milestoneId the id of the milestone being accepted
|
|
function acceptFulfillment(uint _fulfillmentId, uint _milestoneId)
|
|
public
|
|
onlyIssuerOrArbiter
|
|
isAtStage(BountyStages.Active)
|
|
validateMilestoneIndex(_milestoneId)
|
|
validateFulfillmentArrayIndex(_fulfillmentId, _milestoneId)
|
|
unpaidAmountRemains(_milestoneId)
|
|
{
|
|
fulfillments[_milestoneId][_fulfillmentId].accepted = true;
|
|
numAccepted[_milestoneId]++;
|
|
|
|
FulfillmentAccepted(msg.sender, _fulfillmentId, _milestoneId);
|
|
}
|
|
|
|
/// @dev fulfillmentPayment(): pay the fulfiller for their work
|
|
/// @param _fulfillmentId the index of the fulfillment being accepted
|
|
/// @param _milestoneId the id of the milestone being paid
|
|
function fulfillmentPayment(uint _fulfillmentId, uint _milestoneId)
|
|
public
|
|
validateMilestoneIndex(_milestoneId)
|
|
validateFulfillmentArrayIndex(_fulfillmentId, _milestoneId)
|
|
onlyFulfiller(_fulfillmentId, _milestoneId)
|
|
checkFulfillmentIsApprovedAndUnpaid(_fulfillmentId, _milestoneId)
|
|
{
|
|
fulfillments[_milestoneId][_fulfillmentId].fulfiller.transfer(fulfillmentAmounts[_milestoneId]);
|
|
fulfillments[_milestoneId][_fulfillmentId].paid = true;
|
|
|
|
numPaid[_milestoneId]++;
|
|
|
|
FulfillmentPaid(msg.sender, _fulfillmentId, _milestoneId);
|
|
}
|
|
|
|
/// @dev killBounty(): drains the contract of it's remaining
|
|
/// funds, and moves the bounty into stage 3 (dead) since it was
|
|
/// either killed in draft stage, or never accepted any fulfillments
|
|
function killBounty()
|
|
public
|
|
onlyIssuer
|
|
{
|
|
issuer.transfer(this.balance - unpaidAmount());
|
|
|
|
transitionToState(BountyStages.Dead);
|
|
|
|
BountyKilled();
|
|
}
|
|
|
|
/// @dev extendDeadline(): allows the issuer to add more time to the
|
|
/// bounty, allowing it to continue accepting fulfillments
|
|
/// @param _newDeadline the new deadline in timestamp format
|
|
function extendDeadline(uint _newDeadline)
|
|
public
|
|
onlyIssuer
|
|
newDeadlineIsValid(_newDeadline)
|
|
{
|
|
deadline = _newDeadline;
|
|
|
|
DeadlineExtended(_newDeadline);
|
|
}
|
|
|
|
/// @dev changeBounty(): allows the issuer to change all bounty storage
|
|
/// members simultaneously
|
|
/// @param _newDeadline the new deadline for the bounty
|
|
/// @param _newContactInfo the new contact information for the issuer
|
|
/// @param _newData the new requirements of the bounty
|
|
/// @param _newFulfillmentAmounts the new fulfillment amounts
|
|
/// @param _newNumMilestones the number of milestones which can be fulfilled
|
|
function changeBounty(uint _newDeadline,
|
|
string _newContactInfo,
|
|
string _newData,
|
|
uint[] _newFulfillmentAmounts,
|
|
uint _newNumMilestones,
|
|
address _newArbiter)
|
|
public
|
|
onlyIssuer
|
|
validateDeadline(_newDeadline)
|
|
amountsNotZero(_newFulfillmentAmounts)
|
|
correctLengths(_newNumMilestones, _newFulfillmentAmounts.length)
|
|
isAtStage(BountyStages.Draft)
|
|
{
|
|
deadline = _newDeadline;
|
|
issuerContact = _newContactInfo;
|
|
data = _newData;
|
|
fulfillmentAmounts = _newFulfillmentAmounts;
|
|
numMilestones = _newNumMilestones;
|
|
arbiter = _newArbiter;
|
|
}
|
|
|
|
/// @dev changeDeadline(): allows the issuer to change the deadline of the bounty
|
|
/// @param _newDeadline the new deadline
|
|
function changeDeadline(uint _newDeadline)
|
|
public
|
|
onlyIssuer
|
|
validateDeadline(_newDeadline)
|
|
isAtStage(BountyStages.Draft)
|
|
{
|
|
deadline = _newDeadline;
|
|
}
|
|
|
|
/// @dev changeData(): allows the issuer to change the data of the bounty
|
|
/// @param _newData the new data
|
|
function changeData(string _newData)
|
|
public
|
|
onlyIssuer
|
|
isAtStage(BountyStages.Draft)
|
|
{
|
|
data = _newData;
|
|
}
|
|
|
|
/// @dev changeContact(): allows the issuer to change the contact of the bounty
|
|
/// @param _newContact the new data
|
|
function changeContact(string _newContact)
|
|
public
|
|
onlyIssuer
|
|
isAtStage(BountyStages.Draft)
|
|
{
|
|
issuerContact = _newContact;
|
|
}
|
|
|
|
/// @dev changeArbiter(): allows the issuer to change the arbiter of the bounty
|
|
/// @param _newArbiter the new data
|
|
function changeArbiter(address _newArbiter)
|
|
public
|
|
onlyIssuer
|
|
isAtStage(BountyStages.Draft)
|
|
{
|
|
arbiter = _newArbiter;
|
|
}
|
|
|
|
/// @dev changeFulfillmentAmounts(): allows the issuer to change the
|
|
/// fulfillment amounts of the bounty
|
|
/// @param _newFulfillmentAmounts the new fulfillment amounts
|
|
/// @param _numMilestones the number of milestones which can be fulfilled
|
|
function changeFulfillmentAmounts(uint[] _newFulfillmentAmounts, uint _numMilestones)
|
|
public
|
|
onlyIssuer
|
|
amountsNotZero(_newFulfillmentAmounts)
|
|
correctLengths(_numMilestones, _newFulfillmentAmounts.length)
|
|
isAtStage(BountyStages.Draft)
|
|
{
|
|
fulfillmentAmounts = _newFulfillmentAmounts;
|
|
}
|
|
|
|
/// @dev getFulfillment(): Returns the fulfillment at a given index
|
|
/// @param _fulfillmentId the index of the fulfillment to return
|
|
/// @param _milestoneId the index of the milestone to return
|
|
function getFulfillment(uint _fulfillmentId, uint _milestoneId)
|
|
public
|
|
constant
|
|
returns (bool, bool, address, string, string)
|
|
{
|
|
return (fulfillments[_milestoneId][_fulfillmentId].paid,
|
|
fulfillments[_milestoneId][_fulfillmentId].accepted,
|
|
fulfillments[_milestoneId][_fulfillmentId].fulfiller,
|
|
fulfillments[_milestoneId][_fulfillmentId].data,
|
|
fulfillments[_milestoneId][_fulfillmentId].dataType);
|
|
}
|
|
|
|
/// @dev getBounty(): Returns the details of the bounty
|
|
function getBounty()
|
|
public
|
|
constant
|
|
returns (address, string, uint, uint, string, uint)
|
|
{
|
|
return (issuer,
|
|
issuerContact,
|
|
uint(bountyStage),
|
|
deadline,
|
|
data,
|
|
numMilestones);
|
|
}
|
|
|
|
/// @dev unpaidAmount(): calculates the amount which
|
|
/// the bounty has yet to pay out
|
|
function unpaidAmount()
|
|
public
|
|
constant
|
|
returns (uint amount)
|
|
{
|
|
for (uint i = 0; i < numMilestones; i++){
|
|
amount += fulfillmentAmounts[i] * (numAccepted[i] - numPaid[i]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Internal functions
|
|
*/
|
|
|
|
/// @dev transitionToState(): transitions the contract to the
|
|
/// state passed in the parameter `_newStage` given the
|
|
/// conditions stated in the body of the function
|
|
/// @param _newStage the new stage to transition to
|
|
function transitionToState(BountyStages _newStage)
|
|
internal
|
|
{
|
|
bountyStage = _newStage;
|
|
}
|
|
}
|