Skip to content

Commit ee64e29

Browse files
feat(aggregation-mode): add subscription limit and arbitrary expiration lists (#2201)
Co-authored-by: Marcos Nicolau <[email protected]>
1 parent 9a529b0 commit ee64e29

File tree

5 files changed

+154
-23
lines changed

5 files changed

+154
-23
lines changed

contracts/script/deploy/AggregationModePaymentServiceDeployer.s.sol

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,27 @@ contract AggregationModePaymentServiceDeployer is Script {
1111
string memory configData = vm.readFile(configPath);
1212

1313
address owner = stdJson.readAddress(configData, ".permissions.paymentServiceOwner");
14+
address admin = stdJson.readAddress(configData, ".permissions.paymentServiceAdmin");
1415
address recipient = stdJson.readAddress(configData, ".permissions.recipient");
1516
uint256 amountToPay = stdJson.readUint(configData, ".amounts.amountToPayInWei");
1617
uint256 paymentExpirationTimeSeconds = stdJson.readUint(configData, ".amounts.paymentExpirationTimeSeconds");
18+
uint256 subscriptionLimit = stdJson.readUint(configData, ".amounts.subscriptionLimit");
19+
uint256 maxSubscriptionTimeAhead = stdJson.readUint(configData, ".amounts.maxSubscriptionTimeAhead");
1720

1821
vm.startBroadcast();
1922

2023
AggregationModePaymentService implementation = new AggregationModePaymentService();
2124
ERC1967Proxy proxy = new ERC1967Proxy(
2225
address(implementation),
2326
abi.encodeWithSignature(
24-
"initialize(address,address,uint256,uint256)",
27+
"initialize(address,address,address,uint256,uint256,uint256,uint256)",
2528
owner,
29+
admin,
2630
recipient,
2731
amountToPay,
28-
paymentExpirationTimeSeconds
32+
paymentExpirationTimeSeconds,
33+
subscriptionLimit,
34+
maxSubscriptionTimeAhead
2935
)
3036
);
3137

contracts/script/deploy/config/devnet/proof-aggregator-service.devnet.config.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@
1111
},
1212
"amounts": {
1313
"amountToPayInWei": 1000000000000000000,
14-
"paymentExpirationTimeSeconds": 86400
14+
"paymentExpirationTimeSeconds": 86400,
15+
"subscriptionLimit": 5,
16+
"maxSubscriptionTimeAhead": 7776000
1517
},
1618
"permissions": {
1719
"owner": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955",
1820
"paymentServiceOwner": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955",
21+
"paymentServiceAdmin": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f",
1922
"recipient": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f"
2023
}
2124
}

contracts/scripts/anvil/state/alignedlayer-deployed-anvil-state.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

contracts/src/core/AggregationModePaymentService.sol

Lines changed: 129 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22
pragma solidity ^0.8.12;
33

44
import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
5-
import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
65
import {UUPSUpgradeable} from "@openzeppelin-upgrades/contracts/proxy/utils/UUPSUpgradeable.sol";
6+
import {AccessControlUpgradeable} from "@openzeppelin-upgrades/contracts/access/AccessControlUpgradeable.sol";
77

