StandardBounties/docs/documentation.md

14 KiB

StandardBounties Complete Documentation

Version 0.0.1

Summary

A bounty is a simple mechanism for individuals or groups to pay out for the completion of tasks. The issuer of the bounty begins by deploying a new bounty contract, during which time any of the storage variables (like bounty requirements or payout amounts) can be altered. Once sufficient funds have been deposited into the contract, the issuer may activate the bounty, allowing bounty hunters to submit fulfillments for the various milestones. The issuer can then approve the submitted work, releasing the payout funds to the bounty hunter in question.

Contract Details

Storage

address public issuer The issuer is the creator of the bounty, and has full control over administering its rewards.

address public arbiter The arbiter is an individual or contract who is able to accept fulfillments on the issuer's behalf. The arbiter is also disallowed from fulfilling the bounty.

BountyStages public bountyStage Bounties are formed in the Draft stage, a period during which the issuer can edit any of the bounty's state variables, and attain sufficient funding. In the draft stage, no fulfillments can be submitted, and no funds can be paid out.

Once the bounty state variables are finalized, and the bounty contract holds sufficient funds to pay out each milestone at least once, it can be transitioned to the Active stage by only the issuer. During the active stage, requirements or payout amounts cannot be altered, however the deadline may be extended. Fulfillments can only be submitted in the Active stage before the deadline, although they may be accepted by the issuer or arbiter even after the deadline has passed. At any point, the issuer can kill the bounty returning all funds to them (less the amount due for already accepted but unpaid submissions), transitioning the bounty into the Dead stage. However, this behaviour is highly discouraged and should be avoided at all costs.

uint public deadline A bounty can only be contributed to, activated, or fulfilled before the given deadline, however fulfillments can be accepted even after the deadline has passed. This deadline can be moved forward or backwards in the draft stage, but once the bounty is activated it can only be extended. This helps maintain the contractual nature of the relationship, where issuers cannot move deadlines forward arbitrarily while individuals are fulfilling the tasks.

string public data All data representing the requirements are stored off-chain, and their hash is updated here. Requirements and auxiliary data are mutable while the bounty is in the Draft stage, but becomes immutable when the bounty is activated, thereby "locking in" the terms of the contract, the requirements for acceptance for each milestone. These should be as rich as possible from the outset, to avoid conflicts stemming from task fulfillers believing they merited the bounty reward.

uint[] public fulfillmentAmounts The total bounty amount is broken down into different payments for each milestone, allowing different individuals to fulfill different pieces of a bounty task. This array stores the amount of wei (or ERC20 token) which will pay out for each milestone when work is accepted. The length of this array is the number of milestones.

mapping(uint=>Fulfillment[]) public fulfillments Work is submitted and a hash is stored on-chain, allowing any deliverable to be submitted for the same bounty.

mapping(uint=>uint) public numAccepted The number of submissions which have been accepted for each milestone.

mapping(uint=>uint) public numPaid The number of submissions which have paid out to task fulfillers for each milestone. numPaid[i] is always less than or equal to numAccepted[i].

External functions

StandardBounty()

Constructs the bounty and instantiates state variables, initializing it in the draft stage. The contract gives tx.origin the issuer privileges so that a factory design pattern can be used. The bounty deadline must be after the time of issuance (contract deployment), and none of the milestones can pay out 0 tokens.

function StandardBounty(
    uint _deadline,
    string _contactInfo,
    string _data,
    uint256[] _fulfillmentAmounts,
    uint _totalFulfillmentAmounts,
    uint _numMilestones,
    address _arbiter
)
    amountsNotZeroAndEqualSum(_fulfillmentAmounts, _totalFulfillmentAmounts)
    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;
    totalFulfillmentAmounts = _totalFulfillmentAmounts;
}

Contribute()

This allows a bounty to receive 3rd party contributions from the crowd. This functionality is only available before the deadline has passed, and while the bounty is not in the Dead stage. The Ether (or tokens) which are deposited are at the mercy of the issuer, who can at any point call killBounty() to drain remaining funds.

