@towns-protocol/contracts 0.0.329 → 0.0.332

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/README.md CHANGED
@@ -119,7 +119,7 @@ If you want to interact with anvil via a front end, you will need to add the loc
119
119
  **To deploy our contracts to your local base and river instances:**
120
120
 
121
121
  1. Duplicate `.env.localhost` file in the [contracts](.) folder of the project and rename it to `.env` (this is excluded from git via .gitignore)
122
- 2. Run `export RIVER_ENV="local_multi"` from your terminal
122
+ 2. Run `export RIVER_ENV="local_dev"` from your terminal
123
123
  3. Execute `./scripts/deploy-contracts.sh` to deploy the entire suite of contracts to your local base-anvil and river-anvil chains
124
124
 
125
125
  ### Diamond Contract Deployment
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@towns-protocol/contracts",
3
- "version": "0.0.329",
3
+ "version": "0.0.332",
4
4
  "packageManager": "yarn@3.8.0",
5
5
  "scripts": {
6
6
  "build": "forge build",
@@ -22,7 +22,7 @@
22
22
  "@openzeppelin/contracts": "^5.4.0",
23
23
  "@openzeppelin/contracts-upgradeable": "^5.4.0",
24
24
  "@prb/math": "^4.1.0",
25
- "@towns-protocol/diamond": "^0.6.1",
25
+ "@towns-protocol/diamond": "^0.6.3",
26
26
  "@uniswap/permit2": "https://github.com/towns-protocol/permit2/archive/refs/tags/v1.0.0.tar.gz",
27
27
  "crypto-lib": "https://github.com/towns-protocol/crypto-lib/archive/refs/tags/v1.0.0.tar.gz",
28
28
  "solady": "^0.1.24"
@@ -33,7 +33,7 @@
33
33
  "@layerzerolabs/oapp-evm": "^0.3.2",
34
34
  "@openzeppelin/merkle-tree": "^1.0.8",
35
35
  "@prb/test": "^0.6.4",
36
- "@towns-protocol/prettier-config": "^0.0.329",
36
+ "@towns-protocol/prettier-config": "^0.0.332",
37
37
  "@typechain/ethers-v5": "^10.1.1",
38
38
  "@wagmi/cli": "^2.2.0",
39
39
  "account-abstraction": "https://github.com/eth-infinitism/account-abstraction/archive/refs/tags/v0.7.0.tar.gz",
@@ -56,5 +56,5 @@
56
56
  "publishConfig": {
57
57
  "access": "public"
58
58
  },
59
- "gitHead": "91e3bc86a96ecd92c40a19ed4a3fbf70d427f0d3"
59
+ "gitHead": "11bbd01060288b210bb03e34943058788185ed84"
60
60
  }
@@ -13,7 +13,6 @@ import {LibString} from "solady/utils/LibString.sol";
13
13
  import {DeployMetadata} from "../facets/DeployMetadata.s.sol";
14
14
  import {DeployAppRegistryFacet} from "../facets/DeployAppRegistryFacet.s.sol";
15
15
  import {DeployUpgradeableBeacon} from "../facets/DeployUpgradeableBeacon.s.sol";
16
- import {DeploySimpleApp} from "../facets/DeploySimpleApp.s.sol";
17
16
 
18
17
  // contracts
19
18
  import {Diamond} from "@towns-protocol/diamond/src/Diamond.sol";
@@ -99,15 +98,13 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
99
98
  facetHelper.add("MultiInit");
100
99
  facetHelper.add("UpgradeableBeaconFacet");
101
100
  facetHelper.add("AppRegistryFacet");
101
+ facetHelper.add("SimpleApp");
102
102
 
103
- // Deploy all facets in a batch
104
103
  facetHelper.deployBatch(deployer);
105
104
 
106
- vm.broadcast(deployer);
107
- address simpleApp = DeploySimpleApp.deploy();
108
-
109
- // Add feature facets
105
+ address simpleApp = facetHelper.getDeployedAddress("SimpleApp");
110
106
  address facet = facetHelper.getDeployedAddress("UpgradeableBeaconFacet");
