/ contracts / StandardBounties.sol
StandardBounties.sol
  1  pragma solidity 0.4.18;
  2  import "./inherited/HumanStandardToken.sol";
  3  
  4  /// @title StandardBounties
  5  /// @dev Used to pay out individuals or groups for task fulfillment through
  6  /// stepwise work submission, acceptance, and payment
  7  /// @author Mark Beylin <mark.beylin@consensys.net>, Gonçalo Sá <goncalo.sa@consensys.net>
  8  contract StandardBounties {
  9  
 10    /*
 11     * Events
 12     */
 13    event BountyIssued(uint bountyId);
 14    event BountyActivated(uint bountyId, address issuer);
 15    event BountyFulfilled(uint bountyId, address indexed fulfiller, uint256 indexed _fulfillmentId);
 16    event FulfillmentUpdated(uint _bountyId, uint _fulfillmentId);
 17    event FulfillmentAccepted(uint bountyId, address indexed fulfiller, uint256 indexed _fulfillmentId);
 18    event BountyKilled(uint bountyId, address indexed issuer);
 19    event ContributionAdded(uint bountyId, address indexed contributor, uint256 value);
 20    event DeadlineExtended(uint bountyId, uint newDeadline);
 21    event BountyChanged(uint bountyId);
 22    event IssuerTransferred(uint _bountyId, address indexed _newIssuer);
 23    event PayoutIncreased(uint _bountyId, uint _newFulfillmentAmount);
 24  
 25  
 26    /*
 27     * Storage
 28     */
 29  
 30    address public owner;
 31  
 32    Bounty[] public bounties;
 33  
 34    mapping(uint=>Fulfillment[]) fulfillments;
 35    mapping(uint=>uint) numAccepted;
 36    mapping(uint=>HumanStandardToken) tokenContracts;
 37  
 38    /*
 39     * Enums
 40     */
 41  
 42    enum BountyStages {
 43        Draft,
 44        Active,
 45        Dead
 46    }
 47  
 48    /*
 49     * Structs
 50     */
 51  
 52    struct Bounty {
 53        address issuer;
 54        uint deadline;
 55        string data;
 56        uint fulfillmentAmount;
 57        address arbiter;
 58        bool paysTokens;
 59        BountyStages bountyStage;
 60        uint balance;
 61    }
 62  
 63    struct Fulfillment {
 64        bool accepted;
 65        address fulfiller;
 66        string data;
 67    }
 68  
 69    /*
 70     * Modifiers
 71     */
 72  
 73    modifier validateNotTooManyBounties(){
 74      require((bounties.length + 1) > bounties.length);
 75      _;
 76    }
 77  
 78    modifier validateNotTooManyFulfillments(uint _bountyId){
 79      require((fulfillments[_bountyId].length + 1) > fulfillments[_bountyId].length);
 80      _;
 81    }
 82  
 83    modifier validateBountyArrayIndex(uint _bountyId){
 84      require(_bountyId < bounties.length);
 85      _;
 86    }
 87  
 88    modifier onlyIssuer(uint _bountyId) {
 89        require(msg.sender == bounties[_bountyId].issuer);
 90        _;
 91    }
 92  
 93    modifier onlyFulfiller(uint _bountyId, uint _fulfillmentId) {
 94        require(msg.sender == fulfillments[_bountyId][_fulfillmentId].fulfiller);
 95        _;
 96    }
 97  
 98    modifier amountIsNotZero(uint _amount) {
 99        require(_amount != 0);
100        _;
101    }
102  
103    modifier transferredAmountEqualsValue(uint _bountyId, uint _amount) {
104        if (bounties[_bountyId].paysTokens){
105          require(msg.value == 0);
106          uint oldBalance = tokenContracts[_bountyId].balanceOf(this);
107          if (_amount != 0){
108            require(tokenContracts[_bountyId].transferFrom(msg.sender, this, _amount));
109          }
110          require((tokenContracts[_bountyId].balanceOf(this) - oldBalance) == _amount);
111  
112        } else {
113          require((_amount * 1 wei) == msg.value);
114        }
115        _;
116    }
117  
118    modifier isBeforeDeadline(uint _bountyId) {
119        require(now < bounties[_bountyId].deadline);
120        _;
121    }
122  
123    modifier validateDeadline(uint _newDeadline) {
124        require(_newDeadline > now);
125        _;
126    }
127  
128    modifier isAtStage(uint _bountyId, BountyStages _desiredStage) {
129        require(bounties[_bountyId].bountyStage == _desiredStage);
130        _;
131    }
132  
133    modifier validateFulfillmentArrayIndex(uint _bountyId, uint _index) {
134        require(_index < fulfillments[_bountyId].length);
135        _;
136    }
137  
138    modifier notYetAccepted(uint _bountyId, uint _fulfillmentId){
139        require(fulfillments[_bountyId][_fulfillmentId].accepted == false);
140        _;
141    }
142  
143    /*
144     * Public functions
145     */
146  
147  
148    /// @dev StandardBounties(): instantiates
149    /// @param _owner the issuer of the standardbounties contract, who has the
150    /// ability to remove bounties
151    function StandardBounties(address _owner)
152        public
153    {
154        owner = _owner;
155    }
156  
157    /// @dev issueBounty(): instantiates a new draft bounty
158    /// @param _issuer the address of the intended issuer of the bounty
159    /// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
160    /// @param _data the requirements of the bounty
161    /// @param _fulfillmentAmount the amount of wei to be paid out for each successful fulfillment
162    /// @param _arbiter the address of the arbiter who can mediate claims
163    /// @param _paysTokens whether the bounty pays in tokens or in ETH
164    /// @param _tokenContract the address of the contract if _paysTokens is true
165    function issueBounty(
166        address _issuer,
167        uint _deadline,
168        string _data,
169        uint256 _fulfillmentAmount,
170        address _arbiter,
171        bool _paysTokens,
172        address _tokenContract
173    )
174        public
175        validateDeadline(_deadline)
176        amountIsNotZero(_fulfillmentAmount)
177        validateNotTooManyBounties
178        returns (uint)
179    {
180        bounties.push(Bounty(_issuer, _deadline, _data, _fulfillmentAmount, _arbiter, _paysTokens, BountyStages.Draft, 0));
181        if (_paysTokens){
182          tokenContracts[bounties.length - 1] = HumanStandardToken(_tokenContract);
183        }
184        BountyIssued(bounties.length - 1);
185        return (bounties.length - 1);
186    }
187  
188    /// @dev issueAndActivateBounty(): instantiates a new draft bounty
189    /// @param _issuer the address of the intended issuer of the bounty
190    /// @param _deadline the unix timestamp after which fulfillments will no longer be accepted
191    /// @param _data the requirements of the bounty
192    /// @param _fulfillmentAmount the amount of wei to be paid out for each successful fulfillment
193    /// @param _arbiter the address of the arbiter who can mediate claims
194    /// @param _paysTokens whether the bounty pays in tokens or in ETH
195    /// @param _tokenContract the address of the contract if _paysTokens is true
196    /// @param _value the total number of tokens being deposited upon activation
197    function issueAndActivateBounty(
198        address _issuer,
199        uint _deadline,
200        string _data,
201        uint256 _fulfillmentAmount,
202        address _arbiter,
203        bool _paysTokens,
204        address _tokenContract,
205        uint256 _value
206    )
207        public
208        payable
209        validateDeadline(_deadline)
210        amountIsNotZero(_fulfillmentAmount)
211        validateNotTooManyBounties
212        returns (uint)
213    {
214        require (_value >= _fulfillmentAmount);
215        if (_paysTokens){
216          require(msg.value == 0);
217          tokenContracts[bounties.length] = HumanStandardToken(_tokenContract);
218          require(tokenContracts[bounties.length].transferFrom(msg.sender, this, _value));
219        } else {
220          require((_value * 1 wei) == msg.value);
221        }
222        bounties.push(Bounty(_issuer,
223                              _deadline,
224                              _data,
225                              _fulfillmentAmount,
226                              _arbiter,
227                              _paysTokens,
228                              BountyStages.Active,
229                              _value));
230        BountyIssued(bounties.length - 1);
231        ContributionAdded(bounties.length - 1, msg.sender, _value);
232        BountyActivated(bounties.length - 1, msg.sender);
233        return (bounties.length - 1);
234    }
235  
236    modifier isNotDead(uint _bountyId) {
237        require(bounties[_bountyId].bountyStage != BountyStages.Dead);
238        _;
239    }
240  
241    /// @dev contribute(): a function allowing anyone to contribute tokens to a
242    /// bounty, as long as it is still before its deadline. Shouldn't keep
243    /// them by accident (hence 'value').
244    /// @param _bountyId the index of the bounty
245    /// @param _value the amount being contributed in ether to prevent accidental deposits
246    /// @notice Please note you funds will be at the mercy of the issuer
247    ///  and can be drained at any moment. Be careful!
248    function contribute (uint _bountyId, uint _value)
249        payable
250        public
251        validateBountyArrayIndex(_bountyId)
252        isBeforeDeadline(_bountyId)
253        isNotDead(_bountyId)
254        amountIsNotZero(_value)
255        transferredAmountEqualsValue(_bountyId, _value)
256    {
257        bounties[_bountyId].balance += _value;
258  
259        ContributionAdded(_bountyId, msg.sender, _value);
260    }
261  
262    /// @notice Send funds to activate the bug bounty
263    /// @dev activateBounty(): activate a bounty so it may pay out
264    /// @param _bountyId the index of the bounty
265    /// @param _value the amount being contributed in ether to prevent
266    /// accidental deposits
267    function activateBounty(uint _bountyId, uint _value)
268        payable
269        public
270        validateBountyArrayIndex(_bountyId)
271        isBeforeDeadline(_bountyId)
272        onlyIssuer(_bountyId)
273        transferredAmountEqualsValue(_bountyId, _value)
274    {
275        bounties[_bountyId].balance += _value;
276        require (bounties[_bountyId].balance >= bounties[_bountyId].fulfillmentAmount);
277        transitionToState(_bountyId, BountyStages.Active);
278  
279        ContributionAdded(_bountyId, msg.sender, _value);
280        BountyActivated(_bountyId, msg.sender);
281    }
282  
283    modifier notIssuerOrArbiter(uint _bountyId) {
284        require(msg.sender != bounties[_bountyId].issuer && msg.sender != bounties[_bountyId].arbiter);
285        _;
286    }
287  
288    /// @dev fulfillBounty(): submit a fulfillment for the given bounty
289    /// @param _bountyId the index of the bounty
290    /// @param _data the data artifacts representing the fulfillment of the bounty
291    function fulfillBounty(uint _bountyId, string _data)
292        public
293        validateBountyArrayIndex(_bountyId)
294        validateNotTooManyFulfillments(_bountyId)
295        isAtStage(_bountyId, BountyStages.Active)
296        isBeforeDeadline(_bountyId)
297        notIssuerOrArbiter(_bountyId)
298    {
299        fulfillments[_bountyId].push(Fulfillment(false, msg.sender, _data));
300  
301        BountyFulfilled(_bountyId, msg.sender, (fulfillments[_bountyId].length - 1));
302    }
303  
304    /// @dev updateFulfillment(): Submit updated data for a given fulfillment
305    /// @param _bountyId the index of the bounty
306    /// @param _fulfillmentId the index of the fulfillment
307    /// @param _data the new data being submitted
308    function updateFulfillment(uint _bountyId, uint _fulfillmentId, string _data)
309        public
310        validateBountyArrayIndex(_bountyId)
311        validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
312        onlyFulfiller(_bountyId, _fulfillmentId)
313        notYetAccepted(_bountyId, _fulfillmentId)
314    {
315        fulfillments[_bountyId][_fulfillmentId].data = _data;
316        FulfillmentUpdated(_bountyId, _fulfillmentId);
317    }
318  
319    modifier onlyIssuerOrArbiter(uint _bountyId) {
320        require(msg.sender == bounties[_bountyId].issuer ||
321           (msg.sender == bounties[_bountyId].arbiter && bounties[_bountyId].arbiter != address(0)));
322        _;
323    }
324  
325    modifier fulfillmentNotYetAccepted(uint _bountyId, uint _fulfillmentId) {
326        require(fulfillments[_bountyId][_fulfillmentId].accepted == false);
327        _;
328    }
329  
330    modifier enoughFundsToPay(uint _bountyId) {
331        require(bounties[_bountyId].balance >= bounties[_bountyId].fulfillmentAmount);
332        _;
333    }
334  
335    /// @dev acceptFulfillment(): accept a given fulfillment
336    /// @param _bountyId the index of the bounty
337    /// @param _fulfillmentId the index of the fulfillment being accepted
338    function acceptFulfillment(uint _bountyId, uint _fulfillmentId)
339        public
340        validateBountyArrayIndex(_bountyId)
341        validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
342        onlyIssuerOrArbiter(_bountyId)
343        isAtStage(_bountyId, BountyStages.Active)
344        fulfillmentNotYetAccepted(_bountyId, _fulfillmentId)
345        enoughFundsToPay(_bountyId)
346    {
347        fulfillments[_bountyId][_fulfillmentId].accepted = true;
348        numAccepted[_bountyId]++;
349        bounties[_bountyId].balance -= bounties[_bountyId].fulfillmentAmount;
350        if (bounties[_bountyId].paysTokens){
351          require(tokenContracts[_bountyId].transfer(fulfillments[_bountyId][_fulfillmentId].fulfiller, bounties[_bountyId].fulfillmentAmount));
352        } else {
353          fulfillments[_bountyId][_fulfillmentId].fulfiller.transfer(bounties[_bountyId].fulfillmentAmount);
354        }
355        FulfillmentAccepted(_bountyId, msg.sender, _fulfillmentId);
356    }
357  
358    /// @dev killBounty(): drains the contract of it's remaining
359    /// funds, and moves the bounty into stage 3 (dead) since it was
360    /// either killed in draft stage, or never accepted any fulfillments
361    /// @param _bountyId the index of the bounty
362    function killBounty(uint _bountyId)
363        public
364        validateBountyArrayIndex(_bountyId)
365        onlyIssuer(_bountyId)
366    {
367        transitionToState(_bountyId, BountyStages.Dead);
368        uint oldBalance = bounties[_bountyId].balance;
369        bounties[_bountyId].balance = 0;
370        if (oldBalance > 0){
371          if (bounties[_bountyId].paysTokens){
372            require(tokenContracts[_bountyId].transfer(bounties[_bountyId].issuer, oldBalance));
373          } else {
374            bounties[_bountyId].issuer.transfer(oldBalance);
375          }
376        }
377        BountyKilled(_bountyId, msg.sender);
378    }
379  
380    modifier newDeadlineIsValid(uint _bountyId, uint _newDeadline) {
381        require(_newDeadline > bounties[_bountyId].deadline);
382        _;
383    }
384  
385    /// @dev extendDeadline(): allows the issuer to add more time to the
386    /// bounty, allowing it to continue accepting fulfillments
387    /// @param _bountyId the index of the bounty
388    /// @param _newDeadline the new deadline in timestamp format
389    function extendDeadline(uint _bountyId, uint _newDeadline)
390        public
391        validateBountyArrayIndex(_bountyId)
392        onlyIssuer(_bountyId)
393        newDeadlineIsValid(_bountyId, _newDeadline)
394    {
395        bounties[_bountyId].deadline = _newDeadline;
396  
397        DeadlineExtended(_bountyId, _newDeadline);
398    }
399  
400    /// @dev transferIssuer(): allows the issuer to transfer ownership of the
401    /// bounty to some new address
402    /// @param _bountyId the index of the bounty
403    /// @param _newIssuer the address of the new issuer
404    function transferIssuer(uint _bountyId, address _newIssuer)
405        public
406        validateBountyArrayIndex(_bountyId)
407        onlyIssuer(_bountyId)
408    {
409        bounties[_bountyId].issuer = _newIssuer;
410        IssuerTransferred(_bountyId, _newIssuer);
411    }
412  
413  
414    /// @dev changeBountyDeadline(): allows the issuer to change a bounty's deadline
415    /// @param _bountyId the index of the bounty
416    /// @param _newDeadline the new deadline for the bounty
417    function changeBountyDeadline(uint _bountyId, uint _newDeadline)
418        public
419        validateBountyArrayIndex(_bountyId)
420        onlyIssuer(_bountyId)
421        validateDeadline(_newDeadline)
422        isAtStage(_bountyId, BountyStages.Draft)
423    {
424        bounties[_bountyId].deadline = _newDeadline;
425        BountyChanged(_bountyId);
426    }
427  
428    /// @dev changeData(): allows the issuer to change a bounty's data
429    /// @param _bountyId the index of the bounty
430    /// @param _newData the new requirements of the bounty
431    function changeBountyData(uint _bountyId, string _newData)
432        public
433        validateBountyArrayIndex(_bountyId)
434        onlyIssuer(_bountyId)
435        isAtStage(_bountyId, BountyStages.Draft)
436    {
437        bounties[_bountyId].data = _newData;
438        BountyChanged(_bountyId);
439    }
440  
441    /// @dev changeBountyfulfillmentAmount(): allows the issuer to change a bounty's fulfillment amount
442    /// @param _bountyId the index of the bounty
443    /// @param _newFulfillmentAmount the new fulfillment amount
444    function changeBountyFulfillmentAmount(uint _bountyId, uint _newFulfillmentAmount)
445        public
446        validateBountyArrayIndex(_bountyId)
447        onlyIssuer(_bountyId)
448        isAtStage(_bountyId, BountyStages.Draft)
449    {
450        bounties[_bountyId].fulfillmentAmount = _newFulfillmentAmount;
451        BountyChanged(_bountyId);
452    }
453  
454    /// @dev changeBountyArbiter(): allows the issuer to change a bounty's arbiter
455    /// @param _bountyId the index of the bounty
456    /// @param _newArbiter the new address of the arbiter
457    function changeBountyArbiter(uint _bountyId, address _newArbiter)
458        public
459        validateBountyArrayIndex(_bountyId)
460        onlyIssuer(_bountyId)
461        isAtStage(_bountyId, BountyStages.Draft)
462    {
463        bounties[_bountyId].arbiter = _newArbiter;
464        BountyChanged(_bountyId);
465    }
466  
467    modifier newFulfillmentAmountIsIncrease(uint _bountyId, uint _newFulfillmentAmount) {
468        require(bounties[_bountyId].fulfillmentAmount < _newFulfillmentAmount);
469        _;
470    }
471  
472    /// @dev increasePayout(): allows the issuer to increase a given fulfillment
473    /// amount in the active stage
474    /// @param _bountyId the index of the bounty
475    /// @param _newFulfillmentAmount the new fulfillment amount
476    /// @param _value the value of the additional deposit being added
477    function increasePayout(uint _bountyId, uint _newFulfillmentAmount, uint _value)
478        public
479        payable
480        validateBountyArrayIndex(_bountyId)
481        onlyIssuer(_bountyId)
482        newFulfillmentAmountIsIncrease(_bountyId, _newFulfillmentAmount)
483        transferredAmountEqualsValue(_bountyId, _value)
484    {
485        bounties[_bountyId].balance += _value;
486        require(bounties[_bountyId].balance >= _newFulfillmentAmount);
487        bounties[_bountyId].fulfillmentAmount = _newFulfillmentAmount;
488        PayoutIncreased(_bountyId, _newFulfillmentAmount);
489    }
490  
491    /// @dev getFulfillment(): Returns the fulfillment at a given index
492    /// @param _bountyId the index of the bounty
493    /// @param _fulfillmentId the index of the fulfillment to return
494    /// @return Returns a tuple for the fulfillment
495    function getFulfillment(uint _bountyId, uint _fulfillmentId)
496        public
497        constant
498        validateBountyArrayIndex(_bountyId)
499        validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
500        returns (bool, address, string)
501    {
502        return (fulfillments[_bountyId][_fulfillmentId].accepted,
503                fulfillments[_bountyId][_fulfillmentId].fulfiller,
504                fulfillments[_bountyId][_fulfillmentId].data);
505    }
506  
507    /// @dev getBounty(): Returns the details of the bounty
508    /// @param _bountyId the index of the bounty
509    /// @return Returns a tuple for the bounty
510    function getBounty(uint _bountyId)
511        public
512        constant
513        validateBountyArrayIndex(_bountyId)
514        returns (address, uint, uint, bool, uint, uint)
515    {
516        return (bounties[_bountyId].issuer,
517                bounties[_bountyId].deadline,
518                bounties[_bountyId].fulfillmentAmount,
519                bounties[_bountyId].paysTokens,
520                uint(bounties[_bountyId].bountyStage),
521                bounties[_bountyId].balance);
522    }
523  
524    /// @dev getBountyArbiter(): Returns the arbiter of the bounty
525    /// @param _bountyId the index of the bounty
526    /// @return Returns an address for the arbiter of the bounty
527    function getBountyArbiter(uint _bountyId)
528        public
529        constant
530        validateBountyArrayIndex(_bountyId)
531        returns (address)
532    {
533        return (bounties[_bountyId].arbiter);
534    }
535  
536    /// @dev getBountyData(): Returns the data of the bounty
537    /// @param _bountyId the index of the bounty
538    /// @return Returns a string for the bounty data
539    function getBountyData(uint _bountyId)
540        public
541        constant
542        validateBountyArrayIndex(_bountyId)
543        returns (string)
544    {
545        return (bounties[_bountyId].data);
546    }
547  
548    /// @dev getBountyToken(): Returns the token contract of the bounty
549    /// @param _bountyId the index of the bounty
550    /// @return Returns an address for the token that the bounty uses
551    function getBountyToken(uint _bountyId)
552        public
553        constant
554        validateBountyArrayIndex(_bountyId)
555        returns (address)
556    {
557        return (tokenContracts[_bountyId]);
558    }
559  
560    /// @dev getNumBounties() returns the number of bounties in the registry
561    /// @return Returns the number of bounties
562    function getNumBounties()
563        public
564        constant
565        returns (uint)
566    {
567        return bounties.length;
568    }
569  
570    /// @dev getNumFulfillments() returns the number of fulfillments for a given milestone
571    /// @param _bountyId the index of the bounty
572    /// @return Returns the number of fulfillments
573    function getNumFulfillments(uint _bountyId)
574        public
575        constant
576        validateBountyArrayIndex(_bountyId)
577        returns (uint)
578    {
579        return fulfillments[_bountyId].length;
580    }
581  
582    /*
583     * Internal functions
584     */
585  
586    /// @dev transitionToState(): transitions the contract to the
587    /// state passed in the parameter `_newStage` given the
588    /// conditions stated in the body of the function
589    /// @param _bountyId the index of the bounty
590    /// @param _newStage the new stage to transition to
591    function transitionToState(uint _bountyId, BountyStages _newStage)
592        internal
593    {
594        bounties[_bountyId].bountyStage = _newStage;
595    }
596  }