-
Notifications
You must be signed in to change notification settings - Fork 88
/
Copy pathStandardBounties.sol
838 lines (729 loc) · 34.8 KB
/
StandardBounties.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
pragma solidity 0.5.0;
pragma experimental ABIEncoderV2;
import "./inherited/ERC20Token.sol";
import "./inherited/ERC721Basic.sol";
/// @title StandardBounties
/// @dev A contract for issuing bounties on Ethereum paying in ETH, ERC20, or ERC721 tokens
/// @author Mark Beylin <mark.beylin@consensys.net>, Gonçalo Sá <goncalo.sa@consensys.net>, Kevin Owocki <kevin.owocki@consensys.net>, Ricardo Guilherme Schmidt (@3esmit), Matt Garnett <matt.garnett@consensys.net>, Craig Williams <craig.williams@consensys.net>
contract StandardBounties {
using SafeMath for uint256;
/*
* Structs
*/
struct Bounty {
address payable [] issuers; // An array of individuals who have complete control over the bounty, and can edit any of its parameters
address [] approvers; // An array of individuals who are allowed to accept the fulfillments for a particular bounty
uint deadline; // The Unix timestamp before which all submissions must be made, and after which refunds may be processed
address token; // The address of the token associated with the bounty (should be disregarded if the tokenVersion is 0)
uint tokenVersion; // The version of the token being used for the bounty (0 for ETH, 20 for ERC20, 721 for ERC721)
uint balance; // The number of tokens which the bounty is able to pay out or refund
bool hasPaidOut; // A boolean storing whether or not the bounty has paid out at least once, meaning refunds are no longer allowed
Fulfillment [] fulfillments; // An array of Fulfillments which store the various submissions which have been made to the bounty
Contribution [] contributions; // An array of Contributions which store the contributions which have been made to the bounty
}
struct Fulfillment {
address payable [] fulfillers; // An array of addresses who should receive payouts for a given submission
address submitter; // The address of the individual who submitted the fulfillment, who is able to update the submission as needed
}
struct Contribution {
address payable contributor; // The address of the individual who contributed
uint amount; // The amount of tokens the user contributed
bool refunded; // A boolean storing whether or not the contribution has been refunded yet
}
/*
* Storage
*/
uint public numBounties; // An integer storing the total number of bounties in the contract
mapping(uint => Bounty) public bounties; // A mapping of bountyIDs to bounties
mapping (uint => mapping (uint => bool)) public tokenBalances; // A mapping of bountyIds to tokenIds to booleans, storing whether a given bounty has a given ERC721 token in its balance
address public owner; // The address of the individual who's allowed to set the metaTxRelayer address
address public metaTxRelayer; // The address of the meta transaction relayer whose _sender is automatically trusted for all contract calls
bool public callStarted; // Ensures mutex for the entire contract
/*
* Modifiers
*/
modifier callNotStarted(){
require(!callStarted);
callStarted = true;
_;
callStarted = false;
}
modifier validateBountyArrayIndex(
uint _index)
{
require(_index < numBounties);
_;
}
modifier validateContributionArrayIndex(
uint _bountyId,
uint _index)
{
require(_index < bounties[_bountyId].contributions.length);
_;
}
modifier validateFulfillmentArrayIndex(
uint _bountyId,
uint _index)
{
require(_index < bounties[_bountyId].fulfillments.length);
_;
}
modifier validateIssuerArrayIndex(
uint _bountyId,
uint _index)
{
require(_index < bounties[_bountyId].issuers.length);
_;
}
modifier validateApproverArrayIndex(
uint _bountyId,
uint _index)
{
require(_index < bounties[_bountyId].approvers.length);
_;
}
modifier onlyIssuer(
address _sender,
uint _bountyId,
uint _issuerId)
{
require(_sender == bounties[_bountyId].issuers[_issuerId]);
_;
}
modifier onlySubmitter(
address _sender,
uint _bountyId,
uint _fulfillmentId)
{
require(_sender ==
bounties[_bountyId].fulfillments[_fulfillmentId].submitter);
_;
}
modifier onlyContributor(
address _sender,
uint _bountyId,
uint _contributionId)
{
require(_sender ==
bounties[_bountyId].contributions[_contributionId].contributor);
_;
}
modifier isApprover(
address _sender,
uint _bountyId,
uint _approverId)
{
require(_sender == bounties[_bountyId].approvers[_approverId]);
_;
}
modifier hasNotPaid(
uint _bountyId)
{
require(!bounties[_bountyId].hasPaidOut);
_;
}
modifier hasNotRefunded(
uint _bountyId,
uint _contributionId)
{
require(!bounties[_bountyId].contributions[_contributionId].refunded);
_;
}
modifier senderIsValid(
address _sender)
{
require(msg.sender == _sender || msg.sender == metaTxRelayer);
_;
}
/*
* Public functions
*/
constructor() public {
// The owner of the contract is automatically designated to be the deployer of the contract
owner = msg.sender;
}
/// @dev setMetaTxRelayer(): Sets the address of the meta transaction relayer
/// @param _relayer the address of the relayer
function setMetaTxRelayer(address _relayer)
external
{
require(msg.sender == owner); // Checks that only the owner can call
require(metaTxRelayer == address(0)); // Ensures the meta tx relayer can only be set once
metaTxRelayer = _relayer;
}
/// @dev issueBounty(): creates a new bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _issuers the array of addresses who will be the issuers of the bounty
/// @param _approvers the array of addresses who will be the approvers of the bounty
/// @param _data the IPFS hash representing the JSON object storing the details of the bounty (see docs for schema details)
/// @param _deadline the timestamp which will become the deadline of the bounty
/// @param _token the address of the token which will be used for the bounty
/// @param _tokenVersion the version of the token being used for the bounty (0 for ETH, 20 for ERC20, 721 for ERC721)
function issueBounty(
address payable _sender,
address payable [] memory _issuers,
address [] memory _approvers,
string memory _data,
uint _deadline,
address _token,
uint _tokenVersion)
public
senderIsValid(_sender)
returns (uint)
{
require(_tokenVersion == 0 || _tokenVersion == 20 || _tokenVersion == 721); // Ensures a bounty can only be issued with a valid token version
require(_issuers.length > 0 || _approvers.length > 0); // Ensures there's at least 1 issuer or approver, so funds don't get stuck
uint bountyId = numBounties; // The next bounty's index will always equal the number of existing bounties
Bounty storage newBounty = bounties[bountyId];
newBounty.issuers = _issuers;
newBounty.approvers = _approvers;
newBounty.deadline = _deadline;
newBounty.tokenVersion = _tokenVersion;
if (_tokenVersion != 0) {
newBounty.token = _token;
}
numBounties = numBounties.add(1); // Increments the number of bounties, since a new one has just been added
emit BountyIssued(bountyId,
_sender,
_issuers,
_approvers,
_data, // Instead of storing the string on-chain, it is emitted within the event for easy off-chain consumption
_deadline,
_token,
_tokenVersion);
return (bountyId);
}
/// @param _depositAmount the amount of tokens being deposited to the bounty, which will create a new contribution to the bounty
function issueAndContribute(
address payable _sender,
address payable [] memory _issuers,
address [] memory _approvers,
string memory _data,
uint _deadline,
address _token,
uint _tokenVersion,
uint _depositAmount)
public
payable
returns(uint)
{
uint bountyId = issueBounty(_sender, _issuers, _approvers, _data, _deadline, _token, _tokenVersion);
contribute(_sender, bountyId, _depositAmount);
}
/// @dev contribute(): Allows users to contribute tokens to a given bounty.
/// Contributing merits no privelages to administer the
/// funds in the bounty or accept submissions. Contributions
/// are refundable but only on the condition that the deadline
/// has elapsed, and the bounty has not yet paid out any funds.
/// All funds deposited in a bounty are at the mercy of a
/// bounty's issuers and approvers, so please be careful!
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _amount the amount of tokens being contributed
function contribute(
address payable _sender,
uint _bountyId,
uint _amount)
public
payable
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
callNotStarted
{
require(_amount > 0); // Contributions of 0 tokens or token ID 0 should fail
bounties[_bountyId].contributions.push(
Contribution(_sender, _amount, false)); // Adds the contribution to the bounty
if (bounties[_bountyId].tokenVersion == 0){
bounties[_bountyId].balance = bounties[_bountyId].balance.add(_amount); // Increments the balance of the bounty
require(msg.value == _amount);
} else if (bounties[_bountyId].tokenVersion == 20) {
bounties[_bountyId].balance = bounties[_bountyId].balance.add(_amount); // Increments the balance of the bounty
require(msg.value == 0); // Ensures users don't accidentally send ETH alongside a token contribution, locking up funds
require(ERC20Token(bounties[_bountyId].token).transferFrom(_sender,
address(this),
_amount));
} else if (bounties[_bountyId].tokenVersion == 721) {
tokenBalances[_bountyId][_amount] = true; // Adds the 721 token to the balance of the bounty
require(msg.value == 0); // Ensures users don't accidentally send ETH alongside a token contribution, locking up funds
ERC721BasicToken(bounties[_bountyId].token).transferFrom(_sender,
address(this),
_amount);
} else {
revert();
}
emit ContributionAdded(_bountyId,
bounties[_bountyId].contributions.length - 1, // The new contributionId
_sender,
_amount);
}
/// @dev refundContribution(): Allows users to refund the contributions they've
/// made to a particular bounty, but only if the bounty
/// has not yet paid out, and the deadline has elapsed.
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _contributionId the index of the contribution being refunded
function refundContribution(
address _sender,
uint _bountyId,
uint _contributionId)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateContributionArrayIndex(_bountyId, _contributionId)
onlyContributor(_sender, _bountyId, _contributionId)
hasNotPaid(_bountyId)
hasNotRefunded(_bountyId, _contributionId)
callNotStarted
{
require(now > bounties[_bountyId].deadline); // Refunds may only be processed after the deadline has elapsed
Contribution storage contribution =
bounties[_bountyId].contributions[_contributionId];
contribution.refunded = true;
transferTokens(_bountyId, contribution.contributor, contribution.amount); // Performs the disbursal of tokens to the contributor
emit ContributionRefunded(_bountyId, _contributionId);
}
/// @dev refundMyContributions(): Allows users to refund their contributions in bulk
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _contributionIds the array of indexes of the contributions being refunded
function refundMyContributions(
address _sender,
uint _bountyId,
uint [] memory _contributionIds)
public
senderIsValid(_sender)
{
for (uint i = 0; i < _contributionIds.length; i++){
refundContribution(_sender, _bountyId, _contributionIds[i]);
}
}
/// @dev refundContributions(): Allows users to refund their contributions in bulk
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is making the call
/// @param _contributionIds the array of indexes of the contributions being refunded
function refundContributions(
address _sender,
uint _bountyId,
uint _issuerId,
uint [] memory _contributionIds)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
onlyIssuer(_sender, _bountyId, _issuerId)
callNotStarted
{
for (uint i = 0; i < _contributionIds.length; i++){
require(_contributionIds[i] <= bounties[_bountyId].contributions.length);
Contribution storage contribution =
bounties[_bountyId].contributions[_contributionIds[i]];
require(!contribution.refunded);
transferTokens(_bountyId, contribution.contributor, contribution.amount); // Performs the disbursal of tokens to the contributor
}
emit ContributionsRefunded(_bountyId, _sender, _contributionIds);
}
/// @dev drainBounty(): Allows an issuer to drain the funds from the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is making the call
/// @param _amounts an array of amounts of tokens to be sent. The length of the array should be 1 if the bounty is in ETH or ERC20 tokens. If it's an ERC721 bounty, the array should be the list of tokenIDs.
function drainBounty(
address payable _sender,
uint _bountyId,
uint _issuerId,
uint [] memory _amounts)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
onlyIssuer(_sender, _bountyId, _issuerId)
callNotStarted
{
if (bounties[_bountyId].tokenVersion == 0 || bounties[_bountyId].tokenVersion == 20){
require(_amounts.length == 1); // ensures there's only 1 amount of tokens to be returned
require(_amounts[0] <= bounties[_bountyId].balance); // ensures an issuer doesn't try to drain the bounty of more tokens than their balance permits
transferTokens(_bountyId, _sender, _amounts[0]); // Performs the draining of tokens to the issuer
} else {
for (uint i = 0; i < _amounts.length; i++){
require(tokenBalances[_bountyId][_amounts[i]]);// ensures an issuer doesn't try to drain the bounty of a token it doesn't have in its balance
transferTokens(_bountyId, _sender, _amounts[i]);
}
}
emit BountyDrained(_bountyId, _sender, _amounts);
}
/// @dev performAction(): Allows users to perform any generalized action
/// associated with a particular bounty, such as applying for it
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _data the IPFS hash corresponding to a JSON object which contains the details of the action being performed (see docs for schema details)
function performAction(
address _sender,
uint _bountyId,
string memory _data)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
{
emit ActionPerformed(_bountyId, _sender, _data); // The _data string is emitted in an event for easy off-chain consumption
}
/// @dev fulfillBounty(): Allows users to fulfill the bounty to get paid out
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _fulfillers the array of addresses which will receive payouts for the submission
/// @param _data the IPFS hash corresponding to a JSON object which contains the details of the submission (see docs for schema details)
function fulfillBounty(
address _sender,
uint _bountyId,
address payable [] memory _fulfillers,
string memory _data)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
{
require(now < bounties[_bountyId].deadline); // Submissions are only allowed to be made before the deadline
require(_fulfillers.length > 0); // Submissions with no fulfillers would mean no one gets paid out
bounties[_bountyId].fulfillments.push(Fulfillment(_fulfillers, _sender));
emit BountyFulfilled(_bountyId,
(bounties[_bountyId].fulfillments.length - 1),
_fulfillers,
_data, // The _data string is emitted in an event for easy off-chain consumption
_sender);
}
/// @dev updateFulfillment(): Allows the submitter of a fulfillment to update their submission
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _fulfillmentId the index of the fulfillment
/// @param _fulfillers the new array of addresses which will receive payouts for the submission
/// @param _data the new IPFS hash corresponding to a JSON object which contains the details of the submission (see docs for schema details)
function updateFulfillment(
address _sender,
uint _bountyId,
uint _fulfillmentId,
address payable [] memory _fulfillers,
string memory _data)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
onlySubmitter(_sender, _bountyId, _fulfillmentId) // Only the original submitter of a fulfillment may update their submission
{
bounties[_bountyId].fulfillments[_fulfillmentId].fulfillers = _fulfillers;
emit FulfillmentUpdated(_bountyId,
_fulfillmentId,
_fulfillers,
_data); // The _data string is emitted in an event for easy off-chain consumption
}
/// @dev acceptFulfillment(): Allows any of the approvers to accept a given submission
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _fulfillmentId the index of the fulfillment to be accepted
/// @param _approverId the index of the approver which is making the call
/// @param _tokenAmounts the array of token amounts which will be paid to the
/// fulfillers, whose length should equal the length of the
/// _fulfillers array of the submission. If the bounty pays
/// in ERC721 tokens, then these should be the token IDs
/// being sent to each of the individual fulfillers
function acceptFulfillment(
address _sender,
uint _bountyId,
uint _fulfillmentId,
uint _approverId,
uint[] memory _tokenAmounts)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateFulfillmentArrayIndex(_bountyId, _fulfillmentId)
isApprover(_sender, _bountyId, _approverId)
callNotStarted
{
// now that the bounty has paid out at least once, refunds are no longer possible
bounties[_bountyId].hasPaidOut = true;
Fulfillment storage fulfillment =
bounties[_bountyId].fulfillments[_fulfillmentId];
require(_tokenAmounts.length == fulfillment.fulfillers.length); // Each fulfiller should get paid some amount of tokens (this can be 0)
for (uint256 i = 0; i < fulfillment.fulfillers.length; i++){
if (_tokenAmounts[i] > 0) {
// for each fulfiller associated with the submission
transferTokens(_bountyId, fulfillment.fulfillers[i], _tokenAmounts[i]);
}
}
emit FulfillmentAccepted(_bountyId,
_fulfillmentId,
_sender,
_tokenAmounts);
}
/// @dev fulfillAndAccept(): Allows any of the approvers to fulfill and accept a submission simultaneously
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _fulfillers the array of addresses which will receive payouts for the submission
/// @param _data the IPFS hash corresponding to a JSON object which contains the details of the submission (see docs for schema details)
/// @param _approverId the index of the approver which is making the call
/// @param _tokenAmounts the array of token amounts which will be paid to the
/// fulfillers, whose length should equal the length of the
/// _fulfillers array of the submission. If the bounty pays
/// in ERC721 tokens, then these should be the token IDs
/// being sent to each of the individual fulfillers
function fulfillAndAccept(
address _sender,
uint _bountyId,
address payable [] memory _fulfillers,
string memory _data,
uint _approverId,
uint[] memory _tokenAmounts)
public
senderIsValid(_sender)
{
// first fulfills the bounty on behalf of the fulfillers
fulfillBounty(_sender, _bountyId, _fulfillers, _data);
// then accepts the fulfillment
acceptFulfillment(_sender,
_bountyId,
bounties[_bountyId].fulfillments.length - 1,
_approverId,
_tokenAmounts);
}
/// @dev changeBounty(): Allows any of the issuers to change the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is calling the function
/// @param _issuers the new array of addresses who will be the issuers of the bounty
/// @param _approvers the new array of addresses who will be the approvers of the bounty
/// @param _data the new IPFS hash representing the JSON object storing the details of the bounty (see docs for schema details)
/// @param _deadline the new timestamp which will become the deadline of the bounty
function changeBounty(
address _sender,
uint _bountyId,
uint _issuerId,
address payable [] memory _issuers,
address payable [] memory _approvers,
string memory _data,
uint _deadline)
public
senderIsValid(_sender)
{
require(_bountyId < numBounties); // makes the validateBountyArrayIndex modifier in-line to avoid stack too deep errors
require(_issuerId < bounties[_bountyId].issuers.length); // makes the validateIssuerArrayIndex modifier in-line to avoid stack too deep errors
require(_sender == bounties[_bountyId].issuers[_issuerId]); // makes the onlyIssuer modifier in-line to avoid stack too deep errors
require(_issuers.length > 0 || _approvers.length > 0); // Ensures there's at least 1 issuer or approver, so funds don't get stuck
bounties[_bountyId].issuers = _issuers;
bounties[_bountyId].approvers = _approvers;
bounties[_bountyId].deadline = _deadline;
emit BountyChanged(_bountyId,
_sender,
_issuers,
_approvers,
_data,
_deadline);
}
/// @dev changeIssuer(): Allows any of the issuers to change a particular issuer of the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is calling the function
/// @param _issuerIdToChange the index of the issuer who is being changed
/// @param _newIssuer the address of the new issuer
function changeIssuer(
address _sender,
uint _bountyId,
uint _issuerId,
uint _issuerIdToChange,
address payable _newIssuer)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateIssuerArrayIndex(_bountyId, _issuerIdToChange)
onlyIssuer(_sender, _bountyId, _issuerId)
{
require(_issuerId < bounties[_bountyId].issuers.length || _issuerId == 0);
bounties[_bountyId].issuers[_issuerIdToChange] = _newIssuer;
emit BountyIssuersUpdated(_bountyId, _sender, bounties[_bountyId].issuers);
}
/// @dev changeApprover(): Allows any of the issuers to change a particular approver of the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is calling the function
/// @param _approverId the index of the approver who is being changed
/// @param _approver the address of the new approver
function changeApprover(
address _sender,
uint _bountyId,
uint _issuerId,
uint _approverId,
address payable _approver)
external
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
onlyIssuer(_sender, _bountyId, _issuerId)
validateApproverArrayIndex(_bountyId, _approverId)
{
bounties[_bountyId].approvers[_approverId] = _approver;
emit BountyApproversUpdated(_bountyId, _sender, bounties[_bountyId].approvers);
}
/// @dev changeData(): Allows any of the issuers to change the data the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is calling the function
/// @param _data the new IPFS hash representing the JSON object storing the details of the bounty (see docs for schema details)
function changeData(
address _sender,
uint _bountyId,
uint _issuerId,
string memory _data)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateIssuerArrayIndex(_bountyId, _issuerId)
onlyIssuer(_sender, _bountyId, _issuerId)
{
emit BountyDataChanged(_bountyId, _sender, _data); // The new _data is emitted within an event rather than being stored on-chain for minimized gas costs
}
/// @dev changeDeadline(): Allows any of the issuers to change the deadline the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is calling the function
/// @param _deadline the new timestamp which will become the deadline of the bounty
function changeDeadline(
address _sender,
uint _bountyId,
uint _issuerId,
uint _deadline)
external
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateIssuerArrayIndex(_bountyId, _issuerId)
onlyIssuer(_sender, _bountyId, _issuerId)
{
bounties[_bountyId].deadline = _deadline;
emit BountyDeadlineChanged(_bountyId, _sender, _deadline);
}
/// @dev addIssuers(): Allows any of the issuers to add more issuers to the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is calling the function
/// @param _issuers the array of addresses to add to the list of valid issuers
function addIssuers(
address _sender,
uint _bountyId,
uint _issuerId,
address payable [] memory _issuers)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateIssuerArrayIndex(_bountyId, _issuerId)
onlyIssuer(_sender, _bountyId, _issuerId)
{
for (uint i = 0; i < _issuers.length; i++){
bounties[_bountyId].issuers.push(_issuers[i]);
}
emit BountyIssuersUpdated(_bountyId, _sender, bounties[_bountyId].issuers);
}
/// @dev replaceIssuers(): Allows any of the issuers to replace the issuers of the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is calling the function
/// @param _issuers the array of addresses to replace the list of valid issuers
function replaceIssuers(
address _sender,
uint _bountyId,
uint _issuerId,
address payable [] memory _issuers)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateIssuerArrayIndex(_bountyId, _issuerId)
onlyIssuer(_sender, _bountyId, _issuerId)
{
require(_issuers.length > 0 || bounties[_bountyId].approvers.length > 0); // Ensures there's at least 1 issuer or approver, so funds don't get stuck
bounties[_bountyId].issuers = _issuers;
emit BountyIssuersUpdated(_bountyId, _sender, bounties[_bountyId].issuers);
}
/// @dev addApprovers(): Allows any of the issuers to add more approvers to the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is calling the function
/// @param _approvers the array of addresses to add to the list of valid approvers
function addApprovers(
address _sender,
uint _bountyId,
uint _issuerId,
address [] memory _approvers)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateIssuerArrayIndex(_bountyId, _issuerId)
onlyIssuer(_sender, _bountyId, _issuerId)
{
for (uint i = 0; i < _approvers.length; i++){
bounties[_bountyId].approvers.push(_approvers[i]);
}
emit BountyApproversUpdated(_bountyId, _sender, bounties[_bountyId].approvers);
}
/// @dev replaceApprovers(): Allows any of the issuers to replace the approvers of the bounty
/// @param _sender the sender of the transaction issuing the bounty (should be the same as msg.sender unless the txn is called by the meta tx relayer)
/// @param _bountyId the index of the bounty
/// @param _issuerId the index of the issuer who is calling the function
/// @param _approvers the array of addresses to replace the list of valid approvers
function replaceApprovers(
address _sender,
uint _bountyId,
uint _issuerId,
address [] memory _approvers)
public
senderIsValid(_sender)
validateBountyArrayIndex(_bountyId)
validateIssuerArrayIndex(_bountyId, _issuerId)
onlyIssuer(_sender, _bountyId, _issuerId)
{
require(bounties[_bountyId].issuers.length > 0 || _approvers.length > 0); // Ensures there's at least 1 issuer or approver, so funds don't get stuck
bounties[_bountyId].approvers = _approvers;
emit BountyApproversUpdated(_bountyId, _sender, bounties[_bountyId].approvers);
}
/// @dev getBounty(): Returns the details of the bounty
/// @param _bountyId the index of the bounty
/// @return Returns a tuple for the bounty
function getBounty(uint _bountyId)
external
view
returns (Bounty memory)
{
return bounties[_bountyId];
}
function transferTokens(uint _bountyId, address payable _to, uint _amount)
internal
{
if (bounties[_bountyId].tokenVersion == 0){
require(_amount > 0); // Sending 0 tokens should throw
require(bounties[_bountyId].balance >= _amount);
bounties[_bountyId].balance = bounties[_bountyId].balance.sub(_amount);
_to.transfer(_amount);
} else if (bounties[_bountyId].tokenVersion == 20) {
require(_amount > 0); // Sending 0 tokens should throw
require(bounties[_bountyId].balance >= _amount);
bounties[_bountyId].balance = bounties[_bountyId].balance.sub(_amount);
require(ERC20Token(bounties[_bountyId].token).transfer(_to, _amount));
} else if (bounties[_bountyId].tokenVersion == 721) {
require(tokenBalances[_bountyId][_amount]);
tokenBalances[_bountyId][_amount] = false; // Removes the 721 token from the balance of the bounty
ERC721BasicToken(bounties[_bountyId].token).transferFrom(address(this),
_to,
_amount);
} else {
revert();
}
}
/*
* Events
*/
event BountyIssued(uint _bountyId, address payable _creator, address payable [] _issuers, address [] _approvers, string _data, uint _deadline, address _token, uint _tokenVersion);
event ContributionAdded(uint _bountyId, uint _contributionId, address payable _contributor, uint _amount);
event ContributionRefunded(uint _bountyId, uint _contributionId);
event ContributionsRefunded(uint _bountyId, address _issuer, uint [] _contributionIds);
event BountyDrained(uint _bountyId, address _issuer, uint [] _amounts);
event ActionPerformed(uint _bountyId, address _fulfiller, string _data);
event BountyFulfilled(uint _bountyId, uint _fulfillmentId, address payable [] _fulfillers, string _data, address _submitter);
event FulfillmentUpdated(uint _bountyId, uint _fulfillmentId, address payable [] _fulfillers, string _data);
event FulfillmentAccepted(uint _bountyId, uint _fulfillmentId, address _approver, uint[] _tokenAmounts);
event BountyChanged(uint _bountyId, address _changer, address payable [] _issuers, address payable [] _approvers, string _data, uint _deadline);
event BountyIssuersUpdated(uint _bountyId, address _changer, address payable [] _issuers);
event BountyApproversUpdated(uint _bountyId, address _changer, address [] _approvers);
event BountyDataChanged(uint _bountyId, address _changer, string _data);
event BountyDeadlineChanged(uint _bountyId, address _changer, uint _deadline);
}