@@ -246,8 +246,10 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
246
246
}
247
247
248
248
/// @inheritdoc IRecurringCollector
249
- function isCollectable (AgreementData memory agreement ) external pure returns (bool ) {
250
- return _isCollectable (agreement);
249
+ function getCollectionInfo (
250
+ AgreementData memory agreement
251
+ ) external view returns (bool isCollectable , uint256 collectionSeconds ) {
252
+ return _getCollectionInfo (agreement);
251
253
}
252
254
253
255
/**
@@ -274,9 +276,14 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
274
276
CollectParams memory _params
275
277
) private returns (uint256 ) {
276
278
AgreementData storage agreement = _getAgreementStorage (_params.agreementId);
279
+
280
+ // Check if agreement exists first (for unknown agreements)
281
+ (bool isCollectable , uint256 collectionSeconds ) = _getCollectionInfo (agreement);
282
+ require (isCollectable, RecurringCollectorAgreementIncorrectState (_params.agreementId, agreement.state));
283
+
277
284
require (
278
- _isCollectable (agreement) ,
279
- RecurringCollectorAgreementIncorrectState (_params.agreementId, agreement.state )
285
+ collectionSeconds > 0 ,
286
+ RecurringCollectorZeroCollectionSeconds (_params.agreementId, block . timestamp , agreement.lastCollectionAt )
280
287
);
281
288
282
289
require (
@@ -297,7 +304,7 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
297
304
298
305
uint256 tokensToCollect = 0 ;
299
306
if (_params.tokens != 0 ) {
300
- tokensToCollect = _requireValidCollect (agreement, _params.agreementId, _params.tokens);
307
+ tokensToCollect = _requireValidCollect (agreement, _params.agreementId, _params.tokens, collectionSeconds );
301
308
302
309
_graphPaymentsEscrow ().collect (
303
310
_paymentType,
@@ -374,53 +381,37 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
374
381
* @param _agreement The agreement data
375
382
* @param _agreementId The ID of the agreement
376
383
* @param _tokens The number of tokens to collect
384
+ * @param _collectionSeconds Collection duration from _getCollectionInfo()
377
385
* @return The number of tokens that can be collected
378
386
*/
379
387
function _requireValidCollect (
380
388
AgreementData memory _agreement ,
381
389
bytes16 _agreementId ,
382
- uint256 _tokens
390
+ uint256 _tokens ,
391
+ uint256 _collectionSeconds
383
392
) private view returns (uint256 ) {
384
393
bool canceledOrElapsed = _agreement.state == AgreementState.CanceledByPayer ||
385
394
block .timestamp > _agreement.endsAt;
386
- uint256 canceledOrNow = _agreement.state == AgreementState.CanceledByPayer
387
- ? _agreement.canceledAt
388
- : block .timestamp ;
389
-
390
- // if canceled by the payer allow collection till canceledAt
391
- // if elapsed allow collection till endsAt
392
- // if both are true, use the earlier one
393
- uint256 collectionEnd = canceledOrElapsed ? Math.min (canceledOrNow, _agreement.endsAt) : block .timestamp ;
394
- uint256 collectionStart = _agreementCollectionStartAt (_agreement);
395
- require (
396
- collectionEnd != collectionStart,
397
- RecurringCollectorZeroCollectionSeconds (_agreementId, block .timestamp , uint64 (collectionStart))
398
- );
399
- require (collectionEnd > collectionStart, RecurringCollectorFinalCollectionDone (_agreementId, collectionStart));
400
-
401
- uint256 collectionSeconds = collectionEnd - collectionStart;
402
- // Check that the collection window is long enough
403
- // If the agreement is canceled or elapsed, allow a shorter collection window
404
395
if (! canceledOrElapsed) {
405
396
require (
406
- collectionSeconds >= _agreement.minSecondsPerCollection,
397
+ _collectionSeconds >= _agreement.minSecondsPerCollection,
407
398
RecurringCollectorCollectionTooSoon (
408
399
_agreementId,
409
- uint32 (collectionSeconds ),
400
+ uint32 (_collectionSeconds ),
410
401
_agreement.minSecondsPerCollection
411
402
)
412
403
);
413
404
}
414
405
require (
415
- collectionSeconds <= _agreement.maxSecondsPerCollection,
406
+ _collectionSeconds <= _agreement.maxSecondsPerCollection,
416
407
RecurringCollectorCollectionTooLate (
417
408
_agreementId,
418
- uint64 (collectionSeconds ),
409
+ uint64 (_collectionSeconds ),
419
410
_agreement.maxSecondsPerCollection
420
411
)
421
412
);
422
413
423
- uint256 maxTokens = _agreement.maxOngoingTokensPerSecond * collectionSeconds ;
414
+ uint256 maxTokens = _agreement.maxOngoingTokensPerSecond * _collectionSeconds ;
424
415
maxTokens += _agreement.lastCollectionAt == 0 ? _agreement.maxInitialTokens : 0 ;
425
416
426
417
return Math.min (_tokens, maxTokens);
@@ -546,20 +537,47 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
546
537
}
547
538
548
539
/**
549
- * @notice Gets the start time for the collection of an agreement.
540
+ * @notice Internal function to get collection info for an agreement
541
+ * @dev This is the single source of truth for collection window logic
550
542
* @param _agreement The agreement data
551
- * @return The start time for the collection of the agreement
543
+ * @return isCollectable Whether the agreement can be collected from
544
+ * @return collectionSeconds The valid collection duration in seconds (0 if not collectable)
552
545
*/
553
- function _agreementCollectionStartAt (AgreementData memory _agreement ) private pure returns (uint256 ) {
554
- return _agreement.lastCollectionAt > 0 ? _agreement.lastCollectionAt : _agreement.acceptedAt;
546
+ function _getCollectionInfo (
547
+ AgreementData memory _agreement
548
+ ) private view returns (bool isCollectable , uint256 collectionSeconds ) {
549
+ // Check if agreement is in collectable state
550
+ isCollectable =
551
+ _agreement.state == AgreementState.Accepted ||
552
+ _agreement.state == AgreementState.CanceledByPayer;
553
+
554
+ if (! isCollectable) {
555
+ return (false , 0 );
556
+ }
557
+
558
+ bool canceledOrElapsed = _agreement.state == AgreementState.CanceledByPayer ||
559
+ block .timestamp > _agreement.endsAt;
560
+ uint256 canceledOrNow = _agreement.state == AgreementState.CanceledByPayer
561
+ ? _agreement.canceledAt
562
+ : block .timestamp ;
563
+
564
+ uint256 collectionEnd = canceledOrElapsed ? Math.min (canceledOrNow, _agreement.endsAt) : block .timestamp ;
565
+ uint256 collectionStart = _agreementCollectionStartAt (_agreement);
566
+
567
+ if (collectionEnd < collectionStart) {
568
+ return (false , 0 );
569
+ }
570
+
571
+ collectionSeconds = collectionEnd - collectionStart;
572
+ return (isCollectable, collectionSeconds);
555
573
}
556
574
557
575
/**
558
- * @notice Requires that the agreement is collectable .
576
+ * @notice Gets the start time for the collection of an agreement .
559
577
* @param _agreement The agreement data
560
- * @return The boolean indicating if the agreement is collectable
578
+ * @return The start time for the collection of the agreement
561
579
*/
562
- function _isCollectable (AgreementData memory _agreement ) private pure returns (bool ) {
563
- return _agreement.state == AgreementState.Accepted || _agreement.state == AgreementState.CanceledByPayer ;
580
+ function _agreementCollectionStartAt (AgreementData memory _agreement ) private pure returns (uint256 ) {
581
+ return _agreement.lastCollectionAt > 0 ? _agreement.lastCollectionAt : _agreement.acceptedAt ;
564
582
}
565
583
}
0 commit comments