@towns-protocol/contracts 0.0.442 → 0.0.444

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 (24) hide show
  1. package/docs/membership_architecture.md +237 -0
  2. package/package.json +3 -3
  3. package/scripts/deployments/diamonds/DeploySpaceFactory.s.sol +2 -2
  4. package/scripts/deployments/facets/DeployMembership.s.sol +2 -1
  5. package/scripts/deployments/utils/DeployMockERC20.s.sol +1 -1
  6. package/scripts/deployments/utils/DeployMockUSDC.s.sol +19 -0
  7. package/scripts/interactions/InteractPostDeploy.s.sol +11 -0
  8. package/src/factory/facets/feature/FeatureManagerFacet.sol +32 -29
  9. package/src/factory/facets/feature/FeatureManagerMod.sol +248 -0
  10. package/src/factory/facets/feature/{IFeatureManagerFacet.sol → IFeatureManager.sol} +2 -35
  11. package/src/factory/facets/fee/FeeManagerFacet.sol +1 -1
  12. package/src/factory/facets/fee/FeeTypesLib.sol +8 -1
  13. package/src/spaces/facets/dispatcher/DispatcherBase.sol +13 -5
  14. package/src/spaces/facets/gated/EntitlementGated.sol +9 -5
  15. package/src/spaces/facets/membership/IMembership.sol +11 -1
  16. package/src/spaces/facets/membership/MembershipBase.sol +30 -59
  17. package/src/spaces/facets/membership/MembershipFacet.sol +19 -1
  18. package/src/spaces/facets/membership/MembershipStorage.sol +1 -0
  19. package/src/spaces/facets/membership/join/MembershipJoin.sol +186 -110
  20. package/src/spaces/facets/treasury/ITreasury.sol +2 -1
  21. package/src/spaces/facets/treasury/Treasury.sol +21 -24
  22. package/src/spaces/facets/xchain/SpaceEntitlementGated.sol +3 -2
  23. package/src/factory/facets/feature/FeatureManagerBase.sol +0 -152
  24. package/src/factory/facets/feature/FeatureManagerStorage.sol +0 -47
