@towns-protocol/contracts 0.0.304 → 0.0.306

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@towns-protocol/contracts",
3
- "version": "0.0.304",
3
+ "version": "0.0.306",
4
4
  "packageManager": "yarn@3.8.0",
5
5
  "scripts": {
6
6
  "build": "forge build",
@@ -32,7 +32,7 @@
32
32
  "@layerzerolabs/oapp-evm": "^0.3.2",
33
33
  "@openzeppelin/merkle-tree": "^1.0.8",
34
34
  "@prb/test": "^0.6.4",
35
- "@towns-protocol/prettier-config": "^0.0.304",
35
+ "@towns-protocol/prettier-config": "^0.0.306",
36
36
  "@wagmi/cli": "^2.2.0",
37
37
  "account-abstraction": "https://github.com/eth-infinitism/account-abstraction/archive/refs/tags/v0.7.0.tar.gz",
38
38
  "forge-std": "github:foundry-rs/forge-std#v1.10.0",
@@ -53,5 +53,5 @@
53
53
  "publishConfig": {
54
54
  "access": "public"
55
55
  },
56
- "gitHead": "f288ff8d07f5c94aa46d291dc8554c8233d6fd60"
56
+ "gitHead": "439633cf1571af6312b1cda88b578853be38afc4"
57
57
  }
@@ -12,13 +12,14 @@ import {FeatureManagerFacet} from "src/factory/facets/feature/FeatureManagerFace
12
12
 
