@towns-protocol/contracts 0.0.452 → 0.0.455

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.
Files changed (63) hide show
  1. package/docs/membership_architecture.md +23 -6
  2. package/package.json +6 -6
  3. package/scripts/common/Interaction.s.sol +3 -3
  4. package/scripts/deployments/diamonds/DeployBaseRegistry.s.sol +1 -4
  5. package/scripts/deployments/diamonds/DeploySpace.s.sol +1 -9
  6. package/scripts/deployments/diamonds/DeploySpaceFactory.s.sol +1 -5
  7. package/scripts/deployments/facets/DeployAppFactoryFacet.s.sol +3 -3
  8. package/scripts/deployments/facets/DeployAppInstallerFacet.s.sol +3 -3
  9. package/scripts/deployments/facets/DeployAppRegistryFacet.s.sol +3 -3
  10. package/scripts/deployments/facets/DeployArchitect.s.sol +3 -3
  11. package/scripts/deployments/facets/DeployAttestationRegistry.s.sol +3 -3
  12. package/scripts/deployments/facets/DeployERC721ANonTransferable.s.sol +3 -3
  13. package/scripts/deployments/facets/DeployFeatureManager.s.sol +3 -3
  14. package/scripts/deployments/facets/DeployMainnetDelegation.s.sol +3 -3
  15. package/scripts/deployments/facets/DeployMembershipMetadata.s.sol +3 -3
  16. package/scripts/deployments/facets/DeployMerkleAirdrop.s.sol +3 -3
  17. package/scripts/deployments/facets/DeployMetadata.s.sol +3 -3
  18. package/scripts/deployments/facets/DeployMockLegacyArchitect.s.sol +3 -3
  19. package/scripts/deployments/facets/DeployNodeOperator.s.sol +3 -3
  20. package/scripts/deployments/facets/DeployPartnerRegistry.s.sol +3 -3
  21. package/scripts/deployments/facets/DeployPricingModules.s.sol +3 -3
  22. package/scripts/deployments/facets/DeploySchemaRegistry.s.sol +3 -3
  23. package/scripts/deployments/facets/DeploySpaceFactoryInit.s.sol +3 -3
  24. package/scripts/deployments/facets/DeploySpaceOwnerFacet.s.sol +3 -3
  25. package/scripts/deployments/facets/DeployTokenMigration.s.sol +3 -3
  26. package/scripts/deployments/facets/DeployXChain.s.sol +3 -3
  27. package/scripts/deployments/utils/DeployAccountFactory.s.sol +3 -3
  28. package/scripts/deployments/utils/DeployEntitlementGatedExample.s.sol +3 -3
  29. package/scripts/deployments/utils/DeployEntrypoint.s.sol +3 -3
  30. package/scripts/deployments/utils/DeployMember.s.sol +3 -3
  31. package/scripts/deployments/utils/DeployMockLegacyMembership.s.sol +3 -3
  32. package/scripts/deployments/utils/DeployMockMessenger.s.sol +3 -3
  33. package/scripts/deployments/utils/DeploySpaceProxyInitializer.s.sol +3 -3
  34. package/scripts/deployments/utils/DeployTieredLogPricingV2.s.sol +3 -3
  35. package/scripts/deployments/utils/DeployTieredLogPricingV3.s.sol +3 -3
  36. package/scripts/deployments/utils/DeployTownsBase.s.sol +3 -3
  37. package/scripts/deployments/utils/DeployTownsMainnet.s.sol +3 -3
  38. package/scripts/deployments/utils/pricing/TieredLogPricing.s.sol +3 -3
  39. package/scripts/interactions/InteractAirdrop.s.sol +3 -3
  40. package/scripts/interactions/InteractBaseBridge.s.sol +3 -3
  41. package/scripts/interactions/InteractRegisterApp.s.sol +2 -2
  42. package/scripts/interactions/InteractRiverRegistrySetTrimByStreamId.s.sol +44 -0
  43. package/scripts/interactions/helpers/RiverConfigValues.sol +1 -2
  44. package/src/account/facets/app/AppManagerFacet.sol +43 -14
  45. package/src/account/facets/app/AppManagerMod.sol +311 -278
  46. package/src/account/facets/hub/AccountHubFacet.sol +12 -11
  47. package/src/account/facets/hub/AccountHubMod.sol +117 -116
  48. package/src/account/facets/tipping/AccountTippingFacet.sol +10 -9
  49. package/src/account/facets/tipping/AccountTippingMod.sol +133 -124
  50. package/src/apps/modules/subscription/ISubscriptionModule.sol +40 -43
  51. package/src/apps/modules/subscription/SubscriptionModuleBase.sol +41 -16
  52. package/src/apps/modules/subscription/SubscriptionModuleFacet.sol +113 -105
  53. package/src/apps/modules/subscription/SubscriptionModuleStorage.sol +2 -1
  54. package/src/base/registry/facets/checker/EntitlementChecker.sol +43 -17
  55. package/src/river/registry/facets/stream/StreamRegistry.sol +4 -1
  56. package/src/spaces/facets/ProtocolFeeLib.sol +43 -0
  57. package/src/spaces/facets/membership/IMembership.sol +6 -1
  58. package/src/spaces/facets/membership/MembershipFacet.sol +16 -7
  59. package/src/spaces/facets/membership/join/MembershipJoin.sol +34 -23
  60. package/src/spaces/facets/tipping/TippingBase.sol +10 -26
  61. package/src/tokens/Member.sol +3 -4
  62. package/src/utils/libraries/CurrencyTransfer.sol +6 -2
  63. package/scripts/interactions/InteractRiverRegistrySetFreq.s.sol +0 -27