88
/**
99
* @title AggregationModePaymentService
1010
* @author Aligned Layer
1111
* @notice Handles deposits that grant time-limited access to aggregation services.
1212
*/
13-
contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUPSUpgradeable {
13+
contract AggregationModePaymentService is Initializable, UUPSUpgradeable, AccessControlUpgradeable {
14+
15+
bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE");
16+
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
17+
1418
/// @notice for how much time the payment is valid in seconds
1519
uint256 public paymentExpirationTimeSeconds;
1620

@@ -20,6 +24,20 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
2024
/// @notice The address where the payment funds will be sent.
2125
address public paymentFundsRecipient;
2226

27+
/// @notice The limit of subscriptions for different addresses
28+
uint256 public subscriptionLimit;
29+
30+
/// @notice Number of current subscriptions
31+
uint256 public activeSubscriptionsAmount;
32+
33+
/// @notice Maximum amount of time (in seconds) an address can be subscribed ahead of the current block timestamp.
34+
/// Prevents stacking multiple short subscriptions and paying them over an extended period.
35+
uint256 public maxSubscriptionTimeAhead;
36+
37+
/// @notice Number of addresses currently subscribed.
38+
/// @dev `expirationTime` is a Unix timestamp (UTC seconds) compared against block timestamps.
39+
mapping(address subscriber => uint256 expirationTime) public subscribedAddresses;
40+
2341
/**
2442
* @notice Emitted when a user deposits funds to purchase service time.
2543
* @param user Address that sent the payment.
@@ -37,6 +55,18 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
3755
/// @param newAmountToPay the new amount to pay for a subscription in wei.
3856
event AmountToPayUpdated(uint256 indexed newAmountToPay);
3957

58+
/// @notice Event emitted when the subscription limit is updated
59+
/// @param newSubscriptionLimit the new subscription limit.
60+
event SubscriptionLimitUpdated(uint256 indexed newSubscriptionLimit);
61+
62+
/// @notice Event emitted when the subscription amount is updated
63+
/// @param newSubscriptionsAmount the new subscriptions amount.
64+
event ActiveSubscriptionsAmountUpdated(uint256 indexed newSubscriptionsAmount);
65+
66+
/// @notice Event emitted when the max subscription time ahead is updated
67+
/// @param newMaxSubscriptionTimeAhead the max time allowed to subscribe ahead the current timestamp.
68+
event MaxSubscriptionTimeAheadUpdated(uint256 indexed newMaxSubscriptionTimeAhead);
69+
4070
/// @notice Event emitted when the funds recipient is updated
4171
/// @param newFundsRecipient the new address for receiving the funds on withdrawal.
4272
event FundsRecipientUpdated(address indexed newFundsRecipient);
@@ -48,6 +78,10 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
4878

4979
error InvalidDepositAmount(uint256 amountReceived, uint256 amountRequired);
5080

81+
error SubscriptionLimitReached(uint256 subscriptionLimit);
82+
83+
error SubscriptionTimeExceedsLimit(uint256 newSubscriptionTime, uint256 timeLimit);
84+
5185
/**
5286
* @notice Disables initializers for the implementation contract.
5387
*/
@@ -58,15 +92,31 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
5892
/**
5993
* @notice Initializes the contract and transfers ownership to the provided address.
6094
* @param _owner Address that becomes the contract owner.
95+
* @param _admin Address that becomes the contract admin.
96+
* @param _paymentFundsRecipient Address that will receive the withdrawal funds.
97+
* @param _amountToPayInWei Amount to pay in wei for the subscription.
98+
* @param _paymentExpirationTimeSeconds The time in seconds that the subscription takes to expire.
99+
* @param _subscriptionLimit The maximum subscribers that can be subscribed at the same time.
100+
*
61101
*/
62-
function initialize(address _owner, address _paymentFundsRecipient, uint256 _amountToPayInWei, uint256 _paymentExpirationTimeSeconds) public initializer {
63-
__Ownable_init();
102+
function initialize(
103+
address _owner,
104+
address _admin,
105+
address _paymentFundsRecipient,
106+
uint256 _amountToPayInWei,
107+
uint256 _paymentExpirationTimeSeconds,
108+
uint256 _subscriptionLimit,
109+
uint256 _maxSubscriptionTimeAhead
110+
) public initializer {
64111
__UUPSUpgradeable_init();
65-
_transferOwnership(_owner);
112+
_grantRole(OWNER_ROLE, _owner);
113+
_grantRole(ADMIN_ROLE, _admin);
66114

67115
paymentExpirationTimeSeconds = _paymentExpirationTimeSeconds;
68116
amountToPayInWei = _amountToPayInWei;
69117
paymentFundsRecipient = _paymentFundsRecipient;
118+
subscriptionLimit = _subscriptionLimit;
119+
maxSubscriptionTimeAhead = _maxSubscriptionTimeAhead;
70120
}
71121

72122
/**
@@ -76,14 +126,14 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
76126
function _authorizeUpgrade(address newImplementation)
77127
internal
78128
override
79-
onlyOwner // solhint-disable-next-line no-empty-blocks
129+
onlyRole(OWNER_ROLE) // solhint-disable-next-line no-empty-blocks
80130
{}
81131

82132
/**
83133
* @notice Sets the new expiration time. Only callable by the owner
84134
* @param newExpirationTimeInSeconds The new expiration time for the users payments in seconds.
85135
*/
86-
function setPaymentExpirationTimeSeconds(uint256 newExpirationTimeInSeconds) public onlyOwner() {
136+
function setPaymentExpirationTimeSeconds(uint256 newExpirationTimeInSeconds) public onlyRole(OWNER_ROLE) {
87137
paymentExpirationTimeSeconds = newExpirationTimeInSeconds;
88138

89139
emit PaymentExpirationTimeUpdated(newExpirationTimeInSeconds);
@@ -93,7 +143,7 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
93143
* @notice Sets the new amount to pay. Only callable by the owner
94144
* @param newRecipient The new address for receiving the funds on withdrawal.
95145
*/
96-
function setFundsRecipientAddress(address newRecipient) public onlyOwner() {
146+
function setFundsRecipientAddress(address newRecipient) public onlyRole(OWNER_ROLE) {
97147
paymentFundsRecipient = newRecipient;
98148

99149
emit FundsRecipientUpdated(newRecipient);
@@ -103,12 +153,61 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
103153
* @notice Sets the new amount to pay. Only callable by the owner
104154
* @param newAmountToPay The new amount to pay for subscription in wei.
105155
*/
106-
function setAmountToPay(uint256 newAmountToPay) public onlyOwner() {
156+
function setAmountToPay(uint256 newAmountToPay) public onlyRole(OWNER_ROLE) {
107157
amountToPayInWei = newAmountToPay;
108158

109159
emit AmountToPayUpdated(newAmountToPay);
110160
}
111161

162+
/**
163+
* @notice Sets the new subscription limit. Only callable by the owner
164+
* @param newSubscriptionLimit The new subscription limit.
165+
*/
166+
function setSubscriptionLimit(uint256 newSubscriptionLimit) public onlyRole(OWNER_ROLE) {
167+
subscriptionLimit = newSubscriptionLimit;
168+
169+
emit SubscriptionLimitUpdated(newSubscriptionLimit);
170+
}
171+
172+
/**
173+
* @notice Sets the subscriptions counter to the value received by parameter. Only callable by the owner
174+
* @param newSubscriptionsAmount The new subscriptions amount.
175+
*/
176+
function setActiveSubscriptionsAmount(uint256 newSubscriptionsAmount) public onlyRole(ADMIN_ROLE) {
177+
activeSubscriptionsAmount = newSubscriptionsAmount;
178+
179+
emit ActiveSubscriptionsAmountUpdated(newSubscriptionsAmount);
180+
}
181+
182+
/**
183+
* @notice Sets the max subscription time ahead to the value received by parameter. Only callable by the owner
184+
* @param newMaxSubscriptionTimeAhead max time allowed to subscribe ahead the current timestamp.
185+
*/
186+
function setMaxSubscriptionTimeAhead(uint256 newMaxSubscriptionTimeAhead) public onlyRole(OWNER_ROLE) {
187+
maxSubscriptionTimeAhead = newMaxSubscriptionTimeAhead;
188+
189+
emit MaxSubscriptionTimeAheadUpdated(newMaxSubscriptionTimeAhead);
190+
}
191+
192+
/**
193+
* @notice Adds an array of addresses to the payment map and emits the Payment event.
194+
* @param addressesToAdd the addresses to be subscribed
195+
* @param expirationTimestamp the expiration timestamp (UTC seconds) for that subscriptions
196+
* Note: this method adds the subscriptions without checking if the final amount of subscriptions surpasses
197+
* the subscriptionLimit
198+
*/
199+
function addSubscriptions(address[] memory addressesToAdd, uint256 expirationTimestamp) public onlyRole(ADMIN_ROLE) {
200+
for (uint256 i=0; i < addressesToAdd.length; ++i) {
201+
address addressToAdd = addressesToAdd[i];
202+
203+
subscribedAddresses[addressToAdd] = expirationTimestamp;
204+
205+
++activeSubscriptionsAmount;
206+
207+
emit UserPayment(addressToAdd, amountToPayInWei, block.timestamp, expirationTimestamp);
208+
}
209+
}
210+
112211
/**
113212
* @notice Accepts payments and validates they meet the minimum requirement.
114213
*/
@@ -119,13 +218,33 @@ contract AggregationModePaymentService is Initializable, OwnableUpgradeable, UUP
119218
revert InvalidDepositAmount(amount, amountToPayInWei);
120219
}
121220

221+
if (activeSubscriptionsAmount >= subscriptionLimit) {
222+
revert SubscriptionLimitReached(subscriptionLimit);
223+
}
224+
225+
if (subscribedAddresses[msg.sender] < block.timestamp) {
226+
// Subscription is inactive/expired: start a new period from now.
227+
subscribedAddresses[msg.sender] = block.timestamp + paymentExpirationTimeSeconds;
228+
} else {
229+
// Subscription is still active: extend the current expiry by one period.
230+
subscribedAddresses[msg.sender] = subscribedAddresses[msg.sender] + paymentExpirationTimeSeconds;
231+
}
232+
233+
uint256 newExpiration = subscribedAddresses[msg.sender];
234+
235+
if (newExpiration - block.timestamp > maxSubscriptionTimeAhead) {
236+
revert SubscriptionTimeExceedsLimit(newExpiration, maxSubscriptionTimeAhead);
237+
}
238+
239+
++activeSubscriptionsAmount;
240+
122241
emit UserPayment(msg.sender, amount, block.timestamp, block.timestamp + paymentExpirationTimeSeconds);
123242
}
124243

125244
/**
126245
* @notice Withdraws the contract balance to the recipient address.
127246
*/
128-
function withdraw() external onlyOwner {
247+
function withdraw() external onlyRole(OWNER_ROLE) {
129248
uint256 balance = address(this).balance;
130249
payable(paymentFundsRecipient).transfer(balance);
131250
emit FundsWithdrawn(paymentFundsRecipient, balance);

0 commit comments

Comments
 (0)