@@ -1,152 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.23;
3
-
4
- // interfaces
5
- import {IFeatureManagerFacetBase} from "./IFeatureManagerFacet.sol";
6
- import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
7
- import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8
-
9
- // libraries
10
- import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
11
- import {FeatureManagerStorage} from "./FeatureManagerStorage.sol";
12
- import {FeatureCondition} from "./IFeatureManagerFacet.sol";
13
- import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
14
- import {LibCall} from "solady/utils/LibCall.sol";
15
-
16
- // contracts
17
-
18
- abstract contract FeatureManagerBase is IFeatureManagerFacetBase {
19
- using EnumerableSetLib for EnumerableSetLib.Bytes32Set;
20
- using CustomRevert for bytes4;
21
-
22
- /// @notice Creates or updates a feature condition based on the create flag
23
- /// @dev Validates token interface compliance before storing the condition
24
- /// @param featureId The unique identifier for the feature
25
- /// @param condition The condition struct containing token, threshold, active status, and extra data
26
- /// @param create True to create new feature, false to update existing
27
- function _upsertFeatureCondition(
28
- bytes32 featureId,
29
- FeatureCondition calldata condition,
30
- bool create
31
- ) internal {
32
- _validateToken(condition);
33
-
34
- FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
35
- if (!create) {
36
- if (!$.featureIds.contains(featureId)) FeatureNotActive.selector.revertWith();
37
- } else {
38
- if (!$.featureIds.add(featureId)) FeatureAlreadyExists.selector.revertWith();
39
- }
40
-
41
- $.conditions[featureId] = condition;
42
- }
43
-
44
- /// @notice Disables a feature by setting its active flag to false
45
- /// @dev This does not delete the condition, only deactivates it
46
- /// @param featureId The unique identifier for the feature to disable
47
- function _disableFeatureCondition(bytes32 featureId) internal {
48
- FeatureCondition storage condition = _getFeatureCondition(featureId);
49
- if (!condition.active) FeatureNotActive.selector.revertWith();
50
- condition.active = false;
51
- }
52
-
53
- /// @notice Retrieves the condition for a specific feature
54
- /// @dev Returns the complete condition struct with all parameters
55
- /// @param featureId The unique identifier for the feature
56
- /// @return The complete condition configuration for the feature
57
- function _getFeatureCondition(
58
- bytes32 featureId
59
- ) internal view returns (FeatureCondition storage) {
60
- return FeatureManagerStorage.getLayout().conditions[featureId];
61
- }
62
-
63
- /// @notice Retrieves all feature conditions
64
- /// @dev Returns an array of all feature conditions
65
- /// @return conditions An array of all feature conditions
66
- function _getFeatureConditions() internal view returns (FeatureCondition[] memory conditions) {
67
- FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
68
- // Use values() over at() for full iteration - avoids bounds checking overhead
69
- bytes32[] memory ids = $.featureIds.values();
70
- uint256 featureCount = ids.length;
71
-
72
- conditions = new FeatureCondition[](featureCount);
73
- for (uint256 i; i < featureCount; ++i) {
74
- conditions[i] = $.conditions[ids[i]];
75
- }
76
- }
77
-
78
- /// @notice Retrieves all feature conditions for a specific space
79
- /// @dev Returns an array of all feature conditions that are active for the space
80
- /// @return conditions An array of all feature conditions that are active for the space
81
- function _getFeatureConditionsForSpace(
82
- address space
83
- ) internal view returns (FeatureCondition[] memory conditions) {
84
- FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
85
- // Use values() over at() for full iteration - avoids bounds checking overhead
86
- bytes32[] memory ids = $.featureIds.values();
87
- uint256 featureCount = ids.length;
88
-
89
- // Gas optimization: Allocate full array then resize (memory cheaper than storage reads)
90
- conditions = new FeatureCondition[](featureCount);
91
- uint256 index;
92
-
93
- for (uint256 i; i < featureCount; ++i) {
94
- FeatureCondition storage cond = $.conditions[ids[i]];
95
-
96
- if (_isValidCondition(cond, space)) {
97
- conditions[index++] = cond;
98
- }
99
- }
100
-
101
- // Resize array to actual number of valid conditions
102
- assembly ("memory-safe") {
103
- mstore(conditions, index)
104
- }
105
- }
106
-
107
- function _validateToken(FeatureCondition calldata condition) internal view {
108
- if (condition.token == address(0)) InvalidToken.selector.revertWith();
109
-
110
- // Check if the token implements IVotes.getVotes with proper return data
111
- (bool success, bool exceededMaxCopy, bytes memory data) = LibCall.tryStaticCall(
112
- condition.token,
113
- gasleft(),
114
- 32,
115
- abi.encodeCall(IVotes.getVotes, (address(this)))
116
- );
117
-
118
- if (!success || exceededMaxCopy || data.length != 32)
119
- InvalidInterface.selector.revertWith();
120
-
121
- // Check if the token implements ERC20.totalSupply with proper return data
122
- (success, exceededMaxCopy, data) = LibCall.tryStaticCall(
123
- condition.token,
124
- gasleft(),
125
- 32,
126
- abi.encodeCall(IERC20.totalSupply, ())
127
- );
128
-
129
- if (!success || exceededMaxCopy || data.length != 32)
130
- InvalidInterface.selector.revertWith();
131
-
132
- uint256 totalSupply = abi.decode(data, (uint256));
133
- if (totalSupply == 0) InvalidTotalSupply.selector.revertWith();
134
- if (condition.threshold > totalSupply) InvalidThreshold.selector.revertWith();
135
- }
136
-
137
- /// @notice Checks if a condition should be included for a given space
138
- /// @dev Returns true if the condition is active, has a valid token, and meets the threshold
139
- /// @param condition The condition to check
140
- /// @param space The space address to check against
141
- /// @return True if the condition should be included, false otherwise
142
- function _isValidCondition(
143
- FeatureCondition storage condition,
144
- address space
145
- ) internal view returns (bool) {
146
- if (!condition.active) return false;
147
- address token = condition.token;
148
- if (token == address(0)) return false;
149
- uint256 votes = IVotes(token).getVotes(space);
150
- return votes >= condition.threshold;
151
- }
152
- }
@@ -1,47 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.23;
3
-
4
- // interfaces
5
-
6
- // libraries
7
- import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
8
-
9
- // contracts
10
-
11
- /// @notice Represents a condition for feature activation
12
- /// @dev Used to determine if a feature should be enabled based on token voting power
13
- /// @param token The address of the token used for voting (must implement IVotes)
14
- /// @param threshold The minimum voting power (votes) required to activate the feature
15
- /// @param active Whether the condition is currently active
16
- /// @param extraData Additional data that might be used for specialized condition logic
17
- struct FeatureCondition {
18
- address token;
19
- bool active;
20
- uint256 threshold;
21
- bytes extraData;
22
- }
23
-
24
- /// @title FeatureManager
25
- library FeatureManagerStorage {
26
- using EnumerableSetLib for EnumerableSetLib.Bytes32Set;
27
-
28
- // keccak256(abi.encode(uint256(keccak256("factory.facets.feature.manager.storage")) - 1)) & ~bytes32(uint256(0xff))
29
- bytes32 constant DEFAULT_STORAGE_SLOT =
30
- 0x20c456a8ea15fcf7965033c954321ffd9dc82a2c65f686a77e2a67da65c29000;
31
-
32
- /// @notice Storage layout for the FeatureManager
33
- /// @dev Maps feature IDs to their activation conditions
34
- /// @custom:storage-location erc7201:towns.storage.FeatureManager
35
- struct Layout {
36
- // Feature IDs
37
- EnumerableSetLib.Bytes32Set featureIds;
38
- // Feature ID => Condition
39
- mapping(bytes32 featureId => FeatureCondition condition) conditions;
40
- }
41
-
42
- function getLayout() internal pure returns (Layout storage $) {
43
- assembly {
44
- $.slot := DEFAULT_STORAGE_SLOT
45
- }
46
- }
47
- }