@towns-protocol/contracts 0.0.352 → 0.0.353
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/scripts/deployments/facets/DeploySubscriptionModuleFacet.s.sol +2 -3
- package/src/apps/modules/subscription/ISubscriptionModule.sol +20 -4
- package/src/apps/modules/subscription/SubscriptionModuleFacet.sol +153 -65
- package/src/apps/modules/subscription/SubscriptionModuleStorage.sol +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@towns-protocol/contracts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.353",
|
|
4
4
|
"packageManager": "yarn@3.8.0",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build-types": "bash scripts/build-contract-types.sh",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@layerzerolabs/oapp-evm": "^0.3.2",
|
|
36
36
|
"@openzeppelin/merkle-tree": "^1.0.8",
|
|
37
37
|
"@prb/test": "^0.6.4",
|
|
38
|
-
"@towns-protocol/prettier-config": "^0.0.
|
|
38
|
+
"@towns-protocol/prettier-config": "^0.0.353",
|
|
39
39
|
"@typechain/ethers-v5": "^10.1.1",
|
|
40
40
|
"@wagmi/cli": "^2.2.0",
|
|
41
41
|
"forge-std": "github:foundry-rs/forge-std#v1.10.0",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"access": "public"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "8ef3c377692fbe02a8825159d53e390684290560"
|
|
61
61
|
}
|
|
@@ -15,7 +15,7 @@ library DeploySubscriptionModuleFacet {
|
|
|
15
15
|
using DynamicArrayLib for DynamicArrayLib.DynamicArray;
|
|
16
16
|
|
|
17
17
|
function selectors() internal pure returns (bytes4[] memory res) {
|
|
18
|
-
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(
|
|
18
|
+
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(19);
|
|
19
19
|
arr.p(SubscriptionModuleFacet.moduleId.selector);
|
|
20
20
|
arr.p(SubscriptionModuleFacet.onInstall.selector);
|
|
21
21
|
arr.p(SubscriptionModuleFacet.onUninstall.selector);
|
|
@@ -26,7 +26,7 @@ library DeploySubscriptionModuleFacet {
|
|
|
26
26
|
arr.p(SubscriptionModuleFacet.preRuntimeValidationHook.selector);
|
|
27
27
|
arr.p(SubscriptionModuleFacet.preSignatureValidationHook.selector);
|
|
28
28
|
arr.p(SubscriptionModuleFacet.batchProcessRenewals.selector);
|
|
29
|
-
arr.p(SubscriptionModuleFacet.
|
|
29
|
+
arr.p(SubscriptionModuleFacet.getRenewalBuffer.selector);
|
|
30
30
|
arr.p(SubscriptionModuleFacet.getSubscription.selector);
|
|
31
31
|
arr.p(SubscriptionModuleFacet.pauseSubscription.selector);
|
|
32
32
|
arr.p(SubscriptionModuleFacet.getEntityIds.selector);
|
|
@@ -34,7 +34,6 @@ library DeploySubscriptionModuleFacet {
|
|
|
34
34
|
arr.p(SubscriptionModuleFacet.grantOperator.selector);
|
|
35
35
|
arr.p(SubscriptionModuleFacet.revokeOperator.selector);
|
|
36
36
|
arr.p(bytes4(keccak256("MAX_BATCH_SIZE()")));
|
|
37
|
-
arr.p(bytes4(keccak256("RENEWAL_BUFFER()")));
|
|
38
37
|
arr.p(bytes4(keccak256("GRACE_PERIOD()")));
|
|
39
38
|
|
|
40
39
|
bytes32[] memory selectors_ = arr.asBytes32Array();
|
|
@@ -39,6 +39,7 @@ interface ISubscriptionModuleBase {
|
|
|
39
39
|
error SubscriptionModule__EmptyBatch();
|
|
40
40
|
error SubscriptionModule__InvalidTokenOwner();
|
|
41
41
|
error SubscriptionModule__InsufficientBalance();
|
|
42
|
+
error SubscriptionModule__ActiveSubscription();
|
|
42
43
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
43
44
|
/* Events */
|
|
44
45
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
@@ -53,6 +54,8 @@ interface ISubscriptionModuleBase {
|
|
|
53
54
|
|
|
54
55
|
event SubscriptionDeactivated(address indexed account, uint32 indexed entityId);
|
|
55
56
|
|
|
57
|
+
event SubscriptionActivated(address indexed account, uint32 indexed entityId);
|
|
58
|
+
|
|
56
59
|
event SubscriptionSpent(
|
|
57
60
|
address indexed account,
|
|
58
61
|
uint32 indexed entityId,
|
|
@@ -66,7 +69,15 @@ interface ISubscriptionModuleBase {
|
|
|
66
69
|
uint256 nextRenewalTime
|
|
67
70
|
);
|
|
68
71
|
|
|
72
|
+
/// @notice Emitted when a subscription's next renewal time is synced to on-chain expiration
|
|
73
|
+
event SubscriptionSynced(
|
|
74
|
+
address indexed account,
|
|
75
|
+
uint32 indexed entityId,
|
|
76
|
+
uint256 newNextRenewalTime
|
|
77
|
+
);
|
|
78
|
+
|
|
69
79
|
event SubscriptionPaused(address indexed account, uint32 indexed entityId);
|
|
80
|
+
event SubscriptionNotDue(address indexed account, uint32 indexed entityId);
|
|
70
81
|
|
|
71
82
|
event BatchRenewalSkipped(address indexed account, uint32 indexed entityId, string reason);
|
|
72
83
|
|
|
@@ -83,10 +94,6 @@ interface ISubscriptionModule is ISubscriptionModuleBase {
|
|
|
83
94
|
/// @param params The parameters for the renewals
|
|
84
95
|
function batchProcessRenewals(RenewalParams[] calldata params) external;
|
|
85
96
|
|
|
86
|
-
/// @notice Processes a single Towns membership renewal
|
|
87
|
-
/// @param params The parameters for the renewal
|
|
88
|
-
function processRenewal(RenewalParams calldata params) external;
|
|
89
|
-
|
|
90
97
|
/// @notice Gets the subscription for an account and entity ID
|
|
91
98
|
/// @param account The address of the account to get the subscription for
|
|
92
99
|
/// @param entityId The entity ID of the subscription to get
|
|
@@ -96,6 +103,15 @@ interface ISubscriptionModule is ISubscriptionModuleBase {
|
|
|
96
103
|
uint32 entityId
|
|
97
104
|
) external view returns (Subscription memory);
|
|
98
105
|
|
|
106
|
+
/// @notice Gets the renewal buffer for an expiration time
|
|
107
|
+
/// @param expirationTime The expiration time to get the renewal buffer for
|
|
108
|
+
/// @return The renewal buffer for the expiration time
|
|
109
|
+
function getRenewalBuffer(uint256 expirationTime) external view returns (uint256);
|
|
110
|
+
|
|
111
|
+
/// @notice Activates a subscription
|
|
112
|
+
/// @param entityId The entity ID of the subscription to activate
|
|
113
|
+
function activateSubscription(uint32 entityId) external;
|
|
114
|
+
|
|
99
115
|
/// @notice Pauses a subscription
|
|
100
116
|
/// @param entityId The entity ID of the subscription to pause
|
|
101
117
|
function pauseSubscription(uint32 entityId) external;
|
|
@@ -43,9 +43,14 @@ contract SubscriptionModuleFacet is
|
|
|
43
43
|
uint256 internal constant _SIG_VALIDATION_FAILED = 1;
|
|
44
44
|
|
|
45
45
|
uint256 public constant MAX_BATCH_SIZE = 50;
|
|
46
|
-
uint256 public constant RENEWAL_BUFFER = 1 days;
|
|
47
46
|
uint256 public constant GRACE_PERIOD = 3 days;
|
|
48
47
|
|
|
48
|
+
// Dynamic buffer times based on expiration proximity
|
|
49
|
+
uint256 public constant BUFFER_IMMEDIATE = 2 minutes; // For expirations within 1 hour
|
|
50
|
+
uint256 public constant BUFFER_SHORT = 1 hours; // For expirations within 6 hours
|
|
51
|
+
uint256 public constant BUFFER_MEDIUM = 6 hours; // For expirations within 24 hours
|
|
52
|
+
uint256 public constant BUFFER_LONG = 12 hours; // For expirations more than 24 hours away
|
|
53
|
+
|
|
49
54
|
function __SubscriptionModule_init() external onlyInitializing {
|
|
50
55
|
_addInterface(type(ISubscriptionModule).interfaceId);
|
|
51
56
|
_addInterface(type(IValidationModule).interfaceId);
|
|
@@ -81,7 +86,8 @@ contract SubscriptionModuleFacet is
|
|
|
81
86
|
sub.space = space;
|
|
82
87
|
sub.active = true;
|
|
83
88
|
sub.tokenId = tokenId;
|
|
84
|
-
sub.
|
|
89
|
+
sub.installTime = uint40(block.timestamp);
|
|
90
|
+
sub.nextRenewalTime = _calculateNextRenewalTime(expiresAt, sub.installTime);
|
|
85
91
|
|
|
86
92
|
$.entityIds[msg.sender].add(entityId);
|
|
87
93
|
|
|
@@ -168,49 +174,68 @@ contract SubscriptionModuleFacet is
|
|
|
168
174
|
|
|
169
175
|
/// @inheritdoc ISubscriptionModule
|
|
170
176
|
function batchProcessRenewals(RenewalParams[] calldata params) external nonReentrant {
|
|
171
|
-
uint256
|
|
172
|
-
if (
|
|
173
|
-
|
|
177
|
+
uint256 paramsLen = params.length;
|
|
178
|
+
if (paramsLen > MAX_BATCH_SIZE)
|
|
179
|
+
SubscriptionModule__ExceedsMaxBatchSize.selector.revertWith();
|
|
180
|
+
if (paramsLen == 0) SubscriptionModule__EmptyBatch.selector.revertWith();
|
|
174
181
|
|
|
175
182
|
SubscriptionModuleStorage.Layout storage $ = SubscriptionModuleStorage.getLayout();
|
|
176
183
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
SubscriptionModule__InvalidCaller.selector.revertWith();
|
|
184
|
+
if (!$.operators.contains(msg.sender))
|
|
185
|
+
SubscriptionModule__InvalidCaller.selector.revertWith();
|
|
180
186
|
|
|
187
|
+
for (uint256 i; i < paramsLen; ++i) {
|
|
181
188
|
Subscription storage sub = $.subscriptions[params[i].account][params[i].entityId];
|
|
182
189
|
|
|
190
|
+
// Skip if renewal not due (check original nextRenewalTime first)
|
|
191
|
+
if (sub.nextRenewalTime > block.timestamp) {
|
|
192
|
+
emit SubscriptionNotDue(params[i].account, params[i].entityId);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
183
196
|
// Skip inactive subscriptions
|
|
184
197
|
if (!sub.active) {
|
|
185
198
|
emit BatchRenewalSkipped(params[i].account, params[i].entityId, "INACTIVE");
|
|
186
199
|
continue;
|
|
187
200
|
}
|
|
188
201
|
|
|
189
|
-
// Skip if
|
|
190
|
-
if (
|
|
191
|
-
|
|
202
|
+
// Skip if past grace period
|
|
203
|
+
if (sub.nextRenewalTime + GRACE_PERIOD < block.timestamp) {
|
|
204
|
+
_pauseSubscription(sub, params[i].account, params[i].entityId);
|
|
205
|
+
emit BatchRenewalSkipped(params[i].account, params[i].entityId, "PAST_GRACE");
|
|
192
206
|
continue;
|
|
193
207
|
}
|
|
194
208
|
|
|
195
|
-
// Skip if
|
|
196
|
-
if (
|
|
197
|
-
|
|
209
|
+
// Skip if account isn't owner anymore (for safety)
|
|
210
|
+
if (IERC721(sub.space).ownerOf(sub.tokenId) != params[i].account) {
|
|
211
|
+
_pauseSubscription(sub, params[i].account, params[i].entityId);
|
|
212
|
+
emit BatchRenewalSkipped(params[i].account, params[i].entityId, "NOT_OWNER");
|
|
198
213
|
continue;
|
|
199
214
|
}
|
|
200
215
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
216
|
+
MembershipFacet membershipFacet = MembershipFacet(sub.space);
|
|
217
|
+
uint256 expiresAt = membershipFacet.expiresAt(sub.tokenId);
|
|
204
218
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
219
|
+
// Sync next renewal time from on-chain expiration if user called renewMembership directly
|
|
220
|
+
uint40 correctNextRenewalTime = _calculateNextRenewalTime(expiresAt, sub.installTime);
|
|
221
|
+
if (sub.nextRenewalTime != correctNextRenewalTime) {
|
|
222
|
+
sub.nextRenewalTime = correctNextRenewalTime;
|
|
223
|
+
emit SubscriptionSynced(params[i].account, params[i].entityId, sub.nextRenewalTime);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
uint256 actualRenewalPrice = membershipFacet.getMembershipRenewalPrice(sub.tokenId);
|
|
227
|
+
|
|
228
|
+
if (params[i].account.balance < actualRenewalPrice) {
|
|
229
|
+
emit BatchRenewalSkipped(
|
|
230
|
+
params[i].account,
|
|
231
|
+
params[i].entityId,
|
|
232
|
+
"INSUFFICIENT_BALANCE"
|
|
233
|
+
);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_processRenewal(sub, params[i], membershipFacet, actualRenewalPrice);
|
|
238
|
+
}
|
|
214
239
|
}
|
|
215
240
|
|
|
216
241
|
/// @inheritdoc ISubscriptionModule
|
|
@@ -221,6 +246,26 @@ contract SubscriptionModuleFacet is
|
|
|
221
246
|
return SubscriptionModuleStorage.getLayout().subscriptions[account][entityId];
|
|
222
247
|
}
|
|
223
248
|
|
|
249
|
+
/// @inheritdoc ISubscriptionModule
|
|
250
|
+
function getRenewalBuffer(uint256 expirationTime) external view returns (uint256) {
|
|
251
|
+
return _getRenewalBuffer(expirationTime);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/// @inheritdoc ISubscriptionModule
|
|
255
|
+
function activateSubscription(uint32 entityId) external {
|
|
256
|
+
Subscription storage sub = SubscriptionModuleStorage.getLayout().subscriptions[msg.sender][
|
|
257
|
+
entityId
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
if (sub.active) SubscriptionModule__ActiveSubscription.selector.revertWith();
|
|
261
|
+
|
|
262
|
+
address owner = IERC721(sub.space).ownerOf(sub.tokenId);
|
|
263
|
+
if (msg.sender != owner) SubscriptionModule__InvalidCaller.selector.revertWith();
|
|
264
|
+
|
|
265
|
+
sub.active = true;
|
|
266
|
+
emit SubscriptionActivated(msg.sender, entityId);
|
|
267
|
+
}
|
|
268
|
+
|
|
224
269
|
/// @inheritdoc ISubscriptionModule
|
|
225
270
|
function pauseSubscription(uint32 entityId) external {
|
|
226
271
|
Subscription storage sub = SubscriptionModuleStorage.getLayout().subscriptions[msg.sender][
|
|
@@ -229,8 +274,10 @@ contract SubscriptionModuleFacet is
|
|
|
229
274
|
|
|
230
275
|
if (!sub.active) SubscriptionModule__InactiveSubscription.selector.revertWith();
|
|
231
276
|
|
|
232
|
-
|
|
233
|
-
|
|
277
|
+
address owner = IERC721(sub.space).ownerOf(sub.tokenId);
|
|
278
|
+
if (msg.sender != owner) SubscriptionModule__InvalidCaller.selector.revertWith();
|
|
279
|
+
|
|
280
|
+
_pauseSubscription(sub, msg.sender, entityId);
|
|
234
281
|
}
|
|
235
282
|
|
|
236
283
|
/// @inheritdoc ISubscriptionModule
|
|
@@ -264,28 +311,12 @@ contract SubscriptionModuleFacet is
|
|
|
264
311
|
/// @dev Processes a single subscription renewal
|
|
265
312
|
/// @param sub The subscription to renew
|
|
266
313
|
/// @param params The parameters for the renewal
|
|
267
|
-
function _processRenewal(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// Check if we're past the grace period
|
|
274
|
-
if (block.timestamp > sub.nextRenewalTime + GRACE_PERIOD) {
|
|
275
|
-
sub.active = false;
|
|
276
|
-
emit SubscriptionPaused(params.account, params.entityId);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
MembershipFacet membershipFacet = MembershipFacet(sub.space);
|
|
281
|
-
|
|
282
|
-
// Get current renewal price from Towns contract
|
|
283
|
-
uint256 actualRenewalPrice = membershipFacet.getMembershipRenewalPrice(sub.tokenId);
|
|
284
|
-
|
|
285
|
-
// Check if the account has enough balance
|
|
286
|
-
if (params.account.balance < actualRenewalPrice)
|
|
287
|
-
SubscriptionModule__InsufficientBalance.selector.revertWith();
|
|
288
|
-
|
|
314
|
+
function _processRenewal(
|
|
315
|
+
Subscription storage sub,
|
|
316
|
+
RenewalParams calldata params,
|
|
317
|
+
MembershipFacet membershipFacet,
|
|
318
|
+
uint256 actualRenewalPrice
|
|
319
|
+
) internal {
|
|
289
320
|
// Construct the renewal call to space contract
|
|
290
321
|
bytes memory renewalCall = abi.encodeCall(MembershipFacet.renewMembership, (sub.tokenId));
|
|
291
322
|
|
|
@@ -301,9 +332,10 @@ contract SubscriptionModuleFacet is
|
|
|
301
332
|
);
|
|
302
333
|
|
|
303
334
|
// Use the proper pack function from ValidationLocatorLib
|
|
304
|
-
bytes memory authorization =
|
|
335
|
+
bytes memory authorization = ValidationLocatorLib.packSignature(
|
|
305
336
|
params.entityId,
|
|
306
|
-
|
|
337
|
+
false, // selector-based
|
|
338
|
+
bytes.concat(hex"ff", abi.encode(sub.space, sub.tokenId))
|
|
307
339
|
);
|
|
308
340
|
|
|
309
341
|
// Call executeWithRuntimeValidation with the correct parameters
|
|
@@ -320,15 +352,74 @@ contract SubscriptionModuleFacet is
|
|
|
320
352
|
|
|
321
353
|
// Get the actual new expiration time after successful renewal
|
|
322
354
|
uint256 newExpiresAt = membershipFacet.expiresAt(sub.tokenId);
|
|
323
|
-
|
|
324
|
-
// Update subscription state after successful renewal
|
|
325
|
-
sub.nextRenewalTime = uint40(newExpiresAt - RENEWAL_BUFFER);
|
|
355
|
+
sub.nextRenewalTime = _calculateNextRenewalTime(newExpiresAt, sub.installTime);
|
|
326
356
|
sub.lastRenewalTime = uint40(block.timestamp);
|
|
327
357
|
sub.spent += actualRenewalPrice;
|
|
328
358
|
|
|
329
359
|
emit SubscriptionRenewed(params.account, params.entityId, sub.nextRenewalTime);
|
|
330
360
|
}
|
|
331
361
|
|
|
362
|
+
/// @dev Determines the appropriate renewal buffer time based on original membership duration
|
|
363
|
+
/// @param expirationTime The expiration timestamp of the membership
|
|
364
|
+
/// @param installTime The time when the subscription was installed
|
|
365
|
+
/// @return The appropriate buffer time in seconds before expiration
|
|
366
|
+
function _getRenewalBuffer(
|
|
367
|
+
uint256 expirationTime,
|
|
368
|
+
uint256 installTime
|
|
369
|
+
) internal pure returns (uint256) {
|
|
370
|
+
uint256 originalDuration = expirationTime >= installTime ? expirationTime - installTime : 0;
|
|
371
|
+
|
|
372
|
+
// For memberships shorter than 1 hour, use immediate buffer (2 minutes)
|
|
373
|
+
if (originalDuration <= 1 hours) {
|
|
374
|
+
return BUFFER_IMMEDIATE;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// For memberships shorter than 6 hours, use short buffer (1 hour)
|
|
378
|
+
if (originalDuration <= 6 hours) {
|
|
379
|
+
return BUFFER_SHORT;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// For memberships shorter than 24 hours, use medium buffer (6 hours)
|
|
383
|
+
if (originalDuration <= 24 hours) {
|
|
384
|
+
return BUFFER_MEDIUM;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// For memberships longer than 24 hours, use long buffer (12 hours)
|
|
388
|
+
return BUFFER_LONG;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/// @dev Legacy function for backward compatibility - uses current time as install time
|
|
392
|
+
/// @param expirationTime The expiration timestamp of the membership
|
|
393
|
+
/// @return The appropriate buffer time in seconds before expiration
|
|
394
|
+
function _getRenewalBuffer(uint256 expirationTime) internal view returns (uint256) {
|
|
395
|
+
return _getRenewalBuffer(expirationTime, block.timestamp);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/// @dev Calculates the correct next renewal time for a given expiration using install time
|
|
399
|
+
/// @param expirationTime The expiration timestamp of the membership
|
|
400
|
+
/// @param installTime The time when the subscription was installed
|
|
401
|
+
/// @return The next renewal time as uint40
|
|
402
|
+
function _calculateNextRenewalTime(
|
|
403
|
+
uint256 expirationTime,
|
|
404
|
+
uint256 installTime
|
|
405
|
+
) internal view returns (uint40) {
|
|
406
|
+
if (expirationTime <= block.timestamp) return uint40(block.timestamp);
|
|
407
|
+
|
|
408
|
+
uint256 buffer = _getRenewalBuffer(expirationTime, installTime);
|
|
409
|
+
uint256 timeUntilExpiration = expirationTime - block.timestamp;
|
|
410
|
+
|
|
411
|
+
if (buffer >= timeUntilExpiration) return uint40(block.timestamp);
|
|
412
|
+
|
|
413
|
+
return uint40(expirationTime - buffer);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/// @dev Legacy function for backward compatibility - uses current time as install time
|
|
417
|
+
/// @param expirationTime The expiration timestamp of the membership
|
|
418
|
+
/// @return The next renewal time as uint40
|
|
419
|
+
function _calculateNextRenewalTime(uint256 expirationTime) internal view returns (uint40) {
|
|
420
|
+
return _calculateNextRenewalTime(expirationTime, block.timestamp);
|
|
421
|
+
}
|
|
422
|
+
|
|
332
423
|
/// @dev Creates the runtime final data for the renewal
|
|
333
424
|
/// @param entityId The entity ID of the subscription
|
|
334
425
|
/// @param finalData The final data for the renewal
|
|
@@ -345,15 +436,12 @@ contract SubscriptionModuleFacet is
|
|
|
345
436
|
);
|
|
346
437
|
}
|
|
347
438
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
) internal view returns (bool) {
|
|
356
|
-
if (account == msg.sender) return true;
|
|
357
|
-
return operators.contains(msg.sender);
|
|
439
|
+
function _pauseSubscription(
|
|
440
|
+
Subscription storage sub,
|
|
441
|
+
address account,
|
|
442
|
+
uint32 entityId
|
|
443
|
+
) internal {
|
|
444
|
+
sub.active = false;
|
|
445
|
+
emit SubscriptionPaused(account, entityId);
|
|
358
446
|
}
|
|
359
447
|
}
|
|
@@ -7,6 +7,7 @@ struct Subscription {
|
|
|
7
7
|
uint256 tokenId; // 32 bytes
|
|
8
8
|
uint256 spent; // 32 bytes
|
|
9
9
|
address space; // 20 bytes
|
|
10
|
+
uint40 installTime; // 5 bytes - when subscription was first installed
|
|
10
11
|
uint40 lastRenewalTime; // 5 bytes
|
|
11
12
|
uint40 nextRenewalTime; // 5 bytes
|
|
12
13
|
bool active; // 1 byte
|