@@ -6,19 +6,19 @@ import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol"
6
6
  import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol";
7
7
  import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol";
8
8
  import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
9
- import {ISubscriptionModule} from "./ISubscriptionModule.sol";
10
9
  import {IMembership} from "../../../spaces/facets/membership/IMembership.sol";
11
10
  import {IBanning} from "../../../spaces/facets/banning/IBanning.sol";
11
+ import {ISubscriptionModule} from "./ISubscriptionModule.sol";
12
12
 
13
13
  // libraries
14
14
  import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
15
15
  import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
16
16
  import {ReentrancyGuardTransient} from "solady/utils/ReentrancyGuardTransient.sol";
17
+ import {SafeCastLib} from "solady/utils/SafeCastLib.sol";
17
18
  import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
18
19
  import {Validator} from "../../../utils/libraries/Validator.sol";
19
20
  import {IArchitect} from "../../../factory/facets/architect/IArchitect.sol";
20
21
  import {Subscription, SubscriptionModuleStorage} from "./SubscriptionModuleStorage.sol";
21
- import {SafeCastLib} from "solady/utils/SafeCastLib.sol";
22
22
 
23
23
  // contracts
24
24
  import {ModuleBase} from "modular-account/src/modules/ModuleBase.sol";
@@ -38,10 +38,10 @@ contract SubscriptionModuleFacet is
38
38
  SubscriptionModuleBase,
39
39
  Facet
40
40
  {
41
- using EnumerableSetLib for EnumerableSetLib.Uint256Set;
42
41
  using EnumerableSetLib for EnumerableSetLib.AddressSet;
43
- using SafeCastLib for uint256;
42
+ using EnumerableSetLib for EnumerableSetLib.Uint256Set;
44
43
  using CustomRevert for bytes4;
44
+ using SafeCastLib for uint256;
45
45
 
46
46
  uint256 internal constant _SIG_VALIDATION_FAILED = 1;
47
47
 
@@ -54,14 +54,34 @@ contract SubscriptionModuleFacet is
54
54
  }
55
55
 
56
56
  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
57
- /* External */
57
+ /* ADMIN FUNCTIONS */
58
58
  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
59
59
 
