@@ -8,13 +8,18 @@ import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
8
8
9
9
import './interfaces/IEscrow.sol ' ;
10
10
11
+ /**
12
+ * @title Escrow Contract
13
+ * @dev This contract manages the lifecycle of an escrow, including funding,
14
+ * setup, payouts, and completion. It supports trusted handlers and oracles
15
+ * for managing the escrow process.
16
+ */
11
17
contract Escrow is IEscrow , ReentrancyGuard {
12
18
bytes4 private constant FUNC_SELECTOR_BALANCE_OF =
13
19
bytes4 (keccak256 ('balanceOf(address) ' ));
14
20
15
21
string constant ERROR_ZERO_ADDRESS = 'Escrow: zero address ' ;
16
22
17
- uint256 private constant BULK_MAX_VALUE = 1e9 * (10 ** 18 );
18
23
uint32 private constant BULK_MAX_COUNT = 100 ;
19
24
20
25
event TrustedHandlerAdded (address _handler );
@@ -73,9 +78,16 @@ contract Escrow is IEscrow, ReentrancyGuard {
73
78
mapping (address => bool ) public areTrustedHandlers;
74
79
75
80
uint256 public remainingFunds;
76
-
77
81
uint256 public reservedFunds;
78
82
83
+ /**
84
+ * @dev Constructor to initialize the escrow contract.
85
+ * @param _token Address of the token used in the escrow.
86
+ * @param _launcher Address of the launcher (creator) of the escrow.
87
+ * @param _canceler Address of the canceler who can cancel the escrow.
88
+ * @param _duration Duration of the escrow in seconds.
89
+ * @param _handlers Array of trusted handler addresses.
90
+ */
79
91
constructor (
80
92
address _token ,
81
93
address _launcher ,
@@ -97,23 +109,38 @@ contract Escrow is IEscrow, ReentrancyGuard {
97
109
_addTrustedHandlers (_handlers);
98
110
}
99
111
112
+ /**
113
+ * @dev Returns the balance of the escrow contract for the main token.
114
+ */
100
115
function getBalance () public view returns (uint256 ) {
101
116
return getTokenBalance (token);
102
117
}
103
118
119
+ /**
120
+ * @dev Returns the balance of the escrow contract for a specific token.
121
+ * @param _token Address of the token to check the balance for.
122
+ */
104
123
function getTokenBalance (address _token ) public view returns (uint256 ) {
105
124
(bool success , bytes memory returnData ) = _token.staticcall (
106
125
abi.encodeWithSelector (FUNC_SELECTOR_BALANCE_OF, address (this ))
107
126
);
108
127
return success ? abi.decode (returnData, (uint256 )) : 0 ;
109
128
}
110
129
130
+ /**
131
+ * @dev Adds trusted handlers to the contract.
132
+ * @param _handlers Array of addresses to be added as trusted handlers.
133
+ */
111
134
function addTrustedHandlers (
112
135
address [] memory _handlers
113
136
) public override trusted {
114
137
_addTrustedHandlers (_handlers);
115
138
}
116
139
140
+ /**
141
+ * @dev Internal function to add trusted handlers.
142
+ * @param _handlers Array of addresses to be added as trusted handlers.
143
+ */
117
144
function _addTrustedHandlers (address [] memory _handlers ) internal {
118
145
for (uint256 i = 0 ; i < _handlers.length ; i++ ) {
119
146
require (_handlers[i] != address (0 ), ERROR_ZERO_ADDRESS);
@@ -122,9 +149,17 @@ contract Escrow is IEscrow, ReentrancyGuard {
122
149
}
123
150
}
124
151
125
- // The escrower puts the Token in the contract without an agentless
126
- // and assigsn a reputation oracle to payout the bounty of size of the
127
- // amount specified
152
+ /**
153
+ * @dev Sets up the escrow with oracles and manifest details.
154
+ * @param _reputationOracle Address of the reputation oracle.
155
+ * @param _recordingOracle Address of the recording oracle.
156
+ * @param _exchangeOracle Address of the exchange oracle.
157
+ * @param _reputationOracleFeePercentage Fee percentage for the reputation oracle.
158
+ * @param _recordingOracleFeePercentage Fee percentage for the recording oracle.
159
+ * @param _exchangeOracleFeePercentage Fee percentage for the exchange oracle.
160
+ * @param _url URL of the manifest.
161
+ * @param _hash Hash of the manifest.
162
+ */
128
163
function setup (
129
164
address _reputationOracle ,
130
165
address _recordingOracle ,
@@ -182,6 +217,10 @@ contract Escrow is IEscrow, ReentrancyGuard {
182
217
emit Fund (remainingFunds);
183
218
}
184
219
220
+ /**
221
+ * @dev Cancels the escrow and transfers remaining funds to the canceler.
222
+ * @return bool indicating success of the cancellation.
223
+ */
185
224
function cancel ()
186
225
public
187
226
override
@@ -195,6 +234,11 @@ contract Escrow is IEscrow, ReentrancyGuard {
195
234
return true ;
196
235
}
197
236
237
+ /**
238
+ * @dev Withdraws excess funds from the escrow for a specific token.
239
+ * @param _token Address of the token to withdraw.
240
+ * @return bool indicating success of the withdrawal.
241
+ */
198
242
function withdraw (
199
243
address _token
200
244
) public override trusted nonReentrant returns (bool ) {
@@ -213,15 +257,18 @@ contract Escrow is IEscrow, ReentrancyGuard {
213
257
return true ;
214
258
}
215
259
260
+ /**
261
+ * @dev Completes the escrow, transferring remaining funds to the launcher.
262
+ */
216
263
function complete () external override notExpired trustedOrReputationOracle {
217
264
require (
218
265
status == EscrowStatuses.Paid || status == EscrowStatuses.Partial,
219
266
'Escrow not in Paid or Partial state '
220
267
);
221
- _complete ();
268
+ _finalize ();
222
269
}
223
270
224
- function _complete () private {
271
+ function _finalize () private {
225
272
if (remainingFunds > 0 ) {
226
273
_safeTransfer (token, launcher, remainingFunds);
227
274
remainingFunds = 0 ;
@@ -236,6 +283,11 @@ contract Escrow is IEscrow, ReentrancyGuard {
236
283
}
237
284
}
238
285
286
+ /**
287
+ * @dev Stores intermediate results during the escrow process.
288
+ * @param _url URL of the intermediate results.
289
+ * @param _hash Hash of the intermediate results.
290
+ */
239
291
function storeResults (
240
292
string memory _url ,
241
293
string memory _hash ,
@@ -270,22 +322,13 @@ contract Escrow is IEscrow, ReentrancyGuard {
270
322
}
271
323
272
324
/**
273
- * @dev Performs bulk payout to multiple workers
274
- * Escrow needs to be completed / cancelled, so that it can be paid out.
275
- * Every recipient is paid with the amount after reputation and recording oracle fees taken out.
276
- * If the amount is less than the fee, the recipient is not paid.
277
- * If the fee is zero, reputation, and recording oracle are not paid.
278
- * Payout will fail if any of the transaction fails.
279
- * If the escrow is fully paid out, meaning that the balance of the escrow is 0, it'll set as Paid.
280
- * If the escrow is partially paid out, meaning that the escrow still has remaining balance, it'll set as Partial.
281
- * This contract is only callable if the contract is not broke, not launched, not paid, not expired, by trusted parties.
282
- *
283
- * @param _recipients Array of recipients
325
+ * @dev Performs bulk payout to multiple recipients with oracle fees deducted.
326
+ * @param _recipients Array of recipient addresses.
284
327
* @param _amounts Array of amounts to be paid to each recipient.
285
- * @param _url URL storing results as transaction details
286
- * @param _hash Hash of the results
287
- * @param _txId Transaction ID
288
- * @param forceComplete Boolean parameter indicating if remaining balance should be transferred to the escrow creator
328
+ * @param _url URL storing results as transaction details.
329
+ * @param _hash Hash of the results.
330
+ * @param _txId Transaction ID.
331
+ * @param forceComplete Boolean indicating if remaining balance should be transferred to the launcher.
289
332
*/
290
333
function bulkPayOut (
291
334
address [] memory _recipients ,
@@ -314,60 +357,80 @@ contract Escrow is IEscrow, ReentrancyGuard {
314
357
status != EscrowStatuses.Cancelled,
315
358
'Invalid status '
316
359
);
317
-
318
- uint256 aggregatedBulkAmount = 0 ;
319
- for (uint256 i = 0 ; i < _amounts.length ; i++ ) {
320
- uint256 amount = _amounts[i];
321
- require (amount > 0 , 'Amount should be greater than zero ' );
322
- aggregatedBulkAmount += amount;
323
- }
324
- require (aggregatedBulkAmount < BULK_MAX_VALUE, 'Bulk value too high ' );
325
360
require (
326
- aggregatedBulkAmount <= reservedFunds ,
327
- 'Not enough reserved funds '
361
+ bytes (_url). length != 0 && bytes (_hash). length != 0 ,
362
+ 'URL or hash is empty '
328
363
);
329
364
330
- reservedFunds -= aggregatedBulkAmount;
331
- remainingFunds -= aggregatedBulkAmount;
332
-
333
- require (bytes (_url).length != 0 , "URL can't be empty " );
334
- require (bytes (_hash).length != 0 , "Hash can't be empty " );
365
+ uint256 totalBulkAmount = 0 ;
366
+ uint256 totalReputationOracleFee = 0 ;
367
+ uint256 totalRecordingOracleFee = 0 ;
368
+ uint256 totalExchangeOracleFee = 0 ;
335
369
336
- finalResultsUrl = _url;
337
- finalResultsHash = _hash;
370
+ for (uint256 i = 0 ; i < _recipients.length ; i++ ) {
371
+ uint256 amount = _amounts[i];
372
+ require (amount > 0 , 'Amount should be greater than zero ' );
373
+ totalBulkAmount += amount;
374
+ totalReputationOracleFee +=
375
+ (reputationOracleFeePercentage * amount) /
376
+ 100 ;
377
+ totalRecordingOracleFee +=
378
+ (recordingOracleFeePercentage * amount) /
379
+ 100 ;
380
+ totalExchangeOracleFee +=
381
+ (exchangeOracleFeePercentage * amount) /
382
+ 100 ;
383
+ }
384
+ require (totalBulkAmount <= reservedFunds, 'Not enough reserved funds ' );
338
385
339
- uint256 totalFeePercentage = reputationOracleFeePercentage +
340
- recordingOracleFeePercentage +
341
- exchangeOracleFeePercentage ;
386
+ uint256 paidReputation = 0 ;
387
+ uint256 paidRecording = 0 ;
388
+ uint256 paidExchange = 0 ;
342
389
343
390
for (uint256 i = 0 ; i < _recipients.length ; i++ ) {
344
391
uint256 amount = _amounts[i];
345
- uint256 amountFee = (totalFeePercentage * amount) / 100 ;
346
- _safeTransfer (token, _recipients[i], amount - amountFee);
347
- }
392
+ uint256 reputationOracleFee = (reputationOracleFeePercentage *
393
+ amount) / 100 ;
394
+ uint256 recordingOracleFee = (recordingOracleFeePercentage *
395
+ amount) / 100 ;
396
+ uint256 exchangeOracleFee = (exchangeOracleFeePercentage * amount) /
397
+ 100 ;
398
+
399
+ if (i == _recipients.length - 1 ) {
400
+ reputationOracleFee = totalReputationOracleFee - paidReputation;
401
+ recordingOracleFee = totalRecordingOracleFee - paidRecording;
402
+ exchangeOracleFee = totalExchangeOracleFee - paidExchange;
403
+ }
404
+
405
+ paidReputation += reputationOracleFee;
406
+ paidRecording += recordingOracleFee;
407
+ paidExchange += exchangeOracleFee;
348
408
349
- // Transfer oracle fees
350
- if (reputationOracleFeePercentage > 0 ) {
351
409
_safeTransfer (
352
410
token,
353
- reputationOracle,
354
- (reputationOracleFeePercentage * aggregatedBulkAmount) / 100
411
+ _recipients[i],
412
+ amount -
413
+ reputationOracleFee -
414
+ recordingOracleFee -
415
+ exchangeOracleFee
355
416
);
356
417
}
418
+
419
+ // Transfer oracle fees
420
+ if (reputationOracleFeePercentage > 0 ) {
421
+ _safeTransfer (token, reputationOracle, totalReputationOracleFee);
422
+ }
357
423
if (recordingOracleFeePercentage > 0 ) {
358
- _safeTransfer (
359
- token,
360
- recordingOracle,
361
- (recordingOracleFeePercentage * aggregatedBulkAmount) / 100
362
- );
424
+ _safeTransfer (token, recordingOracle, totalRecordingOracleFee);
363
425
}
364
426
if (exchangeOracleFeePercentage > 0 ) {
365
- _safeTransfer (
366
- token,
367
- exchangeOracle,
368
- (exchangeOracleFeePercentage * aggregatedBulkAmount) / 100
369
- );
427
+ _safeTransfer (token, exchangeOracle, totalExchangeOracleFee);
370
428
}
429
+ remainingFunds -= totalBulkAmount;
430
+ reservedFunds -= totalBulkAmount;
431
+
432
+ finalResultsUrl = _url;
433
+ finalResultsHash = _hash;
371
434
372
435
if (remainingFunds == 0 || forceComplete) {
373
436
emit BulkTransferV2 (
@@ -377,7 +440,7 @@ contract Escrow is IEscrow, ReentrancyGuard {
377
440
false ,
378
441
finalResultsUrl
379
442
);
380
- _complete ();
443
+ _finalize ();
381
444
} else {
382
445
if (status != EscrowStatuses.ToCancel) {
383
446
status = EscrowStatuses.Partial;
@@ -394,13 +457,11 @@ contract Escrow is IEscrow, ReentrancyGuard {
394
457
395
458
/**
396
459
* @dev Overloaded function to perform bulk payout with default forceComplete set to false.
397
- * Calls the main bulkPayout function with forceComplete as false.
398
- *
399
- * @param _recipients Array of recipients
460
+ * @param _recipients Array of recipient addresses.
400
461
* @param _amounts Array of amounts to be paid to each recipient.
401
- * @param _url URL storing results as transaction details
402
- * @param _hash Hash of the results
403
- * @param _txId Transaction ID
462
+ * @param _url URL storing results as transaction details.
463
+ * @param _hash Hash of the results.
464
+ * @param _txId Transaction ID.
404
465
*/
405
466
function bulkPayOut (
406
467
address [] memory _recipients ,
@@ -412,6 +473,12 @@ contract Escrow is IEscrow, ReentrancyGuard {
412
473
bulkPayOut (_recipients, _amounts, _url, _hash, _txId, false );
413
474
}
414
475
476
+ /**
477
+ * @dev Internal function to safely transfer tokens.
478
+ * @param _token Address of the token to transfer.
479
+ * @param to Address of the recipient.
480
+ * @param value Amount to transfer.
481
+ */
415
482
function _safeTransfer (address _token , address to , uint256 value ) internal {
416
483
SafeERC20.safeTransfer (IERC20 (_token), to, value);
417
484
}
0 commit comments