Skip to content

Commit 8b28b5d

Browse files
f - test .upgrade()
1 parent 041bb6a commit 8b28b5d

File tree

5 files changed

+396
-8
lines changed

5 files changed

+396
-8
lines changed

packages/horizon/contracts/interfaces/IRecurringCollector.sol

+14-3
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ interface IRecurringCollector is IAuthorizable, IPaymentsCollector {
5858
}
5959

6060
struct RecurringCollectionAgreementUpgrade {
61-
// The agreement ID of the RCA
61+
// The agreement ID
6262
bytes16 agreementId;
63-
// The deadline for accepting the RCA
64-
uint256 acceptDeadline;
63+
// The deadline for upgrading
64+
uint256 upgradeDeadline;
6565
// The duration of the agreement in seconds
6666
uint256 duration;
6767
// The maximum amount of tokens that can be collected in the first collection
@@ -143,6 +143,12 @@ interface IRecurringCollector is IAuthorizable, IPaymentsCollector {
143143
*/
144144
error RecurringCollectorAgreementAcceptanceElapsed(uint256 elapsedAt);
145145

146+
/**
147+
* Thrown when calling upgrade() for an agreement with an elapsed upgrade deadline
148+
* @param elapsedAt The timestamp when the upgrade deadline elapsed
149+
*/
150+
error RecurringCollectorAgreementUpgradeElapsed(uint256 elapsedAt);
151+
146152
/**
147153
* Thrown when the signer is invalid
148154
*/
@@ -261,4 +267,9 @@ interface IRecurringCollector is IAuthorizable, IPaymentsCollector {
261267
* @return The address of the signer.
262268
*/
263269
function recoverRCAUSigner(SignedRCAU calldata signedRCAU) external view returns (address);
270+
271+
/**
272+
* @notice Gets an agreement.
273+
*/
274+
function getAgreement(bytes16 agreementId) external view returns (AgreementData memory);
264275
}

packages/horizon/contracts/payments/collectors/RecurringCollector.sol

+12-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
2929
/// @notice The EIP712 typehash for the RecurringCollectionAgreementUpgrade struct
3030
bytes32 public constant EIP712_RCAU_TYPEHASH =
3131
keccak256(
32-
"RecurringCollectionAgreementUpgrade(bytes16 agreementId,uint256 acceptDeadline,uint256 duration,uint256 maxInitialTokens,uint256 maxOngoingTokensPerSecond,uint32 minSecondsPerCollection,uint32 maxSecondsPerCollection,bytes metadata)"
32+
"RecurringCollectionAgreementUpgrade(bytes16 agreementId,uint256 upgradeDeadline,uint256 duration,uint256 maxInitialTokens,uint256 maxOngoingTokensPerSecond,uint32 minSecondsPerCollection,uint32 maxSecondsPerCollection,bytes metadata)"
3333
);
3434

3535
/// @notice Sentinel value to indicate an agreement has been canceled
@@ -126,8 +126,8 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
126126
*/
127127
function upgrade(SignedRCAU calldata signedRCAU) external {
128128
require(
129-
signedRCAU.rcau.acceptDeadline >= block.timestamp,
130-
RecurringCollectorAgreementAcceptanceElapsed(signedRCAU.rcau.acceptDeadline)
129+
signedRCAU.rcau.upgradeDeadline >= block.timestamp,
130+
RecurringCollectorAgreementUpgradeElapsed(signedRCAU.rcau.upgradeDeadline)
131131
);
132132

133133
AgreementData storage agreement = _getForUpdateAgreement(signedRCAU.rcau.agreementId);
@@ -177,6 +177,13 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
177177
return _encodeRCAU(rcau);
178178
}
179179

180+
/**
181+
* @notice See {IRecurringCollector.getAgreement}
182+
*/
183+
function getAgreement(bytes16 agreementId) external view returns (AgreementData memory) {
184+
return _getAgreement(agreementId);
185+
}
186+
180187
/**
181188
* @notice Decodes the collect data.
182189
*/
@@ -319,7 +326,7 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
319326
abi.encode(
320327
EIP712_RCAU_TYPEHASH,
321328
_rcau.agreementId,
322-
_rcau.acceptDeadline,
329+
_rcau.upgradeDeadline,
323330
_rcau.duration,
324331
_rcau.maxInitialTokens,
325332
_rcau.maxOngoingTokensPerSecond,
@@ -364,7 +371,7 @@ contract RecurringCollector is EIP712, GraphDirectory, Authorizable, IRecurringC
364371
}
365372

366373
/**
367-
* @notice Gets an agreement.
374+
* @notice See {IRecurringCollector.getAgreement}
368375
*/
369376
function _getAgreement(bytes16 _agreementId) private view returns (AgreementData memory) {
370377
return agreements[_agreementId];

packages/horizon/test/payments/recurring-collector/RecurringCollector.t.sol

+142
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,132 @@ contract RecurringCollectorTest is Test, Bounder {
392392
assertEq(collected, tokens);
393393
}
394394

395+
function test_Upgrade_Revert_WhenUpgradeElapsed(
396+
IRecurringCollector.RecurringCollectionAgreement memory rca,
397+
uint256 unboundedUpgradeSkip
398+
) public {
399+
rca = _sensibleRCA(rca);
400+
IRecurringCollector.RecurringCollectionAgreementUpgrade memory rcau = _sensibleRCAU(rca);
401+
402+
boundSkipCeil(unboundedUpgradeSkip, type(uint256).max);
403+
rcau.upgradeDeadline = bound(rcau.upgradeDeadline, 0, block.timestamp - 1);
404+
IRecurringCollector.SignedRCAU memory signedRCAU = IRecurringCollector.SignedRCAU({
405+
rcau: rcau,
406+
signature: ""
407+
});
408+
409+
bytes memory expectedErr = abi.encodeWithSelector(
410+
IRecurringCollector.RecurringCollectorAgreementUpgradeElapsed.selector,
411+
rcau.upgradeDeadline
412+
);
413+
vm.expectRevert(expectedErr);
414+
vm.prank(rca.dataService);
415+
_recurringCollector.upgrade(signedRCAU);
416+
}
417+
418+
function test_Upgrade_Revert_WhenNeverAccepted(IRecurringCollector.RecurringCollectionAgreement memory rca) public {
419+
rca = _sensibleRCA(rca);
420+
IRecurringCollector.RecurringCollectionAgreementUpgrade memory rcau = _sensibleRCAU(rca);
421+
422+
rcau.upgradeDeadline = block.timestamp;
423+
IRecurringCollector.SignedRCAU memory signedRCAU = IRecurringCollector.SignedRCAU({
424+
rcau: rcau,
425+
signature: ""
426+
});
427+
428+
bytes memory expectedErr = abi.encodeWithSelector(
429+
IRecurringCollector.RecurringCollectorAgreementNeverAccepted.selector,
430+
rcau.agreementId
431+
);
432+
vm.expectRevert(expectedErr);
433+
vm.prank(rca.dataService);
434+
_recurringCollector.upgrade(signedRCAU);
435+
}
436+
437+
function test_Upgrade_Revert_WhenDataServiceNotAuthorized(
438+
IRecurringCollector.RecurringCollectionAgreement memory rca,
439+
uint256 unboundedKey,
440+
uint256 unboundedUpgradeSkip,
441+
address notDataService
442+
) public {
443+
vm.assume(rca.dataService != notDataService);
444+
rca = _sensibleRCA(rca);
445+
IRecurringCollector.RecurringCollectionAgreementUpgrade memory rcau = _sensibleRCAU(rca);
446+
uint256 signerKey = boundKey(unboundedKey);
447+
_authorizeAndAccept(rca, signerKey);
448+
449+
boundSkipCeil(unboundedUpgradeSkip, type(uint256).max);
450+
rcau.upgradeDeadline = boundTimestampMin(rcau.upgradeDeadline, block.timestamp + 1);
451+
IRecurringCollector.SignedRCAU memory signedRCAU = _recurringCollectorHelper.generateSignedRCAU(
452+
rcau,
453+
signerKey
454+
);
455+
456+
bytes memory expectedErr = abi.encodeWithSelector(
457+
IRecurringCollector.RecurringCollectorDataServiceNotAuthorized.selector,
458+
rcau.agreementId,
459+
notDataService
460+
);
461+
vm.expectRevert(expectedErr);
462+
vm.prank(notDataService);
463+
_recurringCollector.upgrade(signedRCAU);
464+
}
465+
466+
function test_Upgrade_Revert_WhenInvalidSigner(
467+
IRecurringCollector.RecurringCollectionAgreement memory rca,
468+
uint256 unboundedKey,
469+
uint256 unboundedUpgradeSkip,
470+
uint256 unboundedInvalidSignerKey
471+
) public {
472+
uint256 signerKey = boundKey(unboundedKey);
473+
uint256 invalidSignerKey = boundKey(unboundedInvalidSignerKey);
474+
vm.assume(signerKey != invalidSignerKey);
475+
476+
rca = _sensibleRCA(rca);
477+
IRecurringCollector.RecurringCollectionAgreementUpgrade memory rcau = _sensibleRCAU(rca);
478+
_authorizeAndAccept(rca, signerKey);
479+
480+
boundSkipCeil(unboundedUpgradeSkip, type(uint256).max);
481+
rcau.upgradeDeadline = boundTimestampMin(rcau.upgradeDeadline, block.timestamp + 1);
482+
483+
IRecurringCollector.SignedRCAU memory signedRCAU = _recurringCollectorHelper.generateSignedRCAU(
484+
rcau,
485+
invalidSignerKey
486+
);
487+
488+
vm.expectRevert(IRecurringCollector.RecurringCollectorInvalidSigner.selector);
489+
vm.prank(rca.dataService);
490+
_recurringCollector.upgrade(signedRCAU);
491+
}
492+
493+
function test_Upgrade_OK(
494+
IRecurringCollector.RecurringCollectionAgreement memory rca,
495+
uint256 unboundedKey,
496+
uint256 unboundedUpgradeSkip
497+
) public {
498+
rca = _sensibleRCA(rca);
499+
IRecurringCollector.RecurringCollectionAgreementUpgrade memory rcau = _sensibleRCAU(rca);
500+
uint256 signerKey = boundKey(unboundedKey);
501+
_authorizeAndAccept(rca, signerKey);
502+
503+
boundSkipCeil(unboundedUpgradeSkip, type(uint256).max);
504+
rcau.upgradeDeadline = boundTimestampMin(rcau.upgradeDeadline, block.timestamp + 1);
505+
IRecurringCollector.SignedRCAU memory signedRCAU = _recurringCollectorHelper.generateSignedRCAU(
506+
rcau,
507+
signerKey
508+
);
509+
510+
vm.prank(rca.dataService);
511+
_recurringCollector.upgrade(signedRCAU);
512+
513+
IRecurringCollector.AgreementData memory agreement = _recurringCollector.getAgreement(rca.agreementId);
514+
assertEq(rcau.duration, agreement.duration);
515+
assertEq(rcau.maxInitialTokens, agreement.maxInitialTokens);
516+
assertEq(rcau.maxOngoingTokensPerSecond, agreement.maxOngoingTokensPerSecond);
517+
assertEq(rcau.minSecondsPerCollection, agreement.minSecondsPerCollection);
518+
assertEq(rcau.maxSecondsPerCollection, agreement.maxSecondsPerCollection);
519+
}
520+
395521
/* solhint-enable graph/func-name-mixedcase */
396522

397523
function _authorizeAndAccept(
@@ -487,6 +613,22 @@ contract RecurringCollectorTest is Test, Bounder {
487613
return _rca;
488614
}
489615

616+
function _sensibleRCAU(
617+
IRecurringCollector.RecurringCollectionAgreement memory _rca
618+
) private pure returns (IRecurringCollector.RecurringCollectionAgreementUpgrade memory) {
619+
IRecurringCollector.RecurringCollectionAgreementUpgrade memory rcau;
620+
rcau.agreementId = _rca.agreementId;
621+
rcau.minSecondsPerCollection = uint32(bound(_rca.minSecondsPerCollection, 60, 60 * 60 * 24));
622+
rcau.maxSecondsPerCollection = uint32(
623+
bound(_rca.maxSecondsPerCollection, rcau.minSecondsPerCollection * 2, 60 * 60 * 24 * 30)
624+
);
625+
rcau.duration = bound(_rca.duration, rcau.maxSecondsPerCollection * 10, type(uint256).max);
626+
rcau.maxInitialTokens = bound(_rca.maxInitialTokens, 0, 1e18 * 100_000_000);
627+
rcau.maxOngoingTokensPerSecond = bound(_rca.maxOngoingTokensPerSecond, 1, 1e18);
628+
629+
return rcau;
630+
}
631+
490632
function _generateCollectParams(
491633
IRecurringCollector.RecurringCollectionAgreement memory _rca,
492634
bytes32 _collectionId,

packages/horizon/test/payments/recurring-collector/RecurringCollectorHelper.t.sol

+15
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,19 @@ contract RecurringCollectorHelper is AuthorizableHelper {
2828

2929
return signedRCA;
3030
}
31+
32+
function generateSignedRCAU(
33+
IRecurringCollector.RecurringCollectionAgreementUpgrade memory rcau,
34+
uint256 signerPrivateKey
35+
) public view returns (IRecurringCollector.SignedRCAU memory) {
36+
bytes32 messageHash = collector.encodeRCAU(rcau);
37+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, messageHash);
38+
bytes memory signature = abi.encodePacked(r, s, v);
39+
IRecurringCollector.SignedRCAU memory signedRCAU = IRecurringCollector.SignedRCAU({
40+
rcau: rcau,
41+
signature: signature
42+
});
43+
44+
return signedRCAU;
45+
}
3146
}

0 commit comments

Comments
 (0)