function contribute (uint value)
    payable
    isBeforeDeadline
    isNotDead
    amountIsNotZero(value)
    amountEqualsValue(value)
{
    ContributionAdded(msg.sender, value);
}

ActivateBounty()

If the bounty has sufficient funds to pay out each milestone at least once, it can be activated, allowing individuals to add submissions. Only the issuer is allowed to activate their bounty.

function activateBounty(uint value)
    payable
    public
    isBeforeDeadline
    onlyIssuer
    amountEqualsValue(value)
    validateFunding
{
    transitionToState(BountyStages.Active);

    ContributionAdded(msg.sender, msg.value);
    BountyActivated(msg.sender);
}

FulfillBounty()

Once the bounty is active, anyone can fulfill it and submit the necessary deliverables for a given milestone (as long as the deadline has not passed). Anyone can fulfill the bounty, except for the issuer and arbiter, who are disallowed from doing so.

function fulfillBounty(string _data, string _dataType, uint _milestoneId)
    public
    isAtStage(BountyStages.Active)
    isBeforeDeadline
    validateMilestoneIndex(_milestoneId)
    checkFulfillmentsNumber(_milestoneId)
    notIssuerOrArbiter
{
    fulfillments[_milestoneId].push(Fulfillment(false, false, msg.sender, _data, _dataType));

    BountyFulfilled(msg.sender, numFulfillments[_milestoneId]++, _milestoneId);
}

updateFulfillment()

After a bounty has been fulfilled, the data representing the fulfillment deliverables can be changed or updated by the fulfiller, but only before the bounty has been accepted or paid. Individuals may only update the fulfillments which they personally submitted.

function updateFulfillment(string _data, uint _milestoneId, uint _fulfillmentId)
public
validateMilestoneIndex(_milestoneId)
validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
onlyFulfiller(_milestoneId, _fulfillmentId)
notYetAccepted(_milestoneId, _fulfillmentId)
{
  fulfillments[_milestoneId][_fulfillmentId].data = _data;
}

AcceptFulfillment()

Submissions can be accepted by the issuer while the bounty is active, and the contract has sufficient funds to pay out all previously accepted submissions. Arbiters also have the ability to accept work, but should only do so after mediating between the issuer and fulfiller to resolve the conflict.

function acceptFulfillment(uint _milestoneId, uint _fulfillmentId)
    public
    onlyIssuerOrArbiter
    isAtStage(BountyStages.Active)
    validateMilestoneIndex(_milestoneId)
    validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
    unpaidAmountRemains(_milestoneId)
{
    fulfillments[_milestoneId][_fulfillmentId].accepted = true;
    numAccepted[_milestoneId]++;

    FulfillmentAccepted(msg.sender, _milestoneId, _fulfillmentId);
}

FulfillmentPayment()

Once an individuals submission has been accepted, they can claim their reward, transferring the Ether (or tokens) to the successful fulfiller. A payment can only be claimed once for each fulfillment which has been accepted.

function fulfillmentPayment(uint _milestoneId, uint _fulfillmentId)
    public
    validateMilestoneIndex(_milestoneId)
    validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
    onlyFulfiller(_milestoneId, _fulfillmentId)
    checkFulfillmentIsApprovedAndUnpaid(_milestoneId, _fulfillmentId)
{
    fulfillments[_milestoneId][_fulfillmentId].fulfiller.transfer(fulfillmentAmounts[_milestoneId]);
    fulfillments[_milestoneId][_fulfillmentId].paid = true;

    numPaid[_milestoneId]++;

    FulfillmentPaid(msg.sender, _milestoneId, _fulfillmentId);
}

KillBounty()

The issuer of the bounty can transition it into the Dead stage at any point in time, draining the contract of all remaining funds (less the amount still due for successful fulfillments which are yet unpaid).

function killBounty()
    public
    onlyIssuer
{
    issuer.transfer(this.balance - unpaidAmount());

    transitionToState(BountyStages.Dead);

    BountyKilled();
}

ExtendDeadline()

The issuer of the bounty can extend the deadline at any time, allowing more time for submissions to be fulfilled.

