@towns-protocol/contracts 0.0.330 → 0.0.334
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 +1 -1
- package/package.json +4 -4
- package/scripts/deployments/diamonds/DeployAppRegistry.s.sol +3 -6
- package/scripts/deployments/diamonds/DeployBaseRegistry.s.sol +3 -0
- package/scripts/deployments/diamonds/DeploySpace.s.sol +8 -4
- package/scripts/deployments/diamonds/DeploySpaceFactory.s.sol +6 -2
- package/scripts/interactions/InteractAlphaSparse.s.sol +4 -10
- package/scripts/interactions/InteractBaseAlpha.s.sol +115 -24
- package/scripts/interactions/helpers/AlphaHelper.sol +146 -20
- package/src/factory/facets/feature/FeatureManagerBase.sol +42 -57
- package/src/factory/facets/feature/FeatureManagerFacet.sol +13 -8
- package/src/factory/facets/feature/FeatureManagerStorage.sol +1 -1
- package/src/factory/facets/feature/IFeatureManagerFacet.sol +14 -14
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="
|
|
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.
|
|
3
|
+
"version": "0.0.334",
|
|
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.
|
|
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.
|
|
36
|
+
"@towns-protocol/prettier-config": "^0.0.334",
|
|
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": "
|
|
59
|
+
"gitHead": "8e35a9aabd11b73c165ed5200e0be0f76fdc84bc"
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
} else
|
|
33
|
-
|
|
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
|
|
56
|
+
/// @return The complete condition configuration for the feature
|
|
43
57
|
function _getFeatureCondition(
|
|
44
58
|
bytes32 featureId
|
|
45
|
-
) internal view returns (FeatureCondition
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
72
|
+
conditions = new FeatureCondition[](featureCount);
|
|
61
73
|
for (uint256 i; i < featureCount; ++i) {
|
|
62
|
-
conditions[i] = $.conditions[
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
143
|
+
FeatureCondition storage condition,
|
|
160
144
|
address space
|
|
161
145
|
) internal view returns (bool) {
|
|
162
146
|
if (!condition.active) return false;
|
|
163
|
-
|
|
164
|
-
|
|
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
|
|
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
|