107
+
111
108
  addFacet(
112
109
  makeCut(facet, FacetCutAction.Add, DeployUpgradeableBeacon.selectors()),
113
110
  facet,
@@ -88,6 +88,9 @@ contract DeployBaseRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
88
88
  facetHelper.add("NodeOperatorFacet");
89
89
  facetHelper.add("MetadataFacet");
90
90
  facetHelper.add("EntitlementChecker");
91
+
92
+ facetHelper.deployBatch(deployer);
93
+
91
94
  facetHelper.add("RewardsDistributionV2");
92
95
  facetHelper.add("SpaceDelegationFacet");
93
96
  facetHelper.add("MainnetDelegation");
@@ -104,17 +104,21 @@ contract DeploySpace is IDiamondInitHelper, DiamondHelper, Deployer {
104
104
  facetHelper.add("MembershipFacet");
105
105
  facetHelper.add("MembershipMetadata");
106
106
  facetHelper.add("EntitlementDataQueryable");
107
- facetHelper.add("EntitlementsManager");
108
- facetHelper.add("Roles");
109
- facetHelper.add("Channels");
110
107
 
111
108
  // Deploy the first batch of facets
112
109
  facetHelper.deployBatch(deployer);
113
110
 
111
+ facetHelper.add("EntitlementsManager");
112
+ facetHelper.add("Roles");
113
+ facetHelper.add("Channels");
114
114
  facetHelper.add("TokenPausableFacet");
115
115
  facetHelper.add("PrepayFacet");
116
116
  facetHelper.add("ReferralsFacet");
117
117
  facetHelper.add("ReviewFacet");
118
+
119
+ // Deploy the second batch of facets
120
+ facetHelper.deployBatch(deployer);
121
+
118
122
  facetHelper.add("SpaceEntitlementGated");
119
123
  facetHelper.add("SwapFacet");
120
124
  facetHelper.add("TippingFacet");
@@ -126,7 +130,7 @@ contract DeploySpace is IDiamondInitHelper, DiamondHelper, Deployer {
126
130
  facetHelper.add("MockLegacyMembership");
127
131
  }
128
132
 
129
- // Deploy the second batch of facets
133
+ // Deploy the third batch of facets
130
134
  facetHelper.deployBatch(deployer);
131
135
 
132
136
  // deploy and add facets one by one to avoid stack too deep
@@ -134,7 +134,7 @@ contract DeploySpaceFactory is IDiamondInitHelper, DiamondHelper, Deployer {
134
134
  }
135
135
 
136
136
  function diamondInitParams(address deployer) public returns (Diamond.InitParams memory) {
137
- // Queue up all feature facets for batch deployment
137
+ // Queue up feature facets for batch deployment
138
138
  facetHelper.add("MultiInit");
139
139
  facetHelper.add("MetadataFacet");
140
140
  facetHelper.add("Architect");
@@ -145,6 +145,10 @@ contract DeploySpaceFactory is IDiamondInitHelper, DiamondHelper, Deployer {
145
145
  facetHelper.add("PricingModulesFacet");
146
146
  facetHelper.add("ImplementationRegistryFacet");
147
147
  facetHelper.add("SCL_EIP6565");
148
+
149
+ // Deploy the second batch of facets
150
+ facetHelper.deployBatch(deployer);
151
+
148
152
  facetHelper.add("WalletLink");
149
153
  facetHelper.add("EIP712Facet");
150
154
  facetHelper.add("PartnerRegistry");
@@ -157,7 +161,7 @@ contract DeploySpaceFactory is IDiamondInitHelper, DiamondHelper, Deployer {
157
161
  facetHelper.add("MockLegacyArchitect");
158
162
  }
159
163
 
160
- // Deploy the feature facets in a batch
164
+ // Deploy the third batch of facets
161
165
  facetHelper.deployBatch(deployer);
162
166
 
163
167
  if (isAnvil()) {
@@ -5,19 +5,13 @@ pragma solidity ^0.8.23;
5
5
  import {IDiamondCut} from "@towns-protocol/diamond/src/facets/cut/IDiamondCut.sol";
6
6
  import {IDiamondInitHelper} from "../deployments/diamonds/IDiamondInitHelper.sol";
7
7
 
8
- import {DiamondHelper} from "@towns-protocol/diamond/scripts/common/helpers/DiamondHelper.s.sol";
9
- import {Diamond} from "@towns-protocol/diamond/src/Diamond.sol";
10
-
11
8
  // libraries
9
+ import {console} from "forge-std/console.sol";
12
10
  import {stdJson} from "forge-std/StdJson.sol";
13
- import "forge-std/console.sol";
14
11
 
15
12
  // contracts
16
-
17
13
  import {AlphaHelper, DiamondFacetData, FacetData} from "scripts/interactions/helpers/AlphaHelper.sol";
18
-
19
14
  import {DeployBaseRegistry} from "scripts/deployments/diamonds/DeployBaseRegistry.s.sol";
20
-
21
15
  import {DeployRiverAirdrop} from "scripts/deployments/diamonds/DeployRiverAirdrop.s.sol";
22
16
  import {DeploySpace} from "scripts/deployments/diamonds/DeploySpace.s.sol";
23
17
  import {DeploySpaceFactory} from "scripts/deployments/diamonds/DeploySpaceFactory.s.sol";
@@ -99,7 +93,7 @@ contract InteractAlphaSparse is AlphaHelper {
99
93
  uint256 updatedDiamondLen = abi.decode(vm.parseJson(jsonData, ".numUpdated"), (uint256));
100
94
  DiamondFacetData[] memory diamonds = new DiamondFacetData[](updatedDiamondLen);
101
95
 
102
- for (uint256 i = 0; i < updatedDiamondLen; i++) {
96
+ for (uint256 i; i < updatedDiamondLen; ++i) {
103
97
  // Decode diamond name and number of facets
104
98
  DiamondFacetData memory diamondData = abi.decode(
105
99
  vm.parseJson(jsonData, string.concat("$.updated[", vm.toString(i), "]")),
@@ -143,7 +137,7 @@ contract InteractAlphaSparse is AlphaHelper {
143
137
  console.log("interact::diamonds decoded", diamonds.length);
144
138
 
145
139
  // Iterate over diamonds array and process each diamond
146
- for (uint256 i = 0; i < diamonds.length; i++) {
140
+ for (uint256 i; i < diamonds.length; ++i) {
147
141
  console.log("interact::diamondName", diamonds[i].diamond);
148
142
  string memory diamondName = diamonds[i].diamond;
149
143
  address diamondDeployedAddress;
@@ -152,7 +146,7 @@ contract InteractAlphaSparse is AlphaHelper {
152
146
  string[] memory facetNames = new string[](numFacets);
153
147
  address[] memory facetAddresses = new address[](numFacets);
154
148
 
155
- for (uint256 j = 0; j < numFacets; j++) {
149
+ for (uint256 j; j < numFacets; ++j) {
156
150
  FacetData memory facetData = diamonds[i].facets[j];
157
151
  facetAddresses[j] = facetData.deployedAddress;
158
152
  facetNames[j] = facetData.facetName;
@@ -5,6 +5,7 @@ pragma solidity ^0.8.23;
5
5
  import {IDiamondCut} from "@towns-protocol/diamond/src/facets/cut/IDiamondCut.sol";
6
6
 
7
7
  // libraries
8
+ import {console} from "forge-std/console.sol";
8
9
 
9
10
  // contracts
10
11
  import {DeployBaseRegistry} from "scripts/deployments/diamonds/DeployBaseRegistry.s.sol";
@@ -12,6 +13,7 @@ import {DeployRiverAirdrop} from "scripts/deployments/diamonds/DeployRiverAirdro
12
13
  import {DeploySpace} from "scripts/deployments/diamonds/DeploySpace.s.sol";
13
14
  import {DeploySpaceFactory} from "scripts/deployments/diamonds/DeploySpaceFactory.s.sol";
14
15
  import {DeploySpaceOwner} from "scripts/deployments/diamonds/DeploySpaceOwner.s.sol";
16
+ import {DeployAppRegistry} from "scripts/deployments/diamonds/DeployAppRegistry.s.sol";
15
17
  import {AlphaHelper} from "scripts/interactions/helpers/AlphaHelper.sol";
16
18
 
17
19
  contract InteractBaseAlpha is AlphaHelper {
@@ -20,64 +22,153 @@ contract InteractBaseAlpha is AlphaHelper {
20
22
  DeployBaseRegistry deployBaseRegistry = new DeployBaseRegistry();
21
23
  DeploySpaceOwner deploySpaceOwner = new DeploySpaceOwner();
22
24
  DeployRiverAirdrop deployRiverAirdrop = new DeployRiverAirdrop();
25
+ DeployAppRegistry deployAppRegistry = new DeployAppRegistry();
23
26
 
24
27
  function __interact(address deployer) internal override {
28
+ vm.pauseGasMetering();
29
+
25
30
  address space = getDeployment("space");
26
31
  address spaceOwner = getDeployment("spaceOwner");
27
32
  address spaceFactory = getDeployment("spaceFactory");
28
33
  address baseRegistry = getDeployment("baseRegistry");
29
34
  address riverAirdrop = getDeployment("riverAirdrop");
30
-
31
- vm.pauseGasMetering();
32
- removeRemoteFacets(deployer, space);
33
- removeRemoteFacets(deployer, spaceOwner);
34
- removeRemoteFacets(deployer, spaceFactory);
35
- removeRemoteFacets(deployer, baseRegistry);
36
- removeRemoteFacets(deployer, riverAirdrop);
35
+ address appRegistry = getDeployment("appRegistry");
37
36
 
38
37
  deploySpaceCuts(deployer, space);
39
38
  deploySpaceOwnerCuts(deployer, spaceOwner);
40
39
  deploySpaceFactoryCuts(deployer, spaceFactory);
41
40
  deployBaseRegistryCuts(deployer, baseRegistry);
42
41
  deployRiverAirdropCuts(deployer, riverAirdrop);
42
+ deployAppRegistryCuts(deployer, appRegistry);
43
43
 
44
44
  vm.resumeGasMetering();
45
45
  }
46
46
 
47
47
  function deploySpaceCuts(address deployer, address space) internal {
48
+ console.log("[INFO]: === Upgrading Space diamond ===");
48
49
  deploySpace.diamondInitParams(deployer);
49
- FacetCut[] memory newCuts = deploySpace.getCuts();
50
- vm.broadcast(deployer);
51
- IDiamondCut(space).diamondCut(newCuts, address(0), "");
50
+ FacetCut[] memory proposedCuts = deploySpace.getCuts();
51
+ FacetCut[] memory smartCuts = generateSmartCuts(space, proposedCuts);
52
+
53
+ console.log(
54
+ "[INFO]: Generated %d smart cuts from %d proposed cuts",
55
+ smartCuts.length,
56
+ proposedCuts.length
57
+ );
58
+
59
+ if (smartCuts.length > 0) {
60
+ vm.broadcast(deployer);
61
+ IDiamondCut(space).diamondCut(smartCuts, address(0), "");
62
+ console.log("[INFO]: \u2705 Space diamond upgrade completed");
63
+ } else {
64
+ console.log("[INFO]: Space diamond already up to date - no cuts needed");
65
+ }
52
66
  }
53
67
 
54
68
  function deploySpaceOwnerCuts(address deployer, address spaceOwner) internal {
69
+ console.log("[INFO]: === Upgrading SpaceOwner diamond ===");
55
70
  deploySpaceOwner.diamondInitParams(deployer);
56
- FacetCut[] memory newCuts = deploySpaceOwner.getCuts();
57
- vm.broadcast(deployer);
58
- IDiamondCut(spaceOwner).diamondCut(newCuts, address(0), "");
71
+ FacetCut[] memory proposedCuts = deploySpaceOwner.getCuts();
72
+ FacetCut[] memory smartCuts = generateSmartCuts(spaceOwner, proposedCuts);
73
+
74
+ console.log(
75
+ "[INFO]: Generated %d smart cuts from %d proposed cuts",
76
+ smartCuts.length,
77
+ proposedCuts.length
78
+ );
79
+
80
+ if (smartCuts.length > 0) {
81
+ vm.broadcast(deployer);
82
+ IDiamondCut(spaceOwner).diamondCut(smartCuts, address(0), "");
83
+ console.log("[INFO]: \u2705 SpaceOwner diamond upgrade completed");
84
+ } else {
85
+ console.log("[INFO]: SpaceOwner diamond already up to date - no cuts needed");
86
+ }
59
87
  }
60
88
 
61
89
  function deploySpaceFactoryCuts(address deployer, address spaceFactory) internal {
90
+ console.log("[INFO]: === Upgrading SpaceFactory diamond ===");
62
91
  deploySpaceFactory.diamondInitParams(deployer);
63
- FacetCut[] memory newCuts = deploySpaceFactory.getCuts();
64
- address spaceFactoryInit = deploySpaceFactory.spaceFactoryInit();
65
- bytes memory initData = deploySpaceFactory.spaceFactoryInitData();
66
- vm.broadcast(deployer);
67
- IDiamondCut(spaceFactory).diamondCut(newCuts, spaceFactoryInit, initData);
92
+ FacetCut[] memory proposedCuts = deploySpaceFactory.getCuts();
93
+ FacetCut[] memory smartCuts = generateSmartCuts(spaceFactory, proposedCuts);
94
+
95
+ console.log(
96
+ "[INFO]: Generated %d smart cuts from %d proposed cuts",
97
+ smartCuts.length,
98
+ proposedCuts.length
99
+ );
100
+
101
+ if (smartCuts.length > 0) {
102
+ address spaceFactoryInit = deploySpaceFactory.spaceFactoryInit();
103
+ bytes memory initData = deploySpaceFactory.spaceFactoryInitData();
104
+ vm.broadcast(deployer);
105
+ IDiamondCut(spaceFactory).diamondCut(smartCuts, spaceFactoryInit, initData);
106
+ console.log("[INFO]: \u2705 SpaceFactory diamond upgrade completed");
107
+ } else {
108
+ console.log("[INFO]: SpaceFactory diamond already up to date - no cuts needed");
109
+ }
68
110
  }
69
111
 
70
112
  function deployBaseRegistryCuts(address deployer, address baseRegistry) internal {
113
+ console.log("[INFO]: === Upgrading BaseRegistry diamond ===");
71
114
  deployBaseRegistry.diamondInitParams(deployer);
72
- FacetCut[] memory newCuts = deployBaseRegistry.getCuts();
73
- vm.broadcast(deployer);
74
- IDiamondCut(baseRegistry).diamondCut(newCuts, address(0), "");
115
+ FacetCut[] memory proposedCuts = deployBaseRegistry.getCuts();
116
+ FacetCut[] memory smartCuts = generateSmartCuts(baseRegistry, proposedCuts);
117
+
118
+ console.log(
119
+ "[INFO]: Generated %d smart cuts from %d proposed cuts",
120
+ smartCuts.length,
121
+ proposedCuts.length
122
+ );
123
+
124
+ if (smartCuts.length > 0) {
125
+ vm.broadcast(deployer);
126
+ IDiamondCut(baseRegistry).diamondCut(smartCuts, address(0), "");
127
+ console.log("[INFO]: \u2705 BaseRegistry diamond upgrade completed");
128
+ } else {
129
+ console.log("[INFO]: BaseRegistry diamond already up to date - no cuts needed");
130
+ }
75
131
  }
76
132
 
77
133
  function deployRiverAirdropCuts(address deployer, address riverAirdrop) internal {
134
+ console.log("[INFO]: === Upgrading RiverAirdrop diamond ===");
78
135
  deployRiverAirdrop.diamondInitParams(deployer);
79
- FacetCut[] memory newCuts = deployRiverAirdrop.getCuts();
80
- vm.broadcast(deployer);
81
- IDiamondCut(riverAirdrop).diamondCut(newCuts, address(0), "");
136
+ FacetCut[] memory proposedCuts = deployRiverAirdrop.getCuts();
137
+ FacetCut[] memory smartCuts = generateSmartCuts(riverAirdrop, proposedCuts);
138
+
139
+ console.log(
140
+ "[INFO]: Generated %d smart cuts from %d proposed cuts",
141
+ smartCuts.length,
142
+ proposedCuts.length
143
+ );
144
+
145
+ if (smartCuts.length > 0) {
146
+ vm.broadcast(deployer);
147
+ IDiamondCut(riverAirdrop).diamondCut(smartCuts, address(0), "");
148
+ console.log("[INFO]: \u2705 RiverAirdrop diamond upgrade completed");
149
+ } else {
150
+ console.log("[INFO]: RiverAirdrop diamond already up to date - no cuts needed");
151
+ }
152
+ }
153
+
154
+ function deployAppRegistryCuts(address deployer, address appRegistry) internal {
155
+ console.log("[INFO]: === Upgrading AppRegistry diamond ===");
156
+ deployAppRegistry.diamondInitParams(deployer);
157
+ FacetCut[] memory proposedCuts = deployAppRegistry.getCuts();
158
+ FacetCut[] memory smartCuts = generateSmartCuts(appRegistry, proposedCuts);
159
+
160
+ console.log(
161
+ "[INFO]: Generated %d smart cuts from %d proposed cuts",
162
+ smartCuts.length,
163
+ proposedCuts.length
164
+ );
165
+
166
+ if (smartCuts.length > 0) {
167
+ vm.broadcast(deployer);
168
+ IDiamondCut(appRegistry).diamondCut(smartCuts, address(0), "");
169
+ console.log("[INFO]: \u2705 AppRegistry diamond upgrade completed");
170
+ } else {
171
+ console.log("[INFO]: AppRegistry diamond already up to date - no cuts needed");
172
+ }
82
173
  }
83
174
  }
@@ -9,9 +9,12 @@ import {IERC173} from "@towns-protocol/diamond/src/facets/ownable/IERC173.sol";
9
9
  import {IOwnablePending} from "@towns-protocol/diamond/src/facets/ownable/pending/IOwnablePending.sol";
10
10
 
11
11
  // libraries
12
+ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
13
+ import {DynamicArrayLib} from "solady/utils/DynamicArrayLib.sol";
12
14
 
13
15
  // contracts
14
16
  import {DiamondHelper} from "@towns-protocol/diamond/scripts/common/helpers/DiamondHelper.s.sol";
17
+ import {DiamondCutStorage} from "@towns-protocol/diamond/src/facets/cut/DiamondCutStorage.sol";
15
18
  import {Diamond} from "@towns-protocol/diamond/src/Diamond.sol";
16
19
  import {Interaction} from "scripts/common/Interaction.s.sol";
17
20
 
@@ -31,6 +34,18 @@ struct FacetData {
31
34
  }
32
35
 
33
36
  abstract contract AlphaHelper is Interaction, DiamondHelper, IDiamondLoupeBase {
37
+ using DynamicArrayLib for DynamicArrayLib.DynamicArray;
38
+ using EnumerableSet for EnumerableSet.AddressSet;
39
+ using EnumerableSet for EnumerableSet.Bytes32Set;
40
+
41
+ struct DiamondInfo {
42
+ DiamondCutStorage.Layout layout;
43
+ mapping(bytes4 selector => bool isCore) coreSelectors;
44
+ mapping(bytes4 selector => bool isProposed) proposedSelectors;
45
+ }
46
+
47
+ mapping(address diamond => DiamondInfo info) private diamondInfos;
48
+
34
49
  /// @notice Get addresses of core facets that should never be removed
35
50
  /// @param diamond The diamond contract address
36
51
  /// @return coreFacets An array of core facet addresses
@@ -54,12 +69,7 @@ abstract contract AlphaHelper is Interaction, DiamondHelper, IDiamondLoupeBase {
54
69
  address facetAddress,
55
70
  address[] memory coreFacets
56
71
  ) internal pure returns (bool) {
57
- for (uint256 i; i < coreFacets.length; ++i) {
58
- if (facetAddress == coreFacets[i]) {
59
- return true;
60
- }
61
- }
62
- return false;
72
+ return contains(coreFacets, facetAddress);
63
73
  }
64
74
 
65
75
  /// @notice Execute a diamond cut to remove facets
@@ -88,13 +98,7 @@ abstract contract AlphaHelper is Interaction, DiamondHelper, IDiamondLoupeBase {
88
98
  continue;
89
99
  }
90
100
 
91
- addCut(
92
- FacetCut({
93
- facetAddress: facet,
94
- action: FacetCutAction.Remove,
95
- functionSelectors: facets[i].selectors
96
- })
97
- );
101
+ addCut(FacetCut(facet, FacetCutAction.Remove, facets[i].selectors));
98
102
  }
99
103
 
100
104
  executeDiamondCut(deployer, diamond);
@@ -128,15 +132,137 @@ abstract contract AlphaHelper is Interaction, DiamondHelper, IDiamondLoupeBase {
128
132
  }
129
133
 
130
134
  if (contains(facetAddresses, facet)) {
131
- addCut(
132
- FacetCut({
133
- facetAddress: facet,
134
- action: FacetCutAction.Remove,
135
- functionSelectors: facets[i].selectors
136
- })
137
- );
135
+ addCut(FacetCut(facet, FacetCutAction.Remove, facets[i].selectors));
136
+ }
137
+ }
138
+ }
139
+
140
+ /// @notice Generate smart cuts by comparing proposed cuts with existing diamond state
141
+ function generateSmartCuts(
142
+ address diamond,
143
+ FacetCut[] memory proposedCuts
144
+ ) internal returns (FacetCut[] memory) {
145
+ buildDiamondInfo(diamond, proposedCuts);
146
+
147
+ // Collect cuts to execute
148
+ clearCuts();
149
+
150
+ for (uint256 i; i < proposedCuts.length; ++i) {
151
+ processFacetCut(diamond, proposedCuts[i]);
152
+ }
153
+
154
+ addRemovalCuts(diamond);
155
+
156
+ return baseFacets();
157
+ }
158
+
159
+ /// @notice Build diamond facet mapping from current state
160
+ function buildDiamondInfo(address diamond, FacetCut[] memory proposedCuts) internal {
161
+ DiamondInfo storage info = diamondInfos[diamond];
162
+
163
+ Facet[] memory facets = IDiamondLoupe(diamond).facets();
164
+ address[] memory coreFacetAddresses = getCoreFacetAddresses(diamond);
165
+
166
+ for (uint256 i; i < facets.length; ++i) {
167
+ address facetAddr = facets[i].facet;
168
+ bytes4[] memory selectors = facets[i].selectors;
169
+ bool isCore = contains(coreFacetAddresses, facetAddr);
170
+
171
+ info.layout.facets.add(facetAddr);
172
+
173
+ for (uint256 j; j < selectors.length; ++j) {
174
+ bytes4 selector = selectors[j];
175
+ info.layout.facetBySelector[selector] = facetAddr;
176
+ info.layout.selectorsByFacet[facetAddr].add(selector);
177
+
178
+ if (isCore) info.coreSelectors[selector] = true;
138
179
  }
139
180
  }
181
+
182
+ // Mark all selectors in proposed cuts as handled
183
+ for (uint256 i; i < proposedCuts.length; ++i) {
184
+ bytes4[] memory selectors = proposedCuts[i].functionSelectors;
185
+ for (uint256 j; j < selectors.length; ++j) {
186
+ info.proposedSelectors[selectors[j]] = true;
187
+ }
188
+ }
189
+ }
190
+
191
+ /// @notice Process a single facet cut by analyzing selector conflicts with existing diamond state
192
+ /// @dev Determines minimal diamond cuts needed by categorizing selectors as Add or Replace operations
193
+ /// @param diamond The diamond contract address to analyze against
194
+ /// @param cut The proposed facet cut to process
195
+ function processFacetCut(address diamond, FacetCut memory cut) internal {
196
+ DiamondInfo storage info = diamondInfos[diamond];
197
+ address newFacet = cut.facetAddress;
198
+ bytes4[] memory newSelectors = cut.functionSelectors;
199
+
200
+ // Skip if exact facet already exists (CREATE2 deterministic deployment)
201
+ // This prevents redundant cuts for already-deployed facets
202
+ if (info.layout.facets.contains(newFacet)) return;
203
+
204
+ // Create dynamic arrays for this facet's selectors
205
+ DynamicArrayLib.DynamicArray memory addSelectors = DynamicArrayLib.p();
206
+ DynamicArrayLib.DynamicArray memory replaceSelectors = DynamicArrayLib.p();
207
+
208
+ // Categorize each selector based on current diamond state
209
+ for (uint256 i; i < newSelectors.length; ++i) {
210
+ bytes4 selector = newSelectors[i];
211
+ address existingFacet = info.layout.facetBySelector[selector];
212
+
213
+ if (existingFacet == address(0)) {
214
+ // Selector doesn't exist - needs Add operation
215
+ addSelectors.p(selector);
216
+ } else if (existingFacet != newFacet) {
217
+ // Selector exists on different facet - needs Replace operation
218
+ replaceSelectors.p(selector);
219
+ }
220
+ // If existingFacet == newFacet, selector already points to correct facet - no action needed
221
+ }
222
+
223
+ // Generate Add cut for new selectors
224
+ if (addSelectors.length() > 0) {
225
+ addCut(FacetCut(newFacet, FacetCutAction.Add, asBytes4Array(addSelectors)));
226
+ }
227
+
228
+ // Generate Replace cut for conflicting selectors
229
+ if (replaceSelectors.length() > 0) {
230
+ addCut(FacetCut(newFacet, FacetCutAction.Replace, asBytes4Array(replaceSelectors)));
231
+ }
232
+ }
233
+
234
+ /// @notice Add removal cuts for orphaned selectors
235
+ function addRemovalCuts(address diamond) internal {
236
+ DiamondInfo storage info = diamondInfos[diamond];
237
+
238
+ address[] memory existingFacets = info.layout.facets.values();
239
+ for (uint256 i; i < existingFacets.length; ++i) {
240
+ address facetAddr = existingFacets[i];
241
+ bytes32[] memory existingSelectors = info.layout.selectorsByFacet[facetAddr].values();
242
+
243
+ // Create dynamic array for remove selectors
244
+ DynamicArrayLib.DynamicArray memory removeSelectors = DynamicArrayLib.p();
245
+
246
+ for (uint256 j; j < existingSelectors.length; ++j) {
247
+ bytes4 selector = bytes4(existingSelectors[j]);
248
+ if (!(info.proposedSelectors[selector] || info.coreSelectors[selector])) {
249
+ removeSelectors.p(selector);
250
+ }
251
+ }
252
+
253
+ if (removeSelectors.length() > 0) {
254
+ addCut(FacetCut(address(0), FacetCutAction.Remove, asBytes4Array(removeSelectors)));
255
+ }
256
+ }
257
+ }
258
+
259
+ function asBytes4Array(
260
+ DynamicArrayLib.DynamicArray memory input
261
+ ) internal pure returns (bytes4[] memory selectors) {
262
+ bytes32[] memory selectors_ = input.asBytes32Array();
263
+ assembly ("memory-safe") {
264
+ selectors := selectors_
265
+ }
140
266
  }
141
267
 
142
268
  /// @notice Check if an address is in an array of addresses
@@ -7,9 +7,9 @@ import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
7
7
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8
8
 
9
9
  // libraries
10
+ import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
10
11
  import {FeatureManagerStorage} from "./FeatureManagerStorage.sol";
11
12
  import {FeatureCondition} from "./IFeatureManagerFacet.sol";
12
- import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
13
13
  import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
14
14
  import {LibCall} from "solady/utils/LibCall.sol";
15
15
 
@@ -19,6 +19,11 @@ abstract contract FeatureManagerBase is IFeatureManagerFacetBase {
19
19
  using EnumerableSetLib for EnumerableSetLib.Bytes32Set;
20
20
  using CustomRevert for bytes4;
21
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
22
27
  function _upsertFeatureCondition(
23
28
  bytes32 featureId,
24
29
  FeatureCondition calldata condition,
@@ -27,97 +32,76 @@ abstract contract FeatureManagerBase is IFeatureManagerFacetBase {
27
32
  _validateToken(condition);
28
33
 
29
34
  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();
35
+ if (!create) {
36
+ if (!$.featureIds.contains(featureId)) FeatureNotActive.selector.revertWith();
37
+ } else {
38
+ if (!$.featureIds.add(featureId)) FeatureAlreadyExists.selector.revertWith();
34
39
  }
35
40
 
36
41
  $.conditions[featureId] = condition;
37
42
  }
38
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
+
39
53
  /// @notice Retrieves the condition for a specific feature
40
54
  /// @dev Returns the complete condition struct with all parameters
41
55
  /// @param featureId The unique identifier for the feature
42
- /// @return result The complete condition configuration for the feature
56
+ /// @return The complete condition configuration for the feature
43
57
  function _getFeatureCondition(
44
58
  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];
59
+ ) internal view returns (FeatureCondition storage) {
60
+ return FeatureManagerStorage.getLayout().conditions[featureId];
51
61
  }
52
62
 
53
63
  /// @notice Retrieves all feature conditions
54
64
  /// @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) {
65
+ /// @return conditions An array of all feature conditions
66
+ function _getFeatureConditions() internal view returns (FeatureCondition[] memory conditions) {
57
67
  FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
58
- uint256 featureCount = $.featureIds.length();
68
+ // Use values() over at() for full iteration - avoids bounds checking overhead
69
+ bytes32[] memory ids = $.featureIds.values();
70
+ uint256 featureCount = ids.length;
59
71
 
60
- FeatureCondition[] memory conditions = new FeatureCondition[](featureCount);
72
+ conditions = new FeatureCondition[](featureCount);
61
73
  for (uint256 i; i < featureCount; ++i) {
62
- conditions[i] = $.conditions[$.featureIds.at(i)];
74
+ conditions[i] = $.conditions[ids[i]];
63
75
  }
64
- return conditions;
65
76
  }
66
77
 
67
78
  /// @notice Retrieves all feature conditions for a specific space
68
79
  /// @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
80
+ /// @return conditions An array of all feature conditions that are active for the space
70
81
  function _getFeatureConditionsForSpace(
71
82
  address space
72
- ) internal view returns (FeatureCondition[] memory) {
83
+ ) internal view returns (FeatureCondition[] memory conditions) {
73
84
  FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
74
- uint256 featureCount = $.featureIds.length();
85
+ // Use values() over at() for full iteration - avoids bounds checking overhead
86
+ bytes32[] memory ids = $.featureIds.values();
87
+ uint256 featureCount = ids.length;
75
88
 
76
- FeatureCondition[] memory conditions = new FeatureCondition[](featureCount);
89
+ // Gas optimization: Allocate full array then resize (memory cheaper than storage reads)
90
+ conditions = new FeatureCondition[](featureCount);
77
91
  uint256 index;
78
92
 
79
93
  for (uint256 i; i < featureCount; ++i) {
80
- bytes32 id = $.featureIds.at(i);
81
- FeatureCondition storage cond = $.conditions[id];
94
+ FeatureCondition storage cond = $.conditions[ids[i]];
82
95
 
83
96
  if (_isValidCondition(cond, space)) {
84
97
  conditions[index++] = cond;
85
98
  }
86
99
  }
87
100
 
101
+ // Resize array to actual number of valid conditions
88
102
  assembly ("memory-safe") {
89
103
  mstore(conditions, index)
90
104
  }
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
105
  }
122
106
 
123
107
  function _validateToken(FeatureCondition calldata condition) internal view {
@@ -156,12 +140,13 @@ abstract contract FeatureManagerBase is IFeatureManagerFacetBase {
156
140
  /// @param space The space address to check against
157
141
  /// @return True if the condition should be included, false otherwise
158
142
  function _isValidCondition(
159
- FeatureCondition memory condition,
143
+ FeatureCondition storage condition,
160
144
  address space
161
145
  ) internal view returns (bool) {
162
146
  if (!condition.active) return false;
163
- if (condition.token == address(0)) return false;
164
- uint256 votes = IVotes(condition.token).getVotes(space);
147
+ address token = condition.token;
148
+ if (token == address(0)) return false;
149
+ uint256 votes = IVotes(token).getVotes(space);
165
150
  return votes >= condition.threshold;
166
151
  }
167
152
  }
@@ -39,11 +39,22 @@ contract FeatureManagerFacet is IFeatureManagerFacet, OwnableBase, Facet, Featur
39
39
  emit FeatureConditionSet(featureId, condition);
40
40
  }
41
41
 
42
+ /// @inheritdoc IFeatureManagerFacet
43
+ function disableFeatureCondition(bytes32 featureId) external onlyOwner {
44
+ _disableFeatureCondition(featureId);
45
+ emit FeatureConditionDisabled(featureId);
46
+ }
47
+
42
48
  /// @inheritdoc IFeatureManagerFacet
43
49
  function getFeatureCondition(
44
50
  bytes32 featureId
45
- ) external view returns (FeatureCondition memory) {
46
- return _getFeatureCondition(featureId);
51
+ ) external view returns (FeatureCondition memory result) {
52
+ // Gas optimization: Reclaim implicit memory allocation for return variable
53
+ // since we're loading from storage, not using the pre-allocated memory
54
+ assembly ("memory-safe") {
55
+ mstore(0x40, result)
56
+ }
57
+ result = _getFeatureCondition(featureId);
47
58
  }
48
59
 
49
60
  /// @inheritdoc IFeatureManagerFacet
@@ -58,12 +69,6 @@ contract FeatureManagerFacet is IFeatureManagerFacet, OwnableBase, Facet, Featur
58
69
  return _getFeatureConditionsForSpace(space);
59
70
  }
60
71
 
61
- /// @inheritdoc IFeatureManagerFacet
62
- function disableFeatureCondition(bytes32 featureId) external onlyOwner {
63
- _disableFeatureCondition(featureId);
64
- emit FeatureConditionDisabled(featureId);
65
- }
66
-
67
72
  /// @inheritdoc IFeatureManagerFacet
68
73
  function checkFeatureCondition(bytes32 featureId, address space) external view returns (bool) {
69
74
  return _isValidCondition(_getFeatureCondition(featureId), space);
@@ -4,9 +4,9 @@ pragma solidity ^0.8.23;
4
4
  // interfaces
5
5
 
6
6
  // libraries
7
+ import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
7
8
 
8
9
  // contracts
9
- import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
10
10
 
11
11
  /// @notice Represents a condition for feature activation
12
12
  /// @dev Used to determine if a feature should be enabled based on token voting power
@@ -9,6 +9,15 @@ import {FeatureCondition} from "./FeatureManagerStorage.sol";
9
9
  /// @notice Base interface for the FeatureManager facet defining errors and events
10
10
  /// @dev Used by the FeatureManager facet and any other facets that need to access feature conditions
11
11
  interface IFeatureManagerFacetBase {
12
+ /// @notice Emitted when a feature condition is set or updated
13
+ /// @param featureId The unique identifier for the feature whose condition was set
14
+ /// @param condition The condition parameters that were set for the feature
15
+ event FeatureConditionSet(bytes32 indexed featureId, FeatureCondition condition);
16
+
17
+ /// @notice Emitted when a feature condition is disabled
18
+ /// @param featureId The unique identifier for the feature whose condition was disabled
19
+ event FeatureConditionDisabled(bytes32 indexed featureId);
20
+
12
21
  /// @notice Error thrown when a threshold exceeds the token's total supply
13
22
  error InvalidThreshold();
14
23
 
@@ -26,15 +35,6 @@ interface IFeatureManagerFacetBase {
26
35
 
27
36
  /// @notice Error thrown when a feature condition already exists
28
37
  error FeatureAlreadyExists();
29
-
30
- /// @notice Emitted when a feature condition is set or updated
31
- /// @param featureId The unique identifier for the feature whose condition was set
32
- /// @param condition The condition parameters that were set for the feature
33
- event FeatureConditionSet(bytes32 indexed featureId, FeatureCondition condition);
34
-
35
- /// @notice Emitted when a feature condition is disabled
36
- /// @param featureId The unique identifier for the feature whose condition was disabled
37
- event FeatureConditionDisabled(bytes32 indexed featureId);
38
38
  }
39
39
 
40
40
  interface IFeatureManagerFacet is IFeatureManagerFacetBase {
@@ -50,6 +50,11 @@ interface IFeatureManagerFacet is IFeatureManagerFacetBase {
50
50
  /// @dev Only callable by the contract owner
51
51
  function updateFeatureCondition(bytes32 featureId, FeatureCondition memory condition) external;
52
52
 
53
+ /// @notice Disables a feature condition
54
+ /// @param featureId The unique identifier for the feature
55
+ /// @dev Only callable by the contract owner
56
+ function disableFeatureCondition(bytes32 featureId) external;
57
+
53
58
  /// @notice Gets the condition for a feature
54
59
  /// @param featureId The unique identifier for the feature
55
60
  /// @return The condition struct for the specified feature
@@ -66,11 +71,6 @@ interface IFeatureManagerFacet is IFeatureManagerFacetBase {
66
71
  address space
67
72
  ) external view returns (FeatureCondition[] memory);
68
73
 
69
- /// @notice Disables a feature condition
70
- /// @param featureId The unique identifier for the feature
71
- /// @dev Only callable by the contract owner
72
- function disableFeatureCondition(bytes32 featureId) external;
73
-
74
74
  /// @notice Checks if a space meets the condition for a feature
75
75
  /// @param featureId The unique identifier for the feature
76
76
  /// @param space The address of the space to check