@towns-protocol/contracts 0.0.453 → 1.0.1
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 +7 -7
- package/scripts/common/Interaction.s.sol +3 -3
- package/scripts/deployments/diamonds/DeployBaseRegistry.s.sol +1 -4
- package/scripts/deployments/diamonds/DeployL1Resolver.s.sol +155 -0
- package/scripts/deployments/diamonds/DeployL2Registrar.s.sol +173 -0
- package/scripts/deployments/diamonds/DeployL2Resolver.s.sol +196 -0
- package/scripts/deployments/diamonds/DeploySpace.s.sol +1 -9
- package/scripts/deployments/diamonds/DeploySpaceFactory.s.sol +1 -5
- package/scripts/deployments/facets/DeployAddrResolverFacet.s.sol +36 -0
- package/scripts/deployments/facets/DeployAppFactoryFacet.s.sol +3 -3
- package/scripts/deployments/facets/DeployAppInstallerFacet.s.sol +3 -3
- package/scripts/deployments/facets/DeployAppRegistryFacet.s.sol +3 -3
- package/scripts/deployments/facets/DeployArchitect.s.sol +3 -3
- package/scripts/deployments/facets/DeployAttestationRegistry.s.sol +3 -3
- package/scripts/deployments/facets/DeployContentHashResolverFacet.s.sol +34 -0
- package/scripts/deployments/facets/DeployERC721ANonTransferable.s.sol +3 -3
- package/scripts/deployments/facets/DeployExtendedResolverFacet.s.sol +33 -0
- package/scripts/deployments/facets/DeployFeatureManager.s.sol +3 -3
- package/scripts/deployments/facets/DeployL1ResolverFacet.s.sol +61 -0
- package/scripts/deployments/facets/DeployL2RegistrarFacet.s.sol +56 -0
- package/scripts/deployments/facets/DeployL2RegistryFacet.s.sol +71 -0
- package/scripts/deployments/facets/DeployMainnetDelegation.s.sol +3 -3
- package/scripts/deployments/facets/DeployMembershipMetadata.s.sol +3 -3
- package/scripts/deployments/facets/DeployMerkleAirdrop.s.sol +3 -3
- package/scripts/deployments/facets/DeployMetadata.s.sol +3 -3
- package/scripts/deployments/facets/DeployMockLegacyArchitect.s.sol +3 -3
- package/scripts/deployments/facets/DeployNodeOperator.s.sol +3 -3
- package/scripts/deployments/facets/DeployPartnerRegistry.s.sol +3 -3
- package/scripts/deployments/facets/DeployPricingModules.s.sol +3 -3
- package/scripts/deployments/facets/DeploySchemaRegistry.s.sol +3 -3
- package/scripts/deployments/facets/DeploySpaceFactoryInit.s.sol +3 -3
- package/scripts/deployments/facets/DeploySpaceOwnerFacet.s.sol +3 -3
- package/scripts/deployments/facets/DeployTextResolverFacet.s.sol +34 -0
- package/scripts/deployments/facets/DeployTokenMigration.s.sol +3 -3
- package/scripts/deployments/facets/DeployXChain.s.sol +3 -3
- package/scripts/deployments/utils/DeployAccountFactory.s.sol +3 -3
- package/scripts/deployments/utils/DeployEntitlementGatedExample.s.sol +3 -3
- package/scripts/deployments/utils/DeployEntrypoint.s.sol +3 -3
- package/scripts/deployments/utils/DeployMember.s.sol +3 -3
- package/scripts/deployments/utils/DeployMockLegacyMembership.s.sol +3 -3
- package/scripts/deployments/utils/DeployMockMessenger.s.sol +3 -3
- package/scripts/deployments/utils/DeploySpaceProxyInitializer.s.sol +3 -3
- package/scripts/deployments/utils/DeployTieredLogPricingV2.s.sol +3 -3
- package/scripts/deployments/utils/DeployTieredLogPricingV3.s.sol +3 -3
- package/scripts/deployments/utils/DeployTownsBase.s.sol +3 -3
- package/scripts/deployments/utils/DeployTownsMainnet.s.sol +3 -3
- package/scripts/deployments/utils/pricing/TieredLogPricing.s.sol +3 -3
- package/scripts/interactions/InteractAirdrop.s.sol +3 -3
- package/scripts/interactions/InteractBaseBridge.s.sol +3 -3
- package/scripts/interactions/InteractRegisterApp.s.sol +2 -2
- package/scripts/interactions/InteractRiverRegistrySetTrimByStreamId.s.sol +44 -0
- package/scripts/interactions/helpers/RiverConfigValues.sol +1 -2
- package/src/account/facets/app/AppManagerFacet.sol +43 -14
- package/src/account/facets/app/AppManagerMod.sol +311 -278
- package/src/account/facets/hub/AccountHubFacet.sol +12 -11
- package/src/account/facets/hub/AccountHubMod.sol +117 -116
- package/src/account/facets/tipping/AccountTippingFacet.sol +10 -9
- package/src/account/facets/tipping/AccountTippingMod.sol +133 -124
- package/src/apps/modules/subscription/SubscriptionModuleStorage.sol +1 -1
- package/src/domains/facets/l1/IL1ResolverService.sol +20 -0
- package/src/domains/facets/l1/L1ResolverFacet.sol +100 -0
- package/src/domains/facets/l1/L1ResolverMod.sol +245 -0
- package/src/domains/facets/l2/AddrResolverFacet.sol +59 -0
- package/src/domains/facets/l2/ContentHashResolverFacet.sol +41 -0
- package/src/domains/facets/l2/ExtendedResolverFacet.sol +38 -0
- package/src/domains/facets/l2/IL2Registry.sol +79 -0
- package/src/domains/facets/l2/L2RegistryFacet.sol +203 -0
- package/src/domains/facets/l2/TextResolverFacet.sol +43 -0
- package/src/domains/facets/l2/modules/AddrResolverMod.sol +110 -0
- package/src/domains/facets/l2/modules/ContentHashResolverMod.sol +60 -0
- package/src/domains/facets/l2/modules/L2RegistryMod.sol +286 -0
- package/src/domains/facets/l2/modules/TextResolverMod.sol +62 -0
- package/src/domains/facets/l2/modules/VersionRecordMod.sol +42 -0
- package/src/domains/facets/registrar/IL2Registrar.sol +57 -0
- package/src/domains/facets/registrar/L2RegistrarFacet.sol +127 -0
- package/src/domains/facets/registrar/L2RegistrarMod.sol +224 -0
- package/src/domains/hooks/DomainFeeHook.sol +212 -0
- package/src/factory/facets/fee/FeeTypesLib.sol +3 -0
- package/src/river/registry/facets/stream/StreamRegistry.sol +4 -1
- package/src/spaces/facets/ProtocolFeeLib.sol +56 -1
- package/src/tokens/Member.sol +3 -4
- package/LICENSE.txt +0 -21
- package/scripts/interactions/InteractRiverRegistrySetFreq.s.sol +0 -27
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.29;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
import {IL2Registry} from "../l2/IL2Registry.sol";
|
|
6
|
+
import {IModularAccount} from "@erc6900/reference-implementation/interfaces/IModularAccount.sol";
|
|
7
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
8
|
+
import {IFeeManager} from "../../../factory/facets/fee/IFeeManager.sol";
|
|
9
|
+
|
|
10
|
+
// libraries
|
|
11
|
+
import {LibString} from "solady/utils/LibString.sol";
|
|
12
|
+
import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
|
|
13
|
+
import {FeeTypesLib} from "../../../factory/facets/fee/FeeTypesLib.sol";
|
|
14
|
+
import {ProtocolFeeLib} from "../../../spaces/facets/ProtocolFeeLib.sol";
|
|
15
|
+
|
|
16
|
+
// contracts
|
|
17
|
+
import {AddrResolverFacet} from "../l2/AddrResolverFacet.sol";
|
|
18
|
+
|
|
19
|
+
library L2RegistrarMod {
|
|
20
|
+
using CustomRevert for bytes4;
|
|
21
|
+
|
|
22
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
23
|
+
/* LAYOUT */
|
|
24
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
25
|
+
|
|
26
|
+
struct Layout {
|
|
27
|
+
address registry;
|
|
28
|
+
uint256 coinType;
|
|
29
|
+
address spaceFactory;
|
|
30
|
+
address currency;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
34
|
+
/* CONSTANTS */
|
|
35
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
36
|
+
|
|
37
|
+
/// @notice Minimum length required for a subdomain label (3 characters)
|
|
38
|
+
uint256 internal constant MIN_LABEL_LENGTH = 3;
|
|
39
|
+
|
|
40
|
+
/// @notice Maximum length allowed for a subdomain label (63 characters per DNS spec)
|
|
41
|
+
uint256 internal constant MAX_LABEL_LENGTH = 63;
|
|
42
|
+
|
|
43
|
+
/// @notice Allowed characters: lowercase a-z, digits 0-9, and hyphen
|
|
44
|
+
/// @dev Computed as: LOWERCASE_7_BIT_ASCII | DIGITS_7_BIT_ASCII | (1 << 45)
|
|
45
|
+
/// where 45 is the ASCII code for hyphen '-'
|
|
46
|
+
uint128 internal constant ALLOWED_LABEL_CHARS =
|
|
47
|
+
LibString.LOWERCASE_7_BIT_ASCII | LibString.DIGITS_7_BIT_ASCII | 0x200000000000;
|
|
48
|
+
|
|
49
|
+
/// @notice ASCII code for hyphen character
|
|
50
|
+
bytes1 internal constant HYPHEN = 0x2d;
|
|
51
|
+
|
|
52
|
+
/// keccak256(abi.encode(uint256(keccak256("towns.domains.registrar.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
53
|
+
bytes32 internal constant STORAGE_SLOT =
|
|
54
|
+
0xd8d4140311c6e36bf92b2f6d963b373d4b20ef4c79fad106c706a4d652ed9b00;
|
|
55
|
+
|
|
56
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
57
|
+
/* EVENTS */
|
|
58
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
59
|
+
|
|
60
|
+
/// @notice Emitted when a new subdomain is registered
|
|
61
|
+
/// @param label The subdomain label
|
|
62
|
+
/// @param owner The owner of the subdomain
|
|
63
|
+
event NameRegistered(string indexed label, address indexed owner);
|
|
64
|
+
|
|
65
|
+
/// @notice Emitted when the space factory address is updated
|
|
66
|
+
/// @param spaceFactory The new space factory address
|
|
67
|
+
event SpaceFactorySet(address spaceFactory);
|
|
68
|
+
|
|
69
|
+
/// @notice Emitted when the registry address is updated
|
|
70
|
+
/// @param registry The new registry address
|
|
71
|
+
event RegistrySet(address registry);
|
|
72
|
+
|
|
73
|
+
/// @notice Emitted when the currency is updated
|
|
74
|
+
/// @param currency The new currency
|
|
75
|
+
event CurrencySet(address currency);
|
|
76
|
+
|
|
77
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
78
|
+
/* ERRORS */
|
|
79
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
80
|
+
|
|
81
|
+
/// @notice Thrown when a label is invalid (wrong length, chars, or format)
|
|
82
|
+
error L2Registrar__InvalidLabel();
|
|
83
|
+
|
|
84
|
+
/// @notice Thrown when caller is not a Towns smart account (IModularAccount)
|
|
85
|
+
error L2Registrar__NotSmartAccount();
|
|
86
|
+
|
|
87
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
88
|
+
/* FUNCTIONS */
|
|
89
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
90
|
+
|
|
91
|
+
/// @notice Registers a subdomain in the L2 registry
|
|
92
|
+
/// @dev Reverts if the subdomain is not available or the label is invalid
|
|
93
|
+
/// @param $ The storage layout
|
|
94
|
+
/// @param subdomain The subdomain label to register (e.g., "alice")
|
|
95
|
+
/// @param owner The address of the owner of the subdomain
|
|
96
|
+
function register(Layout storage $, string calldata subdomain, address owner) internal {
|
|
97
|
+
// Get registry interface to interact with domain management functions
|
|
98
|
+
IL2Registry registry = IL2Registry($.registry);
|
|
99
|
+
|
|
100
|
+
// Retrieve the base domain hash (e.g., namehash("towns.eth"))
|
|
101
|
+
bytes32 domainHash = registry.baseDomainHash();
|
|
102
|
+
|
|
103
|
+
// Compute the subdomain namehash (e.g., namehash("alice.towns.eth"))
|
|
104
|
+
bytes32 subdomainHash = registry.encodeSubdomain(domainHash, subdomain);
|
|
105
|
+
|
|
106
|
+
// Register the name in the L2 registry
|
|
107
|
+
registry.createSubdomain(domainHash, subdomain, owner, new bytes[](0), "");
|
|
108
|
+
|
|
109
|
+
// Encode owner address as bytes for resolver record storage
|
|
110
|
+
bytes memory addr = abi.encodePacked(owner);
|
|
111
|
+
|
|
112
|
+
// Get address resolver facet to set address records for the subdomain
|
|
113
|
+
AddrResolverFacet addrResolver = AddrResolverFacet($.registry);
|
|
114
|
+
|
|
115
|
+
// Set the forward address for the current chain. This is needed for reverse resolution.
|
|
116
|
+
// E.g. if this contract is deployed to Base, set an address for chainId 8453 which is
|
|
117
|
+
// coinType 2147492101 according to ENSIP-11.
|
|
118
|
+
addrResolver.setAddr(subdomainHash, $.coinType, addr);
|
|
119
|
+
|
|
120
|
+
// Set the forward address for mainnet ETH (coinType 60) for easier debugging.
|
|
121
|
+
addrResolver.setAddr(subdomainHash, 60, addr);
|
|
122
|
+
|
|
123
|
+
// Emit event to notify about the successful registration
|
|
124
|
+
emit NameRegistered(subdomain, owner);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function chargeFee(Layout storage $, string calldata label) internal {
|
|
128
|
+
if ($.spaceFactory == address(0)) return;
|
|
129
|
+
if ($.currency == address(0)) return;
|
|
130
|
+
|
|
131
|
+
bytes memory extraData = abi.encode(bytes(label).length);
|
|
132
|
+
|
|
133
|
+
uint256 expectedFee = IFeeManager($.spaceFactory).calculateFee(
|
|
134
|
+
FeeTypesLib.DOMAIN_REGISTRATION,
|
|
135
|
+
msg.sender,
|
|
136
|
+
0,
|
|
137
|
+
extraData
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
ProtocolFeeLib.chargeAlways(
|
|
141
|
+
$.spaceFactory,
|
|
142
|
+
FeeTypesLib.DOMAIN_REGISTRATION,
|
|
143
|
+
msg.sender,
|
|
144
|
+
$.currency,
|
|
145
|
+
expectedFee,
|
|
146
|
+
expectedFee,
|
|
147
|
+
extraData
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// @notice Validates caller is a Towns smart account (IModularAccount)
|
|
152
|
+
/// @dev Uses ERC-165 supportsInterface check
|
|
153
|
+
function onlySmartAccount() internal view {
|
|
154
|
+
// Must be a contract
|
|
155
|
+
if (msg.sender.code.length == 0) {
|
|
156
|
+
L2Registrar__NotSmartAccount.selector.revertWith();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Must support IModularAccount interface
|
|
160
|
+
try IERC165(msg.sender).supportsInterface(type(IModularAccount).interfaceId) returns (
|
|
161
|
+
bool supported
|
|
162
|
+
) {
|
|
163
|
+
if (!supported) L2Registrar__NotSmartAccount.selector.revertWith();
|
|
164
|
+
} catch {
|
|
165
|
+
L2Registrar__NotSmartAccount.selector.revertWith();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// @notice Checks if a subdomain is available for registration
|
|
170
|
+
/// @dev Returns true if the label is valid and the subdomain doesn't exist
|
|
171
|
+
/// @param $ The storage layout
|
|
172
|
+
/// @param subdomain The subdomain label to check (e.g., "alice")
|
|
173
|
+
/// @return True if the subdomain is available, false otherwise
|
|
174
|
+
function available(Layout storage $, string calldata subdomain) internal view returns (bool) {
|
|
175
|
+
// Check label validity first
|
|
176
|
+
if (!isValidLabel(subdomain)) return false;
|
|
177
|
+
|
|
178
|
+
// Check if subdomain already exists by querying its owner
|
|
179
|
+
// subdomainOwner returns address(0) if the subdomain doesn't exist
|
|
180
|
+
IL2Registry registry = IL2Registry($.registry);
|
|
181
|
+
|
|
182
|
+
// Compute the subdomain namehash
|
|
183
|
+
bytes32 subdomainHash = registry.encodeSubdomain(registry.baseDomainHash(), subdomain);
|
|
184
|
+
|
|
185
|
+
// Available if owner is zero address (subdomain doesn't exist)
|
|
186
|
+
return registry.subdomainOwner(subdomainHash) == address(0);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// @notice Validates a subdomain label for registration
|
|
190
|
+
/// @dev Checks: length (3-63), allowed chars (a-z, 0-9, hyphen), no leading/trailing hyphen
|
|
191
|
+
/// @param label The subdomain label to validate (e.g., "alice")
|
|
192
|
+
function validateLabel(string calldata label) internal pure {
|
|
193
|
+
if (!isValidLabel(label)) L2Registrar__InvalidLabel.selector.revertWith();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// @notice Checks if a label is valid without checking availability
|
|
197
|
+
/// @dev Use this for UI validation before attempting registration
|
|
198
|
+
/// @param label The subdomain label to validate
|
|
199
|
+
/// @return True if the label format is valid
|
|
200
|
+
function isValidLabel(string calldata label) internal pure returns (bool) {
|
|
201
|
+
uint256 len = bytes(label).length;
|
|
202
|
+
|
|
203
|
+
// Check length bounds
|
|
204
|
+
if (len < MIN_LABEL_LENGTH || len > MAX_LABEL_LENGTH) return false;
|
|
205
|
+
|
|
206
|
+
// Check all characters are allowed (a-z, 0-9, hyphen)
|
|
207
|
+
if (!LibString.is7BitASCII(label, ALLOWED_LABEL_CHARS)) return false;
|
|
208
|
+
|
|
209
|
+
// Check hyphen not at start or end
|
|
210
|
+
bytes calldata b = bytes(label);
|
|
211
|
+
if (b[0] == HYPHEN || b[len - 1] == HYPHEN) return false;
|
|
212
|
+
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// @notice Returns the storage layout for the L2Registrar facet
|
|
217
|
+
/// @dev Uses diamond storage pattern to avoid slot collisions between facets
|
|
218
|
+
/// @return $ The storage pointer to the facet's layout
|
|
219
|
+
function getStorage() internal pure returns (Layout storage $) {
|
|
220
|
+
assembly {
|
|
221
|
+
$.slot := STORAGE_SLOT
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.29;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
import {IFeeHook, FeeHookResult} from "../../factory/facets/fee/IFeeHook.sol";
|
|
6
|
+
|
|
7
|
+
// libraries
|
|
8
|
+
import {CustomRevert} from "../../utils/libraries/CustomRevert.sol";
|
|
9
|
+
import {Validator} from "../../utils/libraries/Validator.sol";
|
|
10
|
+
|
|
11
|
+
// contracts
|
|
12
|
+
import {Ownable} from "solady/auth/Ownable.sol";
|
|
13
|
+
|
|
14
|
+
/// @title DomainFeeHook
|
|
15
|
+
/// @notice Fee hook for domain registration with tiered pricing and first-free logic
|
|
16
|
+
/// @dev Implements IFeeHook to integrate with FeeManager system
|
|
17
|
+
contract DomainFeeHook is IFeeHook, Ownable {
|
|
18
|
+
using CustomRevert for bytes4;
|
|
19
|
+
|
|
20
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
21
|
+
/* STORAGE */
|
|
22
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
23
|
+
|
|
24
|
+
struct Layout {
|
|
25
|
+
uint256 defaultPrice;
|
|
26
|
+
mapping(uint256 length => uint256 price) priceTiers;
|
|
27
|
+
mapping(address account => uint256 count) registrationCount;
|
|
28
|
+
address feeManager;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// keccak256(abi.encode(uint256(keccak256("towns.domains.registrar.domain.feehook.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
32
|
+
bytes32 internal constant STORAGE_SLOT =
|
|
33
|
+
0x54805b38b00d627862a1394d5554c4c16c8fd44fa1e31b86f37dcbff65fe4e00;
|
|
34
|
+
|
|
35
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
36
|
+
/* EVENTS */
|
|
37
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
38
|
+
|
|
39
|
+
/// @notice Emitted when the default price is updated
|
|
40
|
+
event DefaultPriceSet(uint256 price);
|
|
41
|
+
|
|
42
|
+
/// @notice Emitted when a price tier is set
|
|
43
|
+
event PriceTierSet(uint256 indexed length, uint256 price);
|
|
44
|
+
|
|
45
|
+
/// @notice Emitted when the fee manager is updated
|
|
46
|
+
event FeeManagerSet(address indexed feeManager);
|
|
47
|
+
|
|
48
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
49
|
+
/* ERRORS */
|
|
50
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
51
|
+
|
|
52
|
+
/// @notice Thrown when context data is invalid or missing
|
|
53
|
+
error DomainFeeHook__InvalidContext();
|
|
54
|
+
|
|
55
|
+
/// @notice Thrown when array lengths don't match in batch operations
|
|
56
|
+
error DomainFeeHook__LengthMismatch();
|
|
57
|
+
|
|
58
|
+
/// @notice Thrown when caller is not the authorized FeeManager
|
|
59
|
+
error DomainFeeHook__Unauthorized();
|
|
60
|
+
|
|
61
|
+
/// @notice Thrown when the number of lengths exceeds the maximum allowed
|
|
62
|
+
error DomainFeeHook__TooManyLengths();
|
|
63
|
+
|
|
64
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
65
|
+
/* CONSTRUCTOR */
|
|
66
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
67
|
+
|
|
68
|
+
/// @notice Initializes the fee hook with an owner and default price
|
|
69
|
+
/// @param owner The owner address
|
|
70
|
+
/// @param defaultPrice The default price for registrations
|
|
71
|
+
constructor(address owner, address feeManager, uint256 defaultPrice) {
|
|
72
|
+
_initializeOwner(owner);
|
|
73
|
+
Layout storage $ = getStorage();
|
|
74
|
+
Validator.checkAddress(feeManager);
|
|
75
|
+
($.feeManager, $.defaultPrice) = (feeManager, defaultPrice);
|
|
76
|
+
emit FeeManagerSet(feeManager);
|
|
77
|
+
emit DefaultPriceSet(defaultPrice);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
81
|
+
/* FEE HOOK */
|
|
82
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
83
|
+
|
|
84
|
+
/// @inheritdoc IFeeHook
|
|
85
|
+
function onChargeFee(
|
|
86
|
+
bytes32,
|
|
87
|
+
address user,
|
|
88
|
+
uint256,
|
|
89
|
+
bytes calldata context
|
|
90
|
+
) external returns (FeeHookResult memory result) {
|
|
91
|
+
Layout storage $ = getStorage();
|
|
92
|
+
|
|
93
|
+
// Restrict to authorized FeeManager only
|
|
94
|
+
if (msg.sender != $.feeManager) DomainFeeHook__Unauthorized.selector.revertWith();
|
|
95
|
+
|
|
96
|
+
result = _calculateFee(user, context);
|
|
97
|
+
// Increment registration count (state change)
|
|
98
|
+
++$.registrationCount[user];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// @notice Sets the fee manager address
|
|
102
|
+
/// @param feeManager The fee manager address authorized to call onChargeFee
|
|
103
|
+
function setFeeManager(address feeManager) external onlyOwner {
|
|
104
|
+
Validator.checkAddress(feeManager);
|
|
105
|
+
getStorage().feeManager = feeManager;
|
|
106
|
+
emit FeeManagerSet(feeManager);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// @notice Sets the default price for registrations
|
|
110
|
+
/// @param price The new default price
|
|
111
|
+
function setDefaultPrice(uint256 price) external onlyOwner {
|
|
112
|
+
getStorage().defaultPrice = price;
|
|
113
|
+
emit DefaultPriceSet(price);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// @notice Sets the price for a specific label length
|
|
117
|
+
/// @param length The label length
|
|
118
|
+
/// @param price The price for that length
|
|
119
|
+
function setPriceTier(uint256 length, uint256 price) external onlyOwner {
|
|
120
|
+
getStorage().priceTiers[length] = price;
|
|
121
|
+
emit PriceTierSet(length, price);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// @notice Sets multiple price tiers at once
|
|
125
|
+
/// @param lengths Array of label lengths
|
|
126
|
+
/// @param prices Array of prices corresponding to each length
|
|
127
|
+
function setPriceTiers(
|
|
128
|
+
uint256[] calldata lengths,
|
|
129
|
+
uint256[] calldata prices
|
|
130
|
+
) external onlyOwner {
|
|
131
|
+
if (lengths.length != prices.length) DomainFeeHook__LengthMismatch.selector.revertWith();
|
|
132
|
+
if (lengths.length > 10) DomainFeeHook__TooManyLengths.selector.revertWith();
|
|
133
|
+
|
|
134
|
+
Layout storage $ = getStorage();
|
|
135
|
+
for (uint256 i; i < lengths.length; ++i) {
|
|
136
|
+
$.priceTiers[lengths[i]] = prices[i];
|
|
137
|
+
emit PriceTierSet(lengths[i], prices[i]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// @inheritdoc IFeeHook
|
|
142
|
+
function calculateFee(
|
|
143
|
+
bytes32,
|
|
144
|
+
address user,
|
|
145
|
+
uint256,
|
|
146
|
+
bytes calldata context
|
|
147
|
+
) external view returns (FeeHookResult memory) {
|
|
148
|
+
return _calculateFee(user, context);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// @notice Returns the price for a specific label length
|
|
152
|
+
/// @param length The label length to query
|
|
153
|
+
/// @return The price for that length (or default if not set)
|
|
154
|
+
function getPrice(uint256 length) external view returns (uint256) {
|
|
155
|
+
Layout storage $ = getStorage();
|
|
156
|
+
uint256 price = $.priceTiers[length];
|
|
157
|
+
return price == 0 ? $.defaultPrice : price;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// @notice Returns the default price for registrations
|
|
161
|
+
/// @return The default price
|
|
162
|
+
function getDefaultPrice() external view returns (uint256) {
|
|
163
|
+
return getStorage().defaultPrice;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// @notice Returns the registration count for a user
|
|
167
|
+
/// @param user The user to query
|
|
168
|
+
/// @return The registration count
|
|
169
|
+
function getRegistrationCount(address user) external view returns (uint256) {
|
|
170
|
+
return getStorage().registrationCount[user];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// @notice Returns the authorized fee manager address
|
|
174
|
+
/// @return The fee manager address
|
|
175
|
+
function getFeeManager() external view returns (address) {
|
|
176
|
+
return getStorage().feeManager;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// @notice Internal fee calculation logic
|
|
180
|
+
/// @param user The user being charged
|
|
181
|
+
/// @param context ABI-encoded label length
|
|
182
|
+
/// @return result The fee calculation result
|
|
183
|
+
function _calculateFee(
|
|
184
|
+
address user,
|
|
185
|
+
bytes calldata context
|
|
186
|
+
) internal view returns (FeeHookResult memory) {
|
|
187
|
+
Layout storage $ = getStorage();
|
|
188
|
+
// First registration is free
|
|
189
|
+
if ($.registrationCount[user] == 0) {
|
|
190
|
+
return FeeHookResult({finalFee: 0, metadata: ""});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Decode label length from context
|
|
194
|
+
if (context.length < 32) DomainFeeHook__InvalidContext.selector.revertWith();
|
|
195
|
+
uint256 labelLength = abi.decode(context, (uint256));
|
|
196
|
+
|
|
197
|
+
// Get tiered price (fall back to default)
|
|
198
|
+
uint256 price = $.priceTiers[labelLength];
|
|
199
|
+
if (price == 0) price = $.defaultPrice;
|
|
200
|
+
|
|
201
|
+
return FeeHookResult({finalFee: price, metadata: ""});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// @notice Returns the storage layout for the L2Registrar facet
|
|
205
|
+
/// @dev Uses diamond storage pattern to avoid slot collisions between facets
|
|
206
|
+
/// @return $ The storage pointer to the facet's layout
|
|
207
|
+
function getStorage() internal pure returns (Layout storage $) {
|
|
208
|
+
assembly {
|
|
209
|
+
$.slot := STORAGE_SLOT
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -26,6 +26,9 @@ library FeeTypesLib {
|
|
|
26
26
|
/// @notice Fee for bot actions
|
|
27
27
|
bytes32 internal constant BOT_ACTION = keccak256("FEE_TYPE.BOT_ACTION");
|
|
28
28
|
|
|
29
|
+
/// @notice Fee for domain registration
|
|
30
|
+
bytes32 internal constant DOMAIN_REGISTRATION = keccak256("FEE_TYPE.DOMAIN_REGISTRATION");
|
|
31
|
+
|
|
29
32
|
/// @notice Generates fee type for membership based on currency
|
|
30
33
|
/// @param currency The payment currency address
|
|
31
34
|
/// @return The fee type identifier for the given currency
|
|
@@ -110,7 +110,10 @@ contract StreamRegistry is IStreamRegistry, RegistryModifiers {
|
|
|
110
110
|
|
|
111
111
|
// Check if the lastMiniblockNum is the next expected miniblock and
|
|
112
112
|
// the prevMiniblockHash is correct
|
|
113
|
-
if (
|
|
113
|
+
if (
|
|
114
|
+
stream.lastMiniblockNum + 1 != miniblock.lastMiniblockNum ||
|
|
115
|
+
stream.lastMiniblockHash != miniblock.prevMiniBlockHash
|
|
116
|
+
) {
|
|
114
117
|
emit StreamLastMiniblockUpdateFailed(
|
|
115
118
|
miniblock.streamId,
|
|
116
119
|
miniblock.lastMiniblockHash,
|
|
@@ -23,6 +23,27 @@ library ProtocolFeeLib {
|
|
|
23
23
|
address currency,
|
|
24
24
|
uint256 amount,
|
|
25
25
|
uint256 expectedFee
|
|
26
|
+
) internal returns (uint256 protocolFee) {
|
|
27
|
+
return charge(spaceFactory, feeType, user, currency, amount, expectedFee, "");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// @notice Charges protocol fee via FeeManager with ERC20 approval handling and extra data
|
|
31
|
+
/// @param spaceFactory The FeeManager address
|
|
32
|
+
/// @param feeType The type of fee being charged
|
|
33
|
+
/// @param user The user paying the fee
|
|
34
|
+
/// @param currency The payment currency (NATIVE_TOKEN or ERC20)
|
|
35
|
+
/// @param amount The base amount for fee calculation
|
|
36
|
+
/// @param expectedFee The pre-calculated expected fee
|
|
37
|
+
/// @param extraData Additional data for fee calculation (e.g., label length for domain registration)
|
|
38
|
+
/// @return protocolFee The actual fee charged
|
|
39
|
+
function charge(
|
|
40
|
+
address spaceFactory,
|
|
41
|
+
bytes32 feeType,
|
|
42
|
+
address user,
|
|
43
|
+
address currency,
|
|
44
|
+
uint256 amount,
|
|
45
|
+
uint256 expectedFee,
|
|
46
|
+
bytes memory extraData
|
|
26
47
|
) internal returns (uint256 protocolFee) {
|
|
27
48
|
if (expectedFee == 0) return 0;
|
|
28
49
|
|
|
@@ -35,9 +56,43 @@ library ProtocolFeeLib {
|
|
|
35
56
|
amount,
|
|
36
57
|
currency,
|
|
37
58
|
expectedFee,
|
|
38
|
-
|
|
59
|
+
extraData
|
|
39
60
|
);
|
|
40
61
|
|
|
41
62
|
if (!isNative) currency.safeApprove(spaceFactory, 0);
|
|
42
63
|
}
|
|
64
|
+
|
|
65
|
+
/// @notice Charges protocol fee via FeeManager, always calling chargeFee even when expectedFee is 0
|
|
66
|
+
/// @dev Use this when the fee hook needs to track calls regardless of fee amount (e.g., domain registration count)
|
|
67
|
+
/// @param spaceFactory The FeeManager address
|
|
68
|
+
/// @param feeType The type of fee being charged
|
|
69
|
+
/// @param user The user paying the fee
|
|
70
|
+
/// @param currency The payment currency (NATIVE_TOKEN or ERC20)
|
|
71
|
+
/// @param amount The base amount for fee calculation
|
|
72
|
+
/// @param expectedFee The pre-calculated expected fee
|
|
73
|
+
/// @param extraData Additional data for fee calculation
|
|
74
|
+
/// @return protocolFee The actual fee charged
|
|
75
|
+
function chargeAlways(
|
|
76
|
+
address spaceFactory,
|
|
77
|
+
bytes32 feeType,
|
|
78
|
+
address user,
|
|
79
|
+
address currency,
|
|
80
|
+
uint256 amount,
|
|
81
|
+
uint256 expectedFee,
|
|
82
|
+
bytes memory extraData
|
|
83
|
+
) internal returns (uint256 protocolFee) {
|
|
84
|
+
bool isNative = currency == CurrencyTransfer.NATIVE_TOKEN;
|
|
85
|
+
if (!isNative && expectedFee > 0) currency.safeApproveWithRetry(spaceFactory, expectedFee);
|
|
86
|
+
|
|
87
|
+
protocolFee = IFeeManager(spaceFactory).chargeFee{value: isNative ? expectedFee : 0}(
|
|
88
|
+
feeType,
|
|
89
|
+
user,
|
|
90
|
+
amount,
|
|
91
|
+
currency,
|
|
92
|
+
expectedFee,
|
|
93
|
+
extraData
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (!isNative && expectedFee > 0) currency.safeApprove(spaceFactory, 0);
|
|
97
|
+
}
|
|
43
98
|
}
|
package/src/tokens/Member.sol
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.24;
|
|
3
3
|
|
|
4
|
-
//interfaces
|
|
4
|
+
// interfaces
|
|
5
5
|
|
|
6
|
-
//libraries
|
|
6
|
+
// libraries
|
|
7
7
|
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
|
|
8
8
|
|
|
9
|
-
//contracts
|
|
10
|
-
|
|
9
|
+
// contracts
|
|
11
10
|
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
12
11
|
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
13
12
|
|
package/LICENSE.txt
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 River Association
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.24;
|
|
3
|
-
|
|
4
|
-
// interfaces
|
|
5
|
-
import {IRiverConfig} from "src/river/registry/facets/config/IRiverConfig.sol";
|
|
6
|
-
|
|
7
|
-
// libraries
|
|
8
|
-
import {RiverConfigValues} from "scripts/interactions/helpers/RiverConfigValues.sol";
|
|
9
|
-
|
|
10
|
-
// contracts
|
|
11
|
-
import {Interaction} from "scripts/common/Interaction.s.sol";
|
|
12
|
-
|
|
13
|
-
contract InteractRiverRegistrySetFreq is Interaction {
|
|
14
|
-
function __interact(address deployer) internal override {
|
|
15
|
-
address riverRegistry = getDeployment("riverRegistry");
|
|
16
|
-
|
|
17
|
-
uint64 value = 10;
|
|
18
|
-
|
|
19
|
-
vm.startBroadcast(deployer);
|
|
20
|
-
IRiverConfig(riverRegistry).setConfiguration(
|
|
21
|
-
RiverConfigValues.STREAM_MINIBLOCK_REGISTRATION_FREQUENCY,
|
|
22
|
-
0,
|
|
23
|
-
abi.encode(value)
|
|
24
|
-
);
|
|
25
|
-
vm.stopBroadcast();
|
|
26
|
-
}
|
|
27
|
-
}
|