@towns-protocol/contracts 0.0.375 → 0.0.377

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.375",
3
+ "version": "0.0.377",
4
4
  "packageManager": "yarn@3.8.0",
5
5
  "scripts": {
6
6
  "build-types": "bash scripts/build-contract-types.sh",
@@ -35,7 +35,7 @@
35
35
  "@layerzerolabs/oapp-evm": "^0.3.2",
36
36
  "@openzeppelin/merkle-tree": "^1.0.8",
37
37
  "@prb/test": "^0.6.4",
38
- "@towns-protocol/prettier-config": "^0.0.375",
38
+ "@towns-protocol/prettier-config": "^0.0.377",
39
39
  "@typechain/ethers-v5": "^11.1.2",
40
40
  "@wagmi/cli": "^2.2.0",
41
41
  "forge-std": "github:foundry-rs/forge-std#v1.10.0",
@@ -57,5 +57,5 @@
57
57
  "publishConfig": {
58
58
  "access": "public"
59
59
  },
60
- "gitHead": "f64537fbf8d7eca815c5c5e52f601a387e713a0a"
60
+ "gitHead": "a289c507ab3ae080d0b07e9f881743223580fdf4"
61
61
  }
@@ -13,6 +13,8 @@ import {LibString} from "solady/utils/LibString.sol";
13
13
  import {DeployMetadata} from "../facets/DeployMetadata.s.sol";
14
14
  import {DeployAppRegistryFacet} from "../facets/DeployAppRegistryFacet.s.sol";
15
15
  import {DeployUpgradeableBeacon} from "../facets/DeployUpgradeableBeacon.s.sol";
16
+ import {DeployAppInstallerFacet} from "../facets/DeployAppInstallerFacet.s.sol";
17
+ import {DeploySpaceFactory} from "../diamonds/DeploySpaceFactory.s.sol";
16
18
 
17
19
  // contracts
18
20
  import {Diamond} from "@towns-protocol/diamond/src/Diamond.sol";
@@ -26,29 +28,19 @@ import {Deployer} from "../../common/Deployer.s.sol";
26
28
  contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
27
29
  using LibString for string;
28
30
 
29
- address private SPACE_FACTORY;
30
-
31
31
  DeployFacet private facetHelper = new DeployFacet();
32
+ DeploySpaceFactory private deploySpaceFactory = new DeploySpaceFactory();
32
33
 
33
34
  string internal constant APP_REGISTRY_SCHEMA = "address app, address client";
35
+ address internal spaceFactory;
34
36
 
35
37
  function versionName() public pure override returns (string memory) {
36
38
  return "appRegistry";
37
39
  }
38
40
 
39
- function setSpaceFactory(address factory) public {
40
- SPACE_FACTORY = factory;
41
- }
42
-
43
- function getSpaceFactory() public returns (address) {
44
- if (SPACE_FACTORY != address(0)) {
45
- return SPACE_FACTORY;
46
- }
47
-
48
- return getDeployment("spaceFactory");
49
- }
50
-
51
41
  function addImmutableCuts(address deployer) internal {
42
+ spaceFactory = deploySpaceFactory.deploy(deployer);
43
+
52
44
  // Queue up all core facets for batch deployment
53
45
  facetHelper.add("DiamondCutFacet");
54
46
  facetHelper.add("DiamondLoupeFacet");
@@ -94,6 +86,7 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
94
86
  facetHelper.add("MetadataFacet");
95
87
  facetHelper.add("UpgradeableBeaconFacet");
96
88
  facetHelper.add("AppRegistryFacet");
89
+ facetHelper.add("AppInstallerFacet");
97
90
  facetHelper.add("SimpleApp");
98
91
 
99
92
  facetHelper.deployBatch(deployer);
@@ -118,7 +111,14 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
118
111
  addFacet(
119
112
  makeCut(facet, FacetCutAction.Add, DeployAppRegistryFacet.selectors()),
120
113
  facet,
121
- DeployAppRegistryFacet.makeInitData(getSpaceFactory(), APP_REGISTRY_SCHEMA, address(0))
114
+ DeployAppRegistryFacet.makeInitData(spaceFactory, APP_REGISTRY_SCHEMA, address(0))
115
+ );
116
+
117
+ facet = facetHelper.getDeployedAddress("AppInstallerFacet");
118
+ addFacet(
119
+ makeCut(facet, FacetCutAction.Add, DeployAppInstallerFacet.selectors()),
120
+ facet,
121
+ DeployAppInstallerFacet.makeInitData()
122
122
  );
123
123
 
124
124
  address multiInit = facetHelper.getDeployedAddress("MultiInit");
@@ -150,6 +150,9 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
150
150
  if (facetName.eq("AppRegistryFacet")) {
151
151
  addCut(makeCut(facet, FacetCutAction.Add, DeployAppRegistryFacet.selectors()));
152
152
  }
153
+ if (facetName.eq("AppInstallerFacet")) {
154
+ addCut(makeCut(facet, FacetCutAction.Add, DeployAppInstallerFacet.selectors()));
155
+ }
153
156
  }
