@towns-protocol/contracts 0.0.441 → 0.0.443
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/docs/membership_architecture.md +237 -0
- package/package.json +3 -3
- package/scripts/deployments/diamonds/DeploySpace.s.sol +0 -7
- package/scripts/deployments/diamonds/DeploySpaceFactory.s.sol +2 -2
- package/scripts/deployments/facets/DeployMembership.s.sol +2 -1
- package/scripts/deployments/utils/DeployMockERC20.s.sol +1 -1
- package/scripts/deployments/utils/DeployMockUSDC.s.sol +19 -0
- package/scripts/interactions/InteractBaseAlpha.s.sol +3 -0
- package/scripts/interactions/InteractPostDeploy.s.sol +11 -0
- package/src/apps/facets/registry/AppRegistryBase.sol +4 -2
- package/src/apps/facets/registry/IAppRegistry.sol +1 -1
- package/src/factory/facets/architect/IArchitect.sol +1 -0
- package/src/factory/facets/create/CreateSpaceBase.sol +2 -9
- package/src/factory/facets/feature/FeatureManagerFacet.sol +32 -29
- package/src/factory/facets/feature/FeatureManagerMod.sol +248 -0
- package/src/factory/facets/feature/{IFeatureManagerFacet.sol → IFeatureManager.sol} +2 -35
- package/src/factory/facets/fee/FeeManagerFacet.sol +1 -1
- package/src/factory/facets/fee/FeeTypesLib.sol +8 -1
- package/src/spaces/facets/dispatcher/DispatcherBase.sol +13 -5
- package/src/spaces/facets/gated/EntitlementGated.sol +9 -5
- package/src/spaces/facets/membership/IMembership.sol +11 -17
- package/src/spaces/facets/membership/MembershipBase.sol +30 -59
- package/src/spaces/facets/membership/MembershipFacet.sol +19 -1
- package/src/spaces/facets/membership/MembershipStorage.sol +1 -0
- package/src/spaces/facets/membership/join/MembershipJoin.sol +192 -125
- package/src/spaces/facets/treasury/ITreasury.sol +2 -1
- package/src/spaces/facets/treasury/Treasury.sol +21 -24
- package/src/spaces/facets/xchain/SpaceEntitlementGated.sol +3 -4
- package/scripts/deployments/facets/DeployPrepayFacet.s.sol +0 -31
- package/scripts/interactions/InteractPrepay.s.sol +0 -30
- package/src/factory/facets/feature/FeatureManagerBase.sol +0 -152
- package/src/factory/facets/feature/FeatureManagerStorage.sol +0 -47
- package/src/spaces/facets/prepay/IPrepay.sol +0 -44
- package/src/spaces/facets/prepay/PrepayBase.sol +0 -27
- package/src/spaces/facets/prepay/PrepayFacet.sol +0 -65
- package/src/spaces/facets/prepay/PrepayStorage.sol +0 -26
|
@@ -2,53 +2,50 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
-
|
|
6
5
|
import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
|
|
7
|
-
import {IMembershipBase} from "
|
|
8
|
-
import {ITreasury} from "
|
|
6
|
+
import {IMembershipBase} from "../membership/IMembership.sol";
|
|
7
|
+
import {ITreasury} from "./ITreasury.sol";
|
|
9
8
|
|
|
10
9
|
// libraries
|
|
11
|
-
import {
|
|
10
|
+
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
|
|
11
|
+
import {CurrencyTransfer} from "../../../utils/libraries/CurrencyTransfer.sol";
|
|
12
|
+
import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
|
|
12
13
|
|
|
13
14
|
// contracts
|
|
14
|
-
|
|
15
15
|
import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
|
|
16
16
|
import {TokenOwnableBase} from "@towns-protocol/diamond/src/facets/ownable/token/TokenOwnableBase.sol";
|
|
17
|
-
import {MembershipStorage} from "src/spaces/facets/membership/MembershipStorage.sol";
|
|
18
|
-
import {CustomRevert} from "src/utils/libraries/CustomRevert.sol";
|
|
19
17
|
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
|
|
20
18
|
|
|
21
19
|
contract Treasury is TokenOwnableBase, ReentrancyGuard, Facet, ITreasury {
|
|
20
|
+
using CustomRevert for bytes4;
|
|
21
|
+
using SafeTransferLib for address;
|
|
22
|
+
|
|
22
23
|
function __Treasury_init() external onlyInitializing {
|
|
23
24
|
_addInterface(type(IERC1155Receiver).interfaceId);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
function withdraw(address account) external onlyOwner nonReentrant {
|
|
28
|
-
if (account == address(0))
|
|
29
|
-
CustomRevert.revertWith(IMembershipBase.Membership__InvalidAddress.selector);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// get the balance
|
|
33
|
-
uint256 balance = address(this).balance;
|
|
27
|
+
/// @inheritdoc ITreasury
|
|
28
|
+
function withdraw(address currency, address account) external onlyOwner nonReentrant {
|
|
29
|
+
if (account == address(0)) IMembershipBase.Membership__InvalidAddress.selector.revertWith();
|
|
34
30
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
// Get balance based on currency type
|
|
32
|
+
uint256 balance = currency == CurrencyTransfer.NATIVE_TOKEN
|
|
33
|
+
? address(this).balance
|
|
34
|
+
: currency.balanceOf(address(this));
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
// Verify the balance is not 0
|
|
37
|
+
if (balance == 0) IMembershipBase.Membership__InsufficientPayment.selector.revertWith();
|
|
41
38
|
|
|
42
39
|
CurrencyTransfer.transferCurrency(currency, address(this), account, balance);
|
|
43
40
|
|
|
44
|
-
emit IMembershipBase.MembershipWithdrawal(account, balance);
|
|
41
|
+
emit IMembershipBase.MembershipWithdrawal(currency, account, balance);
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
48
45
|
/* Hooks */
|
|
49
46
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
50
47
|
|
|
51
|
-
|
|
48
|
+
/// @inheritdoc ITreasury
|
|
52
49
|
function onERC721Received(
|
|
53
50
|
address,
|
|
54
51
|
address,
|
|
@@ -58,7 +55,7 @@ contract Treasury is TokenOwnableBase, ReentrancyGuard, Facet, ITreasury {
|
|
|
58
55
|
return this.onERC721Received.selector;
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
|
|
58
|
+
/// @inheritdoc ITreasury
|
|
62
59
|
function onERC1155Received(
|
|
63
60
|
address,
|
|
64
61
|
address,
|
|
@@ -69,7 +66,7 @@ contract Treasury is TokenOwnableBase, ReentrancyGuard, Facet, ITreasury {
|
|
|
69
66
|
return this.onERC1155Received.selector;
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
|
|
69
|
+
/// @inheritdoc ITreasury
|
|
73
70
|
function onERC1155BatchReceived(
|
|
74
71
|
address,
|
|
75
72
|
address,
|
|
@@ -4,15 +4,12 @@ pragma solidity ^0.8.23;
|
|
|
4
4
|
// interfaces
|
|
5
5
|
import {IMembership} from "../membership/IMembership.sol";
|
|
6
6
|
|
|
7
|
-
// libraries
|
|
8
|
-
|
|
9
7
|
// contracts
|
|
10
8
|
import {EntitlementGated} from "../gated/EntitlementGated.sol";
|
|
11
9
|
import {MembershipJoin} from "../membership/join/MembershipJoin.sol";
|
|
12
10
|
|
|
13
11
|
/// @title SpaceEntitlementGated
|
|
14
12
|
/// @notice Handles entitlement-gated access to spaces and membership token issuance
|
|
15
|
-
/// @dev Inherits from ISpaceEntitlementGatedBase, MembershipJoin, and EntitlementGated
|
|
16
13
|
contract SpaceEntitlementGated is MembershipJoin, EntitlementGated {
|
|
17
14
|
/// @notice Processes the result of an entitlement check
|
|
18
15
|
/// @dev This function is called when the result of an entitlement check is posted
|
|
@@ -34,7 +31,9 @@ contract SpaceEntitlementGated is MembershipJoin, EntitlementGated {
|
|
|
34
31
|
if (result == NodeVoteStatus.PASSED) {
|
|
35
32
|
PricingDetails memory joinDetails = _getPricingDetails();
|
|
36
33
|
|
|
37
|
-
if (joinDetails.shouldCharge) {
|
|
34
|
+
if (!joinDetails.shouldCharge) {
|
|
35
|
+
_afterChargeForJoinSpace(transactionId, receiver, 0);
|
|
36
|
+
} else {
|
|
38
37
|
uint256 payment = _getCapturedValue(transactionId);
|
|
39
38
|
|
|
40
39
|
if (payment < joinDetails.amountDue) {
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.23;
|
|
3
|
-
|
|
4
|
-
//interfaces
|
|
5
|
-
import {IDiamond} from "@towns-protocol/diamond/src/Diamond.sol";
|
|
6
|
-
import {IPrepay} from "src/spaces/facets/prepay/IPrepay.sol";
|
|
7
|
-
|
|
8
|
-
//libraries
|
|
9
|
-
import {LibDeploy} from "@towns-protocol/diamond/src/utils/LibDeploy.sol";
|
|
10
|
-
|
|
11
|
-
//contracts
|
|
12
|
-
|
|
13
|
-
library DeployPrepayFacet {
|
|
14
|
-
function selectors() internal pure returns (bytes4[] memory res) {
|
|
15
|
-
res = new bytes4[](3);
|
|
16
|
-
res[0] = IPrepay.prepayMembership.selector;
|
|
17
|
-
res[1] = IPrepay.prepaidMembershipSupply.selector;
|
|
18
|
-
res[2] = IPrepay.calculateMembershipPrepayFee.selector;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function makeCut(
|
|
22
|
-
address facetAddress,
|
|
23
|
-
IDiamond.FacetCutAction action
|
|
24
|
-
) internal pure returns (IDiamond.FacetCut memory) {
|
|
25
|
-
return IDiamond.FacetCut(facetAddress, action, selectors());
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function deploy() internal returns (address) {
|
|
29
|
-
return LibDeploy.deployCode("PrepayFacet.sol", "");
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.23;
|
|
3
|
-
|
|
4
|
-
// interfaces
|
|
5
|
-
|
|
6
|
-
// libraries
|
|
7
|
-
|
|
8
|
-
// contracts
|
|
9
|
-
import {Interaction} from "scripts/common/Interaction.s.sol";
|
|
10
|
-
import {IPrepay} from "src/spaces/facets/prepay/IPrepay.sol";
|
|
11
|
-
|
|
12
|
-
// debuggging
|
|
13
|
-
import {console} from "forge-std/console.sol";
|
|
14
|
-
|
|
15
|
-
contract InteractPrepay is Interaction {
|
|
16
|
-
IPrepay prepay = IPrepay(0x0000000000000000000000000000000000000000);
|
|
17
|
-
|
|
18
|
-
function __interact(address deployer) internal override {
|
|
19
|
-
uint256 expectedAmount = 1000;
|
|
20
|
-
uint256 totalAmount = prepay.calculateMembershipPrepayFee(expectedAmount);
|
|
21
|
-
|
|
22
|
-
console.log("paying:", totalAmount);
|
|
23
|
-
|
|
24
|
-
vm.startBroadcast(deployer);
|
|
25
|
-
IPrepay(prepay).prepayMembership{value: totalAmount}(expectedAmount);
|
|
26
|
-
vm.stopBroadcast();
|
|
27
|
-
|
|
28
|
-
console.log("prepaidSupply", prepay.prepaidMembershipSupply());
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.23;
|
|
3
|
-
|
|
4
|
-
// interfaces
|
|
5
|
-
import {IFeatureManagerFacetBase} from "./IFeatureManagerFacet.sol";
|
|
6
|
-
import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
|
|
7
|
-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
8
|
-
|
|
9
|
-
// libraries
|
|
10
|
-
import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
|
|
11
|
-
import {FeatureManagerStorage} from "./FeatureManagerStorage.sol";
|
|
12
|
-
import {FeatureCondition} from "./IFeatureManagerFacet.sol";
|
|
13
|
-
import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
|
|
14
|
-
import {LibCall} from "solady/utils/LibCall.sol";
|
|
15
|
-
|
|
16
|
-
// contracts
|
|
17
|
-
|
|
18
|
-
abstract contract FeatureManagerBase is IFeatureManagerFacetBase {
|
|
19
|
-
using EnumerableSetLib for EnumerableSetLib.Bytes32Set;
|
|
20
|
-
using CustomRevert for bytes4;
|
|
21
|
-
|
|
22
|
-
/// @notice Creates or updates a feature condition based on the create flag
|
|
23
|
-
/// @dev Validates token interface compliance before storing the condition
|
|
24
|
-
/// @param featureId The unique identifier for the feature
|
|
25
|
-
/// @param condition The condition struct containing token, threshold, active status, and extra data
|
|
26
|
-
/// @param create True to create new feature, false to update existing
|
|
27
|
-
function _upsertFeatureCondition(
|
|
28
|
-
bytes32 featureId,
|
|
29
|
-
FeatureCondition calldata condition,
|
|
30
|
-
bool create
|
|
31
|
-
) internal {
|
|
32
|
-
_validateToken(condition);
|
|
33
|
-
|
|
34
|
-
FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
|
|
35
|
-
if (!create) {
|
|
36
|
-
if (!$.featureIds.contains(featureId)) FeatureNotActive.selector.revertWith();
|
|
37
|
-
} else {
|
|
38
|
-
if (!$.featureIds.add(featureId)) FeatureAlreadyExists.selector.revertWith();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
$.conditions[featureId] = condition;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/// @notice Disables a feature by setting its active flag to false
|
|
45
|
-
/// @dev This does not delete the condition, only deactivates it
|
|
46
|
-
/// @param featureId The unique identifier for the feature to disable
|
|
47
|
-
function _disableFeatureCondition(bytes32 featureId) internal {
|
|
48
|
-
FeatureCondition storage condition = _getFeatureCondition(featureId);
|
|
49
|
-
if (!condition.active) FeatureNotActive.selector.revertWith();
|
|
50
|
-
condition.active = false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/// @notice Retrieves the condition for a specific feature
|
|
54
|
-
/// @dev Returns the complete condition struct with all parameters
|
|
55
|
-
/// @param featureId The unique identifier for the feature
|
|
56
|
-
/// @return The complete condition configuration for the feature
|
|
57
|
-
function _getFeatureCondition(
|
|
58
|
-
bytes32 featureId
|
|
59
|
-
) internal view returns (FeatureCondition storage) {
|
|
60
|
-
return FeatureManagerStorage.getLayout().conditions[featureId];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/// @notice Retrieves all feature conditions
|
|
64
|
-
/// @dev Returns an array of all feature conditions
|
|
65
|
-
/// @return conditions An array of all feature conditions
|
|
66
|
-
function _getFeatureConditions() internal view returns (FeatureCondition[] memory conditions) {
|
|
67
|
-
FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
|
|
68
|
-
// Use values() over at() for full iteration - avoids bounds checking overhead
|
|
69
|
-
bytes32[] memory ids = $.featureIds.values();
|
|
70
|
-
uint256 featureCount = ids.length;
|
|
71
|
-
|
|
72
|
-
conditions = new FeatureCondition[](featureCount);
|
|
73
|
-
for (uint256 i; i < featureCount; ++i) {
|
|
74
|
-
conditions[i] = $.conditions[ids[i]];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/// @notice Retrieves all feature conditions for a specific space
|
|
79
|
-
/// @dev Returns an array of all feature conditions that are active for the space
|
|
80
|
-
/// @return conditions An array of all feature conditions that are active for the space
|
|
81
|
-
function _getFeatureConditionsForSpace(
|
|
82
|
-
address space
|
|
83
|
-
) internal view returns (FeatureCondition[] memory conditions) {
|
|
84
|
-
FeatureManagerStorage.Layout storage $ = FeatureManagerStorage.getLayout();
|
|
85
|
-
// Use values() over at() for full iteration - avoids bounds checking overhead
|
|
86
|
-
bytes32[] memory ids = $.featureIds.values();
|
|
87
|
-
uint256 featureCount = ids.length;
|
|
88
|
-
|
|
89
|
-
// Gas optimization: Allocate full array then resize (memory cheaper than storage reads)
|
|
90
|
-
conditions = new FeatureCondition[](featureCount);
|
|
91
|
-
uint256 index;
|
|
92
|
-
|
|
93
|
-
for (uint256 i; i < featureCount; ++i) {
|
|
94
|
-
FeatureCondition storage cond = $.conditions[ids[i]];
|
|
95
|
-
|
|
96
|
-
if (_isValidCondition(cond, space)) {
|
|
97
|
-
conditions[index++] = cond;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Resize array to actual number of valid conditions
|
|
102
|
-
assembly ("memory-safe") {
|
|
103
|
-
mstore(conditions, index)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function _validateToken(FeatureCondition calldata condition) internal view {
|
|
108
|
-
if (condition.token == address(0)) InvalidToken.selector.revertWith();
|
|
109
|
-
|
|
110
|
-
// Check if the token implements IVotes.getVotes with proper return data
|
|
111
|
-
(bool success, bool exceededMaxCopy, bytes memory data) = LibCall.tryStaticCall(
|
|
112
|
-
condition.token,
|
|
113
|
-
gasleft(),
|
|
114
|
-
32,
|
|
115
|
-
abi.encodeCall(IVotes.getVotes, (address(this)))
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
if (!success || exceededMaxCopy || data.length != 32)
|
|
119
|
-
InvalidInterface.selector.revertWith();
|
|
120
|
-
|
|
121
|
-
// Check if the token implements ERC20.totalSupply with proper return data
|
|
122
|
-
(success, exceededMaxCopy, data) = LibCall.tryStaticCall(
|
|
123
|
-
condition.token,
|
|
124
|
-
gasleft(),
|
|
125
|
-
32,
|
|
126
|
-
abi.encodeCall(IERC20.totalSupply, ())
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
if (!success || exceededMaxCopy || data.length != 32)
|
|
130
|
-
InvalidInterface.selector.revertWith();
|
|
131
|
-
|
|
132
|
-
uint256 totalSupply = abi.decode(data, (uint256));
|
|
133
|
-
if (totalSupply == 0) InvalidTotalSupply.selector.revertWith();
|
|
134
|
-
if (condition.threshold > totalSupply) InvalidThreshold.selector.revertWith();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/// @notice Checks if a condition should be included for a given space
|
|
138
|
-
/// @dev Returns true if the condition is active, has a valid token, and meets the threshold
|
|
139
|
-
/// @param condition The condition to check
|
|
140
|
-
/// @param space The space address to check against
|
|
141
|
-
/// @return True if the condition should be included, false otherwise
|
|
142
|
-
function _isValidCondition(
|
|
143
|
-
FeatureCondition storage condition,
|
|
144
|
-
address space
|
|
145
|
-
) internal view returns (bool) {
|
|
146
|
-
if (!condition.active) return false;
|
|
147
|
-
address token = condition.token;
|
|
148
|
-
if (token == address(0)) return false;
|
|
149
|
-
uint256 votes = IVotes(token).getVotes(space);
|
|
150
|
-
return votes >= condition.threshold;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.23;
|
|
3
|
-
|
|
4
|
-
// interfaces
|
|
5
|
-
|
|
6
|
-
// libraries
|
|
7
|
-
import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
|
|
8
|
-
|
|
9
|
-
// contracts
|
|
10
|
-
|
|
11
|
-
/// @notice Represents a condition for feature activation
|
|
12
|
-
/// @dev Used to determine if a feature should be enabled based on token voting power
|
|
13
|
-
/// @param token The address of the token used for voting (must implement IVotes)
|
|
14
|
-
/// @param threshold The minimum voting power (votes) required to activate the feature
|
|
15
|
-
/// @param active Whether the condition is currently active
|
|
16
|
-
/// @param extraData Additional data that might be used for specialized condition logic
|
|
17
|
-
struct FeatureCondition {
|
|
18
|
-
address token;
|
|
19
|
-
bool active;
|
|
20
|
-
uint256 threshold;
|
|
21
|
-
bytes extraData;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/// @title FeatureManager
|
|
25
|
-
library FeatureManagerStorage {
|
|
26
|
-
using EnumerableSetLib for EnumerableSetLib.Bytes32Set;
|
|
27
|
-
|
|
28
|
-
// keccak256(abi.encode(uint256(keccak256("factory.facets.feature.manager.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
29
|
-
bytes32 constant DEFAULT_STORAGE_SLOT =
|
|
30
|
-
0x20c456a8ea15fcf7965033c954321ffd9dc82a2c65f686a77e2a67da65c29000;
|
|
31
|
-
|
|
32
|
-
/// @notice Storage layout for the FeatureManager
|
|
33
|
-
/// @dev Maps feature IDs to their activation conditions
|
|
34
|
-
/// @custom:storage-location erc7201:towns.storage.FeatureManager
|
|
35
|
-
struct Layout {
|
|
36
|
-
// Feature IDs
|
|
37
|
-
EnumerableSetLib.Bytes32Set featureIds;
|
|
38
|
-
// Feature ID => Condition
|
|
39
|
-
mapping(bytes32 featureId => FeatureCondition condition) conditions;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function getLayout() internal pure returns (Layout storage $) {
|
|
43
|
-
assembly {
|
|
44
|
-
$.slot := DEFAULT_STORAGE_SLOT
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.23;
|
|
3
|
-
|
|
4
|
-
// interfaces
|
|
5
|
-
|
|
6
|
-
// libraries
|
|
7
|
-
|
|
8
|
-
// contracts
|
|
9
|
-
interface IPrepayBase {
|
|
10
|
-
// =============================================================
|
|
11
|
-
// ERRORS
|
|
12
|
-
// =============================================================
|
|
13
|
-
error Prepay__InvalidSupplyAmount();
|
|
14
|
-
error Prepay__InvalidAmount();
|
|
15
|
-
error Prepay__InvalidAddress();
|
|
16
|
-
error Prepay__InvalidMembership();
|
|
17
|
-
error Prepay__NotAllowed();
|
|
18
|
-
|
|
19
|
-
// =============================================================
|
|
20
|
-
// EVENTS
|
|
21
|
-
// =============================================================
|
|
22
|
-
event Prepay__Prepaid(uint256 supply);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface IPrepay is IPrepayBase {
|
|
26
|
-
/**
|
|
27
|
-
* @notice Prepay a membership
|
|
28
|
-
* @param supply The amount of memberships to prepay
|
|
29
|
-
*/
|
|
30
|
-
function prepayMembership(uint256 supply) external payable;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @notice Get the prepaid supply
|
|
34
|
-
* @return The remaining prepaid supply
|
|
35
|
-
*/
|
|
36
|
-
function prepaidMembershipSupply() external view returns (uint256);
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @notice Calculate the prepay fee for a given supply
|
|
40
|
-
* @param supply The supply to calculate the fee for
|
|
41
|
-
* @return The fee
|
|
42
|
-
*/
|
|
43
|
-
function calculateMembershipPrepayFee(uint256 supply) external view returns (uint256);
|
|
44
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.23;
|
|
3
|
-
|
|
4
|
-
// interfaces
|
|
5
|
-
import {IPrepayBase} from "./IPrepay.sol";
|
|
6
|
-
|
|
7
|
-
// libraries
|
|
8
|
-
import {PrepayStorage} from "./PrepayStorage.sol";
|
|
9
|
-
|
|
10
|
-
// contracts
|
|
11
|
-
|
|
12
|
-
abstract contract PrepayBase is IPrepayBase {
|
|
13
|
-
function _addPrepay(uint256 supply) internal {
|
|
14
|
-
PrepayStorage.Layout storage ds = PrepayStorage.layout();
|
|
15
|
-
ds.supply += supply;
|
|
16
|
-
emit Prepay__Prepaid(supply);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function _reducePrepay(uint256 supply) internal {
|
|
20
|
-
PrepayStorage.Layout storage ds = PrepayStorage.layout();
|
|
21
|
-
ds.supply -= supply;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function _getPrepaidSupply() internal view returns (uint256) {
|
|
25
|
-
return PrepayStorage.layout().supply;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.23;
|
|
3
|
-
|
|
4
|
-
// interfaces
|
|
5
|
-
|
|
6
|
-
import {IPlatformRequirements} from "src/factory/facets/platform/requirements/IPlatformRequirements.sol";
|
|
7
|
-
import {IPrepay} from "src/spaces/facets/prepay/IPrepay.sol";
|
|
8
|
-
|
|
9
|
-
// libraries
|
|
10
|
-
import {MembershipStorage} from "src/spaces/facets/membership/MembershipStorage.sol";
|
|
11
|
-
import {CurrencyTransfer} from "src/utils/libraries/CurrencyTransfer.sol";
|
|
12
|
-
|
|
13
|
-
// contracts
|
|
14
|
-
import {PrepayBase} from "./PrepayBase.sol";
|
|
15
|
-
|
|
16
|
-
import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
|
|
17
|
-
import {Entitled} from "src/spaces/facets/Entitled.sol";
|
|
18
|
-
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
|
|
19
|
-
|
|
20
|
-
contract PrepayFacet is IPrepay, PrepayBase, ReentrancyGuard, Entitled, Facet {
|
|
21
|
-
function __PrepayFacet_init() external onlyInitializing {
|
|
22
|
-
_addInterface(type(IPrepay).interfaceId);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function prepayMembership(uint256 supply) external payable nonReentrant {
|
|
26
|
-
_validatePrepayCaller();
|
|
27
|
-
if (supply == 0) revert Prepay__InvalidSupplyAmount();
|
|
28
|
-
|
|
29
|
-
MembershipStorage.Layout storage ds = MembershipStorage.layout();
|
|
30
|
-
IPlatformRequirements platform = IPlatformRequirements(ds.spaceFactory);
|
|
31
|
-
|
|
32
|
-
uint256 cost = supply * platform.getMembershipFee();
|
|
33
|
-
|
|
34
|
-
// validate payment covers membership fee
|
|
35
|
-
if (msg.value != cost) revert Prepay__InvalidAmount();
|
|
36
|
-
|
|
37
|
-
// add prepay
|
|
38
|
-
_addPrepay(supply);
|
|
39
|
-
|
|
40
|
-
// transfer fee to platform recipient
|
|
41
|
-
address currency = ds.membershipCurrency;
|
|
42
|
-
address platformRecipient = platform.getFeeRecipient();
|
|
43
|
-
CurrencyTransfer.transferCurrency(
|
|
44
|
-
currency,
|
|
45
|
-
msg.sender, // from
|
|
46
|
-
platformRecipient, // to
|
|
47
|
-
cost
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function prepaidMembershipSupply() external view returns (uint256) {
|
|
52
|
-
return _getPrepaidSupply();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function calculateMembershipPrepayFee(uint256 supply) external view returns (uint256) {
|
|
56
|
-
MembershipStorage.Layout storage ds = MembershipStorage.layout();
|
|
57
|
-
IPlatformRequirements platform = IPlatformRequirements(ds.spaceFactory);
|
|
58
|
-
return supply * platform.getMembershipFee();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function _validatePrepayCaller() internal view {
|
|
62
|
-
address spaceFactory = MembershipStorage.layout().spaceFactory;
|
|
63
|
-
if (msg.sender != spaceFactory && msg.sender != _owner()) revert Prepay__NotAllowed();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.23;
|
|
3
|
-
|
|
4
|
-
// interfaces
|
|
5
|
-
|
|
6
|
-
// libraries
|
|
7
|
-
|
|
8
|
-
// contracts
|
|
9
|
-
|
|
10
|
-
library PrepayStorage {
|
|
11
|
-
// keccak256(abi.encode(uint256(keccak256("spaces.facets.prepay.storage")) - 1)) &
|
|
12
|
-
// ~bytes32(uint256(0xff))
|
|
13
|
-
bytes32 constant STORAGE_SLOT =
|
|
14
|
-
0x097b4f25b64e012d0cf55f67e9b34fe5d57f15b11b95baa4ddd136b424967c00;
|
|
15
|
-
|
|
16
|
-
struct Layout {
|
|
17
|
-
uint256 supply;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function layout() internal pure returns (Layout storage l) {
|
|
21
|
-
bytes32 slot = STORAGE_SLOT;
|
|
22
|
-
assembly {
|
|
23
|
-
l.slot := slot
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|