Skip to content

Commit 2cc11c4

Browse files
authored
feat(RNSUnified): add tier attribute to rns record field
1 parent fc84b01 commit 2cc11c4

File tree

8 files changed

+177
-16
lines changed

8 files changed

+177
-16
lines changed

script/20231205-deploy-upgrade-auction-and-deploy-rns-operation/20231205_UpgradeRNSAuctionAndDeployRNSOperation.s.sol

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,20 @@ import { console2 as console } from "forge-std/console2.sol";
55
import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
66
import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
77
import { ContractKey } from "foundry-deployment-kit/configs/ContractConfig.sol";
8-
import { Network, Config__Mainnet20231205 } from "script/20231205-deploy-upgrade-auction-and-deploy-rns-operation/20231205_MainnetConfig.s.sol";
9-
import { INSAuction, RNSAuction, RNSUnified, Migration__20231123_UpgradeAuctionClaimeUnbiddedNames as UpgradeAuctionScript } from "script/20231123-upgrade-auction-claim-unbidded-names/20231123_UpgradeAuctionClaimUnbiddedNames.s.sol";
10-
import { RNSOperation, Migration__20231124_DeployRNSOperation as DeployRNSOperationScript } from "script/20231124-deploy-rns-operation/20231124_DeployRNSOperation.s.sol";
8+
import {
9+
Network,
10+
Config__Mainnet20231205
11+
} from "script/20231205-deploy-upgrade-auction-and-deploy-rns-operation/20231205_MainnetConfig.s.sol";
12+
import {
13+
INSAuction,
14+
RNSAuction,
15+
RNSUnified,
16+
Migration__20231123_UpgradeAuctionClaimeUnbiddedNames as UpgradeAuctionScript
17+
} from "script/20231123-upgrade-auction-claim-unbidded-names/20231123_UpgradeAuctionClaimUnbiddedNames.s.sol";
18+
import {
19+
RNSOperation,
20+
Migration__20231124_DeployRNSOperation as DeployRNSOperationScript
21+
} from "script/20231124-deploy-rns-operation/20231124_DeployRNSOperation.s.sol";
1122