154
157
 
155
158
  return baseFacets();
@@ -0,0 +1,48 @@
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
+
9
+ //contracts
10
+ import {LibDeploy} from "@towns-protocol/diamond/src/utils/LibDeploy.sol";
11
+ import {AppInstallerFacet} from "src/apps/facets/installer/AppInstallerFacet.sol";
12
+ import {DynamicArrayLib} from "solady/utils/DynamicArrayLib.sol";
13
+
14
+ library DeployAppInstallerFacet {
15
+ using DynamicArrayLib for DynamicArrayLib.DynamicArray;
16
+
17
+ function selectors() internal pure returns (bytes4[] memory res) {
18
+ DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(4);
19
+ arr.p(AppInstallerFacet.installApp.selector);
20
+ arr.p(AppInstallerFacet.uninstallApp.selector);
21
+ arr.p(AppInstallerFacet.updateApp.selector);
22
+ arr.p(AppInstallerFacet.renewApp.selector);
23
+ bytes32[] memory selectors_ = arr.asBytes32Array();
24
+ assembly ("memory-safe") {
25
+ res := selectors_
26
+ }
27
+ }
28
+
29
+ function makeCut(
30
+ address facetAddress,
31
+ IDiamond.FacetCutAction action
32
+ ) internal pure returns (IDiamond.FacetCut memory) {
33
+ return
34
+ IDiamond.FacetCut({
35
+ action: action,
36
+ facetAddress: facetAddress,
37
+ functionSelectors: selectors()
38
+ });
39
+ }
40
+
41
+ function makeInitData() internal pure returns (bytes memory) {
42
+ return abi.encodeCall(AppInstallerFacet.__AppInstaller_init, ());
43
+ }
44
+
45
+ function deploy() internal returns (address) {
46
+ return LibDeploy.deployCode("AppInstallerFacet.sol", "");
47
+ }
48
+ }
@@ -16,7 +16,7 @@ library DeployAppRegistryFacet {
16
16
  using DynamicArrayLib for DynamicArrayLib.DynamicArray;
17
17
 
18
18
  function selectors() internal pure returns (bytes4[] memory res) {
19
- DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(18);
19
+ DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(14);
20
20
  arr.p(AppRegistryFacet.getAppSchema.selector);
21
21
  arr.p(AppRegistryFacet.getAppSchemaId.selector);
22
22
  arr.p(AppRegistryFacet.getAppById.selector);
@@ -25,16 +25,12 @@ library DeployAppRegistryFacet {
25
25
  arr.p(AppRegistryFacet.removeApp.selector);
26
26
  arr.p(AppRegistryFacet.createApp.selector);
27
27
  arr.p(AppRegistryFacet.upgradeApp.selector);
28
- arr.p(AppRegistryFacet.installApp.selector);
29
- arr.p(AppRegistryFacet.uninstallApp.selector);
30
- arr.p(AppRegistryFacet.updateApp.selector);
31
28
  arr.p(AppRegistryFacet.getAppPrice.selector);
32
29
  arr.p(AppRegistryFacet.getAppDuration.selector);
33
30
  arr.p(AppRegistryFacet.adminRegisterAppSchema.selector);
34
31
  arr.p(AppRegistryFacet.adminBanApp.selector);
35
32
  arr.p(AppRegistryFacet.isAppBanned.selector);
36
33
  arr.p(AppRegistryFacet.getAppByClient.selector);
37
- arr.p(AppRegistryFacet.renewApp.selector);
38
34
  bytes32[] memory selectors_ = arr.asBytes32Array();
39
35
  assembly ("memory-safe") {
40
36
  res := selectors_
@@ -0,0 +1,66 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.29;
3
+
4
+ // interfaces
5
+ import {ITownsApp} from "../../ITownsApp.sol";
6
+ import {IAppAccount} from "../../../spaces/facets/account/IAppAccount.sol";
7
+ import {IAppInstaller} from "./IAppInstaller.sol";
8
+
9
+ // libraries
10
+
11
+ // contracts
12
+ import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
13
+ import {AppRegistryBase} from "../registry/AppRegistryBase.sol";
14
+ import {ReentrancyGuardTransient} from "solady/utils/ReentrancyGuardTransient.sol";
15
+
16
+ /// @title AppInstallerFacet
17
+ /// @author Towns Protocol
18
+ /// @notice Facet for installing apps to spaces
19
+ contract AppInstallerFacet is IAppInstaller, AppRegistryBase, ReentrancyGuardTransient, Facet {
20
+ function __AppInstaller_init() external onlyInitializing {
21
+ _addInterface(type(IAppInstaller).interfaceId);
22
+ }
23
+
24
+ /// @notice Install an app
25
+ /// @param app The app address to install
26
+ /// @param space The space to install the app to
27
+ /// @param data The data to pass to the app's onInstall function
28
+ function installApp(
29
+ ITownsApp app,
30
+ IAppAccount space,
31
+ bytes calldata data
32
+ ) external payable nonReentrant onlyAllowed(space) {
33
+ _installApp(address(app), address(space), data);
34
+ }
35
+
36
+ /// @notice Uninstall an app
37
+ /// @param app The app address to uninstall
38
+ /// @param space The space to uninstall the app from
39
+ /// @param data The data to pass to the app's onUninstall function
40
+ function uninstallApp(
41
+ ITownsApp app,
42
+ IAppAccount space,
43
+ bytes calldata data
44
+ ) external nonReentrant onlyAllowed(space) {
45
+ _uninstallApp(address(app), address(space), data);
46
+ }
47
+
48
+ /// @notice Update an app to the latest version
49
+ /// @param app The app address to update
50
+ /// @param space The space to update the app to
51
+ function updateApp(ITownsApp app, IAppAccount space) external nonReentrant onlyAllowed(space) {
52
+ _updateApp(address(app), address(space));
53
+ }
54
+
55
+ /// @notice Renew an app
56
+ /// @param app The app address to renew
57
+ /// @param space The space to renew the app for
58
+ /// @param data The data to pass to the app's onRenewApp function
59
+ function renewApp(
60
+ ITownsApp app,
61
+ IAppAccount space,
62
+ bytes calldata data
63
+ ) external payable nonReentrant onlyAllowed(space) {
64
+ _renewApp(address(app), address(space), data);
65
+ }
66
+ }
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.29;
3
+
4
+ // interfaces
5
+ import {ITownsApp} from "../../ITownsApp.sol";
6
+ import {IAppAccount} from "../../../spaces/facets/account/IAppAccount.sol";
7
+
8
+ // libraries
9
+
10
+ // contracts
11
+
12
+ interface IAppInstaller {
13
+ /// @notice Install an app
14
+ /// @param app The app address to install
15
+ /// @param account The account to install the app to
16
+ /// @param data The data to pass to the app's onInstall function
17
+ function installApp(ITownsApp app, IAppAccount account, bytes calldata data) external payable;
18
+
19
+ /// @notice Uninstall an app
20
+ /// @param app The app address to uninstall
21
+ /// @param account The account to uninstall the app from
22
+ /// @param data The data to pass to the app's onUninstall function
23
+ function uninstallApp(ITownsApp app, IAppAccount account, bytes calldata data) external;
24
+
25
+ /// @notice Update an app to the latest version
26
+ /// @param app The app address to update
27
+ /// @param account The account to update the app to
28
+ function updateApp(ITownsApp app, IAppAccount account) external;
29
+
30
+ /// @notice Renew an app
31
+ /// @param app The app address to renew
32
+ /// @param account The account to renew the app for
33
+ /// @param data The data to pass to the app's onRenewApp function
34
+ function renewApp(ITownsApp app, IAppAccount account, bytes calldata data) external payable;
35
+ }
@@ -35,6 +35,11 @@ abstract contract AppRegistryBase is IAppRegistryBase, SchemaBase, AttestationBa
35
35
 
36
36
  uint48 private constant MAX_DURATION = 365 days;
37
37
 
38
+ modifier onlyAllowed(IAppAccount account) {
39
+ if (IERC173(address(account)).owner() != msg.sender) NotAllowed.selector.revertWith();
40
+ _;
41
+ }
42
+
38
43
  function __AppRegistry_init_unchained(
39
44
  address spaceFactory,
40
45
  string calldata schema,
@@ -321,10 +326,6 @@ abstract contract AppRegistryBase is IAppRegistryBase, SchemaBase, AttestationBa
321
326
  emit AppRenewed(app, account, appId);
322
327
  }
323
328
 
324
- function _onlyAllowed(address account) internal view {
325
- if (IERC173(account).owner() != msg.sender) NotAllowed.selector.revertWith();
326
- }
327
-
328
329
  function _getProtocolFee(uint256 installPrice) internal view returns (uint256) {
329
330
  IPlatformRequirements platform = _getPlatformRequirements();
330
331
  uint256 baseFee = platform.getMembershipFee();
@@ -16,6 +16,9 @@ import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
16
16
  import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
17
17
  import {OwnableBase} from "@towns-protocol/diamond/src/facets/ownable/OwnableBase.sol";
18
18
 
19
+ /// @title AppRegistryFacet
20
+ /// @author Towns Protocol
21
+ /// @notice Facet for managing app registry
19
22
  contract AppRegistryFacet is IAppRegistry, AppRegistryBase, OwnableBase, ReentrancyGuard, Facet {
20
23
  function __AppRegistry_init(
21
24
  address spaceFactory,
@@ -95,53 +98,6 @@ contract AppRegistryFacet is IAppRegistry, AppRegistryBase, OwnableBase, Reentra
95
98
  /* Space Functions */
96
99
  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
97
100
 
98
- /// @notice Install an app
99
- /// @param app The app address to install
100
- /// @param space The space to install the app to
101
- /// @param data The data to pass to the app's onInstall function
102
- function installApp(
103
- ITownsApp app,
104
- IAppAccount space,
105
- bytes calldata data
106
- ) external payable nonReentrant {
107
- _onlyAllowed(address(space));
108
- return _installApp(address(app), address(space), data);
109
- }
110
-
111
- /// @notice Uninstall an app
112
- /// @param app The app address to uninstall
113
- /// @param space The space to uninstall the app from
114
- /// @param data The data to pass to the app's onUninstall function
115
- function uninstallApp(
116
- ITownsApp app,
117
- IAppAccount space,
118
- bytes calldata data
119
- ) external nonReentrant {
120
- _onlyAllowed(address(space));
121
- _uninstallApp(address(app), address(space), data);
122
- }
123
-
124
- /// @notice Update an app to the latest version
125
- /// @param app The app address to update
126
- /// @param space The space to update the app to
127
- function updateApp(ITownsApp app, IAppAccount space) external nonReentrant {
128
- _onlyAllowed(address(space));
129
- _updateApp(address(app), address(space));
130
- }
131
-
132
- /// @notice Renew an app
133
- /// @param app The app address to renew
134
- /// @param account The account to renew the app for
135
- /// @param data The data to pass to the app's onRenewApp function
136
- function renewApp(
137
- ITownsApp app,
138
- IAppAccount account,
139
- bytes calldata data
140
- ) external payable nonReentrant {
141
- _onlyAllowed(address(account));
142
- _renewApp(address(app), address(account), data);
143
- }
144
-
145
101
  /// @notice Get the schema structure used for registering modules
146
102
  /// @return The schema structure
147
103
  function getAppSchema() external view returns (string memory) {
@@ -140,29 +140,6 @@ interface IAppRegistry is IAppRegistryBase {
140
140
  /// @param appId The app ID to remove
141
141
  function removeApp(bytes32 appId) external;
142
142
 
143
- /// @notice Renew an app
144
- /// @param app The app address to renew
145
- /// @param account The account to renew the app for
146
- /// @param data The data to pass to the app's onRenewApp function
147
- function renewApp(ITownsApp app, IAppAccount account, bytes calldata data) external payable;
148
-
149
- /// @notice Install an app
150
- /// @param app The app address to install
151
- /// @param account The account to install the app to
152
- /// @param data The data to pass to the app's onInstall function
153
- function installApp(ITownsApp app, IAppAccount account, bytes calldata data) external payable;
154
-
155
- /// @notice Uninstall an app
156
- /// @param app The app address to uninstall
157
- /// @param account The account to uninstall the app from
158
- /// @param data The data to pass to the app's onUninstall function
159
- function uninstallApp(ITownsApp app, IAppAccount account, bytes calldata data) external;
160
-
161
- /// @notice Update an app to the latest version
162
- /// @param app The app address to update
163
- /// @param account The account to update the app to
164
- function updateApp(ITownsApp app, IAppAccount account) external;
165
-
166
143
  /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
167
144
  /* Admin */
168
145
  /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
@@ -14,13 +14,13 @@ import {CustomRevert} from "../../utils/libraries/CustomRevert.sol";
14
14
  import {EntitlementsManagerStorage} from "./entitlements/EntitlementsManagerStorage.sol";
15
15
  import {MembershipStorage} from "./membership/MembershipStorage.sol";
16
16
  import {BanningStorage} from "./banning/BanningStorage.sol";
17
- import {AppAccountStorage} from "./account/AppAccountStorage.sol";
17
+ import {AppAccountBase} from "./account/AppAccountBase.sol";
18
18
  import {DependencyLib} from "./DependencyLib.sol";
19
19
 
20
20
  // contracts
21
21
  import {TokenOwnableBase} from "@towns-protocol/diamond/src/facets/ownable/token/TokenOwnableBase.sol";
22
22
 
23
- abstract contract Entitled is IEntitlementBase, TokenOwnableBase {
23
+ abstract contract Entitled is IEntitlementBase, TokenOwnableBase, AppAccountBase {
24
24
  using EnumerableSet for EnumerableSet.AddressSet;
25
25
  using EnumerableSet for EnumerableSet.UintSet;
26
26
  using CustomRevert for bytes4;
@@ -135,7 +135,7 @@ abstract contract Entitled is IEntitlementBase, TokenOwnableBase {
135
135
  address app = DependencyLib.getAppRegistry().getAppByClient(client);
136
136
  if (app == address(0)) return false;
137
137
 
138
- return AppAccountStorage.isAppEntitled(app, client, permission);
138
+ return _isAppEntitled(app, client, permission);
139
139
  }
140
140
 
141
141
  function _hasAnyBannedWallet(address[] memory wallets) internal view returns (bool) {
@@ -241,7 +241,27 @@ abstract contract AppAccountBase is
241
241
  address client,
242
242
  bytes32 permission
243
243
  ) internal view returns (bool) {
244
- return AppAccountStorage.isAppEntitled(module, client, permission);
244
+ bytes32 appId = AppAccountStorage.getLayout().appIdByApp[module];
245
+ if (appId == EMPTY_UID) return false;
246
+
247
+ IAppRegistry registry = DependencyLib.getAppRegistry();
248
+ if (registry.isAppBanned(module)) return false;
249
+
250
+ IAppRegistry.App memory app = registry.getAppById(appId);
251
+ if (app.appId == EMPTY_UID) return false;
252
+
253
+ (bool hasClientAccess, , bool isGroupActive) = ExecutorStorage.hasGroupAccess(
254
+ app.appId,
255
+ client
256
+ );
257
+ if (!hasClientAccess || !isGroupActive) return false;
258
+
259
+ uint256 permissionsLength = app.permissions.length;
260
+ for (uint256 i; i < permissionsLength; ++i) {
261
+ if (app.permissions[i] == permission) return true;
262
+ }
263
+
264
+ return false;
245
265
  }
246
266
 
247
267
  /// @notice Gets the ID of the installed app.
@@ -37,27 +37,4 @@ library AppAccountStorage {
37
37
  if (appId == EMPTY_UID) return app;
38
38
  return DependencyLib.getAppRegistry().getAppById(appId);
39
39
  }
40
-
41
- function isAppEntitled(
42
- address module,
43
- address client,
44
- bytes32 permission
45
- ) internal view returns (bool) {
46
- IAppRegistry.App memory app = getApp(module);
47
-
48
- if (app.appId == EMPTY_UID) return false;
49
-
50
- (bool hasClientAccess, , bool isGroupActive) = ExecutorStorage.hasGroupAccess(
51
- app.appId,
52
- client
53
- );
54
- if (!hasClientAccess || !isGroupActive) return false;
55
-
56
- uint256 permissionsLength = app.permissions.length;
57
- for (uint256 i; i < permissionsLength; ++i) {
58
- if (app.permissions[i] == permission) return true;
59
- }
60
-
61
- return false;
62
- }
63
40
  }
@@ -60,7 +60,7 @@ interface ITippingBase {
60
60
  TipRecipientType indexed recipientType,
61
61
  address currency,
62
62
  uint256 amount,
63
- uint256 tokenId // 0 if not a member tip
63
+ bytes data
64
64
  );
65
65
 
66
66
  // Maintain legacy event for backwards compatibility
@@ -84,7 +84,7 @@ abstract contract TippingBase is ITippingBase, PointsBase {
84
84
  TipRecipientType.Member,
85
85
  tipRequest.currency,
86
86
  tipAmount,
87
- tipRequest.tokenId
87
+ abi.encode(tipRequest.tokenId)
88
88
  );
89
89
  }
90
90
 
@@ -121,7 +121,7 @@ abstract contract TippingBase is ITippingBase, PointsBase {
121
121
  TipRecipientType.Member,
122
122
  params.currency,
123
123
  tipAmount,
124
- params.tokenId
124
+ params.metadata.data
125
125
  );
126
126
 
127
127
  emit Tip(
@@ -165,7 +165,7 @@ abstract contract TippingBase is ITippingBase, PointsBase {
165
165
  TipRecipientType.Bot,
166
166
  params.currency,
167
167
  tipAmount,
168
- 0
168
+ params.metadata.data
169
169
  );
170
170
  }
171
171