Skip to content

Commit 9575ac7

Browse files
authored
Merge pull request #7 from ensdomains/feat/bet-552-aliasing
- added placeholder OwnedResolver.sol - added setAlias(fromName, toName) - added getAlias(fromName): toName | undefined - added tests
2 parents b6697a0 + 5d3d1e4 commit 9575ac7

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {NameCoder} from "@ens/contracts/utils/NameCoder.sol";
5+
6+
import {OwnedResolverLib} from "./libraries/OwnedResolverLib.sol";
7+
8+
/// @notice An owned resolver that supports multiple names and internal aliasing.
9+
///
10+
/// * Resolved names find the longest match.
11+
/// * Successful matches recursively check for additional aliasing.
12+
/// * Cycles of length 1 apply once.
13+
/// * Cycles of length 2+ result in OOG.
14+
///
15+
/// `setAlias("a.eth", "b.eth")`
16+
/// eg. `getAlias("a.eth") => "b.eth"`
17+
/// eg. `getAlias("[sub].a.eth") => "[sub].b.eth"`
18+
/// eg. `getAlias("[x.y].a.eth") => "[x.y].b.eth"`
19+
/// eg. `getAlias("abc.eth") => ""`
20+
///
21+
contract OwnedResolver {
22+
////////////////////////////////////////////////////////////////////////
23+
// Events
24+
////////////////////////////////////////////////////////////////////////
25+
26+
event AliasChanged(bytes indexed fromName, bytes indexed toName);
27+
28+
////////////////////////////////////////////////////////////////////////
29+
// Implementation
30+
////////////////////////////////////////////////////////////////////////
31+
32+
/// @notice Create an alias from `fromName` to `toName`.
33+
///
34+
/// @param fromName The source DNS-encoded name.
35+
/// @param toName The destination DNS-encoded name.
36+
function setAlias(bytes calldata fromName, bytes calldata toName) external {
37+
_storage().aliases[NameCoder.namehash(fromName, 0)] = toName;
38+
emit AliasChanged(fromName, toName);
39+
}
40+
41+
/// @notice Determine which name is queried when `fromName` is resolved.
42+
///
43+
/// @param fromName The source DNS-encoded name.
44+
///
45+
/// @return toName The destination DNS-encoded name or empty if not aliased.
46+
function getAlias(bytes memory fromName) public view returns (bytes memory toName) {
47+
bytes32 prev;
48+
for (;;) {
49+
bytes memory matchName;
50+
(matchName, fromName) = _resolveAlias(fromName);
51+
if (fromName.length == 0) break; // no alias
52+
bytes32 next = keccak256(matchName);
53+
if (next == prev) break; // same alias
54+
toName = fromName;
55+
prev = next;
56+
}
57+
}
58+
59+
////////////////////////////////////////////////////////////////////////
60+
// Internal Functions
61+
////////////////////////////////////////////////////////////////////////
62+
63+
/// @dev Apply one round of longest match aliasing.
64+
///
65+
/// @param fromName The source DNS-encoded name.
66+
///
67+
/// @return matchName The alias that matched.
68+
/// @return toName The destination DNS-encoded name or empty if no match.
69+
function _resolveAlias(
70+
bytes memory fromName
71+
) internal view returns (bytes memory matchName, bytes memory toName) {
72+
mapping(bytes32 => bytes) storage A = _storage().aliases;
73+
uint256 offset;
74+
while (offset < fromName.length) {
75+
matchName = A[NameCoder.namehash(fromName, offset)];
76+
if (matchName.length > 0) {
77+
if (offset > 0) {
78+
// rewrite prefix: [x.y].{fromName[offset:]} => [x.y].{matchName}
79+
toName = new bytes(offset + matchName.length);
80+
assembly {
81+
mcopy(add(toName, 32), add(fromName, 32), offset) // copy prefix
82+
mcopy(add(toName, add(32, offset)), add(matchName, 32), mload(matchName)) // copy suffix
83+
}
84+
} else {
85+
toName = matchName;
86+
}
87+
break;
88+
}
89+
(, offset) = NameCoder.nextLabel(fromName, offset);
90+
}
91+
}
92+
93+
/// @dev Access global storage pointer.
94+
function _storage() internal pure returns (OwnedResolverLib.Storage storage S) {
95+
uint256 slot = OwnedResolverLib.NAMED_SLOT;
96+
assembly {
97+
S.slot := slot
98+
}
99+
}
100+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.13;
3+
4+
/// @notice Storage layout and roles for OwnedResolver.
5+
library OwnedResolverLib {
6+
struct Storage {
7+
mapping(bytes32 node => bytes) aliases;
8+
}
9+
10+
uint256 internal constant NAMED_SLOT = uint256(keccak256("eth.ens.storage.OwnedResolver"));
11+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.13;
3+
4+
import {Test} from "forge-std/Test.sol";
5+
6+
import {NameCoder} from "@ens/contracts/utils/NameCoder.sol";
7+
8+
import {OwnedResolver} from "~src/resolver/OwnedResolver.sol";
9+
10+
contract OwnedResolverTest is Test {
11+
OwnedResolver resolver;
12+
13+
function setUp() external {
14+
resolver = new OwnedResolver();
15+
}
16+
17+
function test_alias_noMatch() external view {
18+
assertEq(resolver.getAlias(NameCoder.encode("test.eth")), "", "test");
19+
assertEq(resolver.getAlias(NameCoder.encode("")), "", "root");
20+
assertEq(resolver.getAlias(NameCoder.encode("xyz")), "", "xyz");
21+
}
22+
23+
function test_alias_root() external {
24+
resolver.setAlias(NameCoder.encode(""), NameCoder.encode("test.eth"));
25+
26+
assertEq(resolver.getAlias(NameCoder.encode("")), NameCoder.encode("test.eth"), "root");
27+
assertEq(
28+
resolver.getAlias(NameCoder.encode("sub")),
29+
NameCoder.encode("sub.test.eth"),
30+
"sub"
31+
);
32+
}
33+
34+
function test_alias_exact() external {
35+
resolver.setAlias(NameCoder.encode("other.eth"), NameCoder.encode("test.eth"));
36+
37+
assertEq(
38+
resolver.getAlias(NameCoder.encode("other.eth")),
39+
NameCoder.encode("test.eth"),
40+
"exact"
41+
);
42+
}
43+
44+
function test_alias_subdomain() external {
45+
resolver.setAlias(NameCoder.encode("com"), NameCoder.encode("eth"));
46+
47+
assertEq(resolver.getAlias(NameCoder.encode("com")), NameCoder.encode("eth"), "exact");
48+
assertEq(
49+
resolver.getAlias(NameCoder.encode("test.com")),
50+
NameCoder.encode("test.eth"),
51+
"alias"
52+
);
53+
}
54+
55+
function test_alias_recursive() external {
56+
resolver.setAlias(NameCoder.encode("ens.xyz"), NameCoder.encode("com"));
57+
resolver.setAlias(NameCoder.encode("com"), NameCoder.encode("eth"));
58+
59+
assertEq(
60+
resolver.getAlias(NameCoder.encode("test.ens.xyz")),
61+
NameCoder.encode("test.eth"),
62+
"alias"
63+
);
64+
}
65+
}

0 commit comments

Comments
 (0)