function extendDeadline(uint _newDeadline)
    public
    onlyIssuer
    newDeadlineIsValid(_newDeadline)
{
    deadline = _newDeadline;

    DeadlineExtended(_newDeadline);
}

transferIssuer()

At any point, the issuer can transfer ownership of the bounty to a new address that they supply. This gives full power and authority to the new issuer address, and releases the old issuer address from the ability to administer the bounty.

function transferIssuer(address _newIssuer)
public
onlyIssuer
{
  issuer = _newIssuer;
}

ChangeBounty()

The issuer of the bounty can change all bounty variables at once while the bounty is in the Draft stage. This is not allowed when the bounty is in the Active or Dead stage.

function changeBounty(uint _newDeadline,
                      string _newContactInfo,
                      string _newData,
                      uint[] _newFulfillmentAmounts,
                      uint _totalFulfillmentAmounts,
                      uint _newNumMilestones,
                      address _newArbiter)
    public
    onlyIssuer
    validateDeadline(_newDeadline)
    amountsNotZeroAndEqualSum(_newFulfillmentAmounts, _totalFulfillmentAmounts)
    correctLengths(_newNumMilestones, _newFulfillmentAmounts.length)
    isAtStage(BountyStages.Draft)
{
  deadline = _newDeadline;
  issuerContact = _newContactInfo;
  data = _newData;
  fulfillmentAmounts = _newFulfillmentAmounts;
  totalFulfillmentAmounts = _totalFulfillmentAmounts;
  numMilestones = _newNumMilestones;
  arbiter = _newArbiter;
}

getFulfillment()

Returns all of the information describing a given fulfillment for a given milestone.

function getFulfillment(uint _milestoneId, uint _fulfillmentId)
    public
    constant
    validateMilestoneIndex(_milestoneId)
    validateFulfillmentArrayIndex(_milestoneId, _fulfillmentId)
    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);
}

getBounty()

Returns a tuple of the variables describing the bounty.

function getBounty()
    public
    constant
    returns (address, string, uint, uint, string, uint)
{
    return (issuer,
            issuerContact,
            uint(bountyStage),
            deadline,
            data,
            numMilestones);
}

UnpaidAmount()

Returns the amount of wei or tokens which are owed for submissions which were already accepted, but have not yet been paid.

function unpaidAmount()
    public
    constant
    returns (uint amount)
{
    for (uint i = 0; i < numMilestones; i++){
        amount = SafeMath.add(amount, SafeMath.mul(fulfillmentAmounts[i], SafeMath.sub(numAccepted[i], numPaid[i])));
    }
}

getNumFulfillments()

Returns the number of fulfillments which have been submitted for a given milestone

function getNumFulfillments(uint _milestoneId)
    public
    constant
    validateMilestoneIndex(_milestoneId)
    returns (uint)
{
    return fulfillments[_milestoneId].length;
}

unpaidAmount()

Returns the amount of ETH or tokens which must still be paid out for previously accepted fulfillments.

function unpaidAmount()
    public
    constant
    returns (uint amount)
{
    for (uint i = 0; i < fulfillmentAmounts.length; i++){
        amount = (amount + (fulfillmentAmounts[i]* (numAccepted[i]- numPaid[i])));
    }
}

Events

event BountyActivated(address issuer); This is emitted when the bounty gets activated by the issuer

event FulfillmentAccepted(address indexed fulfiller, uint256 indexed _fulfillmentId, uint256 indexed _milestoneId); This is emitted when the fulfillment for _milestoneId at index _fulfillmentId is accepted by the issuer.

event FulfillmentPaid(address indexed fulfiller, uint256 indexed _fulfillmentId, uint256 indexed _milestoneId); This is emitted when the fulfillment or _milestoneId at index _fulfillmentId is paid out to the fulfiller.

event BountyKilled(); This is emitted when the issuer kills the bounty, draining the remaining funds

event ContributionAdded(address indexed contributor, uint256 value); This is emitted when someone at address contributor contributes to the bounty

event DeadlineExtended(uint newDeadline); This is emitted when the issuer extends the deadline of the bounty.