60
- /// @inheritdoc IModule
61
- function moduleId() external pure returns (string memory) {
62
- return "towns.subscription-module.1.0.0";
60
+ /// @inheritdoc ISubscriptionModule
61
+ function setSpaceFactory(address spaceFactory) external onlyOwner {
62
+ Validator.checkAddress(spaceFactory);
63
+ SubscriptionModuleStorage.getLayout().spaceFactory = spaceFactory;
64
+ emit SpaceFactoryChanged(spaceFactory);
63
65
  }
64
66
 
67
+ /// @inheritdoc ISubscriptionModule
68
+ function grantOperator(address operator) external onlyOwner {
69
+ Validator.checkAddress(operator);
70
+ SubscriptionModuleStorage.getLayout().operators.add(operator);
71
+ emit OperatorGranted(operator);
72
+ }
73
+
74
+ /// @inheritdoc ISubscriptionModule
75
+ function revokeOperator(address operator) external onlyOwner {
76
+ Validator.checkAddress(operator);
77
+ SubscriptionModuleStorage.getLayout().operators.remove(operator);
78
+ emit OperatorRevoked(operator);
79
+ }
80
+
81
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
82
+ /* MODULE LIFECYCLE */
83
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
84
+
65
85
  /// @inheritdoc IModule
66
86
  function onInstall(bytes calldata data) external override nonReentrant {
67
87
  (uint32 entityId, address space, uint256 tokenId) = abi.decode(
@@ -135,69 +155,9 @@ contract SubscriptionModuleFacet is
135
155
  emit SubscriptionDeactivated(msg.sender, entityId);
136
156
  }
137
157
 
138
- /// @inheritdoc IValidationModule
139
- function validateUserOp(
140
- uint32,
141
- PackedUserOperation calldata,
142
- bytes32
143
- ) external pure override returns (uint256) {
144
- return _SIG_VALIDATION_FAILED;
145
- }
146
-
147
- /// @inheritdoc IValidationModule
148
- function validateSignature(
149
- address,
150
- uint32,
151
- address,
152
- bytes32,
153
- bytes calldata
154
- ) external pure override returns (bytes4) {
155
- return 0xffffffff;
156
- }
157
-
158
- /// @inheritdoc IValidationModule
159
- function validateRuntime(
160
- address account,
161
- uint32 entityId,
162
- address sender,
163
- uint256,
164
- bytes calldata,
165
- bytes calldata
166
- ) external view override {
167
- if (sender != address(this)) SubscriptionModule__InvalidSender.selector.revertWith();
168
- bool active = SubscriptionModuleStorage.getLayout().subscriptions[account][entityId].active;
169
- if (!active) SubscriptionModule__InactiveSubscription.selector.revertWith();
170
- }
171
-
172
- /// @inheritdoc IValidationHookModule
173
- function preUserOpValidationHook(
174
- uint32 /* entityId */,
175
- PackedUserOperation calldata /* userOp */,
176
- bytes32 /* userOpHash */
177
- ) external pure override returns (uint256) {
178
- return _SIG_VALIDATION_FAILED;
179
- }
180
-
181
- /// @inheritdoc IValidationHookModule
182
- function preRuntimeValidationHook(
183
- uint32 /* entityId */,
184
- address /* sender */,
185
- uint256 /* value */,
186
- bytes calldata /* data */,
187
- bytes calldata /* authorization */
188
- ) external pure override {
189
- SubscriptionModule__NotSupported.selector.revertWith();
190
- }
191
-
192
- /// @inheritdoc IValidationHookModule
193
- function preSignatureValidationHook(
194
- uint32 /* entityId */,
195
- address /* sender */,
196
- bytes32 /* hash */,
197
- bytes calldata /* signature */
198
- ) external pure override {
199
- SubscriptionModule__NotSupported.selector.revertWith();
200
- }
158
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
159
+ /* STATE-CHANGING FUNCTIONS */
160
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
201
161
 
202
162
  /// @inheritdoc ISubscriptionModule
203
163
  function batchProcessRenewals(RenewalParams[] calldata params) external nonReentrant {
@@ -257,19 +217,6 @@ contract SubscriptionModuleFacet is
257
217
  }
258
218
  }
259
219
 
260
- /// @inheritdoc ISubscriptionModule
261
- function getSubscription(
262
- address account,
263
- uint32 entityId
264
- ) external view returns (Subscription memory) {
265
- return SubscriptionModuleStorage.getLayout().subscriptions[account][entityId];
266
- }
267
-
268
- /// @inheritdoc ISubscriptionModule
269
- function getRenewalBuffer(uint256 duration) external pure returns (uint256) {
270
- return _getRenewalBuffer(duration);
271
- }
272
-
273
220
  /// @inheritdoc ISubscriptionModule
274
221
  function activateSubscription(uint32 entityId) external nonReentrant {
275
222
  SubscriptionModuleStorage.Layout storage $ = SubscriptionModuleStorage.getLayout();
@@ -304,16 +251,26 @@ contract SubscriptionModuleFacet is
304
251
  _pauseSubscription(sub, msg.sender, entityId);
305
252
  }
306
253
 
254
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
255
+ /* GETTERS */
256
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
257
+
307
258
  /// @inheritdoc ISubscriptionModule
308
- function getEntityIds(address account) external view returns (uint256[] memory) {
309
- return SubscriptionModuleStorage.getLayout().entityIds[account].values();
259
+ function getSpaceFactory() external view returns (address) {
260
+ return SubscriptionModuleStorage.getLayout().spaceFactory;
310
261
  }
311
262
 
312
263
  /// @inheritdoc ISubscriptionModule
313
- function grantOperator(address operator) external onlyOwner {
314
- Validator.checkAddress(operator);
315
- SubscriptionModuleStorage.getLayout().operators.add(operator);
316
- emit OperatorGranted(operator);
264
+ function getSubscription(
265
+ address account,
266
+ uint32 entityId
267
+ ) external view returns (Subscription memory) {
268
+ return SubscriptionModuleStorage.getLayout().subscriptions[account][entityId];
269
+ }
270
+
271
+ /// @inheritdoc ISubscriptionModule
272
+ function getEntityIds(address account) external view returns (uint256[] memory) {
273
+ return SubscriptionModuleStorage.getLayout().entityIds[account].values();
317
274
  }
318
275
 
319
276
  /// @inheritdoc ISubscriptionModule
@@ -321,26 +278,77 @@ contract SubscriptionModuleFacet is
321
278
  return SubscriptionModuleStorage.getLayout().operators.contains(operator);
322
279
  }
323
280
 
324
- /// @inheritdoc ISubscriptionModule
325
- function revokeOperator(address operator) external onlyOwner {
326
- Validator.checkAddress(operator);
327
- SubscriptionModuleStorage.getLayout().operators.remove(operator);
328
- emit OperatorRevoked(operator);
281
+ /// @inheritdoc IValidationModule
282
+ function validateRuntime(
283
+ address account,
284
+ uint32 entityId,
285
+ address sender,
286
+ uint256,
287
+ bytes calldata,
288
+ bytes calldata
289
+ ) external view override {
290
+ if (sender != address(this)) SubscriptionModule__InvalidSender.selector.revertWith();
291
+ bool active = SubscriptionModuleStorage.getLayout().subscriptions[account][entityId].active;
292
+ if (!active) SubscriptionModule__InactiveSubscription.selector.revertWith();
329
293
  }
330
294
 
331
- /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
332
- /* SPACE FACTORY */
333
- /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
295
+ /// @inheritdoc IModule
296
+ function moduleId() external pure returns (string memory) {
297
+ return "towns.subscription-module.1.0.0";
298
+ }
334
299
 
335
300
  /// @inheritdoc ISubscriptionModule
336
- function setSpaceFactory(address spaceFactory) external onlyOwner {
337
- Validator.checkAddress(spaceFactory);
338
- SubscriptionModuleStorage.getLayout().spaceFactory = spaceFactory;
339
- emit SpaceFactoryChanged(spaceFactory);
301
+ function getRenewalBuffer(uint256 duration) external pure returns (uint256) {
302
+ return _getRenewalBuffer(duration);
340
303
  }
341
304
 
342
- /// @inheritdoc ISubscriptionModule
343
- function getSpaceFactory() external view returns (address) {
344
- return SubscriptionModuleStorage.getLayout().spaceFactory;
305
+ /// @inheritdoc IValidationModule
306
+ function validateUserOp(
307
+ uint32,
308
+ PackedUserOperation calldata,
309
+ bytes32
310
+ ) external pure override returns (uint256) {
311
+ return _SIG_VALIDATION_FAILED;
312
+ }
313
+
314
+ /// @inheritdoc IValidationModule
315
+ function validateSignature(
316
+ address,
317
+ uint32,
318
+ address,
319
+ bytes32,
320
+ bytes calldata
321
+ ) external pure override returns (bytes4) {
322
+ return 0xffffffff;
323
+ }
324
+
325
+ /// @inheritdoc IValidationHookModule
326
+ function preUserOpValidationHook(
327
+ uint32 /* entityId */,
328
+ PackedUserOperation calldata /* userOp */,
329
+ bytes32 /* userOpHash */
330
+ ) external pure override returns (uint256) {
331
+ return _SIG_VALIDATION_FAILED;
332
+ }
333
+
334
+ /// @inheritdoc IValidationHookModule
335
+ function preRuntimeValidationHook(
336
+ uint32 /* entityId */,
337
+ address /* sender */,
338
+ uint256 /* value */,
339
+ bytes calldata /* data */,
340
+ bytes calldata /* authorization */
341
+ ) external pure override {
342
+ SubscriptionModule__NotSupported.selector.revertWith();
343
+ }
344
+
345
+ /// @inheritdoc IValidationHookModule
346
+ function preSignatureValidationHook(
347
+ uint32 /* entityId */,
348
+ address /* sender */,
349
+ bytes32 /* hash */,
350
+ bytes calldata /* signature */
351
+ ) external pure override {
352
+ SubscriptionModule__NotSupported.selector.revertWith();
345
353
  }
346
354
  }
@@ -14,6 +14,7 @@ struct Subscription {
14
14
  uint64 duration; // 8 bytes
15
15
  uint256 lastKnownRenewalPrice; // 32 bytes
16
16
  uint256 lastKnownExpiresAt; // 32 bytes
17
+ address lastKnownCurrency; // 20 bytes - currency at install/sync time
17
18
  }
18
19
 
19
20
  struct OperatorConfig {
@@ -39,7 +40,7 @@ library SubscriptionModuleStorage {
39
40
  }
40
41
 
41
42
  // keccak256(abi.encode(uint256(keccak256("towns.subscription.validation.module.storage")) - 1)) & ~bytes32(uint256(0xff))
42
- bytes32 private constant STORAGE_SLOT =
43
+ bytes32 internal constant STORAGE_SLOT =
43
44
  0xd241b3ceee256b40f80fe7a66fe789234ac389ed1408c472c4ee1cbb1deb8600;
44
45
 
45
46
  function getLayout() internal pure returns (Layout storage $) {
@@ -121,17 +121,36 @@ contract EntitlementChecker is IEntitlementChecker, Facet {
121
121
  function requestEntitlementCheck(CheckType checkType, bytes calldata data) external payable {
122
122
  if (checkType == CheckType.V1) {
123
123
  if (msg.value != 0) EntitlementChecker_InvalidValue.selector.revertWith();
124
- (address receiver, bytes32 transactionId, uint256 roleId, address[] memory nodes) = abi
125
- .decode(data, (address, bytes32, uint256, address[]));
124
+ // equivalent: abi.decode(data, (address, bytes32, uint256, address[]))
125
+ address receiver;
126
+ bytes32 transactionId;
127
+ uint256 roleId;
128
+ address[] calldata nodes;
129
+ assembly {
130
+ receiver := shr(96, shl(96, calldataload(data.offset)))
131
+ transactionId := calldataload(add(data.offset, 0x20))
132
+ roleId := calldataload(add(data.offset, 0x40))
133
+ // nodes is dynamic: offset at 0x60, array starts at data.offset + offset
134
+ let nodesPtr := add(data.offset, calldataload(add(data.offset, 0x60)))
135
+ nodes.length := calldataload(nodesPtr)
136
+ nodes.offset := add(nodesPtr, 0x20)
137
+ }
126
138
  emit EntitlementCheckRequested(receiver, msg.sender, transactionId, roleId, nodes);
127
139
  } else if (checkType == CheckType.V2) {
128
- (
129
- address receiver,
130
- bytes32 transactionId,
131
- uint256 requestId,
132
- bytes memory extraData
133
- ) = abi.decode(data, (address, bytes32, uint256, bytes));
134
- address sender = abi.decode(extraData, (address));
140
+ // equivalent: abi.decode(data, (address, bytes32, uint256, bytes))
141
+ // extraData contains: (address sender)
142
+ address receiver;
143
+ bytes32 transactionId;
144
+ uint256 requestId;
145
+ address sender;
146
+ assembly {
147
+ receiver := shr(96, shl(96, calldataload(data.offset)))
148
+ transactionId := calldataload(add(data.offset, 0x20))
149
+ requestId := calldataload(add(data.offset, 0x40))
150
+ // extraData offset at 0x60, sender is first word after length
151
+ let extraDataPtr := add(data.offset, calldataload(add(data.offset, 0x60)))
152
+ sender := shr(96, shl(96, calldataload(add(extraDataPtr, 0x20))))
153
+ }
135
154
  _requestEntitlementCheck(
136
155
  receiver,
137
156
  transactionId,
@@ -141,14 +160,21 @@ contract EntitlementChecker is IEntitlementChecker, Facet {
141
160
  sender
142
161
  );
143
162
  } else if (checkType == CheckType.V3) {
144
- (
145
- address receiver,
146
- bytes32 transactionId,
147
- uint256 requestId,
148
- address currency,
149
- uint256 amount,
150
- address sender
151
- ) = abi.decode(data, (address, bytes32, uint256, address, uint256, address));
163
+ // equivalent: abi.decode(data, (address, bytes32, uint256, address, uint256, address))
164
+ address receiver;
165
+ bytes32 transactionId;
166
+ uint256 requestId;
167
+ address currency;
168
+ uint256 amount;
169
+ address sender;
170
+ assembly {
171
+ receiver := shr(96, shl(96, calldataload(data.offset)))
172
+ transactionId := calldataload(add(data.offset, 0x20))
173
+ requestId := calldataload(add(data.offset, 0x40))
174
+ currency := shr(96, shl(96, calldataload(add(data.offset, 0x60))))
175
+ amount := calldataload(add(data.offset, 0x80))
176
+ sender := shr(96, shl(96, calldataload(add(data.offset, 0xa0))))
177
+ }
152
178
  _requestEntitlementCheck(receiver, transactionId, requestId, currency, amount, sender);
153
179
  } else {
154
180
  EntitlementChecker_InvalidCheckType.selector.revertWith();
@@ -110,7 +110,10 @@ contract StreamRegistry is IStreamRegistry, RegistryModifiers {
110
110
 
111
111
  // Check if the lastMiniblockNum is the next expected miniblock and
112
112
  // the prevMiniblockHash is correct
113
- if (stream.lastMiniblockNum >= miniblock.lastMiniblockNum) {
113
+ if (
114
+ stream.lastMiniblockNum + 1 != miniblock.lastMiniblockNum ||
115
+ stream.lastMiniblockHash != miniblock.prevMiniBlockHash
116
+ ) {
114
117
  emit StreamLastMiniblockUpdateFailed(
115
118
  miniblock.streamId,
116
119
  miniblock.lastMiniblockHash,
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
5
+ import {IFeeManager} from "../../factory/facets/fee/IFeeManager.sol";
6
+ import {CurrencyTransfer} from "../../utils/libraries/CurrencyTransfer.sol";
7
+
8
+ library ProtocolFeeLib {
9
+ using SafeTransferLib for address;
10
+
11
+ /// @notice Charges protocol fee via FeeManager with ERC20 approval handling
12
+ /// @param spaceFactory The FeeManager address
13
+ /// @param feeType The type of fee being charged
14
+ /// @param user The user paying the fee
15
+ /// @param currency The payment currency (NATIVE_TOKEN or ERC20)
16
+ /// @param amount The base amount for fee calculation
17
+ /// @param expectedFee The pre-calculated expected fee
18
+ /// @return protocolFee The actual fee charged
19
+ function charge(
20
+ address spaceFactory,
21
+ bytes32 feeType,
22
+ address user,
23
+ address currency,
24
+ uint256 amount,
25
+ uint256 expectedFee
26
+ ) internal returns (uint256 protocolFee) {
27
+ if (expectedFee == 0) return 0;
28
+
29
+ bool isNative = currency == CurrencyTransfer.NATIVE_TOKEN;
30
+ if (!isNative) currency.safeApproveWithRetry(spaceFactory, expectedFee);
31
+
32
+ protocolFee = IFeeManager(spaceFactory).chargeFee{value: isNative ? expectedFee : 0}(
33
+ feeType,
34
+ user,
35
+ amount,
36
+ currency,
37
+ expectedFee,
38
+ ""
39
+ );
40
+
41
+ if (!isNative) currency.safeApprove(spaceFactory, 0);
42
+ }
43
+ }
@@ -75,6 +75,11 @@ interface IMembershipBase {
75
75
  event MembershipFreeAllocationUpdated(uint256 indexed allocation);
76
76
  event MembershipWithdrawal(address indexed currency, address indexed recipient, uint256 amount);
77
77
  event MembershipTokenIssued(address indexed recipient, uint256 indexed tokenId);
78
+ /// @notice Emitted when a membership payment is processed (new membership or renewal)
79
+ /// @param currency The currency used for payment (0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for ETH)
80
+ /// @param price The base membership price paid
81
+ /// @param protocolFee The protocol fee paid
82
+ event MembershipPaid(address indexed currency, uint256 price, uint256 protocolFee);
78
83
  event MembershipTokenRejected(address indexed recipient);
79
84
  }
80
85
 
@@ -97,7 +102,7 @@ interface IMembership is IMembershipBase {
97
102
  /// @param referral The referral data
98
103
  function joinSpaceWithReferral(
99
104
  address receiver,
100
- ReferralTypes memory referral
105
+ ReferralTypes calldata referral
101
106
  ) external payable;
102
107
 
103
108
  /// @notice Renew a space membership
@@ -2,8 +2,8 @@
2
2
  pragma solidity ^0.8.23;
3
3
 
4
4
  // interfaces
5
- import {IMembership} from "./IMembership.sol";
6
5
  import {IMembershipPricing} from "./pricing/IMembershipPricing.sol";
6
+ import {IMembership} from "./IMembership.sol";
7
7
 
8
8
  // libraries
9
9
  import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
@@ -26,13 +26,22 @@ contract MembershipFacet is IMembership, MembershipJoin, ReentrancyGuard, Facet
26
26
  /// @inheritdoc IMembership
27
27
  function joinSpace(JoinType action, bytes calldata data) external payable nonReentrant {
28
28
  if (action == JoinType.Basic) {
29
- address receiver = abi.decode(data, (address));
29
+ // equivalent: abi.decode(data, (address))
30
+ address receiver;
31
+ assembly {
32
+ receiver := shr(96, shl(96, calldataload(data.offset)))
33
+ }
30
34
  _joinSpace(receiver);
31
35
  } else if (action == JoinType.WithReferral) {
32
- (address receiver, ReferralTypes memory referral) = abi.decode(
33
- data,
34
- (address, ReferralTypes)
35
- );
36
+ // equivalent: abi.decode(data, (address, ReferralTypes))
37
+ address receiver;
38
+ ReferralTypes calldata referral;
39
+ assembly {
40
+ receiver := shr(96, shl(96, calldataload(data.offset)))
41
+ // this is a variable length struct, so (data.offset + 0x20) contains
42
+ // the offset from data.offset at which the struct begins
43
+ referral := add(data.offset, calldataload(add(data.offset, 0x20)))
44
+ }
36
45
  _joinSpaceWithReferral(receiver, referral);
37
46
  } else {
38
47
  Membership__InvalidAction.selector.revertWith();
@@ -47,7 +56,7 @@ contract MembershipFacet is IMembership, MembershipJoin, ReentrancyGuard, Facet
47
56
  /// @inheritdoc IMembership
48
57
  function joinSpaceWithReferral(
49
58
  address receiver,
50
- ReferralTypes memory referral
59
+ ReferralTypes calldata referral
51
60
  ) external payable nonReentrant {
52
61
  _joinSpaceWithReferral(receiver, referral);
53
62
  }
@@ -16,6 +16,7 @@ import {CurrencyTransfer} from "../../../../utils/libraries/CurrencyTransfer.sol
16
16
  import {CustomRevert} from "../../../../utils/libraries/CustomRevert.sol";
17
17
  import {MembershipStorage} from "../MembershipStorage.sol";
18
18
  import {Permissions} from "../../Permissions.sol";
19
+ import {ProtocolFeeLib} from "../../ProtocolFeeLib.sol";
19
20
 
20
21
  // contracts
21
22
  import {ERC5643Base} from "../../../../diamond/facets/token/ERC5643/ERC5643Base.sol";
@@ -67,6 +68,7 @@ abstract contract MembershipJoin is
67
68
 
68
69
  struct PricingDetails {
69
70
  uint256 basePrice;
71
+ uint256 protocolFee;
70
72
  uint256 amountDue;
71
73
  bool shouldCharge;
72
74
  }
@@ -100,8 +102,12 @@ abstract contract MembershipJoin is
100
102
  return joinDetails;
101
103
  }
102
104
 
103
- (uint256 totalRequired, ) = _getTotalMembershipPayment(membershipPrice);
104
- (joinDetails.amountDue, joinDetails.shouldCharge) = (totalRequired, true);
105
+ (uint256 totalRequired, uint256 protocolFee) = _getTotalMembershipPayment(membershipPrice);
106
+ (joinDetails.protocolFee, joinDetails.amountDue, joinDetails.shouldCharge) = (
107
+ protocolFee,
108
+ totalRequired,
109
+ true
110
+ );
105
111
  }
106
112
 
107
113
  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -150,7 +156,7 @@ abstract contract MembershipJoin is
150
156
  /// @notice Handles the process of joining a space with a referral
151
157
  /// @param receiver The address that will receive the membership token
152
158
  /// @param referral The referral information
153
- function _joinSpaceWithReferral(address receiver, ReferralTypes memory referral) internal {
159
+ function _joinSpaceWithReferral(address receiver, ReferralTypes calldata referral) internal {
154
160
  _validateJoinSpace(receiver);
155
161
 
156
162
  PricingDetails memory joinDetails = _getPricingDetails();
@@ -229,7 +235,10 @@ abstract contract MembershipJoin is
229
235
  return amountRequired;
230
236
  }
231
237
 
232
- function _validateUserReferral(address receiver, ReferralTypes memory referral) internal view {
238
+ function _validateUserReferral(
239
+ address receiver,
240
+ ReferralTypes calldata referral
241
+ ) internal view {
233
242
  if (referral.userReferral == receiver || referral.userReferral == msg.sender) {
234
243
  Membership__InvalidAddress.selector.revertWith();
235
244
  }
@@ -341,7 +350,10 @@ abstract contract MembershipJoin is
341
350
  Membership__InvalidTransactionType.selector.revertWith();
342
351
  }
343
352
 
344
- _payProtocolFee(_getMembershipCurrency(), joinDetails.basePrice);
353
+ address currency = _getMembershipCurrency();
354
+ _payProtocolFee(currency, joinDetails.basePrice, joinDetails.protocolFee);
355
+
356
+ emit MembershipPaid(currency, joinDetails.basePrice, joinDetails.protocolFee);
345
357
 
346
358
  _afterChargeForJoinSpace(transactionId, receiver, joinDetails.amountDue);
347
359
  }
@@ -365,7 +377,7 @@ abstract contract MembershipJoin is
365
377
  ReferralTypes memory referral = abi.decode(referralData, (ReferralTypes));
366
378
 
367
379
  address currency = _getMembershipCurrency();
368
- _payProtocolFee(currency, joinDetails.basePrice);
380
+ _payProtocolFee(currency, joinDetails.basePrice, joinDetails.protocolFee);
369
381
  _payPartnerFee(currency, referral.partner, joinDetails.basePrice);
370
382
  _payReferralFee(
371
383
  currency,
@@ -375,6 +387,8 @@ abstract contract MembershipJoin is
375
387
  joinDetails.basePrice
376
388
  );
377
389
 
390
+ emit MembershipPaid(currency, joinDetails.basePrice, joinDetails.protocolFee);
391
+
378
392
  _afterChargeForJoinSpace(transactionId, receiver, joinDetails.amountDue);
379
393
  }
380
394
 
@@ -410,7 +424,6 @@ abstract contract MembershipJoin is
410
424
  // set expiration of membership
411
425
  _renewSubscription(tokenId, _getMembershipDuration());
412
426
 
413
- // emit event
414
427
  emit MembershipTokenIssued(receiver, tokenId);
415
428
  }
416
429
 
@@ -447,21 +460,18 @@ abstract contract MembershipJoin is
447
460
  /* FEE DISTRIBUTION */
448
461
  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
449
462
 
450
- /// @notice Pays the protocol fee to the platform fee recipient
463
+ /// @notice Pays the protocol fee via FeeManager
451
464
  /// @param currency The currency to pay in
452
- /// @param membershipPrice The price of the membership
453
- /// @return protocolFee The amount of protocol fee paid
454
- function _payProtocolFee(
455
- address currency,
456
- uint256 membershipPrice
457
- ) internal returns (uint256 protocolFee) {
458
- protocolFee = _getProtocolFee(membershipPrice);
459
-
460
- CurrencyTransfer.transferCurrency(
465
+ /// @param basePrice The base price of the membership (for fee calculation)
466
+ /// @param expectedFee The pre-calculated protocol fee
467
+ function _payProtocolFee(address currency, uint256 basePrice, uint256 expectedFee) internal {
468
+ ProtocolFeeLib.charge(
469
+ _getSpaceFactory(),
470
+ _getMembershipFeeType(currency),
471
+ msg.sender,
461
472
  currency,
462
- address(this),
463
- _getPlatformRequirements().getFeeRecipient(),
464
- protocolFee
473
+ basePrice,
474
+ expectedFee
465
475
  );
466
476
  }
467
477
 
@@ -542,13 +552,13 @@ abstract contract MembershipJoin is
542
552
  return;
543
553
  }
544
554
 
545
- (uint256 totalRequired, ) = _getTotalMembershipPayment(basePrice);
555
+ (uint256 totalRequired, uint256 protocolFee) = _getTotalMembershipPayment(basePrice);
546
556
 
547
557
  if (currency == CurrencyTransfer.NATIVE_TOKEN) {
548
558
  // ETH payment: validate msg.value
549
559
  if (totalRequired > msg.value) Membership__InvalidPayment.selector.revertWith();
550
560
 
551
- _payProtocolFee(currency, basePrice);
561
+ _payProtocolFee(currency, basePrice, protocolFee);
552
562
 
553
563
  // Handle excess payment
554
564
  uint256 excess = msg.value - totalRequired;
@@ -562,9 +572,10 @@ abstract contract MembershipJoin is
562
572
  // Transfer ERC20 from payer to contract
563
573
  _transferIn(currency, payer, totalRequired);
564
574
 
565
- _payProtocolFee(currency, basePrice);
575
+ _payProtocolFee(currency, basePrice, protocolFee);
566
576
  }
567
577
 
578
+ emit MembershipPaid(currency, basePrice, protocolFee);
568
579
  _mintMembershipPoints(receiver, totalRequired);
569
580
  _renewSubscription(tokenId, uint64(duration));
570
581
  }