@towns-protocol/contracts 0.0.337 → 0.0.339

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@towns-protocol/contracts",
3
- "version": "0.0.337",
3
+ "version": "0.0.339",
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.337",
37
+ "@towns-protocol/prettier-config": "^0.0.339",
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": "a8dae6ecef2c3bc7713acf56e6738fd47d608482"
60
+ "gitHead": "0ad93e42f93eba4356839b148fa166a818cd0f9d"
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(11);
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 {SimpleApp} from "src/apps/helpers/SimpleApp.sol";
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 address(new SimpleApp());
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(19);
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
- deploySpaceCuts(deployer, space);
38
- deploySpaceOwnerCuts(deployer, spaceOwner);
39
- deploySpaceFactoryCuts(deployer, spaceFactory);
40
- deployBaseRegistryCuts(deployer, baseRegistry);
41
- deployRiverAirdropCuts(deployer, riverAirdrop);
42
- deployAppRegistryCuts(deployer, appRegistry);
43
-
44
- vm.resumeGasMetering();
45
- }
46
-
47
- function deploySpaceCuts(address deployer, address space) internal {
48
- console.log("[INFO]: === Upgrading Space diamond ===");
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
- 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
- }
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
- 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
- }
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
- removeRemoteFacets(deployer, riverRegistry);
22
- FacetCut[] memory newCuts;
23
-
24
- deployRiverRegistry.diamondInitParams(deployer);
25
- newCuts = deployRiverRegistry.getCuts();
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
- // Update state BEFORE external calls to prevent reentrancy
276
- // Set to a far future time to prevent re-entry while processing
277
- // This ensures the "renewal not due" check will fail if re-entered
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
- address space;
8
- bool active;
9
- uint40 lastRenewalTime;
10
- uint40 nextRenewalTime;
11
- uint256 spent;
12
- uint256 tokenId;
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.setTransientExecutionId(executionId);
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.clearTransientStorage(target);
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.getTransientExecutionId();
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.getTransientExecutionId() == _hashExecutionId(target, selector);
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 getTransientExecutionId() internal view returns (bytes32 id) {
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 setTransientExecutionId(bytes32 id) internal {
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 clearTransientStorage(address target) internal {
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
  }