@towns-protocol/contracts 0.0.337 → 0.0.338
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 +3 -3
- package/scripts/deployments/diamonds/IDiamondInitHelper.sol +3 -1
- package/scripts/deployments/facets/DeployAppAccount.s.sol +2 -1
- package/scripts/deployments/facets/DeploySimpleApp.s.sol +2 -2
- package/scripts/deployments/facets/DeploySubscriptionModuleFacet.s.sol +2 -1
- package/scripts/interactions/InteractBaseAlpha.s.sol +16 -135
- package/scripts/interactions/InteractRiverAlpha.s.sol +6 -10
- package/scripts/interactions/helpers/AlphaHelper.sol +65 -0
- package/src/apps/modules/subscription/ISubscriptionModule.sol +8 -0
- package/src/apps/modules/subscription/SubscriptionModuleFacet.sol +22 -5
- package/src/apps/modules/subscription/SubscriptionModuleStorage.sol +6 -6
- package/src/spaces/facets/account/AppAccount.sol +6 -0
- package/src/spaces/facets/account/AppAccountBase.sol +15 -0
- package/src/spaces/facets/account/IAppAccount.sol +16 -0
- package/src/spaces/facets/executor/ExecutorBase.sol +6 -4
- package/src/spaces/facets/executor/ExecutorStorage.sol +9 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@towns-protocol/contracts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.338",
|
|
4
4
|
"packageManager": "yarn@3.8.0",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build-types": "bash scripts/build-contract-types.sh",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@layerzerolabs/oapp-evm": "^0.3.2",
|
|
35
35
|
"@openzeppelin/merkle-tree": "^1.0.8",
|
|
36
36
|
"@prb/test": "^0.6.4",
|
|
37
|
-
"@towns-protocol/prettier-config": "^0.0.
|
|
37
|
+
"@towns-protocol/prettier-config": "^0.0.338",
|
|
38
38
|
"@typechain/ethers-v5": "^10.1.1",
|
|
39
39
|
"@wagmi/cli": "^2.2.0",
|
|
40
40
|
"account-abstraction": "https://github.com/eth-infinitism/account-abstraction/archive/refs/tags/v0.7.0.tar.gz",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"access": "public"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "c6e34eeab8c1753ecae0ed36e1953f2a828f08d8"
|
|
61
61
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
-
import {IDiamond} from "@towns-protocol/diamond/src/Diamond.sol";
|
|
4
|
+
import {IDiamond, Diamond} from "@towns-protocol/diamond/src/Diamond.sol";
|
|
5
5
|
|
|
6
6
|
interface IDiamondInitHelper is IDiamond {
|
|
7
7
|
function diamondInitHelper(
|
|
8
8
|
address deployer,
|
|
9
9
|
string[] memory facetNames
|
|
10
10
|
) external returns (FacetCut[] memory);
|
|
11
|
+
|
|
12
|
+
function diamondInitParams(address deployer) external returns (Diamond.InitParams memory);
|
|
11
13
|
}
|
|
@@ -15,11 +15,12 @@ library DeployAppAccount {
|
|
|
15
15
|
using DynamicArrayLib for DynamicArrayLib.DynamicArray;
|
|
16
16
|
|
|
17
17
|
function selectors() internal pure returns (bytes4[] memory res) {
|
|
18
|
-
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(
|
|
18
|
+
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(12);
|
|
19
19
|
arr.p(AppAccount.execute.selector);
|
|
20
20
|
arr.p(AppAccount.onInstallApp.selector);
|
|
21
21
|
arr.p(AppAccount.onUninstallApp.selector);
|
|
22
22
|
arr.p(AppAccount.onRenewApp.selector);
|
|
23
|
+
arr.p(AppAccount.isAppExecuting.selector);
|
|
23
24
|
arr.p(AppAccount.isAppEntitled.selector);
|
|
24
25
|
arr.p(AppAccount.disableApp.selector);
|
|
25
26
|
arr.p(AppAccount.getInstalledApps.selector);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.29;
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {LibDeploy} from "@towns-protocol/diamond/src/utils/LibDeploy.sol";
|
|
5
5
|
|
|
6
6
|
library DeploySimpleApp {
|
|
7
7
|
function deploy() internal returns (address) {
|
|
8
|
-
return
|
|
8
|
+
return LibDeploy.deployCode("SimpleApp.sol", "");
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -15,7 +15,7 @@ library DeploySubscriptionModuleFacet {
|
|
|
15
15
|
using DynamicArrayLib for DynamicArrayLib.DynamicArray;
|
|
16
16
|
|
|
17
17
|
function selectors() internal pure returns (bytes4[] memory res) {
|
|
18
|
-
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(
|
|
18
|
+
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(20);
|
|
19
19
|
arr.p(SubscriptionModuleFacet.moduleId.selector);
|
|
20
20
|
arr.p(SubscriptionModuleFacet.onInstall.selector);
|
|
21
21
|
arr.p(SubscriptionModuleFacet.onUninstall.selector);
|
|
@@ -30,6 +30,7 @@ library DeploySubscriptionModuleFacet {
|
|
|
30
30
|
arr.p(SubscriptionModuleFacet.getSubscription.selector);
|
|
31
31
|
arr.p(SubscriptionModuleFacet.pauseSubscription.selector);
|
|
32
32
|
arr.p(SubscriptionModuleFacet.getEntityIds.selector);
|
|
33
|
+
arr.p(SubscriptionModuleFacet.isOperator.selector);
|
|
33
34
|
arr.p(SubscriptionModuleFacet.grantOperator.selector);
|
|
34
35
|
arr.p(SubscriptionModuleFacet.revokeOperator.selector);
|
|
35
36
|
arr.p(bytes4(keccak256("MAX_BATCH_SIZE()")));
|
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
-
import {IDiamondCut} from "@towns-protocol/diamond/src/facets/cut/IDiamondCut.sol";
|
|
6
5
|
|
|
7
6
|
// libraries
|
|
8
|
-
import {console} from "forge-std/console.sol";
|
|
9
7
|
|
|
10
8
|
// contracts
|
|
11
9
|
import {DeployBaseRegistry} from "scripts/deployments/diamonds/DeployBaseRegistry.s.sol";
|
|
@@ -34,141 +32,24 @@ contract InteractBaseAlpha is AlphaHelper {
|
|
|
34
32
|
address riverAirdrop = getDeployment("riverAirdrop");
|
|
35
33
|
address appRegistry = getDeployment("appRegistry");
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
deploySpace.diamondInitParams(deployer);
|
|
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
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function deploySpaceOwnerCuts(address deployer, address spaceOwner) internal {
|
|
69
|
-
console.log("[INFO]: === Upgrading SpaceOwner diamond ===");
|
|
70
|
-
deploySpaceOwner.diamondInitParams(deployer);
|
|
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
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function deploySpaceFactoryCuts(address deployer, address spaceFactory) internal {
|
|
90
|
-
console.log("[INFO]: === Upgrading SpaceFactory diamond ===");
|
|
91
|
-
deploySpaceFactory.diamondInitParams(deployer);
|
|
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
|
|
35
|
+
executeDiamondCutsWithLogging(deployer, space, "Space", deploySpace);
|
|
36
|
+
executeDiamondCutsWithLogging(deployer, spaceOwner, "SpaceOwner", deploySpaceOwner);
|
|
37
|
+
|
|
38
|
+
address spaceFactoryInit = deploySpaceFactory.spaceFactoryInit();
|
|
39
|
+
bytes memory initData = deploySpaceFactory.spaceFactoryInitData();
|
|
40
|
+
executeDiamondCutsWithLogging(
|
|
41
|
+
deployer,
|
|
42
|
+
spaceFactory,
|
|
43
|
+
"SpaceFactory",
|
|
44
|
+
deploySpaceFactory,
|
|
45
|
+
spaceFactoryInit,
|
|
46
|
+
initData
|
|
99
47
|
);
|
|
100
48
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function deployBaseRegistryCuts(address deployer, address baseRegistry) internal {
|
|
113
|
-
console.log("[INFO]: === Upgrading BaseRegistry diamond ===");
|
|
114
|
-
deployBaseRegistry.diamondInitParams(deployer);
|
|
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
|
-
);
|
|
49
|
+
executeDiamondCutsWithLogging(deployer, baseRegistry, "BaseRegistry", deployBaseRegistry);
|
|
50
|
+
executeDiamondCutsWithLogging(deployer, riverAirdrop, "RiverAirdrop", deployRiverAirdrop);
|
|
51
|
+
executeDiamondCutsWithLogging(deployer, appRegistry, "AppRegistry", deployAppRegistry);
|
|
123
52
|
|
|
124
|
-
|
|
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
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function deployRiverAirdropCuts(address deployer, address riverAirdrop) internal {
|
|
134
|
-
console.log("[INFO]: === Upgrading RiverAirdrop diamond ===");
|
|
135
|
-
deployRiverAirdrop.diamondInitParams(deployer);
|
|
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
|
-
}
|
|
53
|
+
vm.resumeGasMetering();
|
|
173
54
|
}
|
|
174
55
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
-
import {IDiamondCut} from "@towns-protocol/diamond/src/facets/cut/IDiamondCut.sol";
|
|
6
5
|
|
|
7
6
|
// libraries
|
|
8
7
|
|
|
@@ -15,16 +14,13 @@ contract InteractRiverAlpha is AlphaHelper {
|
|
|
15
14
|
DeployRiverRegistry deployRiverRegistry = new DeployRiverRegistry();
|
|
16
15
|
|
|
17
16
|
function __interact(address deployer) internal override {
|
|
18
|
-
vm.setEnv("OVERRIDE_DEPLOYMENTS", "1");
|
|
19
17
|
address riverRegistry = getDeployment("riverRegistry");
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
vm.broadcast(deployer);
|
|
28
|
-
IDiamondCut(riverRegistry).diamondCut(newCuts, address(0), "");
|
|
19
|
+
executeDiamondCutsWithLogging(
|
|
20
|
+
deployer,
|
|
21
|
+
riverRegistry,
|
|
22
|
+
"RiverRegistry",
|
|
23
|
+
deployRiverRegistry
|
|
24
|
+
);
|
|
29
25
|
}
|
|
30
26
|
}
|
|
@@ -7,10 +7,12 @@ import {IDiamondCut} from "@towns-protocol/diamond/src/facets/cut/IDiamondCut.so
|
|
|
7
7
|
import {IDiamondLoupe, IDiamondLoupeBase} from "@towns-protocol/diamond/src/facets/loupe/IDiamondLoupe.sol";
|
|
8
8
|
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
|
+
import {IDiamondInitHelper} from "scripts/deployments/diamonds/IDiamondInitHelper.sol";
|
|
10
11
|
|
|
11
12
|
// libraries
|
|
12
13
|
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
13
14
|
import {DynamicArrayLib} from "solady/utils/DynamicArrayLib.sol";
|
|
15
|
+
import {LibString} from "solady/utils/LibString.sol";
|
|
14
16
|
|
|
15
17
|
// contracts
|
|
16
18
|
import {DiamondHelper} from "@towns-protocol/diamond/scripts/common/helpers/DiamondHelper.s.sol";
|
|
@@ -37,6 +39,7 @@ abstract contract AlphaHelper is Interaction, DiamondHelper, IDiamondLoupeBase {
|
|
|
37
39
|
using DynamicArrayLib for DynamicArrayLib.DynamicArray;
|
|
38
40
|
using EnumerableSet for EnumerableSet.AddressSet;
|
|
39
41
|
using EnumerableSet for EnumerableSet.Bytes32Set;
|
|
42
|
+
using LibString for *;
|
|
40
43
|
|
|
41
44
|
struct DiamondInfo {
|
|
42
45
|
DiamondCutStorage.Layout layout;
|
|
@@ -256,6 +259,68 @@ abstract contract AlphaHelper is Interaction, DiamondHelper, IDiamondLoupeBase {
|
|
|
256
259
|
}
|
|
257
260
|
}
|
|
258
261
|
|
|
262
|
+
/// @notice Execute diamond cuts with smart cut optimization and logging
|
|
263
|
+
/// @param deployer The address that will execute the diamond cut
|
|
264
|
+
/// @param diamond The diamond contract address
|
|
265
|
+
/// @param diamondName Name of the diamond for logging purposes
|
|
266
|
+
/// @param deployContract The deployment contract that provides cuts and initialization
|
|
267
|
+
/// @param initAddress Optional initialization contract address
|
|
268
|
+
/// @param initData Optional initialization data
|
|
269
|
+
function executeDiamondCutsWithLogging(
|
|
270
|
+
address deployer,
|
|
271
|
+
address diamond,
|
|
272
|
+
string memory diamondName,
|
|
273
|
+
IDiamondInitHelper deployContract,
|
|
274
|
+
address initAddress,
|
|
275
|
+
bytes memory initData
|
|
276
|
+
) internal {
|
|
277
|
+
info(string.concat("=== Upgrading ", diamondName, " diamond ==="), "");
|
|
278
|
+
|
|
279
|
+
deployContract.diamondInitParams(deployer);
|
|
280
|
+
FacetCut[] memory proposedCuts = DiamondHelper(address(deployContract)).getCuts();
|
|
281
|
+
FacetCut[] memory smartCuts = generateSmartCuts(diamond, proposedCuts);
|
|
282
|
+
|
|
283
|
+
info(
|
|
284
|
+
string.concat(
|
|
285
|
+
"Generated ",
|
|
286
|
+
smartCuts.length.toString(),
|
|
287
|
+
" smart cuts from ",
|
|
288
|
+
proposedCuts.length.toString(),
|
|
289
|
+
" proposed cuts"
|
|
290
|
+
),
|
|
291
|
+
""
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
if (smartCuts.length > 0) {
|
|
295
|
+
vm.broadcast(deployer);
|
|
296
|
+
IDiamondCut(diamond).diamondCut(smartCuts, initAddress, initData);
|
|
297
|
+
info(string.concat(unicode"✅ ", diamondName, " diamond upgrade completed"), "");
|
|
298
|
+
} else {
|
|
299
|
+
info(string.concat(diamondName, " diamond already up to date - no cuts needed"), "");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/// @notice Execute diamond cuts with smart cut optimization and logging (no initialization)
|
|
304
|
+
/// @param deployer The address that will execute the diamond cut
|
|
305
|
+
/// @param diamond The diamond contract address
|
|
306
|
+
/// @param diamondName Name of the diamond for logging purposes
|
|
307
|
+
/// @param deployContract The deployment contract that provides cuts and initialization
|
|
308
|
+
function executeDiamondCutsWithLogging(
|
|
309
|
+
address deployer,
|
|
310
|
+
address diamond,
|
|
311
|
+
string memory diamondName,
|
|
312
|
+
IDiamondInitHelper deployContract
|
|
313
|
+
) internal {
|
|
314
|
+
executeDiamondCutsWithLogging(
|
|
315
|
+
deployer,
|
|
316
|
+
diamond,
|
|
317
|
+
diamondName,
|
|
318
|
+
deployContract,
|
|
319
|
+
address(0),
|
|
320
|
+
""
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
259
324
|
function asBytes4Array(
|
|
260
325
|
DynamicArrayLib.DynamicArray memory input
|
|
261
326
|
) internal pure returns (bytes4[] memory selectors) {
|
|
@@ -38,6 +38,7 @@ interface ISubscriptionModuleBase {
|
|
|
38
38
|
error SubscriptionModule__ExceedsMaxBatchSize();
|
|
39
39
|
error SubscriptionModule__EmptyBatch();
|
|
40
40
|
error SubscriptionModule__InvalidTokenOwner();
|
|
41
|
+
error SubscriptionModule__InsufficientBalance();
|
|
41
42
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
42
43
|
/* Events */
|
|
43
44
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
@@ -68,6 +69,9 @@ interface ISubscriptionModuleBase {
|
|
|
68
69
|
event SubscriptionPaused(address indexed account, uint32 indexed entityId);
|
|
69
70
|
|
|
70
71
|
event BatchRenewalSkipped(address indexed account, uint32 indexed entityId, string reason);
|
|
72
|
+
|
|
73
|
+
event OperatorGranted(address indexed operator);
|
|
74
|
+
event OperatorRevoked(address indexed operator);
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
interface ISubscriptionModule is ISubscriptionModuleBase {
|
|
@@ -101,6 +105,10 @@ interface ISubscriptionModule is ISubscriptionModuleBase {
|
|
|
101
105
|
/// @return The entity IDs for the account
|
|
102
106
|
function getEntityIds(address account) external view returns (uint256[] memory);
|
|
103
107
|
|
|
108
|
+
/// @notice Checks if an operator has access to call processRenewal
|
|
109
|
+
/// @param operator The address of the operator to check
|
|
110
|
+
function isOperator(address operator) external view returns (bool);
|
|
111
|
+
|
|
104
112
|
/// @notice Grants an operator access to call processRenewal
|
|
105
113
|
/// @param operator The address of the operator to grant
|
|
106
114
|
function grantOperator(address operator) external;
|
|
@@ -242,21 +242,31 @@ contract SubscriptionModuleFacet is
|
|
|
242
242
|
function grantOperator(address operator) external onlyOwner {
|
|
243
243
|
Validator.checkAddress(operator);
|
|
244
244
|
SubscriptionModuleStorage.getLayout().operators.add(operator);
|
|
245
|
+
emit OperatorGranted(operator);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/// @inheritdoc ISubscriptionModule
|
|
249
|
+
function isOperator(address operator) external view returns (bool) {
|
|
250
|
+
return SubscriptionModuleStorage.getLayout().operators.contains(operator);
|
|
245
251
|
}
|
|
246
252
|
|
|
247
253
|
/// @inheritdoc ISubscriptionModule
|
|
248
254
|
function revokeOperator(address operator) external onlyOwner {
|
|
249
255
|
Validator.checkAddress(operator);
|
|
250
256
|
SubscriptionModuleStorage.getLayout().operators.remove(operator);
|
|
257
|
+
emit OperatorRevoked(operator);
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
254
261
|
/* Internal */
|
|
255
262
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
256
263
|
|
|
264
|
+
/// @dev Processes a single subscription renewal
|
|
265
|
+
/// @param sub The subscription to renew
|
|
266
|
+
/// @param params The parameters for the renewal
|
|
257
267
|
function _processRenewal(Subscription storage sub, RenewalParams calldata params) internal {
|
|
258
|
-
// Validate subscription state
|
|
259
268
|
if (!sub.active) SubscriptionModule__InactiveSubscription.selector.revertWith();
|
|
269
|
+
|
|
260
270
|
if (block.timestamp < sub.nextRenewalTime)
|
|
261
271
|
SubscriptionModule__RenewalNotDue.selector.revertWith();
|
|
262
272
|
|
|
@@ -272,10 +282,9 @@ contract SubscriptionModuleFacet is
|
|
|
272
282
|
// Get current renewal price from Towns contract
|
|
273
283
|
uint256 actualRenewalPrice = membershipFacet.getMembershipRenewalPrice(sub.tokenId);
|
|
274
284
|
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
sub.nextRenewalTime = uint40(block.timestamp + 365 days);
|
|
285
|
+
// Check if the account has enough balance
|
|
286
|
+
if (params.account.balance < actualRenewalPrice)
|
|
287
|
+
SubscriptionModule__InsufficientBalance.selector.revertWith();
|
|
279
288
|
|
|
280
289
|
// Construct the renewal call to space contract
|
|
281
290
|
bytes memory renewalCall = abi.encodeCall(MembershipFacet.renewMembership, (sub.tokenId));
|
|
@@ -320,6 +329,10 @@ contract SubscriptionModuleFacet is
|
|
|
320
329
|
emit SubscriptionRenewed(params.account, params.entityId, sub.nextRenewalTime);
|
|
321
330
|
}
|
|
322
331
|
|
|
332
|
+
/// @dev Creates the runtime final data for the renewal
|
|
333
|
+
/// @param entityId The entity ID of the subscription
|
|
334
|
+
/// @param finalData The final data for the renewal
|
|
335
|
+
/// @return The runtime final data
|
|
323
336
|
function _runtimeFinal(
|
|
324
337
|
uint32 entityId,
|
|
325
338
|
bytes memory finalData
|
|
@@ -332,6 +345,10 @@ contract SubscriptionModuleFacet is
|
|
|
332
345
|
);
|
|
333
346
|
}
|
|
334
347
|
|
|
348
|
+
/// @dev Checks if the caller is allowed to call the function
|
|
349
|
+
/// @param operators The set of operators
|
|
350
|
+
/// @param account The account to check
|
|
351
|
+
/// @return True if the caller is allowed to call the function
|
|
335
352
|
function _isAllowed(
|
|
336
353
|
EnumerableSetLib.AddressSet storage operators,
|
|
337
354
|
address account
|
|
@@ -4,12 +4,12 @@ pragma solidity ^0.8.29;
|
|
|
4
4
|
import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
|
|
5
5
|
|
|
6
6
|
struct Subscription {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
uint40
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
uint256 tokenId; // 32 bytes
|
|
8
|
+
uint256 spent; // 32 bytes
|
|
9
|
+
address space; // 20 bytes
|
|
10
|
+
uint40 lastRenewalTime; // 5 bytes
|
|
11
|
+
uint40 nextRenewalTime; // 5 bytes
|
|
12
|
+
bool active; // 1 byte
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
library SubscriptionModuleStorage {
|
|
@@ -22,6 +22,7 @@ contract AppAccount is IAppAccount, AppAccountBase, ReentrancyGuard, Facet {
|
|
|
22
22
|
/* Execution */
|
|
23
23
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
24
24
|
|
|
25
|
+
/// @inheritdoc IAppAccount
|
|
25
26
|
function execute(
|
|
26
27
|
address target,
|
|
27
28
|
uint256 value,
|
|
@@ -90,4 +91,9 @@ contract AppAccount is IAppAccount, AppAccountBase, ReentrancyGuard, Facet {
|
|
|
90
91
|
) external view returns (bool) {
|
|
91
92
|
return _isAppEntitled(app, publicKey, permission);
|
|
92
93
|
}
|
|
94
|
+
|
|
95
|
+
/// @inheritdoc IAppAccount
|
|
96
|
+
function isAppExecuting(address app) external view returns (bool) {
|
|
97
|
+
return _isAppExecuting(app);
|
|
98
|
+
}
|
|
93
99
|
}
|
|
@@ -18,6 +18,7 @@ import {DependencyLib} from "../DependencyLib.sol";
|
|
|
18
18
|
import {LibCall} from "solady/utils/LibCall.sol";
|
|
19
19
|
import {AppAccountStorage} from "./AppAccountStorage.sol";
|
|
20
20
|
import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
|
|
21
|
+
import {ExecutorStorage} from "../executor/ExecutorStorage.sol";
|
|
21
22
|
|
|
22
23
|
// contracts
|
|
23
24
|
import {ExecutorBase} from "../executor/ExecutorBase.sol";
|
|
@@ -207,6 +208,20 @@ abstract contract AppAccountBase is
|
|
|
207
208
|
return AppAccountStorage.getLayout().installedApps.values();
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
function _isAppExecuting(address app) internal view returns (bool) {
|
|
212
|
+
bytes32 currentExecutionId = ExecutorStorage.getExecutionId();
|
|
213
|
+
if (currentExecutionId == bytes32(0)) return false;
|
|
214
|
+
|
|
215
|
+
bytes32 targetId = ExecutorStorage.getTargetExecutionId(app);
|
|
216
|
+
if (targetId == bytes32(0)) return false;
|
|
217
|
+
if (currentExecutionId != targetId) return false;
|
|
218
|
+
|
|
219
|
+
bytes32 appId = _getInstalledAppId(app);
|
|
220
|
+
if (appId == EMPTY_UID) return false;
|
|
221
|
+
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
210
225
|
function _isAppInstalled(address module) internal view returns (bool) {
|
|
211
226
|
return AppAccountStorage.getLayout().installedApps.contains(module);
|
|
212
227
|
}
|
|
@@ -72,4 +72,20 @@ interface IAppAccount is IAppAccountBase {
|
|
|
72
72
|
address publicKey,
|
|
73
73
|
bytes32 permission
|
|
74
74
|
) external view returns (bool);
|
|
75
|
+
|
|
76
|
+
/// @notice Checks if an app is executing
|
|
77
|
+
/// @param app The address of the app to check
|
|
78
|
+
/// @return True if the app is executing, false otherwise
|
|
79
|
+
function isAppExecuting(address app) external view returns (bool);
|
|
80
|
+
|
|
81
|
+
/// @notice Executes a function on the app
|
|
82
|
+
/// @param target The address of the app to execute the function on
|
|
83
|
+
/// @param value The value to send with the function
|
|
84
|
+
/// @param data The data to send with the function
|
|
85
|
+
/// @return The result of the function
|
|
86
|
+
function execute(
|
|
87
|
+
address target,
|
|
88
|
+
uint256 value,
|
|
89
|
+
bytes calldata data
|
|
90
|
+
) external payable returns (bytes memory);
|
|
75
91
|
}
|
|
@@ -395,8 +395,9 @@ abstract contract ExecutorBase is IExecutorBase {
|
|
|
395
395
|
_executePreHooks($, target, selector, value, data);
|
|
396
396
|
|
|
397
397
|
// Set the executionId for the target and selector using transient storage
|
|
398
|
+
bytes32 executionIdBefore = ExecutorStorage.getExecutionId();
|
|
398
399
|
bytes32 executionId = _hashExecutionId(target, selector);
|
|
399
|
-
ExecutorStorage.
|
|
400
|
+
ExecutorStorage.setExecutionId(executionId);
|
|
400
401
|
ExecutorStorage.setTargetExecutionId(target, executionId);
|
|
401
402
|
|
|
402
403
|
// Call the target
|
|
@@ -406,7 +407,8 @@ abstract contract ExecutorBase is IExecutorBase {
|
|
|
406
407
|
_executePostHooks($, target, selector);
|
|
407
408
|
|
|
408
409
|
// Clear transient storage to prevent composability issues
|
|
409
|
-
ExecutorStorage.
|
|
410
|
+
ExecutorStorage.setExecutionId(executionIdBefore);
|
|
411
|
+
ExecutorStorage.clearTargetExecutionId(target);
|
|
410
412
|
}
|
|
411
413
|
|
|
412
414
|
/// @notice Gets the scheduled timepoint for an operation.
|
|
@@ -421,7 +423,7 @@ abstract contract ExecutorBase is IExecutorBase {
|
|
|
421
423
|
/// @param target The target contract.
|
|
422
424
|
/// @return True if currently executing.
|
|
423
425
|
function _isTargetExecuting(address target) internal view returns (bool) {
|
|
424
|
-
bytes32 globalId = ExecutorStorage.
|
|
426
|
+
bytes32 globalId = ExecutorStorage.getExecutionId();
|
|
425
427
|
bytes32 targetId = ExecutorStorage.getTargetExecutionId(target);
|
|
426
428
|
return globalId != 0 && targetId == globalId;
|
|
427
429
|
}
|
|
@@ -524,7 +526,7 @@ abstract contract ExecutorBase is IExecutorBase {
|
|
|
524
526
|
/// @param selector The function selector.
|
|
525
527
|
/// @return True if currently executing.
|
|
526
528
|
function _isExecuting(address target, bytes4 selector) private view returns (bool) {
|
|
527
|
-
return ExecutorStorage.
|
|
529
|
+
return ExecutorStorage.getExecutionId() == _hashExecutionId(target, selector);
|
|
528
530
|
}
|
|
529
531
|
|
|
530
532
|
/// @dev Computes a unique hash for the execution context.
|
|
@@ -66,13 +66,13 @@ library ExecutorStorage {
|
|
|
66
66
|
/* Transient Execution Id */
|
|
67
67
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
68
68
|
|
|
69
|
-
function
|
|
69
|
+
function getExecutionId() internal view returns (bytes32 id) {
|
|
70
70
|
assembly {
|
|
71
71
|
id := tload(TRANSIENT_STORAGE_SLOT)
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
function
|
|
75
|
+
function setExecutionId(bytes32 id) internal {
|
|
76
76
|
assembly {
|
|
77
77
|
tstore(TRANSIENT_STORAGE_SLOT, id)
|
|
78
78
|
}
|
|
@@ -90,10 +90,16 @@ library ExecutorStorage {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function
|
|
93
|
+
function clearExecutionId(address target) internal {
|
|
94
94
|
assembly {
|
|
95
95
|
tstore(TRANSIENT_STORAGE_SLOT, 0)
|
|
96
96
|
tstore(target, 0)
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
function clearTargetExecutionId(address target) internal {
|
|
101
|
+
assembly {
|
|
102
|
+
tstore(target, 0)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
99
105
|
}
|