@towns-protocol/contracts 0.0.336 → 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 +4 -3
- package/scripts/deployments/diamonds/DeploySubscriptionModule.s.sol +118 -0
- 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 +65 -0
- 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/BaseApp.sol +0 -1
- package/src/apps/modules/subscription/ISubscriptionModule.sol +119 -0
- package/src/apps/modules/subscription/SubscriptionModuleFacet.sol +359 -0
- package/src/apps/modules/subscription/SubscriptionModuleStorage.sol +35 -0
- 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",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
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
|
+
"modular-account": "https://github.com/towns-protocol/modular-account/archive/refs/tags/v1.0.0.tar.gz",
|
|
28
29
|
"solady": "^0.1.24"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"@layerzerolabs/oapp-evm": "^0.3.2",
|
|
34
35
|
"@openzeppelin/merkle-tree": "^1.0.8",
|
|
35
36
|
"@prb/test": "^0.6.4",
|
|
36
|
-
"@towns-protocol/prettier-config": "^0.0.
|
|
37
|
+
"@towns-protocol/prettier-config": "^0.0.338",
|
|
37
38
|
"@typechain/ethers-v5": "^10.1.1",
|
|
38
39
|
"@wagmi/cli": "^2.2.0",
|
|
39
40
|
"account-abstraction": "https://github.com/eth-infinitism/account-abstraction/archive/refs/tags/v0.7.0.tar.gz",
|
|
@@ -56,5 +57,5 @@
|
|
|
56
57
|
"publishConfig": {
|
|
57
58
|
"access": "public"
|
|
58
59
|
},
|
|
59
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "c6e34eeab8c1753ecae0ed36e1953f2a828f08d8"
|
|
60
61
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
// libraries
|
|
5
|
+
import {DeployDiamondCut} from "@towns-protocol/diamond/scripts/deployments/facets/DeployDiamondCut.sol";
|
|
6
|
+
import {DeployDiamondLoupe} from "@towns-protocol/diamond/scripts/deployments/facets/DeployDiamondLoupe.sol";
|
|
7
|
+
import {DeployIntrospection} from "@towns-protocol/diamond/scripts/deployments/facets/DeployIntrospection.sol";
|
|
8
|
+
import {DeployOwnable} from "@towns-protocol/diamond/scripts/deployments/facets/DeployOwnable.sol";
|
|
9
|
+
import {DeployMetadata} from "../facets/DeployMetadata.s.sol";
|
|
10
|
+
import {DeploySubscriptionModuleFacet} from "../facets/DeploySubscriptionModuleFacet.s.sol";
|
|
11
|
+
import {LibString} from "solady/utils/LibString.sol";
|
|
12
|
+
|
|
13
|
+
// contracts
|
|
14
|
+
import {Diamond} from "@towns-protocol/diamond/src/Diamond.sol";
|
|
15
|
+
import {MultiInit} from "@towns-protocol/diamond/src/initializers/MultiInit.sol";
|
|
16
|
+
import {DiamondHelper} from "@towns-protocol/diamond/scripts/common/helpers/DiamondHelper.s.sol";
|
|
17
|
+
|
|
18
|
+
// deployers
|
|
19
|
+
import {DeployFacet} from "../../common/DeployFacet.s.sol";
|
|
20
|
+
import {Deployer} from "../../common/Deployer.s.sol";
|
|
21
|
+
|
|
22
|
+
contract DeploySubscriptionModule is DiamondHelper, Deployer {
|
|
23
|
+
using LibString for string;
|
|
24
|
+
|
|
25
|
+
DeployFacet private facetHelper = new DeployFacet();
|
|
26
|
+
|
|
27
|
+
bytes32 internal constant METADATA_NAME = bytes32("SubscriptionModule");
|
|
28
|
+
|
|
29
|
+
function versionName() public pure override returns (string memory) {
|
|
30
|
+
return "subscriptionModule";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function diamondInitParams(address deployer) public returns (Diamond.InitParams memory) {
|
|
34
|
+
// Queue up feature facets for batch deployment
|
|
35
|
+
facetHelper.add("MultiInit");
|
|
36
|
+
facetHelper.add("SubscriptionModuleFacet");
|
|
37
|
+
|
|
38
|
+
// Deploy all facets in a batch
|
|
39
|
+
facetHelper.deployBatch(deployer);
|
|
40
|
+
|
|
41
|
+
// Add feature facets
|
|
42
|
+
address facet = facetHelper.getDeployedAddress("SubscriptionModuleFacet");
|
|
43
|
+
addFacet(
|
|
44
|
+
makeCut(facet, FacetCutAction.Add, DeploySubscriptionModuleFacet.selectors()),
|
|
45
|
+
facet,
|
|
46
|
+
DeploySubscriptionModuleFacet.makeInitData()
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
address multiInit = facetHelper.getDeployedAddress("MultiInit");
|
|
50
|
+
|
|
51
|
+
return
|
|
52
|
+
Diamond.InitParams({
|
|
53
|
+
baseFacets: baseFacets(),
|
|
54
|
+
init: multiInit,
|
|
55
|
+
initData: abi.encodeCall(MultiInit.multiInit, (_initAddresses, _initDatas))
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
60
|
+
/* Internal */
|
|
61
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
62
|
+
|
|
63
|
+
function _coreFacets(address deployer) private {
|
|
64
|
+
// Queue up all core facets for batch deployment
|
|
65
|
+
facetHelper.add("DiamondCutFacet");
|
|
66
|
+
facetHelper.add("DiamondLoupeFacet");
|
|
67
|
+
facetHelper.add("IntrospectionFacet");
|
|
68
|
+
facetHelper.add("OwnableFacet");
|
|
69
|
+
facetHelper.add("MetadataFacet");
|
|
70
|
+
|
|
71
|
+
// Get predicted addresses
|
|
72
|
+
address facet = facetHelper.predictAddress("DiamondCutFacet");
|
|
73
|
+
addFacet(
|
|
74
|
+
makeCut(facet, FacetCutAction.Add, DeployDiamondCut.selectors()),
|
|
75
|
+
facet,
|
|
76
|
+
DeployDiamondCut.makeInitData()
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
facet = facetHelper.predictAddress("DiamondLoupeFacet");
|
|
80
|
+
addFacet(
|
|
81
|
+
makeCut(facet, FacetCutAction.Add, DeployDiamondLoupe.selectors()),
|
|
82
|
+
facet,
|
|
83
|
+
DeployDiamondLoupe.makeInitData()
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
facet = facetHelper.predictAddress("IntrospectionFacet");
|
|
87
|
+
addFacet(
|
|
88
|
+
makeCut(facet, FacetCutAction.Add, DeployIntrospection.selectors()),
|
|
89
|
+
facet,
|
|
90
|
+
DeployIntrospection.makeInitData()
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
facet = facetHelper.predictAddress("OwnableFacet");
|
|
94
|
+
addFacet(
|
|
95
|
+
makeCut(facet, FacetCutAction.Add, DeployOwnable.selectors()),
|
|
96
|
+
facet,
|
|
97
|
+
DeployOwnable.makeInitData(deployer)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
facet = facetHelper.predictAddress("MetadataFacet");
|
|
101
|
+
addFacet(
|
|
102
|
+
makeCut(facet, FacetCutAction.Add, DeployMetadata.selectors()),
|
|
103
|
+
facet,
|
|
104
|
+
DeployMetadata.makeInitData(METADATA_NAME, "")
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function __deploy(address deployer) internal override returns (address) {
|
|
109
|
+
_coreFacets(deployer);
|
|
110
|
+
|
|
111
|
+
Diamond.InitParams memory initDiamondCut = diamondInitParams(deployer);
|
|
112
|
+
|
|
113
|
+
vm.broadcast(deployer);
|
|
114
|
+
Diamond diamond = new Diamond(initDiamondCut);
|
|
115
|
+
|
|
116
|
+
return address(diamond);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
import {IDiamond} from "@towns-protocol/diamond/src/IDiamond.sol";
|
|
6
|
+
|
|
7
|
+
// libraries
|
|
8
|
+
import {LibDeploy} from "@towns-protocol/diamond/src/utils/LibDeploy.sol";
|
|
9
|
+
|
|
10
|
+
// contracts
|
|
11
|
+
import {SubscriptionModuleFacet} from "../../../src/apps/modules/subscription/SubscriptionModuleFacet.sol";
|
|
12
|
+
import {DynamicArrayLib} from "solady/utils/DynamicArrayLib.sol";
|
|
13
|
+
|
|
14
|
+
library DeploySubscriptionModuleFacet {
|
|
15
|
+
using DynamicArrayLib for DynamicArrayLib.DynamicArray;
|
|
16
|
+
|
|
17
|
+
function selectors() internal pure returns (bytes4[] memory res) {
|
|
18
|
+
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(20);
|
|
19
|
+
arr.p(SubscriptionModuleFacet.moduleId.selector);
|
|
20
|
+
arr.p(SubscriptionModuleFacet.onInstall.selector);
|
|
21
|
+
arr.p(SubscriptionModuleFacet.onUninstall.selector);
|
|
22
|
+
arr.p(SubscriptionModuleFacet.validateUserOp.selector);
|
|
23
|
+
arr.p(SubscriptionModuleFacet.validateSignature.selector);
|
|
24
|
+
arr.p(SubscriptionModuleFacet.validateRuntime.selector);
|
|
25
|
+
arr.p(SubscriptionModuleFacet.preUserOpValidationHook.selector);
|
|
26
|
+
arr.p(SubscriptionModuleFacet.preRuntimeValidationHook.selector);
|
|
27
|
+
arr.p(SubscriptionModuleFacet.preSignatureValidationHook.selector);
|
|
28
|
+
arr.p(SubscriptionModuleFacet.batchProcessRenewals.selector);
|
|
29
|
+
arr.p(SubscriptionModuleFacet.processRenewal.selector);
|
|
30
|
+
arr.p(SubscriptionModuleFacet.getSubscription.selector);
|
|
31
|
+
arr.p(SubscriptionModuleFacet.pauseSubscription.selector);
|
|
32
|
+
arr.p(SubscriptionModuleFacet.getEntityIds.selector);
|
|
33
|
+
arr.p(SubscriptionModuleFacet.isOperator.selector);
|
|
34
|
+
arr.p(SubscriptionModuleFacet.grantOperator.selector);
|
|
35
|
+
arr.p(SubscriptionModuleFacet.revokeOperator.selector);
|
|
36
|
+
arr.p(bytes4(keccak256("MAX_BATCH_SIZE()")));
|
|
37
|
+
arr.p(bytes4(keccak256("RENEWAL_BUFFER()")));
|
|
38
|
+
arr.p(bytes4(keccak256("GRACE_PERIOD()")));
|
|
39
|
+
|
|
40
|
+
bytes32[] memory selectors_ = arr.asBytes32Array();
|
|
41
|
+
assembly ("memory-safe") {
|
|
42
|
+
res := selectors_
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeInitData() internal pure returns (bytes memory) {
|
|
47
|
+
return abi.encodeCall(SubscriptionModuleFacet.__SubscriptionModule_init, ());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function makeCut(
|
|
51
|
+
address facetAddress,
|
|
52
|
+
IDiamond.FacetCutAction action
|
|
53
|
+
) internal pure returns (IDiamond.FacetCut memory) {
|
|
54
|
+
return
|
|
55
|
+
IDiamond.FacetCut({
|
|
56
|
+
action: action,
|
|
57
|
+
facetAddress: facetAddress,
|
|
58
|
+
functionSelectors: selectors()
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function deploy() internal returns (address) {
|
|
63
|
+
return LibDeploy.deployCode("SubscriptionModuleFacet.sol", "");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -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) {
|
package/src/apps/BaseApp.sol
CHANGED
|
@@ -11,7 +11,6 @@ import {ITownsApp} from "./ITownsApp.sol";
|
|
|
11
11
|
/// @dev Provides base implementation for module installation/uninstallation and interface support
|
|
12
12
|
/// @dev Inheriting contracts should override _onInstall and _onUninstall as needed
|
|
13
13
|
/// @dev Implements IModule, IExecutionModule, and ITownsApp interfaces
|
|
14
|
-
|
|
15
14
|
abstract contract BaseApp is ITownsApp {
|
|
16
15
|
receive() external payable {
|
|
17
16
|
_onPayment(msg.sender, msg.value);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
|
|
6
|
+
// libraries
|
|
7
|
+
|
|
8
|
+
// contracts
|
|
9
|
+
|
|
10
|
+
import {Subscription} from "./SubscriptionModuleStorage.sol";
|
|
11
|
+
|
|
12
|
+
interface ISubscriptionModuleBase {
|
|
13
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
14
|
+
/* Structs */
|
|
15
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
16
|
+
|
|
17
|
+
/// @notice Parameters for renewing a subscription
|
|
18
|
+
/// @param account The address of the account to renew the subscription for
|
|
19
|
+
/// @param entityId The entity ID of the subscription to renew
|
|
20
|
+
struct RenewalParams {
|
|
21
|
+
address account;
|
|
22
|
+
uint32 entityId;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
26
|
+
/* Errors */
|
|
27
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
28
|
+
|
|
29
|
+
error SubscriptionModule__InactiveSubscription();
|
|
30
|
+
error SubscriptionModule__InvalidSpace();
|
|
31
|
+
error SubscriptionModule__RenewalNotDue();
|
|
32
|
+
error SubscriptionModule__RenewalFailed();
|
|
33
|
+
error SubscriptionModule__InvalidSender();
|
|
34
|
+
error SubscriptionModule__NotSupported();
|
|
35
|
+
error SubscriptionModule__InvalidEntityId();
|
|
36
|
+
error SubscriptionModule__InvalidCaller();
|
|
37
|
+
error SubscriptionModule__InvalidAddress();
|
|
38
|
+
error SubscriptionModule__ExceedsMaxBatchSize();
|
|
39
|
+
error SubscriptionModule__EmptyBatch();
|
|
40
|
+
error SubscriptionModule__InvalidTokenOwner();
|
|
41
|
+
error SubscriptionModule__InsufficientBalance();
|
|
42
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
43
|
+
/* Events */
|
|
44
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
45
|
+
|
|
46
|
+
event SubscriptionConfigured(
|
|
47
|
+
address indexed account,
|
|
48
|
+
uint32 indexed entityId,
|
|
49
|
+
address indexed space,
|
|
50
|
+
uint256 tokenId,
|
|
51
|
+
uint64 nextRenewalTime
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
event SubscriptionDeactivated(address indexed account, uint32 indexed entityId);
|
|
55
|
+
|
|
56
|
+
event SubscriptionSpent(
|
|
57
|
+
address indexed account,
|
|
58
|
+
uint32 indexed entityId,
|
|
59
|
+
uint256 amount,
|
|
60
|
+
uint256 totalSpent
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
event SubscriptionRenewed(
|
|
64
|
+
address indexed account,
|
|
65
|
+
uint32 indexed entityId,
|
|
66
|
+
uint256 nextRenewalTime
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
event SubscriptionPaused(address indexed account, uint32 indexed entityId);
|
|
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);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface ISubscriptionModule is ISubscriptionModuleBase {
|
|
78
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
79
|
+
/* Functions */
|
|
80
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
81
|
+
|
|
82
|
+
/// @notice Processes multiple Towns membership renewals in batch
|
|
83
|
+
/// @param params The parameters for the renewals
|
|
84
|
+
function batchProcessRenewals(RenewalParams[] calldata params) external;
|
|
85
|
+
|
|
86
|
+
/// @notice Processes a single Towns membership renewal
|
|
87
|
+
/// @param params The parameters for the renewal
|
|
88
|
+
function processRenewal(RenewalParams calldata params) external;
|
|
89
|
+
|
|
90
|
+
/// @notice Gets the subscription for an account and entity ID
|
|
91
|
+
/// @param account The address of the account to get the subscription for
|
|
92
|
+
/// @param entityId The entity ID of the subscription to get
|
|
93
|
+
/// @return The subscription for the account and entity ID
|
|
94
|
+
function getSubscription(
|
|
95
|
+
address account,
|
|
96
|
+
uint32 entityId
|
|
97
|
+
) external view returns (Subscription memory);
|
|
98
|
+
|
|
99
|
+
/// @notice Pauses a subscription
|
|
100
|
+
/// @param entityId The entity ID of the subscription to pause
|
|
101
|
+
function pauseSubscription(uint32 entityId) external;
|
|
102
|
+
|
|
103
|
+
/// @notice Gets the entity IDs for an account
|
|
104
|
+
/// @param account The address of the account to get the entity IDs for
|
|
105
|
+
/// @return The entity IDs for the account
|
|
106
|
+
function getEntityIds(address account) external view returns (uint256[] memory);
|
|
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
|
+
|
|
112
|
+
/// @notice Grants an operator access to call processRenewal
|
|
113
|
+
/// @param operator The address of the operator to grant
|
|
114
|
+
function grantOperator(address operator) external;
|
|
115
|
+
|
|
116
|
+
/// @notice Revokes an operator access to call processRenewal
|
|
117
|
+
/// @param operator The address of the operator to revoke
|
|
118
|
+
function revokeOperator(address operator) external;
|
|
119
|
+
}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.29;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol";
|
|
6
|
+
import {IValidationModule} from "@erc6900/reference-implementation/interfaces/IValidationModule.sol";
|
|
7
|
+
import {IValidationHookModule} from "@erc6900/reference-implementation/interfaces/IValidationHookModule.sol";
|
|
8
|
+
import {ISubscriptionModule} from "./ISubscriptionModule.sol";
|
|
9
|
+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
10
|
+
import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
|
|
11
|
+
|
|
12
|
+
// libraries
|
|
13
|
+
import {Subscription, SubscriptionModuleStorage} from "./SubscriptionModuleStorage.sol";
|
|
14
|
+
import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interfaces/PackedUserOperation.sol";
|
|
15
|
+
import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
|
|
16
|
+
import {LibCall} from "solady/utils/LibCall.sol";
|
|
17
|
+
import {ValidationLocatorLib} from "modular-account/src/libraries/ValidationLocatorLib.sol";
|
|
18
|
+
import {ReentrancyGuardTransient} from "solady/utils/ReentrancyGuardTransient.sol";
|
|
19
|
+
import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
|
|
20
|
+
import {Validator} from "../../../utils/libraries/Validator.sol";
|
|
21
|
+
|
|
22
|
+
// contracts
|
|
23
|
+
import {ModuleBase} from "modular-account/src/modules/ModuleBase.sol";
|
|
24
|
+
import {OwnableBase} from "@towns-protocol/diamond/src/facets/ownable/OwnableBase.sol";
|
|
25
|
+
import {MembershipFacet} from "../../../spaces/facets/membership/MembershipFacet.sol";
|
|
26
|
+
import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
|
|
27
|
+
|
|
28
|
+
/// @title Subscription Module
|
|
29
|
+
/// @notice Module for managing subscriptions to spaces
|
|
30
|
+
contract SubscriptionModuleFacet is
|
|
31
|
+
ISubscriptionModule,
|
|
32
|
+
IValidationModule,
|
|
33
|
+
IValidationHookModule,
|
|
34
|
+
ModuleBase,
|
|
35
|
+
OwnableBase,
|
|
36
|
+
ReentrancyGuardTransient,
|
|
37
|
+
Facet
|
|
38
|
+
{
|
|
39
|
+
using EnumerableSetLib for EnumerableSetLib.Uint256Set;
|
|
40
|
+
using EnumerableSetLib for EnumerableSetLib.AddressSet;
|
|
41
|
+
using CustomRevert for bytes4;
|
|
42
|
+
|
|
43
|
+
uint256 internal constant _SIG_VALIDATION_FAILED = 1;
|
|
44
|
+
|
|
45
|
+
uint256 public constant MAX_BATCH_SIZE = 50;
|
|
46
|
+
uint256 public constant RENEWAL_BUFFER = 1 days;
|
|
47
|
+
uint256 public constant GRACE_PERIOD = 3 days;
|
|
48
|
+
|
|
49
|
+
function __SubscriptionModule_init() external onlyInitializing {
|
|
50
|
+
_addInterface(type(ISubscriptionModule).interfaceId);
|
|
51
|
+
_addInterface(type(IValidationModule).interfaceId);
|
|
52
|
+
_addInterface(type(IValidationHookModule).interfaceId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
56
|
+
/* External */
|
|
57
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
58
|
+
|
|
59
|
+
/// @inheritdoc IModule
|
|
60
|
+
function moduleId() external pure returns (string memory) {
|
|
61
|
+
return "towns.subscription-module.1.0.0";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// @inheritdoc IModule
|
|
65
|
+
function onInstall(bytes calldata data) external override nonReentrant {
|
|
66
|
+
(uint32 entityId, address space, uint256 tokenId) = abi.decode(
|
|
67
|
+
data,
|
|
68
|
+
(uint32, address, uint256)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
Validator.checkAddress(space);
|
|
72
|
+
|
|
73
|
+
if (IERC721(space).ownerOf(tokenId) != msg.sender)
|
|
74
|
+
SubscriptionModule__InvalidTokenOwner.selector.revertWith();
|
|
75
|
+
|
|
76
|
+
MembershipFacet membershipFacet = MembershipFacet(space);
|
|
77
|
+
uint256 expiresAt = membershipFacet.expiresAt(tokenId);
|
|
78
|
+
|
|
79
|
+
SubscriptionModuleStorage.Layout storage $ = SubscriptionModuleStorage.getLayout();
|
|
80
|
+
Subscription storage sub = $.subscriptions[msg.sender][entityId];
|
|
81
|
+
sub.space = space;
|
|
82
|
+
sub.active = true;
|
|
83
|
+
sub.tokenId = tokenId;
|
|
84
|
+
sub.nextRenewalTime = uint40(expiresAt - RENEWAL_BUFFER);
|
|
85
|
+
|
|
86
|
+
$.entityIds[msg.sender].add(entityId);
|
|
87
|
+
|
|
88
|
+
emit SubscriptionConfigured(msg.sender, entityId, space, tokenId, sub.nextRenewalTime);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// @inheritdoc IModule
|
|
92
|
+
function onUninstall(bytes calldata data) external override nonReentrant {
|
|
93
|
+
uint32 entityId = abi.decode(data, (uint32));
|
|
94
|
+
|
|
95
|
+
SubscriptionModuleStorage.Layout storage $ = SubscriptionModuleStorage.getLayout();
|
|
96
|
+
|
|
97
|
+
if (!$.entityIds[msg.sender].remove(entityId))
|
|
98
|
+
SubscriptionModule__InvalidEntityId.selector.revertWith();
|
|
99
|
+
delete $.subscriptions[msg.sender][entityId];
|
|
100
|
+
|
|
101
|
+
emit SubscriptionDeactivated(msg.sender, entityId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// @inheritdoc IValidationModule
|
|
105
|
+
function validateUserOp(
|
|
106
|
+
uint32,
|
|
107
|
+
PackedUserOperation calldata,
|
|
108
|
+
bytes32
|
|
109
|
+
) external pure override returns (uint256) {
|
|
110
|
+
return _SIG_VALIDATION_FAILED;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// @inheritdoc IValidationModule
|
|
114
|
+
function validateSignature(
|
|
115
|
+
address,
|
|
116
|
+
uint32,
|
|
117
|
+
address,
|
|
118
|
+
bytes32,
|
|
119
|
+
bytes calldata
|
|
120
|
+
) external pure override returns (bytes4) {
|
|
121
|
+
return 0xffffffff;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// @inheritdoc IValidationModule
|
|
125
|
+
function validateRuntime(
|
|
126
|
+
address account,
|
|
127
|
+
uint32 entityId,
|
|
128
|
+
address sender,
|
|
129
|
+
uint256,
|
|
130
|
+
bytes calldata,
|
|
131
|
+
bytes calldata
|
|
132
|
+
) external view override {
|
|
133
|
+
if (sender != address(this)) SubscriptionModule__InvalidSender.selector.revertWith();
|
|
134
|
+
bool active = SubscriptionModuleStorage.getLayout().subscriptions[account][entityId].active;
|
|
135
|
+
if (!active) SubscriptionModule__InactiveSubscription.selector.revertWith();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// @inheritdoc IValidationHookModule
|
|
140
|
+
function preUserOpValidationHook(
|
|
141
|
+
uint32 /* entityId */,
|
|
142
|
+
PackedUserOperation calldata /* userOp */,
|
|
143
|
+
bytes32 /* userOpHash */
|
|
144
|
+
) external pure override returns (uint256) {
|
|
145
|
+
return _SIG_VALIDATION_FAILED;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// @inheritdoc IValidationHookModule
|
|
149
|
+
function preRuntimeValidationHook(
|
|
150
|
+
uint32 /* entityId */,
|
|
151
|
+
address /* sender */,
|
|
152
|
+
uint256 /* value */,
|
|
153
|
+
bytes calldata /* data */,
|
|
154
|
+
bytes calldata /* authorization */
|
|
155
|
+
) external pure override {
|
|
156
|
+
SubscriptionModule__NotSupported.selector.revertWith();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// @inheritdoc IValidationHookModule
|
|
160
|
+
function preSignatureValidationHook(
|
|
161
|
+
uint32 /* entityId */,
|
|
162
|
+
address /* sender */,
|
|
163
|
+
bytes32 /* hash */,
|
|
164
|
+
bytes calldata /* signature */
|
|
165
|
+
) external pure override {
|
|
166
|
+
SubscriptionModule__NotSupported.selector.revertWith();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// @inheritdoc ISubscriptionModule
|
|
170
|
+
function batchProcessRenewals(RenewalParams[] calldata params) external nonReentrant {
|
|
171
|
+
uint256 length = params.length;
|
|
172
|
+
if (length > MAX_BATCH_SIZE) SubscriptionModule__ExceedsMaxBatchSize.selector.revertWith();
|
|
173
|
+
if (length == 0) SubscriptionModule__EmptyBatch.selector.revertWith();
|
|
174
|
+
|
|
175
|
+
SubscriptionModuleStorage.Layout storage $ = SubscriptionModuleStorage.getLayout();
|
|
176
|
+
|
|
177
|
+
for (uint256 i; i < length; ++i) {
|
|
178
|
+
if (!_isAllowed($.operators, params[i].account))
|
|
179
|
+
SubscriptionModule__InvalidCaller.selector.revertWith();
|
|
180
|
+
|
|
181
|
+
Subscription storage sub = $.subscriptions[params[i].account][params[i].entityId];
|
|
182
|
+
|
|
183
|
+
// Skip inactive subscriptions
|
|
184
|
+
if (!sub.active) {
|
|
185
|
+
emit BatchRenewalSkipped(params[i].account, params[i].entityId, "INACTIVE");
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Skip if renewal not due
|
|
190
|
+
if (block.timestamp < sub.nextRenewalTime) {
|
|
191
|
+
emit BatchRenewalSkipped(params[i].account, params[i].entityId, "NOT_DUE");
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Skip if past grace period (will be handled by individual call)
|
|
196
|
+
if (block.timestamp > sub.nextRenewalTime + GRACE_PERIOD) {
|
|
197
|
+
emit BatchRenewalSkipped(params[i].account, params[i].entityId, "PAST_GRACE");
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_processRenewal(sub, params[i]);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// @inheritdoc ISubscriptionModule
|
|
206
|
+
function processRenewal(RenewalParams calldata renewalParams) external nonReentrant {
|
|
207
|
+
SubscriptionModuleStorage.Layout storage $ = SubscriptionModuleStorage.getLayout();
|
|
208
|
+
if (!_isAllowed($.operators, renewalParams.account))
|
|
209
|
+
SubscriptionModule__InvalidCaller.selector.revertWith();
|
|
210
|
+
_processRenewal(
|
|
211
|
+
$.subscriptions[renewalParams.account][renewalParams.entityId],
|
|
212
|
+
renewalParams
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// @inheritdoc ISubscriptionModule
|
|
217
|
+
function getSubscription(
|
|
218
|
+
address account,
|
|
219
|
+
uint32 entityId
|
|
220
|
+
) external view returns (Subscription memory) {
|
|
221
|
+
return SubscriptionModuleStorage.getLayout().subscriptions[account][entityId];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// @inheritdoc ISubscriptionModule
|
|
225
|
+
function pauseSubscription(uint32 entityId) external {
|
|
226
|
+
Subscription storage sub = SubscriptionModuleStorage.getLayout().subscriptions[msg.sender][
|
|
227
|
+
entityId
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
if (!sub.active) SubscriptionModule__InactiveSubscription.selector.revertWith();
|
|
231
|
+
|
|
232
|
+
sub.active = false;
|
|
233
|
+
emit SubscriptionPaused(msg.sender, entityId);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// @inheritdoc ISubscriptionModule
|
|
237
|
+
function getEntityIds(address account) external view returns (uint256[] memory) {
|
|
238
|
+
return SubscriptionModuleStorage.getLayout().entityIds[account].values();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// @inheritdoc ISubscriptionModule
|
|
242
|
+
function grantOperator(address operator) external onlyOwner {
|
|
243
|
+
Validator.checkAddress(operator);
|
|
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);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// @inheritdoc ISubscriptionModule
|
|
254
|
+
function revokeOperator(address operator) external onlyOwner {
|
|
255
|
+
Validator.checkAddress(operator);
|
|
256
|
+
SubscriptionModuleStorage.getLayout().operators.remove(operator);
|
|
257
|
+
emit OperatorRevoked(operator);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
261
|
+
/* Internal */
|
|
262
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
263
|
+
|
|
264
|
+
/// @dev Processes a single subscription renewal
|
|
265
|
+
/// @param sub The subscription to renew
|
|
266
|
+
/// @param params The parameters for the renewal
|
|
267
|
+
function _processRenewal(Subscription storage sub, RenewalParams calldata params) internal {
|
|
268
|
+
if (!sub.active) SubscriptionModule__InactiveSubscription.selector.revertWith();
|
|
269
|
+
|
|
270
|
+
if (block.timestamp < sub.nextRenewalTime)
|
|
271
|
+
SubscriptionModule__RenewalNotDue.selector.revertWith();
|
|
272
|
+
|
|
273
|
+
// Check if we're past the grace period
|
|
274
|
+
if (block.timestamp > sub.nextRenewalTime + GRACE_PERIOD) {
|
|
275
|
+
sub.active = false;
|
|
276
|
+
emit SubscriptionPaused(params.account, params.entityId);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
MembershipFacet membershipFacet = MembershipFacet(sub.space);
|
|
281
|
+
|
|
282
|
+
// Get current renewal price from Towns contract
|
|
283
|
+
uint256 actualRenewalPrice = membershipFacet.getMembershipRenewalPrice(sub.tokenId);
|
|
284
|
+
|
|
285
|
+
// Check if the account has enough balance
|
|
286
|
+
if (params.account.balance < actualRenewalPrice)
|
|
287
|
+
SubscriptionModule__InsufficientBalance.selector.revertWith();
|
|
288
|
+
|
|
289
|
+
// Construct the renewal call to space contract
|
|
290
|
+
bytes memory renewalCall = abi.encodeCall(MembershipFacet.renewMembership, (sub.tokenId));
|
|
291
|
+
|
|
292
|
+
// Create the data parameter for executeWithRuntimeValidation
|
|
293
|
+
// This should be an execute() call to the space contract
|
|
294
|
+
bytes memory executeData = abi.encodeCall(
|
|
295
|
+
IModularAccount.execute,
|
|
296
|
+
(
|
|
297
|
+
sub.space, // target
|
|
298
|
+
actualRenewalPrice, // value
|
|
299
|
+
renewalCall // data
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// Use the proper pack function from ValidationLocatorLib
|
|
304
|
+
bytes memory authorization = _runtimeFinal(
|
|
305
|
+
params.entityId,
|
|
306
|
+
abi.encode(sub.space, sub.tokenId)
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Call executeWithRuntimeValidation with the correct parameters
|
|
310
|
+
bytes memory runtimeValidationCall = abi.encodeCall(
|
|
311
|
+
IModularAccount.executeWithRuntimeValidation,
|
|
312
|
+
(
|
|
313
|
+
executeData, // The execute() call data
|
|
314
|
+
authorization // Authorization for validation
|
|
315
|
+
)
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// External call happens here
|
|
319
|
+
LibCall.callContract(params.account, 0, runtimeValidationCall);
|
|
320
|
+
|
|
321
|
+
// Get the actual new expiration time after successful renewal
|
|
322
|
+
uint256 newExpiresAt = membershipFacet.expiresAt(sub.tokenId);
|
|
323
|
+
|
|
324
|
+
// Update subscription state after successful renewal
|
|
325
|
+
sub.nextRenewalTime = uint40(newExpiresAt - RENEWAL_BUFFER);
|
|
326
|
+
sub.lastRenewalTime = uint40(block.timestamp);
|
|
327
|
+
sub.spent += actualRenewalPrice;
|
|
328
|
+
|
|
329
|
+
emit SubscriptionRenewed(params.account, params.entityId, sub.nextRenewalTime);
|
|
330
|
+
}
|
|
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
|
|
336
|
+
function _runtimeFinal(
|
|
337
|
+
uint32 entityId,
|
|
338
|
+
bytes memory finalData
|
|
339
|
+
) internal pure returns (bytes memory) {
|
|
340
|
+
return
|
|
341
|
+
ValidationLocatorLib.packSignature(
|
|
342
|
+
entityId,
|
|
343
|
+
false, // selector-based
|
|
344
|
+
bytes.concat(hex"ff", finalData)
|
|
345
|
+
);
|
|
346
|
+
}
|
|
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
|
|
352
|
+
function _isAllowed(
|
|
353
|
+
EnumerableSetLib.AddressSet storage operators,
|
|
354
|
+
address account
|
|
355
|
+
) internal view returns (bool) {
|
|
356
|
+
if (account == msg.sender) return true;
|
|
357
|
+
return operators.contains(msg.sender);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.29;
|
|
3
|
+
|
|
4
|
+
import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
|
|
5
|
+
|
|
6
|
+
struct Subscription {
|
|
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
|
+
}
|
|
14
|
+
|
|
15
|
+
library SubscriptionModuleStorage {
|
|
16
|
+
using EnumerableSetLib for EnumerableSetLib.Uint256Set;
|
|
17
|
+
using EnumerableSetLib for EnumerableSetLib.AddressSet;
|
|
18
|
+
|
|
19
|
+
/// @custom:storage-location erc7201:towns.subscription.validation.module.storage
|
|
20
|
+
struct Layout {
|
|
21
|
+
EnumerableSetLib.AddressSet operators;
|
|
22
|
+
mapping(address account => mapping(uint32 entityId => Subscription)) subscriptions;
|
|
23
|
+
mapping(address account => EnumerableSetLib.Uint256Set entityIds) entityIds;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// keccak256(abi.encode(uint256(keccak256("towns.subscription.validation.module.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
27
|
+
bytes32 private constant STORAGE_SLOT =
|
|
28
|
+
0xd241b3ceee256b40f80fe7a66fe789234ac389ed1408c472c4ee1cbb1deb8600;
|
|
29
|
+
|
|
30
|
+
function getLayout() internal pure returns (Layout storage $) {
|
|
31
|
+
assembly {
|
|
32
|
+
$.slot := STORAGE_SLOT
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -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
|
}
|