@towns-protocol/contracts 0.0.426 → 0.0.428
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/scripts/interactions/InteractPostDeploy.s.sol +0 -8
- package/src/factory/facets/fee/FeeManagerFacet.sol +36 -0
- package/src/spaces/facets/membership/IMembership.sol +0 -1
- package/src/spaces/facets/membership/MembershipBase.sol +26 -42
- package/src/spaces/facets/membership/MembershipFacet.sol +10 -14
- package/src/spaces/facets/membership/join/MembershipJoin.sol +18 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@towns-protocol/contracts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.428",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"clean": "forge clean",
|
|
6
6
|
"compile": "forge build",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@layerzerolabs/oapp-evm": "^0.3.2",
|
|
34
34
|
"@openzeppelin/merkle-tree": "^1.0.8",
|
|
35
35
|
"@prb/test": "^0.6.4",
|
|
36
|
-
"@towns-protocol/prettier-config": "^0.0.
|
|
36
|
+
"@towns-protocol/prettier-config": "^0.0.428",
|
|
37
37
|
"@wagmi/cli": "^2.2.0",
|
|
38
38
|
"forge-std": "github:foundry-rs/forge-std#v1.10.0",
|
|
39
39
|
"prettier": "^3.5.3",
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "8500096a8786765d97f2dbe88c17f24f051796db"
|
|
57
57
|
}
|
|
@@ -51,14 +51,6 @@ contract InteractPostDeploy is Interaction {
|
|
|
51
51
|
IImplementationRegistry(spaceFactory).addImplementation(baseRegistry);
|
|
52
52
|
IImplementationRegistry(spaceFactory).addImplementation(riverAirdrop);
|
|
53
53
|
IImplementationRegistry(spaceFactory).addImplementation(appRegistry);
|
|
54
|
-
IFeeManager(spaceFactory).setFeeConfig(
|
|
55
|
-
FeeTypesLib.TIP_MEMBER,
|
|
56
|
-
deployer,
|
|
57
|
-
FeeCalculationMethod.PERCENT,
|
|
58
|
-
50,
|
|
59
|
-
0,
|
|
60
|
-
true
|
|
61
|
-
);
|
|
62
54
|
ISubscriptionModule(subscriptionModule).setSpaceFactory(spaceFactory);
|
|
63
55
|
IMainnetDelegation(baseRegistry).setProxyDelegation(proxyDelegation);
|
|
64
56
|
IRewardsDistribution(baseRegistry).setRewardNotifier(deployer, true);
|
|
@@ -8,6 +8,9 @@ import {OwnableBase} from "@towns-protocol/diamond/src/facets/ownable/OwnableBas
|
|
|
8
8
|
import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
|
|
9
9
|
import {ReentrancyGuardTransient} from "solady/utils/ReentrancyGuardTransient.sol";
|
|
10
10
|
|
|
11
|
+
// libraries
|
|
12
|
+
import {FeeTypesLib} from "./FeeTypesLib.sol";
|
|
13
|
+
|
|
11
14
|
/// @title FeeManagerFacet
|
|
12
15
|
/// @notice Facet for unified fee management across the protocol
|
|
13
16
|
contract FeeManagerFacet is
|
|
@@ -28,6 +31,7 @@ contract FeeManagerFacet is
|
|
|
28
31
|
|
|
29
32
|
function __FeeManagerFacet__init_unchained(address protocolRecipient) internal {
|
|
30
33
|
_setProtocolFeeRecipient(protocolRecipient);
|
|
34
|
+
_setInitialFeeConfigs(protocolRecipient);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
@@ -100,4 +104,36 @@ contract FeeManagerFacet is
|
|
|
100
104
|
function getProtocolFeeRecipient() external view returns (address recipient) {
|
|
101
105
|
return _getProtocolFeeRecipient();
|
|
102
106
|
}
|
|
107
|
+
|
|
108
|
+
function _setInitialFeeConfigs(address protocolRecipient) internal {
|
|
109
|
+
// tipping fee
|
|
110
|
+
_setFeeConfig(
|
|
111
|
+
FeeTypesLib.TIP_MEMBER,
|
|
112
|
+
protocolRecipient,
|
|
113
|
+
FeeCalculationMethod.PERCENT,
|
|
114
|
+
50,
|
|
115
|
+
0,
|
|
116
|
+
true
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// membership fee
|
|
120
|
+
_setFeeConfig(
|
|
121
|
+
FeeTypesLib.MEMBERSHIP,
|
|
122
|
+
protocolRecipient,
|
|
123
|
+
FeeCalculationMethod.HYBRID,
|
|
124
|
+
1000, // 10%
|
|
125
|
+
0.0005 ether,
|
|
126
|
+
true
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// app installation fee
|
|
130
|
+
_setFeeConfig(
|
|
131
|
+
FeeTypesLib.APP_INSTALL,
|
|
132
|
+
protocolRecipient,
|
|
133
|
+
FeeCalculationMethod.HYBRID,
|
|
134
|
+
1000, // 10%
|
|
135
|
+
0.0005 ether,
|
|
136
|
+
true
|
|
137
|
+
);
|
|
138
|
+
}
|
|
103
139
|
}
|
|
@@ -63,7 +63,6 @@ interface IMembershipBase {
|
|
|
63
63
|
error Membership__InvalidPricingModule();
|
|
64
64
|
error Membership__AlreadyMember();
|
|
65
65
|
error Membership__InsufficientPayment();
|
|
66
|
-
error Membership__PriceTooLow();
|
|
67
66
|
error Membership__MaxSupplyReached();
|
|
68
67
|
error Membership__InvalidTokenId();
|
|
69
68
|
error Membership__NotExpired();
|
|
@@ -35,7 +35,6 @@ abstract contract MembershipBase is IMembershipBase {
|
|
|
35
35
|
$.freeAllocationEnabled = true;
|
|
36
36
|
|
|
37
37
|
if (info.price > 0) {
|
|
38
|
-
_verifyPrice(info.price);
|
|
39
38
|
if (info.freeAllocation > 0)
|
|
40
39
|
Membership__CannotSetFreeAllocationOnPaidSpace.selector.revertWith();
|
|
41
40
|
IMembershipPricing(info.pricingModule).setPrice(info.price);
|
|
@@ -66,14 +65,29 @@ abstract contract MembershipBase is IMembershipBase {
|
|
|
66
65
|
);
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
function _getMembershipPrice(
|
|
69
|
+
uint256 totalSupply
|
|
70
|
+
) internal view virtual returns (uint256 membershipPrice) {
|
|
71
|
+
address pricingModule = _getPricingModule();
|
|
72
|
+
if (pricingModule == address(0)) return 0;
|
|
73
|
+
uint256 freeAllocation = _getMembershipFreeAllocation();
|
|
74
|
+
membershipPrice = IMembershipPricing(pricingModule).getPrice(freeAllocation, totalSupply);
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
function _getProtocolFee(uint256 membershipPrice) internal view returns (uint256) {
|
|
70
78
|
IPlatformRequirements platform = _getPlatformRequirements();
|
|
79
|
+
uint256 baseFee = platform.getMembershipFee();
|
|
80
|
+
if (membershipPrice == 0) return baseFee;
|
|
81
|
+
uint256 bpsFee = BasisPoints.calculate(membershipPrice, platform.getMembershipBps());
|
|
82
|
+
return FixedPointMathLib.max(bpsFee, baseFee);
|
|
83
|
+
}
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
85
|
+
function _getTotalMembershipPayment(
|
|
86
|
+
uint256 membershipPrice
|
|
87
|
+
) internal view returns (uint256 totalRequired, uint256 protocolFee) {
|
|
88
|
+
protocolFee = _getProtocolFee(membershipPrice);
|
|
89
|
+
if (membershipPrice == 0) return (protocolFee, protocolFee);
|
|
90
|
+
return (membershipPrice + protocolFee, protocolFee);
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
function _transferIn(address from, uint256 amount) internal returns (uint256) {
|
|
@@ -152,31 +166,9 @@ abstract contract MembershipBase is IMembershipBase {
|
|
|
152
166
|
}
|
|
153
167
|
|
|
154
168
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
155
|
-
/*
|
|
169
|
+
/* RENEWAL */
|
|
156
170
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
157
171
|
|
|
158
|
-
function _verifyPrice(uint256 newPrice) internal view {
|
|
159
|
-
uint256 minFee = _getPlatformRequirements().getMembershipFee();
|
|
160
|
-
if (newPrice < minFee) Membership__PriceTooLow.selector.revertWith();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/// @dev Makes it virtual to allow other pricing strategies
|
|
164
|
-
function _getMembershipPrice(
|
|
165
|
-
uint256 totalSupply
|
|
166
|
-
) internal view virtual returns (uint256 membershipPrice) {
|
|
167
|
-
address pricingModule = _getPricingModule();
|
|
168
|
-
IPlatformRequirements platform = _getPlatformRequirements();
|
|
169
|
-
if (pricingModule == address(0)) return platform.getMembershipFee();
|
|
170
|
-
|
|
171
|
-
// get free allocation
|
|
172
|
-
uint256 freeAllocation = _getMembershipFreeAllocation();
|
|
173
|
-
membershipPrice = IMembershipPricing(pricingModule).getPrice(freeAllocation, totalSupply);
|
|
174
|
-
if (membershipPrice == 0) return 0;
|
|
175
|
-
|
|
176
|
-
uint256 minPrice = platform.getMembershipMinPrice();
|
|
177
|
-
if (membershipPrice < minPrice) return platform.getMembershipFee();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
172
|
function _setMembershipRenewalPrice(uint256 tokenId, uint256 pricePaid) internal {
|
|
181
173
|
MembershipStorage.layout().renewalPriceByTokenId[tokenId] = pricePaid;
|
|
182
174
|
}
|
|
@@ -186,22 +178,14 @@ abstract contract MembershipBase is IMembershipBase {
|
|
|
186
178
|
uint256 totalSupply
|
|
187
179
|
) internal view returns (uint256) {
|
|
188
180
|
MembershipStorage.Layout storage $ = MembershipStorage.layout();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
uint256 minFee = platform.getMembershipFee();
|
|
192
|
-
uint256 renewalPrice = $.renewalPriceByTokenId[tokenId];
|
|
181
|
+
uint256 lockedRenewalPrice = $.renewalPriceByTokenId[tokenId];
|
|
193
182
|
uint256 currentPrice = _getMembershipPrice(totalSupply);
|
|
194
183
|
|
|
195
|
-
// If no
|
|
196
|
-
if (
|
|
197
|
-
return FixedPointMathLib.max(currentPrice, minFee);
|
|
198
|
-
}
|
|
184
|
+
// If no locked price, use current price
|
|
185
|
+
if (lockedRenewalPrice == 0) return currentPrice;
|
|
199
186
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
// while maintaining their locked-in rate if prices increase
|
|
203
|
-
uint256 effectivePrice = FixedPointMathLib.min(renewalPrice, currentPrice);
|
|
204
|
-
return FixedPointMathLib.max(effectivePrice, minFee);
|
|
187
|
+
// Return the lower of the two prices (benefits user)
|
|
188
|
+
return FixedPointMathLib.min(lockedRenewalPrice, currentPrice);
|
|
205
189
|
}
|
|
206
190
|
|
|
207
191
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
@@ -98,25 +98,26 @@ contract MembershipFacet is IMembership, MembershipJoin, ReentrancyGuard, Facet
|
|
|
98
98
|
|
|
99
99
|
/// @inheritdoc IMembership
|
|
100
100
|
function setMembershipPrice(uint256 newPrice) external onlyOwner {
|
|
101
|
-
_verifyPrice(newPrice);
|
|
102
|
-
if (newPrice > 0 && _getMembershipFreeAllocation() > 0)
|
|
103
|
-
Membership__CannotSetPriceOnFreeSpace.selector.revertWith();
|
|
104
101
|
IMembershipPricing(_getPricingModule()).setPrice(newPrice);
|
|
105
102
|
}
|
|
106
103
|
|
|
107
104
|
/// @inheritdoc IMembership
|
|
108
|
-
function getMembershipPrice() external view returns (uint256) {
|
|
109
|
-
|
|
105
|
+
function getMembershipPrice() external view returns (uint256 totalRequired) {
|
|
106
|
+
(totalRequired, ) = _getTotalMembershipPayment(_getMembershipPrice(_totalSupply()));
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
/// @inheritdoc IMembership
|
|
113
|
-
function getMembershipRenewalPrice(
|
|
114
|
-
|
|
110
|
+
function getMembershipRenewalPrice(
|
|
111
|
+
uint256 tokenId
|
|
112
|
+
) external view returns (uint256 totalRequired) {
|
|
113
|
+
(totalRequired, ) = _getTotalMembershipPayment(
|
|
114
|
+
_getMembershipRenewalPrice(tokenId, _totalSupply())
|
|
115
|
+
);
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
/// @inheritdoc IMembership
|
|
118
|
-
function getProtocolFee() external view returns (uint256) {
|
|
119
|
-
|
|
119
|
+
function getProtocolFee() external view returns (uint256 protocolFee) {
|
|
120
|
+
(, protocolFee) = _getTotalMembershipPayment(_getMembershipPrice(_totalSupply()));
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
@@ -133,11 +134,6 @@ contract MembershipFacet is IMembership, MembershipJoin, ReentrancyGuard, Facet
|
|
|
133
134
|
Membership__InvalidFreeAllocation.selector.revertWith();
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
if (_getMembershipPrice(_totalSupply()) > 0) {
|
|
137
|
-
Membership__CannotSetFreeAllocationOnPaidSpace.selector.revertWith();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// verify newLimit is not more than the allowed platform limit
|
|
141
137
|
_verifyFreeAllocation(newAllocation);
|
|
142
138
|
_setMembershipFreeAllocation(newAllocation);
|
|
143
139
|
}
|
|
@@ -89,8 +89,8 @@ abstract contract MembershipJoin is
|
|
|
89
89
|
return joinDetails;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
(joinDetails.amountDue, joinDetails.shouldCharge) = (
|
|
92
|
+
(uint256 totalRequired, ) = _getTotalMembershipPayment(membershipPrice);
|
|
93
|
+
(joinDetails.amountDue, joinDetails.shouldCharge) = (totalRequired, true);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/// @notice Handles the process of joining a space
|
|
@@ -317,8 +317,7 @@ abstract contract MembershipJoin is
|
|
|
317
317
|
sender,
|
|
318
318
|
receiver,
|
|
319
319
|
joinDetails.amountDue,
|
|
320
|
-
ownerProceeds
|
|
321
|
-
joinDetails.basePrice
|
|
320
|
+
ownerProceeds
|
|
322
321
|
);
|
|
323
322
|
}
|
|
324
323
|
|
|
@@ -365,8 +364,7 @@ abstract contract MembershipJoin is
|
|
|
365
364
|
sender,
|
|
366
365
|
receiver,
|
|
367
366
|
joinDetails.amountDue,
|
|
368
|
-
ownerProceeds
|
|
369
|
-
joinDetails.basePrice
|
|
367
|
+
ownerProceeds
|
|
370
368
|
);
|
|
371
369
|
}
|
|
372
370
|
|
|
@@ -375,8 +373,7 @@ abstract contract MembershipJoin is
|
|
|
375
373
|
address payer,
|
|
376
374
|
address receiver,
|
|
377
375
|
uint256 paymentRequired,
|
|
378
|
-
uint256 ownerProceeds
|
|
379
|
-
uint256 membershipPrice
|
|
376
|
+
uint256 ownerProceeds
|
|
380
377
|
) internal {
|
|
381
378
|
// account for owner's proceeds
|
|
382
379
|
if (ownerProceeds != 0) _transferIn(payer, ownerProceeds);
|
|
@@ -384,7 +381,7 @@ abstract contract MembershipJoin is
|
|
|
384
381
|
_releaseCapturedValue(transactionId, paymentRequired);
|
|
385
382
|
_deleteCapturedData(transactionId);
|
|
386
383
|
|
|
387
|
-
_mintMembershipPoints(receiver,
|
|
384
|
+
_mintMembershipPoints(receiver, paymentRequired);
|
|
388
385
|
}
|
|
389
386
|
|
|
390
387
|
/// @notice Issues a membership token to the receiver
|
|
@@ -498,22 +495,25 @@ abstract contract MembershipJoin is
|
|
|
498
495
|
|
|
499
496
|
function _renewMembership(address payer, uint256 tokenId) internal {
|
|
500
497
|
address receiver = _ownerOf(tokenId);
|
|
501
|
-
|
|
502
498
|
if (receiver == address(0)) Membership__InvalidAddress.selector.revertWith();
|
|
503
499
|
|
|
504
500
|
uint256 duration = _getMembershipDuration();
|
|
505
|
-
uint256
|
|
501
|
+
uint256 basePrice = _getMembershipRenewalPrice(tokenId, _totalSupply());
|
|
502
|
+
(uint256 totalRequired, ) = _getTotalMembershipPayment(basePrice);
|
|
506
503
|
|
|
507
|
-
if (
|
|
504
|
+
if (totalRequired > msg.value) Membership__InvalidPayment.selector.revertWith();
|
|
508
505
|
|
|
509
|
-
|
|
506
|
+
// Collect protocol fee (transfers from payer)
|
|
507
|
+
uint256 protocolFee = _collectProtocolFee(payer, basePrice);
|
|
510
508
|
|
|
511
|
-
|
|
509
|
+
// Calculate owner proceeds (what goes to space owner)
|
|
510
|
+
uint256 ownerProceeds = totalRequired - protocolFee;
|
|
512
511
|
|
|
513
|
-
|
|
514
|
-
if (
|
|
512
|
+
// Transfer owner proceeds to contract
|
|
513
|
+
if (ownerProceeds > 0) _transferIn(payer, ownerProceeds);
|
|
515
514
|
|
|
516
|
-
|
|
515
|
+
// Handle excess payment
|
|
516
|
+
uint256 excess = msg.value - totalRequired;
|
|
517
517
|
if (excess > 0) {
|
|
518
518
|
CurrencyTransfer.transferCurrency(
|
|
519
519
|
_getMembershipCurrency(),
|
|
@@ -523,6 +523,7 @@ abstract contract MembershipJoin is
|
|
|
523
523
|
);
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
+
_mintMembershipPoints(receiver, totalRequired);
|
|
526
527
|
_renewSubscription(tokenId, uint64(duration));
|
|
527
528
|
}
|
|
528
529
|
|