13
13
  library DeployFeatureManager {
14
14
  function selectors() internal pure returns (bytes4[] memory _selectors) {
15
- _selectors = new bytes4[](6);
15
+ _selectors = new bytes4[](7);
16
16
  _selectors[0] = FeatureManagerFacet.setFeatureCondition.selector;
17
- _selectors[1] = FeatureManagerFacet.getFeatureCondition.selector;
18
- _selectors[2] = FeatureManagerFacet.getFeatureConditions.selector;
19
- _selectors[3] = FeatureManagerFacet.getFeatureConditionsForSpace.selector;
20
- _selectors[4] = FeatureManagerFacet.checkFeatureCondition.selector;
21
- _selectors[5] = FeatureManagerFacet.disableFeatureCondition.selector;
17
+ _selectors[1] = FeatureManagerFacet.updateFeatureCondition.selector;
18
+ _selectors[2] = FeatureManagerFacet.getFeatureCondition.selector;
19
+ _selectors[3] = FeatureManagerFacet.getFeatureConditions.selector;
20
+ _selectors[4] = FeatureManagerFacet.getFeatureConditionsForSpace.selector;
21
+ _selectors[5] = FeatureManagerFacet.checkFeatureCondition.selector;
22
+ _selectors[6] = FeatureManagerFacet.disableFeatureCondition.selector;
22
23
  }
23
24
 
24
25
  function makeCut(
@@ -15,6 +15,7 @@ import {DeployTownsMainnet} from "./DeployTownsMainnet.s.sol";
15
15
 
16
16
  contract DeployProxyBatchDelegation is Deployer {
17
17
  address internal constant CLAIMERS = 0x0bEe55b52d01C4D5d4D0cfcE1d6e0baE6722db05;
18
+ address internal constant CLAIMERS_SEPOLIA = 0xeeDCAB3c3B032D5627dcF5e1475E0e24a88b4A21;
18
19
  address internal constant BASE_REGISTRY = 0x7c0422b31401C936172C897802CF0373B35B7698;
19
20
  address internal constant BASE_REGISTRY_SEPOLIA = 0x08cC41b782F27d62995056a4EF2fCBAe0d3c266F;
20
21
  address internal constant MESSENGER = 0x866E82a600A1414e583f7F13623F1aC5d58b0Afa;
@@ -77,6 +78,8 @@ contract DeployProxyBatchDelegation is Deployer {
77
78
  function _getClaimers(address deployer) internal returns (address) {
78
79
  if (block.chainid == 1) {
79
80
  return CLAIMERS;
81
+ } else if (block.chainid == 11_155_111) {
82
+ return CLAIMERS_SEPOLIA;
80
83
  }
81
84
 
82
85
  vm.broadcast(deployer);
@@ -0,0 +1,167 @@
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 {FeatureManagerStorage} from "./FeatureManagerStorage.sol";
11
+ import {FeatureCondition} from "./IFeatureManagerFacet.sol";
12
+ import {CustomRevert} from "../../../utils/libraries/CustomRevert.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
+ function _upsertFeatureCondition(
23
+ bytes32 featureId,
24
+ FeatureCondition calldata condition,
25
+ bool create
26
+ ) internal {
27
+ _validateToken(condition);
28
+
29
+ FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
30
+ if (create && !$.featureIds.add(featureId)) {
31
+ FeatureAlreadyExists.selector.revertWith();
32
+ } else if (!create && !$.featureIds.contains(featureId)) {
33
+ FeatureNotActive.selector.revertWith();
34
+ }
35
+
36
+ $.conditions[featureId] = condition;
37
+ }
38
+
39
+ /// @notice Retrieves the condition for a specific feature
40
+ /// @dev Returns the complete condition struct with all parameters
41
+ /// @param featureId The unique identifier for the feature
42
+ /// @return result The complete condition configuration for the feature
43
+ function _getFeatureCondition(
44
+ bytes32 featureId
45
+ ) internal view returns (FeatureCondition memory result) {
46
+ FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
47
+ assembly ("memory-safe") {
48
+ mstore(0x40, result)
49
+ }
50
+ result = $.conditions[featureId];
51
+ }
52
+
53
+ /// @notice Retrieves all feature conditions
54
+ /// @dev Returns an array of all feature conditions
55
+ /// @return FeatureCondition[] An array of all feature conditions
56
+ function _getFeatureConditions() internal view returns (FeatureCondition[] memory) {
57
+ FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
58
+ uint256 featureCount = $.featureIds.length();
59
+
60
+ FeatureCondition[] memory conditions = new FeatureCondition[](featureCount);
61
+ for (uint256 i; i < featureCount; ++i) {
62
+ conditions[i] = $.conditions[$.featureIds.at(i)];
63
+ }
64
+ return conditions;
65
+ }
66
+
67
+ /// @notice Retrieves all feature conditions for a specific space
68
+ /// @dev Returns an array of all feature conditions that are active for the space
69
+ /// @return FeatureCondition[] An array of all feature conditions that are active for the space
70
+ function _getFeatureConditionsForSpace(
71
+ address space
72
+ ) internal view returns (FeatureCondition[] memory) {
73
+ FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
74
+ uint256 featureCount = $.featureIds.length();
75
+
76
+ FeatureCondition[] memory conditions = new FeatureCondition[](featureCount);
77
+ uint256 index;
78
+
79
+ for (uint256 i; i < featureCount; ++i) {
80
+ bytes32 id = $.featureIds.at(i);
81
+ FeatureCondition storage cond = $.conditions[id];
82
+
83
+ if (_isValidCondition(cond, space)) {
84
+ conditions[index++] = cond;
85
+ }
86
+ }
87
+
88
+ assembly ("memory-safe") {
89
+ mstore(conditions, index)
90
+ }
91
+
92
+ return conditions;
93
+ }
94
+
95
+ /// @notice Disables a feature by setting its active flag to false
96
+ /// @dev This does not delete the condition, only deactivates it
97
+ /// @param featureId The unique identifier for the feature to disable
98
+ function _disableFeatureCondition(bytes32 featureId) internal {
99
+ FeatureCondition storage condition = FeatureManagerStorage.getLayout().conditions[
100
+ featureId
101
+ ];
102
+ if (!condition.active) FeatureNotActive.selector.revertWith();
103
+ condition.active = false;
104
+ }
105
+
106
+ /// @notice Sets a feature condition (wrapper for upsert with create=true)
107
+ /// @param featureId The unique identifier for the feature
108
+ /// @param condition The condition struct containing token, threshold, active status, and extra data
109
+ function _setFeatureCondition(bytes32 featureId, FeatureCondition calldata condition) internal {
110
+ _upsertFeatureCondition(featureId, condition, true);
111
+ }
112
+
113
+ /// @notice Updates a feature condition (wrapper for upsert with create=false)
114
+ /// @param featureId The unique identifier for the feature
115
+ /// @param condition The condition struct containing token, threshold, active status, and extra data
116
+ function _updateFeatureCondition(
117
+ bytes32 featureId,
118
+ FeatureCondition calldata condition
119
+ ) internal {
120
+ _upsertFeatureCondition(featureId, condition, false);
121
+ }
122
+
123
+ function _validateToken(FeatureCondition calldata condition) internal view {
124
+ if (condition.token == address(0)) InvalidToken.selector.revertWith();
125
+
126
+ // Check if the token implements IVotes.getVotes with proper return data
127
+ (bool success, bool exceededMaxCopy, bytes memory data) = LibCall.tryStaticCall(
128
+ condition.token,
129
+ gasleft(),
130
+ 32,
131
+ abi.encodeCall(IVotes.getVotes, (address(this)))
132
+ );
133
+
134
+ if (!success || exceededMaxCopy || data.length != 32)
135
+ InvalidInterface.selector.revertWith();
136
+
137
+ // Check if the token implements ERC20.totalSupply with proper return data
138
+ (success, exceededMaxCopy, data) = LibCall.tryStaticCall(
139
+ condition.token,
140
+ gasleft(),
141
+ 32,
142
+ abi.encodeCall(IERC20.totalSupply, ())
143
+ );
144
+
145
+ if (!success || exceededMaxCopy || data.length != 32)
146
+ InvalidInterface.selector.revertWith();
147
+
148
+ uint256 totalSupply = abi.decode(data, (uint256));
149
+ if (totalSupply == 0) InvalidTotalSupply.selector.revertWith();
150
+ if (condition.threshold > totalSupply) InvalidThreshold.selector.revertWith();
151
+ }
152
+
153
+ /// @notice Checks if a condition should be included for a given space
154
+ /// @dev Returns true if the condition is active, has a valid token, and meets the threshold
155
+ /// @param condition The condition to check
156
+ /// @param space The space address to check against
157
+ /// @return True if the condition should be included, false otherwise
158
+ function _isValidCondition(
159
+ FeatureCondition memory condition,
160
+ address space
161
+ ) internal view returns (bool) {
162
+ if (!condition.active) return false;
163
+ if (condition.token == address(0)) return false;
164
+ uint256 votes = IVotes(condition.token).getVotes(space);
165
+ return votes >= condition.threshold;
166
+ }
167
+ }
@@ -3,10 +3,12 @@ pragma solidity ^0.8.23;
3
3
 
4
4
  // interfaces
5
5
  import {IFeatureManagerFacet} from "./IFeatureManagerFacet.sol";
6
+ import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
6
7
 
7
8
  // libraries
8
- import {FeatureManagerLib} from "./FeatureManagerLib.sol";
9
- import {FeatureCondition, FeatureConditionLib} from "./FeatureConditionLib.sol";
9
+ import {FeatureManagerBase} from "./FeatureManagerBase.sol";
10
+ import {FeatureCondition} from "./IFeatureManagerFacet.sol";
11
+
10
12
  // contracts
11
13
  import {OwnableBase} from "@towns-protocol/diamond/src/facets/ownable/OwnableBase.sol";
12
14
  import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
@@ -14,10 +16,7 @@ import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
14
16
  /// @title FeatureManagerFacet
15
17
  /// @notice Manages feature conditions and checks for spaces
16
18
  /// @dev This facet is responsible for managing feature conditions and checking if a space meets the condition for a feature to be enabled
17
- contract FeatureManagerFacet is IFeatureManagerFacet, OwnableBase, Facet {
18
- using FeatureManagerLib for FeatureManagerLib.Layout;
19
- using FeatureConditionLib for FeatureCondition;
20
-
19
+ contract FeatureManagerFacet is IFeatureManagerFacet, OwnableBase, Facet, FeatureManagerBase {
21
20
  function __FeatureManagerFacet_init() external onlyInitializing {
22
21
  _addInterface(type(IFeatureManagerFacet).interfaceId);
23
22
  }
@@ -27,7 +26,16 @@ contract FeatureManagerFacet is IFeatureManagerFacet, OwnableBase, Facet {
27
26
  bytes32 featureId,
28
27
  FeatureCondition calldata condition
29
28
  ) external onlyOwner {
30
- FeatureManagerLib.getLayout().setFeatureCondition(featureId, condition);
29
+ _upsertFeatureCondition(featureId, condition, true);
30
+ emit FeatureConditionSet(featureId, condition);
31
+ }
32
+
33
+ /// @inheritdoc IFeatureManagerFacet
34
+ function updateFeatureCondition(
35
+ bytes32 featureId,
36
+ FeatureCondition calldata condition
37
+ ) external onlyOwner {
38
+ _upsertFeatureCondition(featureId, condition, false);
31
39
  emit FeatureConditionSet(featureId, condition);
32
40
  }
33
41
 
@@ -35,32 +43,29 @@ contract FeatureManagerFacet is IFeatureManagerFacet, OwnableBase, Facet {
35
43
  function getFeatureCondition(
36
44
  bytes32 featureId
37
45
  ) external view returns (FeatureCondition memory) {
38
- return FeatureManagerLib.getLayout().getFeatureCondition(featureId);
46
+ return _getFeatureCondition(featureId);
39
47
  }
40
48
 
41
49
  /// @inheritdoc IFeatureManagerFacet
42
50
  function getFeatureConditions() external view returns (FeatureCondition[] memory) {
43
- return FeatureManagerLib.getLayout().getFeatureConditions();
51
+ return _getFeatureConditions();
44
52
  }
45
53
 
46
54
  /// @inheritdoc IFeatureManagerFacet
47
55
  function getFeatureConditionsForSpace(
48
56
  address space
49
57
  ) external view returns (FeatureCondition[] memory) {
50
- return FeatureManagerLib.getLayout().getFeatureConditionsForSpace(space);
58
+ return _getFeatureConditionsForSpace(space);
51
59
  }
52
60
 
53
61
  /// @inheritdoc IFeatureManagerFacet
54
62
  function disableFeatureCondition(bytes32 featureId) external onlyOwner {
55
- FeatureManagerLib.getLayout().disableFeatureCondition(featureId);
63
+ _disableFeatureCondition(featureId);
56
64
  emit FeatureConditionDisabled(featureId);
57
65
  }
58
66
 
59
67
  /// @inheritdoc IFeatureManagerFacet
60
68
  function checkFeatureCondition(bytes32 featureId, address space) external view returns (bool) {
61
- FeatureCondition storage condition = FeatureManagerLib.getLayout().conditions[featureId];
62
- if (!condition.isValid()) return false;
63
- uint256 votes = condition.getVotes(space);
64
- return condition.meetsThreshold(votes);
69
+ return _isValidCondition(_getFeatureCondition(featureId), space);
65
70
  }
66
71
  }
@@ -0,0 +1,47 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ // interfaces
5
+
6
+ // libraries
7
+
8
+ // contracts
9
+ import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
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
+ }
@@ -2,11 +2,8 @@
2
2
  pragma solidity ^0.8.23;
3
3
 
4
4
  // interfaces
5
-
6
- // libraries
7
- import {FeatureCondition} from "./FeatureConditionLib.sol";
8
-
9
- // contracts
5
+ import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
6
+ import {FeatureCondition} from "./FeatureManagerStorage.sol";
10
7
 
11
8
  /// @title IFeatureManagerFacetBase
12
9
  /// @notice Base interface for the FeatureManager facet defining errors and events
@@ -18,15 +15,18 @@ interface IFeatureManagerFacetBase {
18
15
  /// @notice Error thrown when a token has zero total supply
19
16
  error InvalidTotalSupply();
20
17
 
21
- /// @notice Error thrown when an invalid token address is provided (zero address or non-IVotes token)
18
+ /// @notice Error thrown when an invalid token address is provided (e.g., zero address)
22
19
  error InvalidToken();
23
20
 
24
- /// @notice Error thrown when an invalid token interface is provided
21
+ /// @notice Error thrown when the token does not implement the required interfaces (e.g., IVotes and ERC20 totalSupply)
25
22
  error InvalidInterface();
26
23
 
27
24
  /// @notice Error thrown when a feature condition is not active
28
25
  error FeatureNotActive();
29
26
 
27
+ /// @notice Error thrown when a feature condition already exists
28
+ error FeatureAlreadyExists();
29
+
30
30
  /// @notice Emitted when a feature condition is set or updated
31
31
  /// @param featureId The unique identifier for the feature whose condition was set
32
32
  /// @param condition The condition parameters that were set for the feature
@@ -44,6 +44,12 @@ interface IFeatureManagerFacet is IFeatureManagerFacetBase {
44
44
  /// @dev Only callable by the contract owner
45
45
  function setFeatureCondition(bytes32 featureId, FeatureCondition memory condition) external;
46
46
 
47
+ /// @notice Updates the condition for a feature
48
+ /// @param featureId The unique identifier for the feature
49
+ /// @param condition The condition struct containing token, threshold, active status, and extra data
50
+ /// @dev Only callable by the contract owner
51
+ function updateFeatureCondition(bytes32 featureId, FeatureCondition memory condition) external;
52
+
47
53
  /// @notice Gets the condition for a feature
48
54
  /// @param featureId The unique identifier for the feature
49
55
  /// @return The condition struct for the specified feature
@@ -1,53 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.23;
3
-
4
- // interfaces
5
- import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
6
-
7
- /// @notice Represents a condition for feature activation
8
- /// @dev Used to determine if a feature should be enabled based on token voting power
9
- /// @param token The address of the token used for voting (must implement IVotes)
10
- /// @param threshold The minimum number of delegations required to activate the feature
11
- /// @param active Whether the condition is currently active
12
- /// @param extraData Additional data that might be used for specialized condition logic
13
- struct FeatureCondition {
14
- address token;
15
- bool active;
16
- uint256 threshold;
17
- bytes extraData;
18
- }
19
-
20
- /// @title FeatureConditionLib
21
- library FeatureConditionLib {
22
- function isValid(FeatureCondition storage condition) internal view returns (bool) {
23
- return condition.active && condition.token != address(0);
24
- }
25
-
26
- /// @notice Gets the voting power of a space for the condition's token
27
- /// @dev Calls the getVotes function on the token contract
28
- /// @param condition The condition containing the token address
29
- /// @param space The address of the space to check votes for
30
- /// @return The number of votes the space has with the token
31
- function getVotes(
32
- FeatureCondition storage condition,
33
- address space
34
- ) internal view returns (uint256) {
35
- return IVotes(condition.token).getVotes(space);
36
- }
37
-
38
- /// @notice Checks if the given number of votes meets the condition's threshold
39
- /// @dev Returns false if condition is inactive, true if threshold is 0, otherwise compares
40
- /// votes to threshold
41
- /// @param condition The condition containing the threshold and active status
42
- /// @param votes The number of votes to check against the threshold
43
- /// @return True if the condition is met, false otherwise
44
- function meetsThreshold(
45
- FeatureCondition storage condition,
46
- uint256 votes
47
- ) internal view returns (bool) {
48
- if (!isValid(condition)) return false;
49
- uint256 threshold = condition.threshold;
50
- if (threshold == 0) return true;
51
- return votes >= threshold;
52
- }
53
- }
@@ -1,168 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.23;
3
-
4
- // interfaces
5
-
6
- import {IFeatureManagerFacetBase} from "./IFeatureManagerFacet.sol";
7
- import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
8
- import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
9
- import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
10
-
11
- // libraries
12
-
13
- import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
14
- import {FeatureCondition, FeatureConditionLib} from "./FeatureConditionLib.sol";
15
- import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
16
-
17
- /// @title FeatureManager
18
- library FeatureManagerLib {
19
- using EnumerableSetLib for EnumerableSetLib.Bytes32Set;
20
- using FeatureConditionLib for FeatureCondition;
21
-
22
- // keccak256(abi.encode(uint256(keccak256("factory.facets.feature.manager.storage")) - 1)) & ~bytes32(uint256(0xff))
23
- bytes32 constant DEFAULT_STORAGE_SLOT =
24
- 0x20c456a8ea15fcf7965033c954321ffd9dc82a2c65f686a77e2a67da65c29000;
25
-
26
- /// @notice Storage layout for the FeatureManager
27
- /// @dev Maps feature IDs to their activation conditions
28
- /// @custom:storage-location erc7201:towns.storage.FeatureManager
29
- struct Layout {
30
- // Feature IDs
31
- EnumerableSetLib.Bytes32Set featureIds;
32
- // Feature ID => Condition
33
- mapping(bytes32 featureId => FeatureCondition condition) conditions;
34
- }
35
-
36
- /// @notice Sets the condition for a specific feature
37
- /// @dev Validates token, threshold, and total supply to ensure proper configuration
38
- /// @param self The storage layout pointer
39
- /// @param featureId The unique identifier for the feature
40
- /// @param condition The condition parameters for feature activation
41
- /// @custom:error InvalidToken Thrown when token is address(0) or doesn't support IVotes
42
- /// @custom:error InvalidTotalSupply Thrown when token total supply is 0
43
- /// @custom:error InvalidThreshold Thrown when threshold exceeds total supply
44
- function setFeatureCondition(
45
- Layout storage self,
46
- bytes32 featureId,
47
- FeatureCondition calldata condition
48
- ) internal {
49
- if (condition.token == address(0)) {
50
- CustomRevert.revertWith(IFeatureManagerFacetBase.InvalidToken.selector);
51
- }
52
-
53
- _validateGetVotes(condition);
54
-
55
- // Check totalSupply directly
56
- try IERC20(condition.token).totalSupply() returns (uint256 totalSupply) {
57
- if (totalSupply == 0) {
58
- CustomRevert.revertWith(IFeatureManagerFacetBase.InvalidTotalSupply.selector);
59
- }
60
-
61
- if (condition.threshold > totalSupply) {
62
- CustomRevert.revertWith(IFeatureManagerFacetBase.InvalidThreshold.selector);
63
- }
64
- } catch {
65
- CustomRevert.revertWith(IFeatureManagerFacetBase.InvalidToken.selector);
66
- }
67
-
68
- self.featureIds.add(featureId);
69
- self.conditions[featureId] = condition;
70
- }
71
-
72
- /// @notice Retrieves the condition for a specific feature
73
- /// @dev Returns the complete condition struct with all parameters
74
- /// @param self The storage layout pointer
75
- /// @param featureId The unique identifier for the feature
76
- /// @return Condition memory The complete condition configuration for the feature
77
- function getFeatureCondition(
78
- Layout storage self,
79
- bytes32 featureId
80
- ) internal view returns (FeatureCondition memory) {
81
- return self.conditions[featureId];
82
- }
83
-
84
- /// @notice Retrieves all feature conditions
85
- /// @dev Returns an array of all feature conditions
86
- /// @param self The storage layout pointer
87
- /// @return Condition[] An array of all feature conditions
88
- function getFeatureConditions(
89
- Layout storage self
90
- ) internal view returns (FeatureCondition[] memory) {
91
- uint256 featureCount = self.featureIds.length();
92
-
93
- FeatureCondition[] memory conditions = new FeatureCondition[](featureCount);
94
- for (uint256 i; i < featureCount; ++i) {
95
- conditions[i] = self.conditions[self.featureIds.at(i)];
96
- }
97
- return conditions;
98
- }
99
-
100
- /// @notice Retrieves all feature conditions for a specific space
101
- /// @dev Returns an array of all feature conditions that are active for the space
102
- /// @param self The storage layout pointer
103
- /// @param space The address of the space to check conditions for
104
- /// @return Condition[] An array of all feature conditions that are active for the space
105
- function getFeatureConditionsForSpace(
106
- Layout storage self,
107
- address space
108
- ) internal view returns (FeatureCondition[] memory) {
109
- uint256 featureCount = self.featureIds.length();
110
-
111
- FeatureCondition[] memory conditions = new FeatureCondition[](featureCount);
112
- uint256 index;
113
-
114
- for (uint256 i; i < featureCount; ++i) {
115
- FeatureCondition storage condition = self.conditions[self.featureIds.at(i)];
116
- uint256 votes = condition.getVotes(space);
117
-
118
- if (condition.meetsThreshold(votes)) {
119
- conditions[index++] = condition;
120
- }
121
- }
122
-
123
- assembly ("memory-safe") {
124
- mstore(conditions, index)
125
- }
126
-
127
- return conditions;
128
- }
129
-
130
- /// @notice Disables a feature by setting its active flag to false
131
- /// @dev This does not delete the condition, only deactivates it
132
- /// @param self The storage layout pointer
133
- /// @param featureId The unique identifier for the feature to disable
134
- function disableFeatureCondition(Layout storage self, bytes32 featureId) internal {
135
- FeatureCondition storage condition = self.conditions[featureId];
136
- if (!condition.active) {
137
- CustomRevert.revertWith(IFeatureManagerFacetBase.FeatureNotActive.selector);
138
- }
139
- condition.active = false;
140
- }
141
-
142
- function _validateGetVotes(FeatureCondition calldata condition) internal view {
143
- // Check IVotes support by attempting to call getVotes
144
- try IVotes(condition.token).getVotes(address(this)) returns (uint256) {
145
- // If we get here, getVotes is supported
146
- return;
147
- } catch {
148
- CustomRevert.revertWith(IFeatureManagerFacetBase.InvalidInterface.selector);
149
- }
150
- }
151
-
152
- /// @notice Retrieves the layout for the FeatureManager at the default storage slot
153
- /// @dev This is accessed by the facet contract
154
- /// @return $ The layout storage pointer
155
- function getLayout() internal pure returns (Layout storage $) {
156
- return getLayout(DEFAULT_STORAGE_SLOT);
157
- }
158
-
159
- /// @notice Retrieves the layout for the FeatureManager at a custom storage slot
160
- /// @dev This function is used to access the storage layout for a given slot
161
- /// @param slot The storage slot to retrieve the layout for
162
- /// @return $ The layout storage pointer
163
- function getLayout(bytes32 slot) internal pure returns (Layout storage $) {
164
- assembly {
165
- $.slot := slot
166
- }
167
- }
168
- }