-
Notifications
You must be signed in to change notification settings - Fork 496
Add ReverseNamer
#482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
Add ReverseNamer
#482
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| interface IReverseRegistrarStub { | ||
| function setName(string memory name) external; | ||
| } | ||
|
|
||
| /// @notice https://docs.ens.domains/ensip/19/ | ||
| library ReverseNamer { | ||
| address constant MAINNET = address(0); // TODO: ENSv2 addr.reverse registrar | ||
| address constant MAINNET_ROLLUP = | ||
| 0x0000000000D8e504002cC26E3Ec46D81971C1664; | ||
| address constant TESTNET_ROLLUP = | ||
| 0x00000BeEF055f7934784D6d81b6BC86665630dbA; | ||
|
|
||
| function registrarFromChain( | ||
| uint256 chainId | ||
| ) internal pure returns (address) { | ||
| if (isMainnet(chainId)) { | ||
| return MAINNET; | ||
| } else if (isMainnetRollup(chainId)) { | ||
| return MAINNET_ROLLUP; | ||
| } else if (isTestnetRollup(chainId)) { | ||
| return TESTNET_ROLLUP; | ||
| } else { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on line 10 - MAINNET = address(0) so is the else case |
||
| return address(0); | ||
| } | ||
| } | ||
|
|
||
| function setName(string memory primary) internal { | ||
| address registrar = registrarFromChain(block.chainid); | ||
| if (registrar != address(0)) { | ||
| IReverseRegistrarStub(registrar).setName(primary); | ||
| } | ||
| } | ||
|
|
||
| function isMainnet(uint256 chainId) internal pure returns (bool) { | ||
| return | ||
| chainId == 1 || // mainnet | ||
| chainId == 17000 || // holesky | ||
| chainId == 560048 || // hoodi | ||
| chainId == 11155111; // sepolia | ||
| } | ||
|
|
||
| function isMainnetRollup(uint256 chainId) internal pure returns (bool) { | ||
| return | ||
| chainId == 10 || // optimism | ||
| chainId == 8453 || // base | ||
| chainId == 42161 || // arb1 | ||
| chainId == 59144 || // linea | ||
| chainId == 534352; // scroll | ||
| } | ||
|
|
||
| function isTestnetRollup(uint256 chainId) internal pure returns (bool) { | ||
| return | ||
| chainId == 59141 || // linea-sepolia | ||
| chainId == 84532 || // base-sepolia | ||
| chainId == 421614 || // arb1-sepolia | ||
| chainId == 534351 || // scroll-sepolia | ||
| chainId == 11155420; // optimism-sepolia | ||
| } | ||
| } | ||
|
|
||
| /// @dev Compile-time guard to prevent being Ownable. | ||
| contract NotOwnable { | ||
| function owner() internal pure {} | ||
| } | ||
|
|
||
| /// @notice Mixin for naming a contract once. | ||
| contract NamedOnce is NotOwnable { | ||
| constructor(string memory primary) { | ||
| ReverseNamer.setName(primary); | ||
| } | ||
| } | ||
|
|
||
| /// @notice Mixin for delegated contract naming. | ||
| contract NameableBy is NotOwnable { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NotOwnable in case of NamedOnce makes sense as no one can later name the contract but in case of NameableBy doesnt make much sense to it. NameableBy can be seen in a contract with Ownable as an extra delegate who can name contract but still if contract has Owner he can revoke the delegation. I might be wrong but limiting NameableBy to only non ownable contracts doesn't make much sense to me
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I wasn't sure about this design.
I was thinking of it more like a trust curve:
|
||
| address public nameOwner; | ||
|
|
||
| constructor(address owner, string memory primary) { | ||
| nameOwner = owner; | ||
| if (bytes(primary).length > 0) { | ||
| ReverseNamer.setName(primary); | ||
| } | ||
| } | ||
|
|
||
| /// @notice Set the name owner. | ||
| /// @dev Use `address(0)` to revoke ownership. | ||
| function setNameOwner(address owner) public { | ||
| require(msg.sender == nameOwner); | ||
| nameOwner = owner; | ||
| } | ||
|
|
||
| /// @notice Set contract primary name. | ||
| function setName(string memory primary) public { | ||
| require(msg.sender == nameOwner); | ||
| ReverseNamer.setName(primary); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| //SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.4; | ||
|
|
||
| import {ReverseNamer} from "../../reverseRegistrar/ReverseNamer.sol"; | ||
|
|
||
| contract MockReverseNamer { | ||
| function registrarFromChain( | ||
| uint256 chainId | ||
| ) external pure returns (address) { | ||
| return ReverseNamer.registrarFromChain(chainId); | ||
| } | ||
| } | ||
|
|
||
| // import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | ||
| // import {NamedOnce} from "../../reverseRegistrar/ReverseNamer.sol"; | ||
| // contract MockNamedOnce is Ownable, NamedOnce("") {} // compile-time error if Ownable |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| import hre from 'hardhat' | ||
| import { type Address, getAddress, zeroAddress } from 'viem' | ||
| import { readdirSync, readFileSync } from 'node:fs' | ||
| import { coinTypeFromChain } from '../fixtures/ensip19.js' | ||
|
|
||
| const REVERSE_REGISTRAR_MAINNET_ROLLUP = | ||
| '0x0000000000D8e504002cC26E3Ec46D81971C1664' | ||
|
|
||
| const connection = await hre.network.connect({ | ||
| override: { | ||
| chainId: 8453, // appear like an L2 | ||
| }, | ||
| }) | ||
|
|
||
| async function fixture() { | ||
| const [owner] = await connection.viem.getWalletClients() | ||
| const publicClient = await connection.viem.getPublicClient() | ||
| const reverseNamer = await connection.viem.deployContract('MockReverseNamer') | ||
| const l2ReverseRegistrar0 = await connection.viem.deployContract( | ||
| 'L2ReverseRegistrar', | ||
| [coinTypeFromChain(connection.networkConfig.chainId!)], | ||
| ) | ||
| await connection.networkHelpers.setCode( | ||
| REVERSE_REGISTRAR_MAINNET_ROLLUP, | ||
| (await publicClient.getCode(l2ReverseRegistrar0))!, | ||
| ) | ||
| const l2ReverseRegistrar = await connection.viem.getContractAt( | ||
| 'L2ReverseRegistrar', | ||
| REVERSE_REGISTRAR_MAINNET_ROLLUP, | ||
| ) | ||
| return { | ||
| owner, | ||
| reverseNamer, | ||
| l2ReverseRegistrar, | ||
| } | ||
| } | ||
|
|
||
| const PRIMARY = 'mycontract.eth' | ||
|
|
||
| describe('ReverseNamer', () => { | ||
| describe('registryFromChain', () => { | ||
| const dir = new URL('../../deployments/', import.meta.url) | ||
| for (const chainName of readdirSync(dir)) { | ||
| try { | ||
| const chainId = BigInt( | ||
| readFileSync(new URL(`./${chainName}/.chainId`, dir), { | ||
| encoding: 'utf8', | ||
| }), | ||
| ) | ||
| const deploy = JSON.parse( | ||
| readFileSync(new URL(`./${chainName}/L2ReverseRegistrar.json`, dir), { | ||
| encoding: 'utf8', | ||
| }), | ||
| ) as { address: Address } | ||
| it(chainName, async () => { | ||
| const F = await connection.networkHelpers.loadFixture(fixture) | ||
| await expect( | ||
| F.reverseNamer.read.registrarFromChain([chainId]), | ||
| ).resolves.toStrictEqual(deploy.address) | ||
| }) | ||
| } catch (err) {} | ||
| } | ||
| }) | ||
|
|
||
| it('NamedOnce', async () => { | ||
| const F = await connection.networkHelpers.loadFixture(fixture) | ||
| const contract = await connection.viem.deployContract('NamedOnce', [ | ||
| PRIMARY, | ||
| ]) | ||
| await expect( | ||
| F.l2ReverseRegistrar.read.nameForAddr([contract.address]), | ||
| ).resolves.toStrictEqual(PRIMARY) | ||
| }) | ||
|
|
||
| describe('NameableBy', () => { | ||
| it('w/o primary', async () => { | ||
| const F = await connection.networkHelpers.loadFixture(fixture) | ||
| const contract = await connection.viem.deployContract('NameableBy', [ | ||
| F.owner.account.address, | ||
| '', | ||
| ]) | ||
| await expect( | ||
| F.l2ReverseRegistrar.read.nameForAddr([contract.address]), | ||
| 'primary', | ||
| ).resolves.toStrictEqual('') | ||
| await expect(contract.read.nameOwner()).resolves.toStrictEqual( | ||
| getAddress(F.owner.account.address), | ||
| ) | ||
| }) | ||
|
|
||
| it('w/primary', async () => { | ||
| const F = await connection.networkHelpers.loadFixture(fixture) | ||
| const contract = await connection.viem.deployContract('NameableBy', [ | ||
| F.owner.account.address, | ||
| PRIMARY, | ||
| ]) | ||
| await expect( | ||
| F.l2ReverseRegistrar.read.nameForAddr([contract.address]), | ||
| 'primary', | ||
| ).resolves.toStrictEqual(PRIMARY) | ||
| }) | ||
|
|
||
| it('setName', async () => { | ||
| const F = await connection.networkHelpers.loadFixture(fixture) | ||
| const contract = await connection.viem.deployContract('NameableBy', [ | ||
| F.owner.account.address, | ||
| PRIMARY, | ||
| ]) | ||
| const primary = 'new-name' | ||
| await contract.write.setName([primary]) | ||
| await expect( | ||
| F.l2ReverseRegistrar.read.nameForAddr([contract.address]), | ||
| ).resolves.toStrictEqual(primary) | ||
| }) | ||
|
|
||
| it('disown', async () => { | ||
| const F = await connection.networkHelpers.loadFixture(fixture) | ||
| const contract = await connection.viem.deployContract('NameableBy', [ | ||
| F.owner.account.address, | ||
| PRIMARY, | ||
| ]) | ||
| await contract.write.setNameOwner([zeroAddress]) | ||
| await expect(contract.read.nameOwner()).resolves.toStrictEqual( | ||
| zeroAddress, | ||
| ) | ||
| }) | ||
|
|
||
| it('not owner', async () => { | ||
| const contract = await connection.viem.deployContract('NameableBy', [ | ||
| zeroAddress, | ||
| PRIMARY, | ||
| ]) | ||
| await expect(contract.write.setNameOwner([zeroAddress])).rejects.toThrow( | ||
| 'reverted without a reason', | ||
| ) | ||
| await expect(contract.write.setName([''])).rejects.toThrow( | ||
| 'reverted without a reason', | ||
| ) | ||
| }) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to add TESTNET or is it same as MAINNET address? since we have distinct MAINNET_ROLLUP and TESTNET_ROLLUP
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will mean all mainnets (mainnet/sepolia/...).
I speculatively designed ETHReverseResolver as the v2 replacement for
addr.reversethat uses the same tech as the rollups (independent registry + wildcard resolver) but I think we're going with a proper claimable{addr}.addr.reversethat uses the v2 design, but it should be a fixed address deployment and the same on those chains. And If not, you're correct.