1223
contract Migration__20231205_UpgradeRNSAuctionAndDeployRNSOperation is Config__Mainnet20231205 {
1324
function run() public trySetUp onMainnet {

src/RNSAuction.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,6 @@ contract RNSAuction is Initializable, AccessControlEnumerable, INSAuction {
309309
/**
310310
* @inheritdoc INSAuction
311311
*/
312-
313312
function setBidGapRatio(uint256 ratio) external onlyRole(DEFAULT_ADMIN_ROLE) {
314313
_setBidGapRatio(ratio);
315314
}

src/RNSUnified.sol

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ pragma solidity ^0.8.19;
33

44
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
55
import { IERC721State, IERC721, ERC721, INSUnified, RNSToken } from "./RNSToken.sol";
6+
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
67
import { LibRNSDomain } from "./libraries/LibRNSDomain.sol";
8+
import { LibString } from "./libraries/LibString.sol";
79
import { LibSafeRange } from "./libraries/math/LibSafeRange.sol";
810
import { ModifyingField, LibModifyingField } from "./libraries/LibModifyingField.sol";
911
import {
@@ -14,18 +16,24 @@ import {
1416
} from "./types/ModifyingIndicator.sol";
1517

1618
contract RNSUnified is Initializable, RNSToken {
19+
using LibString for string;
1720
using LibRNSDomain for string;
1821
using LibModifyingField for ModifyingField;
1922

2023
bytes32 public constant CONTROLLER_ROLE = keccak256("CONTROLLER_ROLE");
2124
bytes32 public constant RESERVATION_ROLE = keccak256("RESERVATION_ROLE");
25+
bytes32 public constant TIER_SETTLER_ROLE = keccak256("TIER_SETTLER_ROLE");
2226
bytes32 public constant PROTECTED_SETTLER_ROLE = keccak256("PROTECTED_SETTLER_ROLE");
2327
uint64 public constant MAX_EXPIRY = type(uint64).max;
28+
uint8 public constant DEFAULT_MAX_TIER = 5;
2429

2530
/// @dev Gap for upgradeability.
2631
uint256[50] private ____gap;
2732

33+
/// @dev The grace period in second(s).
2834
uint64 internal _gracePeriod;
35+
/// @dev The maximum tier value.
36+
uint8 internal _maxTier;
2937
/// @dev Mapping from token id => record
3038
mapping(uint256 => Record) internal _recordOf;
3139

@@ -65,6 +73,11 @@ contract RNSUnified is Initializable, RNSToken {
6573
return block.timestamp > LibSafeRange.add(_expiry(id), _gracePeriod);
6674
}
6775

76+
/// @inheritdoc INSUnified
77+
function getMaxTier() public view returns (uint8) {
78+
return _maxTier == 0 ? DEFAULT_MAX_TIER : _maxTier;
79+
}
80+
6881
/// @inheritdoc INSUnified
6982
function getGracePeriod() external view returns (uint64) {
7083
return _gracePeriod;
@@ -75,6 +88,11 @@ contract RNSUnified is Initializable, RNSToken {
7588
_setGracePeriod(gracePeriod);
7689
}
7790

91+
/// @inheritdoc INSUnified
92+
function setMaxTier(uint8 maxTier) external whenNotPaused onlyRole(TIER_SETTLER_ROLE) {
93+
_setMaxTier(maxTier);
94+
}
95+
7896
/// @inheritdoc INSUnified
7997
function mint(uint256 parentId, string calldata label, address resolver, address owner, uint64 duration)
8098
external
@@ -92,8 +110,13 @@ contract RNSUnified is Initializable, RNSToken {
92110
_requireValidExpiry(parentId, expiryTime);
93111
Record memory record;
94112
// Preserve previous state of the protected field
95-
record.mut =
96-
MutableRecord({ resolver: resolver, owner: owner, expiry: expiryTime, protected: _recordOf[id].mut.protected });
113+
record.mut = MutableRecord({
114+
resolver: resolver,
115+
owner: owner,
116+
expiry: expiryTime,
117+
protected: _recordOf[id].mut.protected,
118+
tier: 0
119+
});
97120
record.immut = ImmutableRecord({ depth: _recordOf[parentId].immut.depth + 1, parentId: parentId, label: label });
98121
// allow 3rd level domain for {account}.addr.reverse
99122
if (parentId != LibRNSDomain.ADDR_REVERSE_ID && record.immut.depth >= 3) revert ThirdLevelDomainUnallowed();
@@ -112,6 +135,7 @@ contract RNSUnified is Initializable, RNSToken {
112135
record = _recordOf[id];
113136
record.mut.owner = ownerOf(id);
114137
record.mut.expiry = _expiry(id);
138+
record.mut.tier = _tier(id);
115139
}
116140

117141
/// @inheritdoc INSUnified
@@ -173,6 +197,28 @@ contract RNSUnified is Initializable, RNSToken {
173197
}
174198
}
175199

200+
/// @inheritdoc INSUnified
201+
function bulkSetTier(uint256[] calldata ids, uint8[] calldata tiers) external onlyRole(TIER_SETTLER_ROLE) {
202+
uint256 id;
203+
uint8 tier;
204+
Record memory record;
205+
uint256 maxTier = getMaxTier();
206+
ModifyingIndicator indicator = ModifyingField.Tier.indicator();
207+
208+
for (uint256 i; i < ids.length;) {
209+
id = ids[i];
210+
if (_tier(id) != (tier = tiers[i])) {
211+
if (tier > maxTier) revert ExceedMaxTier();
212+
_recordOf[id].mut.tier = record.mut.tier = tier;
213+
emit RecordUpdated(id, indicator, record);
214+
}
215+
216+
unchecked {
217+
++i;
218+
}
219+
}
220+
}
221+
176222
/// @inheritdoc INSUnified
177223
function setRecord(uint256 id, ModifyingIndicator indicator, MutableRecord calldata mutRecord)
178224
external
@@ -191,6 +237,10 @@ contract RNSUnified is Initializable, RNSToken {
191237
if (indicator.hasAny(ModifyingField.Resolver.indicator())) {
192238
sMutRecord.resolver = record.mut.resolver = mutRecord.resolver;
193239
}
240+
if (indicator.hasAny(ModifyingField.Tier.indicator())) {
241+
if (mutRecord.tier > getMaxTier()) revert ExceedMaxTier();
242+
sMutRecord.tier = record.mut.tier = mutRecord.tier;
243+
}
194244
emit RecordUpdated(id, indicator, record);
195245

196246
// Updating owner might emit more {RecordUpdated} events. See method {_transfer}.
@@ -219,6 +269,9 @@ contract RNSUnified is Initializable, RNSToken {
219269
if (indicator.hasAny(ModifyingField.Protected.indicator()) && !hasRole(PROTECTED_SETTLER_ROLE, requester)) {
220270
return (false, MissingProtectedSettlerRole.selector);
221271
}
272+
if (indicator.hasAny(ModifyingField.Tier.indicator()) && !hasRole(TIER_SETTLER_ROLE, requester)) {
273+
return (false, MissingTierSettlerRole.selector);
274+
}
222275
bool hasControllerRole = hasRole(CONTROLLER_ROLE, requester);
223276
if (indicator.hasAny(ModifyingField.Expiry.indicator()) && !hasControllerRole) {
224277
return (false, MissingControllerRole.selector);
@@ -257,6 +310,15 @@ contract RNSUnified is Initializable, RNSToken {
257310
return _recordOf[id].mut.expiry;
258311
}
259312

313+
/**
314+
* @dev Helper method to get tier of a token.
315+
*/
316+
function _tier(uint256 id) internal view returns (uint8 tier) {
317+
Record storage $ = _recordOf[id];
318+
tier = $.mut.tier;
319+
if (tier == 0) tier = uint8(Math.min($.immut.label.strlen(), getMaxTier()));
320+
}
321+
260322
/**
261323
* @dev Helper method to check whether the address is owner of parent token.
262324
*/
@@ -328,6 +390,16 @@ contract RNSUnified is Initializable, RNSToken {
328390
emit GracePeriodUpdated(_msgSender(), gracePeriod);
329391
}
330392

393+
/**
394+
* @dev Helper method to set maximum tier.
395+
*
396+
* Emits an event {MaxTierUpdated}.
397+
*/
398+
function _setMaxTier(uint8 maxTier) internal {
399+
_maxTier = maxTier;
400+
emit MaxTierUpdated(_msgSender(), maxTier);
401+
}
402+
331403
/// @dev Override {ERC721-_transfer}.
332404
function _transfer(address from, address to, uint256 id) internal override {
333405
super._transfer(from, to, id);

src/interfaces/INSUnified.sol

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
2424
error CannotSetImmutableField();
2525
/// @dev Error: Missing protected settler role required for modification.
2626
error MissingProtectedSettlerRole();
27+
/// @dev Error: Missing tier settler role required for modification.
28+
error MissingTierSettlerRole();
29+
/// @dev Error: The provided tier is greater than the maximum allowed tier.
30+
error ExceedMaxTier();
2731
/// @dev Error: Attempting to set an expiry time that is not larger than the previous one.
2832
error ExpiryTimeMustBeLargerThanTheOldOne();
2933
/// @dev Error: The provided name must be registered or is in a grace period.
@@ -46,12 +50,13 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
4650
}
4751

4852
/**
49-
* | Fields\Idc,Roles | Modifying Indicator | Controller | Protected setter | (Parent) Owner/Spender |
50-
* | ---------------- | ------------------- | ---------- | ---------------- | ---------------------- |
51-
* | resolver | 0b00001000 | x | | x |
52-
* | owner | 0b00010000 | x | | x |
53-
* | expiry | 0b00100000 | x | | |
54-
* | protected | 0b01000000 | | x | |
53+
* | Fields\Idc,Roles | Modifying Indicator | Controller | Protected setter | (Parent) Owner/Spender | Tier setter |
54+
* | ---------------- | ------------------- | ---------- | ---------------- | ---------------------- | ----------- |
55+
* | resolver | 0b00001000 | x | | x | |
56+
* | owner | 0b00010000 | x | | x | |
57+
* | expiry | 0b00100000 | x | | | |
58+
* | protected | 0b01000000 | | x | | |
59+
* | tier | 0b10000000 | | | | x |
5560
* Note: (Parent) Owner/Spender means parent owner or current owner or current token spender.
5661
*/
5762
struct MutableRecord {
@@ -63,6 +68,8 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
6368
uint64 expiry;
6469
// Flag indicating whether the token is protected or not.
6570
bool protected;
71+
// Number indicate the tier of the token
72+
uint8 tier;
6673
}
6774

6875
struct Record {
@@ -74,6 +81,8 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
7481
event BaseURIUpdated(address indexed operator, string newURI);
7582
/// @dev Emitted when the grace period for all domain is updated.
7683
event GracePeriodUpdated(address indexed operator, uint64 newGracePeriod);
84+
/// @dev Emitted when the max tier value is updated.
85+
event MaxTierUpdated(address indexed operator, uint8 newMaxTier);
7786

7887
/**
7988
* @dev Emitted when the record of node is updated.
@@ -95,6 +104,12 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
95104
*/
96105
function PROTECTED_SETTLER_ROLE() external pure returns (bytes32);
97106

107+
/**
108+
* @dev Returns the tier setter role.
109+
* @notice Can set field {Record.mut.tier} in token record by using method `bulkSetTier`.
110+
*/
111+
function TIER_SETTLER_ROLE() external pure returns (bytes32);
112+
98113
/**
99114
* @dev Returns the reservation role.
100115
* @notice Never expire for token owner has this role.
@@ -106,6 +121,11 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
106121
*/
107122
function MAX_EXPIRY() external pure returns (uint64);
108123

124+
/**
125+
* @dev Returns the default max tier value.
126+
*/
127+
function DEFAULT_MAX_TIER() external pure returns (uint8);
128+
109129
/**
110130
* @dev Returns the name hash output of a domain.
111131
*/
@@ -117,6 +137,11 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
117137
*/
118138
function available(uint256 id) external view returns (bool);
119139

140+
/**
141+
* @dev Returns the max tier value.
142+
*/
143+
function getMaxTier() external view returns (uint8);
144+
120145
/**
121146
* @dev Returns the grace period in second(s).
122147
* Note: This period affects the availability of the domain.
@@ -139,6 +164,14 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
139164
*/
140165
function setGracePeriod(uint64) external;
141166

167+
/**
168+
* @dev Sets the max tier value.
169+
*
170+
* Requirements:
171+
* - The method caller must have tier settler role.
172+
*/
173+
function setMaxTier(uint8) external;
174+
142175
/**
143176
* @dev Sets the base uri.
144177
*
@@ -237,4 +270,14 @@ interface INSUnified is IAccessControlEnumerable, IERC721Metadata {
237270
* Emits events {RecordUpdated}.
238271
*/
239272
function bulkSetProtected(uint256[] calldata ids, bool protected) external;
273+
274+
/**
275+
* @dev Override the tier of a list of ids. Update operation for {Record.mut.tier}.
276+
*
277+
* Requirements:
278+
* - The method caller must have tier setter role.
279+
*
280+
* Emits events {RecordUpdated}.
281+
*/
282+
function bulkSetTier(uint256[] calldata ids, uint8[] calldata tiers) external;
240283
}

src/libraries/LibModifyingField.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ enum ModifyingField {
1010
Resolver,
1111
Owner,
1212
Expiry,
13-
Protected
13+
Protected,
14+
Tier
1415
}
1516

1617
library LibModifyingField {

test/RNSUnified/RNSUnified.namehash.t.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
pragma solidity ^0.8.19;
33

44
import "./RNSUnified.t.sol";
5-
import { LibString } from "solady/utils/LibString.sol";
5+
import { LibString as SoladyLibString } from "solady/utils/LibString.sol";
66

77
contract RNSUnified_NameHash_Test is RNSUnifiedTest {
8-
using LibString for *;
8+
using SoladyLibString for *;
99

1010
function testGas_namehash(string calldata domainName) external view {
1111
_rns.namehash(domainName);

test/RNSUnified/RNSUnified.setRecord.t.sol

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,27 @@ contract RNSUnified_SetRecord_Test is RNSUnifiedTest {
1919
_setRecord(id, ModifyingField.Protected.indicator(), mutRecord, _noError);
2020
}
2121

22+
function testFuzz_WhenMinted_AsTierSettler_CanSetMutableField_canSetRecord(
23+
MintParam calldata mintParam,
24+
INSUnified.MutableRecord memory mutRecord
25+
) external mintAs(_controller) setRecordAs(_tierSettler) {
26+
mutRecord.tier = uint8(Math.min(mutRecord.tier, _rns.getMaxTier()));
27+
vm.assume(mutRecord.tier > 0);
28+
(, uint256 id) = _mint(_ronId, mintParam, _noError);
29+
(bool allowed, bytes4 error) = _rns.canSetRecord(_tierSettler, id, ModifyingField.Tier.indicator());
30+
assertTrue(allowed, _errorIndentifier[error]);
31+
32+
_setRecord(id, ModifyingField.Tier.indicator(), mutRecord, _noError);
33+
}
34+
2235
function testFuzz_WhenMinted_AsController_CanSetMutableField_canSetRecord(
2336
ModifyingIndicator indicator,
2437
MintParam calldata mintParam,
2538
INSUnified.MutableRecord calldata mutRecord
2639
) external mintAs(_controller) setRecordAs(_controller) {
2740
vm.assume(!indicator.hasAny(IMMUTABLE_FIELDS_INDICATOR));
2841
vm.assume(!indicator.hasAny(ModifyingField.Protected.indicator()));
42+
vm.assume(!indicator.hasAny(ModifyingField.Tier.indicator()));
2943
if (indicator.hasAny(ModifyingField.Owner.indicator())) {
3044
_assumeValidAccount(mutRecord.owner);
3145
}

0 commit comments

Comments
 (0)