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 }