├── .nvmrc ├── deployments ├── goerli │ ├── .chainId │ ├── .migrations.json │ └── solcInputs │ │ ├── 2d8cd8af817b3996918016eaf0684f54.json │ │ └── 9ab134ee99f7410d077d71824d3e2f84.json ├── base │ ├── .chainId │ └── .migrations.json ├── holesky │ ├── .chainId │ └── .migrations.json ├── linea │ ├── .chainId │ └── .migrations.json ├── mainnet │ ├── .chainId │ └── .migrations.json ├── optimism │ ├── .chainId │ └── .migrations.json ├── ropsten │ ├── .chainId │ ├── .migrations.json │ ├── solcInputs │ │ ├── 2d8cd8af817b3996918016eaf0684f54.json │ │ └── 9ab134ee99f7410d077d71824d3e2f84.json │ └── DefaultReverseResolver.json ├── scroll │ ├── .chainId │ └── .migrations.json ├── arbitrum │ ├── .chainId │ └── .migrations.json ├── baseSepolia │ └── .chainId ├── lineaSepolia │ └── .chainId ├── sepolia │ ├── .chainId │ └── .migrations.json ├── arbitrumSepolia │ └── .chainId ├── scrollSepolia │ └── .chainId ├── optimismSepolia │ └── .chainId └── archive │ ├── ETHRegistrarController_mainnet_9380471.sol │ ├── StringUtils.json │ ├── IERC165.json │ └── PriceOracle.json │ └── PublicResolver_mainnet_9412610.sol │ ├── Buffer.json │ ├── RRUtils.json │ ├── BytesUtils.json │ └── ResolverBase.json ├── .husky └── pre-commit ├── .npmignore ├── .vscode └── settings.json ├── .env.org ├── test ├── dnssec-oracle │ ├── TestRRUtils.test.ts │ ├── TestAlgorithms.test.ts │ ├── fixtures │ │ └── digests.ts │ └── TestDigests.test.ts ├── utils │ ├── TestBytesUtils.test.ts │ ├── features.ts │ ├── TestFeatures.test.ts │ ├── TestGatewayProvider.test.ts │ ├── TestERC20Recoverable.test.ts │ └── TestStringUtils.test.ts ├── fixtures │ ├── expectVar.ts │ ├── externalArtifacts.ts │ ├── constants.ts │ ├── dnsDecodeName.ts │ ├── utils.ts │ ├── deployRegistryFixture.ts │ ├── deployDefaultReverseFixture.ts │ ├── dnsEncodeName.ts │ ├── ensip19.ts │ ├── anchors.ts │ ├── runSolidityTests.ts │ ├── dnssecFixture.ts │ ├── createInterfaceId.ts │ └── createChainReverseResolverDeployment.ts ├── ethregistrar │ ├── exponentialPremiumScript.sh │ └── TestStablePriceOracle.test.ts ├── setup.ts ├── wrapper │ └── functions │ │ ├── ownerOf.ts │ │ └── getApproved.ts ├── dnsregistrar │ └── TestTLDPublicSuffixList.test.ts ├── universalResolver │ └── ownedEnsFixture.ts └── root │ └── TestRoot.test.ts ├── .solhint.json ├── .prettierignore ├── contracts ├── dnsregistrar │ ├── PublicSuffixList.sol │ ├── IDNSRegistrar.sol │ ├── TLDPublicSuffixList.sol │ ├── mocks │ │ ├── DummyLegacyTextResolver.sol │ │ ├── DummyExtendedDNSSECResolver.sol │ │ ├── DummyDnsRegistrarDNSSEC.sol │ │ └── DummyNonCCIPAwareResolver.sol │ └── SimplePublicSuffixList.sol ├── wrapper │ ├── IMetadataService.sol │ ├── INameWrapperUpgrade.sol │ ├── StaticMetadataService.sol │ ├── Controllable.sol │ ├── test │ │ └── TestNameWrapperReentrancy.sol │ └── mocks │ │ └── ERC1155ReceiverMock.sol ├── test │ └── mocks │ │ ├── MockOwnable.sol │ │ ├── MockReverseClaimerImplementer.sol │ │ ├── MockERC20.sol │ │ ├── MockSmartContractWallet.sol │ │ ├── MockOffchainResolver.sol │ │ └── MockERC6492WalletFactory.sol ├── resolvers │ ├── profiles │ │ ├── IExtendedResolver.sol │ │ ├── IVersionableResolver.sol │ │ ├── IExtendedDNSResolver.sol │ │ ├── IContentHashResolver.sol │ │ ├── IAddressResolver.sol │ │ ├── INameResolver.sol │ │ ├── IAddrResolver.sol │ │ ├── IHasAddressResolver.sol │ │ ├── IDNSZoneResolver.sol │ │ ├── IPubkeyResolver.sol │ │ ├── ExtendedResolver.sol │ │ ├── ITextResolver.sol │ │ ├── IABIResolver.sol │ │ ├── IInterfaceResolver.sol │ │ ├── IDNSRecordResolver.sol │ │ ├── NameResolver.sol │ │ ├── ContentHashResolver.sol │ │ ├── TextResolver.sol │ │ └── PubkeyResolver.sol │ ├── mocks │ │ └── DummyNameWrapper.sol │ ├── IMulticallable.sol │ ├── ResolverFeatures.sol │ ├── ResolverBase.sol │ ├── OwnedResolver.sol │ └── Multicallable.sol ├── ethregistrar │ ├── ILinearPremiumPriceOracle.sol │ ├── DummyOracle.sol │ ├── IBulkRenewal.sol │ ├── IPriceOracle.sol │ ├── IETHRegistrarController.sol │ ├── IBaseRegistrar.sol │ ├── SafeMath.sol │ └── StaticBulkRenewal.sol ├── utils │ ├── DummyOldResolver.sol.ignore │ ├── TestStringUtils.sol │ ├── IERC7996.sol │ ├── ERC20Recoverable.sol │ ├── TestENSIP19.sol │ ├── AddressUtils.sol │ ├── TestHexUtils.sol │ ├── TestNameCoder.sol │ ├── BytesUtils_LEGACY.sol │ └── MigrationHelper.sol ├── universalResolver │ ├── mocks │ │ └── MockResolverCaller.sol │ ├── UniversalResolver.sol │ └── RegistryUtils.sol ├── ccipRead │ ├── IGatewayProvider.sol │ ├── GatewayProvider.sol │ ├── EIP3668.sol │ ├── IBatchGateway.sol │ └── ShuffledGatewayProvider.sol ├── dnssec-oracle │ ├── digests │ │ ├── DummyDigest.sol │ │ ├── Digest.sol │ │ ├── SHA256Digest.sol │ │ └── SHA1Digest.sol │ ├── Owned.sol │ ├── algorithms │ │ ├── DummyAlgorithm.sol │ │ ├── Algorithm.sol │ │ ├── RSAVerify.sol │ │ ├── ModexpPrecompile.sol │ │ ├── P256SHA256Algorithm.sol │ │ ├── RSASHA256Algorithm.sol │ │ └── RSASHA1Algorithm.sol │ ├── DNSSEC.sol │ └── IDNSGateway.sol ├── reverseResolver │ ├── INameReverser.sol │ └── DefaultReverseResolver.sol ├── reverseRegistrar │ ├── ReverseClaimer.sol │ ├── IStandaloneReverseRegistrar.sol │ ├── IReverseRegistrar.sol │ ├── IDefaultReverseRegistrar.sol │ ├── StandaloneReverseRegistrar.sol │ ├── README.md │ └── IL2ReverseRegistrar.sol ├── root │ ├── Controllable.sol │ ├── Ownable.sol │ └── Root.sol └── registry │ ├── FIFSRegistrar.sol │ ├── TestRegistrar.sol │ ├── ENSRegistryWithFallback.sol │ └── ENS.sol ├── .gitignore ├── .soliumrc.json ├── remappings.txt ├── tasks ├── accounts.ts ├── archive_scan.ts └── save.ts ├── .eslintrc.js ├── .prettierrc.json ├── deploy ├── resolvers │ ├── 00_deploy_extended_dns_resolver.ts │ ├── 00_deploy_legacy_public_resolver.ts │ └── 00_deploy_eth_owned_resolver.ts ├── root │ ├── 00_deploy_root.ts │ └── 00_setup_root.ts ├── utils │ ├── 00_deploy_batch_gateway_provider.ts │ ├── 01_deploy_universal_resolver.ts │ └── 10_deploy_migration_helper.ts ├── dnsregistrar │ └── 00_deploy_offchain_dns_resolver.ts ├── ethregistrar │ ├── 00_deploy_base_registrar_implementation.ts │ ├── 01_deploy_exponential_premium_price_oracle.ts │ ├── 00_setup_base_registrar.ts │ ├── 02_deploy_legacy_eth_registrar_controller.ts │ └── 05_deploy_static_bulk_renewal.ts ├── dnssec-oracle │ ├── 00_deploy_digests.ts │ └── 00_deploy_algorithms.ts ├── reverseregistrar │ ├── 01_deploy_default_reverse_registrar.ts │ ├── 02_deploy_chain_reverse_resolver │ │ ├── 00_base.ts │ │ ├── 00_linea.ts │ │ ├── 00_scroll.ts │ │ ├── 00_arbitrum.ts │ │ └── 00_optimism.ts │ └── 00_deploy_reverse_registrar.ts ├── wrapper │ └── 00_deploy_static_metadata_service.ts └── reverseresolver │ └── 00_deploy_default_reverse_resolver.ts ├── vitest.config.ts ├── tsconfig.json ├── LICENSE.txt └── scripts ├── deploy-test.ts └── interfaces.ts /.nvmrc: -------------------------------------------------------------------------------- 1 | v24.6.0 2 | -------------------------------------------------------------------------------- /deployments/goerli/.chainId: -------------------------------------------------------------------------------- 1 | 5 -------------------------------------------------------------------------------- /deployments/base/.chainId: -------------------------------------------------------------------------------- 1 | 8453 -------------------------------------------------------------------------------- /deployments/holesky/.chainId: -------------------------------------------------------------------------------- 1 | 17000 -------------------------------------------------------------------------------- /deployments/linea/.chainId: -------------------------------------------------------------------------------- 1 | 59144 -------------------------------------------------------------------------------- /deployments/mainnet/.chainId: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /deployments/optimism/.chainId: -------------------------------------------------------------------------------- 1 | 10 -------------------------------------------------------------------------------- /deployments/ropsten/.chainId: -------------------------------------------------------------------------------- 1 | 3 -------------------------------------------------------------------------------- /deployments/scroll/.chainId: -------------------------------------------------------------------------------- 1 | 534352 -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | bun run format 2 | -------------------------------------------------------------------------------- /deployments/arbitrum/.chainId: -------------------------------------------------------------------------------- 1 | 42161 -------------------------------------------------------------------------------- /deployments/baseSepolia/.chainId: -------------------------------------------------------------------------------- 1 | 84532 -------------------------------------------------------------------------------- /deployments/lineaSepolia/.chainId: -------------------------------------------------------------------------------- 1 | 59141 -------------------------------------------------------------------------------- /deployments/sepolia/.chainId: -------------------------------------------------------------------------------- 1 | 11155111 -------------------------------------------------------------------------------- /deployments/arbitrumSepolia/.chainId: -------------------------------------------------------------------------------- 1 | 421614 -------------------------------------------------------------------------------- /deployments/scrollSepolia/.chainId: -------------------------------------------------------------------------------- 1 | 534351 -------------------------------------------------------------------------------- /deployments/optimismSepolia/.chainId: -------------------------------------------------------------------------------- 1 | 11155420 -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | node_modules/ 3 | 4 | #Hardhat files 5 | cache/ 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /deployments/ropsten/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "ens": 1580387832, 3 | "root": 1580387832 4 | } -------------------------------------------------------------------------------- /.env.org: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | ETHERSCAN_API_KEY= 3 | INFURA_API_KEY= 4 | METADATA_ADDRESS= 5 | WRAPPER_ADDRESS= 6 | RESOLVER_ADDRESS= -------------------------------------------------------------------------------- /deployments/base/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "UniversalSigValidator v1.0.0": 1750389297, 3 | "L2ReverseRegistrar v1.0.0": 1750406565 4 | } -------------------------------------------------------------------------------- /deployments/linea/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "UniversalSigValidator v1.0.0": 1750389215, 3 | "L2ReverseRegistrar v1.0.0": 1750406587 4 | } -------------------------------------------------------------------------------- /deployments/scroll/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "UniversalSigValidator v1.0.0": 1750389230, 3 | "L2ReverseRegistrar v1.0.0": 1750406595 4 | } -------------------------------------------------------------------------------- /deployments/arbitrum/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "UniversalSigValidator v1.0.0": 1750389177, 3 | "L2ReverseRegistrar v1.0.0": 1750406574 4 | } -------------------------------------------------------------------------------- /deployments/optimism/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "UniversalSigValidator v1.0.0": 1750386950, 3 | "L2ReverseRegistrar v1.0.0": 1750406553 4 | } -------------------------------------------------------------------------------- /test/dnssec-oracle/TestRRUtils.test.ts: -------------------------------------------------------------------------------- 1 | import { runSolidityTests } from '../fixtures/runSolidityTests.js' 2 | 3 | await runSolidityTests('TestRRUtils') 4 | -------------------------------------------------------------------------------- /test/utils/TestBytesUtils.test.ts: -------------------------------------------------------------------------------- 1 | import { runSolidityTests } from '../fixtures/runSolidityTests.js' 2 | 3 | await runSolidityTests('TestBytesUtils') 4 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default", 3 | "rules": { 4 | "indent": 2, 5 | "max-line-length": false, 6 | "quotes": "double" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | gasreport-* 5 | .env 6 | *.DS_Store 7 | node_modules 8 | build 9 | forge-cache 10 | out 11 | *.sh 12 | deployments 13 | generated -------------------------------------------------------------------------------- /contracts/dnsregistrar/PublicSuffixList.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | interface PublicSuffixList { 4 | function isPublicSuffix(bytes calldata name) external view returns (bool); 5 | } 6 | -------------------------------------------------------------------------------- /deployments/goerli/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "ens": 2086611, 3 | "root": 2086752, 4 | "registrar": 2086621, 5 | "legacy-controller": 2086640, 6 | "legacy-resolver": 2086614 7 | } -------------------------------------------------------------------------------- /test/fixtures/expectVar.ts: -------------------------------------------------------------------------------- 1 | // expectVar({ x }) <==> expect(x, 'x') 2 | export function expectVar(obj: Record) { 3 | const [[k, v]] = Object.entries(obj) 4 | return expect(v, k) 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | gasreport-* 5 | generated 6 | .env 7 | *.DS_Store 8 | build 9 | forge-cache 10 | out 11 | deployments/**/.pendingSafeTransactions 12 | deployments/localhost -------------------------------------------------------------------------------- /contracts/wrapper/IMetadataService.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ~0.8.17; 3 | 4 | interface IMetadataService { 5 | function uri(uint256) external view returns (string memory); 6 | } 7 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:all", 3 | "rules": { 4 | "indentation": ["error", 4], 5 | "quotes": ["error", "double"], 6 | "arg-overflow": "off", 7 | "blank-lines": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/externalArtifacts.ts: -------------------------------------------------------------------------------- 1 | export function urgArtifact(name: string) { 2 | return new URL( 3 | `../../node_modules/@unruggable/gateways/artifacts/${name}.sol/${name}.json`, 4 | import.meta.url, 5 | ) 6 | } 7 | -------------------------------------------------------------------------------- /contracts/test/mocks/MockOwnable.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17 <0.9.0; 3 | 4 | contract MockOwnable { 5 | address public owner; 6 | 7 | constructor(address _owner) { 8 | owner = _owner; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/ethregistrar/exponentialPremiumScript.sh: -------------------------------------------------------------------------------- 1 | # rerun if logic changes significantly for the ExponentialPremiumPriceOracle 2 | for i in $(seq 0 11); do for j in $(seq 0 4); do OFFSET=$((i+j*12)) bun run test test/ethregistrar/TestExponentialPremiumPriceOracle.js; done & done -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @unruggable/gateways/=node_modules/@unruggable/gateways/contracts 2 | @openzeppelin/contracts-v5/=node_modules/@openzeppelin/contracts-v5/ 3 | @openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ 4 | clones-with-immutable-args/=node_modules/clones-with-immutable-args/ -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IExtendedResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | interface IExtendedResolver { 5 | function resolve( 6 | bytes memory name, 7 | bytes memory data 8 | ) external view returns (bytes memory); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/ethregistrar/ILinearPremiumPriceOracle.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ~0.8.17; 3 | 4 | interface ILinearPremiumPriceOracle { 5 | function timeUntilPremium( 6 | uint256 expires, 7 | uint256 amount 8 | ) external view returns (uint256); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/resolvers/mocks/DummyNameWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | /// @dev Implements a dummy NameWrapper which returns the caller's address 4 | contract DummyNameWrapper { 5 | function ownerOf(uint256 /* id */) public view returns (address) { 6 | return tx.origin; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IVersionableResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface IVersionableResolver { 5 | event VersionChanged(bytes32 indexed node, uint64 newVersion); 6 | 7 | function recordVersions(bytes32 node) external view returns (uint64); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/utils/DummyOldResolver.sol.ignore: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.4.11; 3 | 4 | contract DummyOldResolver { 5 | function test() public returns (bool) { 6 | return true; 7 | } 8 | 9 | function name(bytes32) public returns (string memory) { 10 | return 'test.eth'; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IExtendedDNSResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | interface IExtendedDNSResolver { 5 | function resolve( 6 | bytes memory name, 7 | bytes memory data, 8 | bytes memory context 9 | ) external view returns (bytes memory); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/universalResolver/mocks/MockResolverCaller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {CCIPReader} from "../../ccipRead/CCIPReader.sol"; 5 | import {ResolverCaller} from "../ResolverCaller.sol"; 6 | 7 | contract MockResolverCaller is ResolverCaller { 8 | constructor() CCIPReader(0) {} 9 | } 10 | -------------------------------------------------------------------------------- /contracts/ccipRead/IGatewayProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /// @notice Interface for shared gateway URLs. 5 | /// @dev Interface selector: `0x093a86d3` 6 | interface IGatewayProvider { 7 | /// @notice Get the gateways. 8 | /// @return The gateway URLs. 9 | function gateways() external view returns (string[] memory); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/wrapper/INameWrapperUpgrade.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ~0.8.17; 3 | 4 | interface INameWrapperUpgrade { 5 | function wrapFromUpgrade( 6 | bytes calldata name, 7 | address wrappedOwner, 8 | uint32 fuses, 9 | uint64 expiry, 10 | address approved, 11 | bytes calldata extraData 12 | ) external; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/wrapper/StaticMetadataService.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ~0.8.17; 3 | 4 | contract StaticMetadataService { 5 | string private _uri; 6 | 7 | constructor(string memory _metaDataUri) { 8 | _uri = _metaDataUri; 9 | } 10 | 11 | function uri(uint256) public view returns (string memory) { 12 | return _uri; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tasks/accounts.ts: -------------------------------------------------------------------------------- 1 | import type { NewTaskActionFunction } from 'hardhat/types/tasks' 2 | 3 | const taskAccounts: NewTaskActionFunction = async (_, hre) => { 4 | const { viem } = await hre.network.connect() 5 | const accounts = await viem.getWalletClients() 6 | 7 | for (const { account } of accounts) { 8 | console.log(account.address) 9 | } 10 | } 11 | 12 | export default taskAccounts 13 | -------------------------------------------------------------------------------- /test/utils/features.ts: -------------------------------------------------------------------------------- 1 | import { keccak256, slice, stringToHex } from 'viem' 2 | 3 | export function makeFeature(s: string) { 4 | return slice(keccak256(stringToHex(s)), 0, 4) 5 | } 6 | 7 | export const FEATURES = { 8 | RESOLVER: { 9 | RESOLVE_MULTICALL: makeFeature('eth.ens.resolver.extended.multicall'), 10 | SINGULAR: makeFeature('eth.ens.resolver.singular'), 11 | }, 12 | } as const 13 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/digests/DummyDigest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "./Digest.sol"; 4 | 5 | /// @dev Implements a dummy DNSSEC digest that approves all hashes, for testing. 6 | contract DummyDigest is Digest { 7 | function verify( 8 | bytes calldata, 9 | bytes calldata 10 | ) external pure override returns (bool) { 11 | return true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/ethregistrar/DummyOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.4; 2 | 3 | contract DummyOracle { 4 | int256 value; 5 | 6 | constructor(int256 _value) public { 7 | set(_value); 8 | } 9 | 10 | function set(int256 _value) public { 11 | value = _value; 12 | } 13 | 14 | function latestAnswer() public view returns (int256) { 15 | return value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/resolvers/IMulticallable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | interface IMulticallable { 5 | function multicall( 6 | bytes[] calldata data 7 | ) external returns (bytes[] memory results); 8 | 9 | function multicallWithNodeCheck( 10 | bytes32, 11 | bytes[] calldata data 12 | ) external returns (bytes[] memory results); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/test/mocks/MockReverseClaimerImplementer.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17 <0.9.0; 3 | 4 | import {ENS} from "../../../contracts/registry/ENS.sol"; 5 | import {ReverseClaimer} from "../../../contracts/reverseRegistrar/ReverseClaimer.sol"; 6 | 7 | contract MockReverseClaimerImplementer is ReverseClaimer { 8 | constructor(ENS ens, address claimant) ReverseClaimer(ens, claimant) {} 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/constants.ts: -------------------------------------------------------------------------------- 1 | export const FUSES = { 2 | CAN_DO_EVERYTHING: 0, 3 | CANNOT_UNWRAP: 1, 4 | CANNOT_BURN_FUSES: 2, 5 | CANNOT_TRANSFER: 4, 6 | CANNOT_SET_RESOLVER: 8, 7 | CANNOT_SET_TTL: 16, 8 | CANNOT_CREATE_SUBDOMAIN: 32, 9 | CANNOT_APPROVE: 64, 10 | PARENT_CANNOT_CONTROL: 2 ** 16, 11 | IS_DOT_ETH: 2 ** 17, 12 | CAN_EXTEND_EXPIRY: 2 ** 18, 13 | } as const 14 | 15 | export const DAY = 24n * 60n * 60n 16 | -------------------------------------------------------------------------------- /contracts/ethregistrar/IBulkRenewal.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | interface IBulkRenewal { 5 | function rentPrice( 6 | string[] calldata names, 7 | uint256 duration 8 | ) external view returns (uint256 total); 9 | 10 | function renewAll( 11 | string[] calldata names, 12 | uint256 duration, 13 | bytes32 referrer 14 | ) external payable; 15 | } 16 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/Owned.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | /// @dev Contract mixin for 'owned' contracts. 4 | contract Owned { 5 | address public owner; 6 | 7 | modifier owner_only() { 8 | require(msg.sender == owner); 9 | _; 10 | } 11 | 12 | constructor() public { 13 | owner = msg.sender; 14 | } 15 | 16 | function setOwner(address newOwner) public owner_only { 17 | owner = newOwner; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IContentHashResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface IContentHashResolver { 5 | event ContenthashChanged(bytes32 indexed node, bytes hash); 6 | 7 | /// Returns the contenthash associated with an ENS node. 8 | /// @param node The ENS node to query. 9 | /// @return The associated contenthash. 10 | function contenthash(bytes32 node) external view returns (bytes memory); 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true, 4 | }, 5 | plugins: ['babel'], 6 | rules: { 7 | 'import/extensions': [ 8 | 'error', 9 | 'ignorePackages', 10 | { 11 | js: 'never', 12 | ts: 'never', 13 | }, 14 | ], 15 | 'import/prefer-default-export': 'off', 16 | 'prefer-destructuring': 'off', 17 | 'prefer-template': 'off', 18 | 'no-console': 'off', 19 | 'func-names': 'off', 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "endOfLine": "lf", 4 | "printWidth": 80, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 2, 8 | "trailingComma": "all", 9 | "overrides": [ 10 | { 11 | "files": "*.sol", 12 | "options": { 13 | "printWidth": 80, 14 | "tabWidth": 4, 15 | "useTabs": false, 16 | "singleQuote": false, 17 | "bracketSpacing": false 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/algorithms/DummyAlgorithm.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "./Algorithm.sol"; 4 | 5 | /// @dev Implements a dummy DNSSEC (signing) algorithm that approves all 6 | /// signatures, for testing. 7 | contract DummyAlgorithm is Algorithm { 8 | function verify( 9 | bytes calldata, 10 | bytes calldata, 11 | bytes calldata 12 | ) external view override returns (bool) { 13 | return true; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IAddressResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | /// Interface for the new (multicoin) addr function. 5 | interface IAddressResolver { 6 | event AddressChanged( 7 | bytes32 indexed node, 8 | uint256 coinType, 9 | bytes newAddress 10 | ); 11 | 12 | function addr( 13 | bytes32 node, 14 | uint256 coinType 15 | ) external view returns (bytes memory); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/utils/TestStringUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {StringUtils} from "../../contracts/utils/StringUtils.sol"; 5 | 6 | library TestStringUtils { 7 | function escape(string memory s) external pure returns (string memory) { 8 | return StringUtils.escape(s); 9 | } 10 | 11 | function strlen(string memory s) external pure returns (uint256) { 12 | return StringUtils.strlen(s); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/INameResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface INameResolver { 5 | event NameChanged(bytes32 indexed node, string name); 6 | 7 | /// Returns the name associated with an ENS node, for reverse records. 8 | /// Defined in EIP181. 9 | /// @param node The ENS node to query. 10 | /// @return The associated name. 11 | function name(bytes32 node) external view returns (string memory); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IAddrResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | /// Interface for the legacy (ETH-only) addr function. 5 | interface IAddrResolver { 6 | event AddrChanged(bytes32 indexed node, address a); 7 | 8 | /// Returns the address associated with an ENS node. 9 | /// @param node The ENS node to query. 10 | /// @return The associated address. 11 | function addr(bytes32 node) external view returns (address payable); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/utils/IERC7996.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /// @notice Interface for expressing contract features not visible from the ABI. 5 | /// @dev Interface selector: `0x582de3e7` 6 | interface IERC7996 { 7 | /// @notice Check if a feature is supported. 8 | /// @param featureId The feature identifier. 9 | /// @return `true` if the feature is supported by the contract. 10 | function supportsFeature(bytes4 featureId) external view returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/digests/Digest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | /// @dev An interface for contracts implementing a DNSSEC digest. 4 | interface Digest { 5 | /// @dev Verifies a cryptographic hash. 6 | /// @param data The data to hash. 7 | /// @param hash The hash to compare to. 8 | /// @return True iff the hashed data matches the provided hash value. 9 | function verify( 10 | bytes calldata data, 11 | bytes calldata hash 12 | ) external pure virtual returns (bool); 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/dnsDecodeName.ts: -------------------------------------------------------------------------------- 1 | import { type Hex, toBytes, bytesToString } from 'viem' 2 | 3 | export function dnsDecodeName(dns: Hex) { 4 | const v = toBytes(dns) 5 | const labels = [] 6 | let pos = 0 7 | while (pos < v.length) { 8 | const size = v[pos++] 9 | if (size == 0 || pos + size > v.length) break 10 | labels.push(bytesToString(v.subarray(pos, (pos += size)))) 11 | } 12 | if (pos != v.length) 13 | throw new Error(`malformed DNS-encoding: ${dns} @ ${pos}`) 14 | return labels.join('.') 15 | } 16 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IHasAddressResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface IHasAddressResolver { 5 | /// @notice Determine if an addresss is stored for the coin type of the associated ENS node. 6 | /// @param node The node to query. 7 | /// @param coinType The coin type. 8 | /// @return True if the associated address is not empty. 9 | function hasAddr( 10 | bytes32 node, 11 | uint256 coinType 12 | ) external view returns (bool); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/dnsregistrar/IDNSRegistrar.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "../dnssec-oracle/DNSSEC.sol"; 5 | 6 | interface IDNSRegistrar { 7 | function proveAndClaim( 8 | bytes memory name, 9 | DNSSEC.RRSetWithSignature[] memory input 10 | ) external; 11 | 12 | function proveAndClaimWithResolver( 13 | bytes memory name, 14 | DNSSEC.RRSetWithSignature[] memory input, 15 | address resolver, 16 | address addr 17 | ) external; 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/utils.ts: -------------------------------------------------------------------------------- 1 | import type { NetworkConnection } from 'hardhat/types/network' 2 | import { hexToBigInt, labelhash, namehash, type Hex } from 'viem' 3 | 4 | export const toTokenId = (hash: Hex) => hexToBigInt(hash) 5 | export const toLabelId = (label: string) => toTokenId(labelhash(label)) 6 | export const toNameId = (name: string) => toTokenId(namehash(name)) 7 | 8 | export const getAccounts = async (connection: NetworkConnection) => 9 | connection.viem 10 | .getWalletClients() 11 | .then((clients) => clients.map((c) => c.account)) 12 | -------------------------------------------------------------------------------- /contracts/resolvers/ResolverFeatures.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | library ResolverFeatures { 5 | /// @notice Implements `resolve(multicall([...]))`. 6 | /// @dev Feature: `0x96b62db8` 7 | bytes4 constant RESOLVE_MULTICALL = 8 | bytes4(keccak256("eth.ens.resolver.extended.multicall")); 9 | 10 | /// @notice Returns the same records independent of name or node. 11 | /// @dev Feature: `0x86fb8da8` 12 | bytes4 constant SINGULAR = bytes4(keccak256("eth.ens.resolver.singular")); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/digests/SHA256Digest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "./Digest.sol"; 4 | import "../../utils/BytesUtils.sol"; 5 | 6 | /// @dev Implements the DNSSEC SHA256 digest. 7 | contract SHA256Digest is Digest { 8 | using BytesUtils for *; 9 | 10 | function verify( 11 | bytes calldata data, 12 | bytes calldata hash 13 | ) external pure override returns (bool) { 14 | require(hash.length == 32, "Invalid sha256 hash length"); 15 | return sha256(data) == hash.readBytes32(0); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/dnsregistrar/TLDPublicSuffixList.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "../utils/BytesUtils.sol"; 4 | import "./PublicSuffixList.sol"; 5 | 6 | /// @dev A public suffix list that treats all TLDs as public suffixes. 7 | contract TLDPublicSuffixList is PublicSuffixList { 8 | using BytesUtils for bytes; 9 | 10 | function isPublicSuffix( 11 | bytes calldata name 12 | ) external view override returns (bool) { 13 | uint256 labellen = name.readUint8(0); 14 | return labellen > 0 && name.readUint8(labellen + 1) == 0; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/reverseResolver/INameReverser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /// @dev Interface selector: `0xe38f7138` 5 | interface INameReverser { 6 | /// @notice Resolve multiple EVM addresses to names. 7 | /// Caller should enable EIP-3668. 8 | /// @dev This function may execute over multiple steps. 9 | /// @param addrs The addresses to resolve. 10 | /// @return names The resolved names. 11 | function resolveNames( 12 | address[] memory addrs 13 | ) external view returns (string[] memory names); 14 | } 15 | -------------------------------------------------------------------------------- /deploy/resolvers/00_deploy_extended_dns_resolver.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, namedAccounts }) => { 5 | const { deployer } = namedAccounts 6 | 7 | // Deploy ExtendedDNSResolver 8 | await deploy('ExtendedDNSResolver', { 9 | account: deployer, 10 | artifact: artifacts.ExtendedDNSResolver, 11 | args: [], 12 | }) 13 | }, 14 | { 15 | id: 'ExtendedDNSResolver v1.0.0', 16 | tags: ['category:resolvers', 'ExtendedDNSResolver'], 17 | dependencies: [], 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: [ 6 | 'test/**/*.test.ts', 7 | 'test/reverseResolver/Test*.ts', 8 | 'test/reverseRegistrar/Test*.ts', 9 | ...(process.env.TEST_REMOTE ? ['test/**/*.remote.ts'] : []), 10 | ], 11 | exclude: ['test/**/*.behaviour.ts'], 12 | reporters: ["verbose"], 13 | environment: 'node', 14 | globals: true, 15 | setupFiles: ['./test/setup.ts'], 16 | }, 17 | esbuild: { 18 | target: 'node22', 19 | format: 'esm', 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IDNSZoneResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface IDNSZoneResolver { 5 | // DNSZonehashChanged is emitted whenever a given node's zone hash is updated. 6 | event DNSZonehashChanged( 7 | bytes32 indexed node, 8 | bytes lastzonehash, 9 | bytes zonehash 10 | ); 11 | 12 | /// zonehash obtains the hash for the zone. 13 | /// @param node The ENS node to query. 14 | /// @return The associated contenthash. 15 | function zonehash(bytes32 node) external view returns (bytes memory); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/test/mocks/MockERC20.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17 <0.9.0; 3 | 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockERC20 is ERC20 { 7 | constructor( 8 | string memory name, 9 | string memory symbol, 10 | address[] memory addresses 11 | ) ERC20(name, symbol) { 12 | _mint(msg.sender, 100 * 10 ** uint256(decimals())); 13 | 14 | for (uint256 i = 0; i < addresses.length; i++) { 15 | _mint(addresses[i], 100 * 10 ** uint256(decimals())); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/algorithms/Algorithm.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | /// @dev An interface for contracts implementing a DNSSEC (signing) algorithm. 4 | interface Algorithm { 5 | /// @dev Verifies a signature. 6 | /// @param key The public key to verify with. 7 | /// @param data The signed data to verify. 8 | /// @param signature The signature to verify. 9 | /// @return True iff the signature is valid. 10 | function verify( 11 | bytes calldata key, 12 | bytes calldata data, 13 | bytes calldata signature 14 | ) external view virtual returns (bool); 15 | } 16 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IPubkeyResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface IPubkeyResolver { 5 | event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y); 6 | 7 | /// Returns the SECP256k1 public key associated with an ENS node. 8 | /// Defined in EIP 619. 9 | /// @param node The ENS node to query 10 | /// @return x The X coordinate of the curve point for the public key. 11 | /// @return y The Y coordinate of the curve point for the public key. 12 | function pubkey(bytes32 node) external view returns (bytes32 x, bytes32 y); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/ExtendedResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | contract ExtendedResolver { 5 | function resolve( 6 | bytes memory /* name */, 7 | bytes memory data 8 | ) external view returns (bytes memory) { 9 | (bool success, bytes memory result) = address(this).staticcall(data); 10 | if (success) { 11 | return result; 12 | } else { 13 | // Revert with the reason provided by the call 14 | assembly { 15 | revert(add(result, 0x20), mload(result)) 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/reverseRegistrar/ReverseClaimer.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17 <0.9.0; 3 | 4 | import {ENS} from "../registry/ENS.sol"; 5 | import {IReverseRegistrar} from "../reverseRegistrar/IReverseRegistrar.sol"; 6 | 7 | contract ReverseClaimer { 8 | bytes32 constant ADDR_REVERSE_NODE = 9 | 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; 10 | 11 | constructor(ENS ens, address claimant) { 12 | IReverseRegistrar reverseRegistrar = IReverseRegistrar( 13 | ens.owner(ADDR_REVERSE_NODE) 14 | ); 15 | reverseRegistrar.claim(claimant); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/ITextResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface ITextResolver { 5 | event TextChanged( 6 | bytes32 indexed node, 7 | string indexed indexedKey, 8 | string key, 9 | string value 10 | ); 11 | 12 | /// Returns the text data associated with an ENS node and key. 13 | /// @param node The ENS node to query. 14 | /// @param key The text data key to query. 15 | /// @return The associated text data. 16 | function text( 17 | bytes32 node, 18 | string calldata key 19 | ) external view returns (string memory); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/digests/SHA1Digest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "./Digest.sol"; 4 | import "../../utils/BytesUtils.sol"; 5 | import "@ensdomains/solsha1/contracts/SHA1.sol"; 6 | 7 | /// @dev Implements the DNSSEC SHA1 digest. 8 | contract SHA1Digest is Digest { 9 | using BytesUtils for *; 10 | 11 | function verify( 12 | bytes calldata data, 13 | bytes calldata hash 14 | ) external pure override returns (bool) { 15 | require(hash.length == 20, "Invalid sha1 hash length"); 16 | bytes32 expected = hash.readBytes20(0); 17 | bytes20 computed = SHA1.sha1(data); 18 | return expected == computed; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IABIResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface IABIResolver { 5 | event ABIChanged(bytes32 indexed node, uint256 indexed contentType); 6 | 7 | /// Returns the ABI associated with an ENS node. 8 | /// Defined in EIP205. 9 | /// @param node The ENS node to query 10 | /// @param contentTypes A bitwise OR of the ABI formats accepted by the caller. 11 | /// @return contentType The content type of the return value 12 | /// @return data The ABI data 13 | function ABI( 14 | bytes32 node, 15 | uint256 contentTypes 16 | ) external view returns (uint256, bytes memory); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/dnsregistrar/mocks/DummyLegacyTextResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "../../resolvers/profiles/ITextResolver.sol"; 5 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 6 | 7 | contract DummyLegacyTextResolver is ITextResolver, IERC165 { 8 | function supportsInterface( 9 | bytes4 interfaceId 10 | ) external pure override returns (bool) { 11 | return interfaceId == type(ITextResolver).interfaceId; 12 | } 13 | 14 | function text( 15 | bytes32 /* node */, 16 | string calldata key 17 | ) external view override returns (string memory) { 18 | return key; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/algorithms/RSAVerify.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "./ModexpPrecompile.sol"; 4 | import "../../utils/BytesUtils.sol"; 5 | 6 | library RSAVerify { 7 | /// @dev Recovers the input data from an RSA signature, returning the result in S. 8 | /// @param N The RSA public modulus. 9 | /// @param E The RSA public exponent. 10 | /// @param S The signature to recover. 11 | /// @return True if the recovery succeeded. 12 | function rsarecover( 13 | bytes memory N, 14 | bytes memory E, 15 | bytes memory S 16 | ) internal view returns (bool, bytes memory) { 17 | return ModexpPrecompile.modexp(S, E, N); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- 1 | // Setup file for vitest tests 2 | import '@ensdomains/hardhat-chai-matchers-viem' 3 | 4 | // Since the package doesn't export the matchers directly and relies on Hardhat hooks, 5 | // manually initialized the chai matchers for vitest 6 | 7 | async function setupChaiMatchers() { 8 | try { 9 | const { hardhatChaiMatchers } = await import( 10 | /* @vite-ignore */ 11 | '../node_modules/@ensdomains/hardhat-chai-matchers-viem/dist/matchers.js' 12 | ) 13 | // Use the global chai from vitest 14 | chai.use(hardhatChaiMatchers) 15 | } catch (error) { 16 | console.error('Failed to load chai matchers:', error) 17 | } 18 | } 19 | 20 | await setupChaiMatchers() 21 | -------------------------------------------------------------------------------- /contracts/root/Controllable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "@openzeppelin/contracts/access/Ownable.sol"; 4 | 5 | contract Controllable is Ownable { 6 | mapping(address => bool) public controllers; 7 | 8 | event ControllerChanged(address indexed controller, bool enabled); 9 | 10 | modifier onlyController() { 11 | require( 12 | controllers[msg.sender], 13 | "Controllable: Caller is not a controller" 14 | ); 15 | _; 16 | } 17 | 18 | function setController(address controller, bool enabled) public onlyOwner { 19 | controllers[controller] = enabled; 20 | emit ControllerChanged(controller, enabled); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/test/mocks/MockSmartContractWallet.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17 <0.9.0; 3 | // import signatureVerifier by openzepellin 4 | import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; 5 | 6 | contract MockSmartContractWallet { 7 | address public owner; 8 | 9 | constructor(address _owner) { 10 | owner = _owner; 11 | } 12 | 13 | function isValidSignature( 14 | bytes32 hash, 15 | bytes memory signature 16 | ) public view returns (bytes4) { 17 | if (SignatureChecker.isValidSignatureNow(owner, hash, signature)) { 18 | return 0x1626ba7e; 19 | } 20 | return 0xffffffff; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/root/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | contract Ownable { 4 | address public owner; 5 | 6 | event OwnershipTransferred( 7 | address indexed previousOwner, 8 | address indexed newOwner 9 | ); 10 | 11 | modifier onlyOwner() { 12 | require(isOwner(msg.sender)); 13 | _; 14 | } 15 | 16 | constructor() public { 17 | owner = msg.sender; 18 | } 19 | 20 | function transferOwnership(address newOwner) public onlyOwner { 21 | emit OwnershipTransferred(owner, newOwner); 22 | owner = newOwner; 23 | } 24 | 25 | function isOwner(address addr) public view returns (bool) { 26 | return owner == addr; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/wrapper/Controllable.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ~0.8.17; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | 6 | contract Controllable is Ownable { 7 | mapping(address => bool) public controllers; 8 | 9 | event ControllerChanged(address indexed controller, bool active); 10 | 11 | function setController(address controller, bool active) public onlyOwner { 12 | controllers[controller] = active; 13 | emit ControllerChanged(controller, active); 14 | } 15 | 16 | modifier onlyController() { 17 | require( 18 | controllers[msg.sender], 19 | "Controllable: Caller is not a controller" 20 | ); 21 | _; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/reverseRegistrar/IStandaloneReverseRegistrar.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice Interface for a standalone reverse registrar. 5 | interface IStandaloneReverseRegistrar { 6 | /// @notice Emitted when the name for an address is changed. 7 | /// 8 | /// @param addr The address of the reverse record. 9 | /// @param name The name of the reverse record. 10 | event NameForAddrChanged(address indexed addr, string name); 11 | 12 | /// @notice Returns the name for an address. 13 | /// 14 | /// @param addr The address to get the name for. 15 | /// @return The name for the address. 16 | function nameForAddr(address addr) external view returns (string memory); 17 | } 18 | -------------------------------------------------------------------------------- /deploy/root/00_deploy_root.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, get, namedAccounts, network }) => { 5 | const { deployer } = namedAccounts 6 | 7 | if (!network.tags?.use_root) { 8 | return 9 | } 10 | 11 | // Get dependencies 12 | const registry = get<(typeof artifacts.ENSRegistry)['abi']>('ENSRegistry') 13 | 14 | // Deploy Root 15 | await deploy('Root', { 16 | account: deployer, 17 | artifact: artifacts.Root, 18 | args: [registry.address], 19 | }) 20 | }, 21 | { 22 | id: 'Root:contract v1.0.0', 23 | tags: ['category:root', 'Root', 'Root:contract'], 24 | dependencies: ['ENSRegistry'], 25 | }, 26 | ) 27 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/DNSSEC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | pragma experimental ABIEncoderV2; 4 | 5 | abstract contract DNSSEC { 6 | bytes public anchors; 7 | 8 | struct RRSetWithSignature { 9 | bytes rrset; 10 | bytes sig; 11 | } 12 | 13 | event AlgorithmUpdated(uint8 id, address addr); 14 | event DigestUpdated(uint8 id, address addr); 15 | 16 | function verifyRRSet( 17 | RRSetWithSignature[] memory input 18 | ) external view virtual returns (bytes memory rrs, uint32 inception); 19 | 20 | function verifyRRSet( 21 | RRSetWithSignature[] memory input, 22 | uint256 now 23 | ) public view virtual returns (bytes memory rrs, uint32 inception); 24 | } 25 | -------------------------------------------------------------------------------- /contracts/dnsregistrar/SimplePublicSuffixList.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../root/Ownable.sol"; 5 | import "./PublicSuffixList.sol"; 6 | 7 | contract SimplePublicSuffixList is PublicSuffixList, Ownable { 8 | mapping(bytes => bool) suffixes; 9 | 10 | event SuffixAdded(bytes suffix); 11 | 12 | function addPublicSuffixes(bytes[] memory names) public onlyOwner { 13 | for (uint256 i = 0; i < names.length; i++) { 14 | suffixes[names[i]] = true; 15 | emit SuffixAdded(names[i]); 16 | } 17 | } 18 | 19 | function isPublicSuffix( 20 | bytes calldata name 21 | ) external view override returns (bool) { 22 | return suffixes[name]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /deployments/archive/ETHRegistrarController_mainnet_9380471.sol/StringUtils.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "StringUtils", 4 | "sourceName": "contracts/legacy/ETHRegistrarController_mainnet_9380471.sol", 5 | "abi": [], 6 | "bytecode": "0x60556023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea265627a7a72315820aaf7b8318e8004dd02fab147570660ad0d00db8ef375be9f7efc8927224409f864736f6c63430005110032", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea265627a7a72315820aaf7b8318e8004dd02fab147570660ad0d00db8ef375be9f7efc8927224409f864736f6c63430005110032", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /contracts/ethregistrar/IPriceOracle.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17 <0.9.0; 3 | 4 | interface IPriceOracle { 5 | struct Price { 6 | uint256 base; 7 | uint256 premium; 8 | } 9 | 10 | /// @dev Returns the price to register or renew a name. 11 | /// @param name The name being registered or renewed. 12 | /// @param expires When the name presently expires (0 if this is a new registration). 13 | /// @param duration How long the name is being registered or extended for, in seconds. 14 | /// @return base premium tuple of base price + premium price 15 | function price( 16 | string calldata name, 17 | uint256 expires, 18 | uint256 duration 19 | ) external view returns (Price calldata); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "../../resolvers/profiles/IExtendedDNSResolver.sol"; 5 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 6 | 7 | contract DummyExtendedDNSSECResolver is IExtendedDNSResolver, IERC165 { 8 | function supportsInterface( 9 | bytes4 interfaceId 10 | ) external pure override returns (bool) { 11 | return interfaceId == type(IExtendedDNSResolver).interfaceId; 12 | } 13 | 14 | function resolve( 15 | bytes memory /* name */, 16 | bytes memory /* data */, 17 | bytes memory context 18 | ) external view override returns (bytes memory) { 19 | return abi.encode(context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/ccipRead/GatewayProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Ownable} from "@openzeppelin/contracts-v5/access/Ownable.sol"; 5 | 6 | import {IGatewayProvider} from "./IGatewayProvider.sol"; 7 | 8 | contract GatewayProvider is Ownable, IGatewayProvider { 9 | string[] _urls; 10 | 11 | constructor(address owner, string[] memory urls) Ownable(owner) { 12 | _urls = urls; 13 | } 14 | 15 | /// @inheritdoc IGatewayProvider 16 | function gateways() external view returns (string[] memory) { 17 | return _urls; 18 | } 19 | 20 | /// @notice Set the gateways. 21 | /// @param urls The gateway URLs. 22 | function setGateways(string[] memory urls) external onlyOwner { 23 | _urls = urls; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/IDNSGateway.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {DNSSEC} from "./DNSSEC.sol"; 5 | 6 | /// @notice Interface for the offchain DNSSEC oracle gateway. 7 | /// https://docs.ens.domains/ensip/17#dnssec-gateway-api 8 | /// @dev Interface selector: `0x31b137b9` 9 | interface IDNSGateway { 10 | /// @dev Fetch verifiable DNSSEC resource records of a specific type for a name. 11 | /// @param name The DNS-encoded name. 12 | /// @param qtype The DNS record query type according to RFC-1034. 13 | /// @return The list of verifiable DNS resource records according to RFC-4035. 14 | function resolve( 15 | bytes memory name, 16 | uint16 qtype 17 | ) external returns (DNSSEC.RRSetWithSignature[] memory); 18 | } 19 | -------------------------------------------------------------------------------- /deployments/archive/PublicResolver_mainnet_9412610.sol/Buffer.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "Buffer", 4 | "sourceName": "contracts/legacy/PublicResolver_mainnet_9412610.sol", 5 | "abi": [], 6 | "bytecode": "0x60636023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea365627a7a7231582010b3f0480e604d1ec4a8378abf0cc88b870bf9f1dde6dba8cdabe13e76b810126c6578706572696d656e74616cf564736f6c63430005110040", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea365627a7a7231582010b3f0480e604d1ec4a8378abf0cc88b870bf9f1dde6dba8cdabe13e76b810126c6578706572696d656e74616cf564736f6c63430005110040", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /deployments/archive/PublicResolver_mainnet_9412610.sol/RRUtils.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "RRUtils", 4 | "sourceName": "contracts/legacy/PublicResolver_mainnet_9412610.sol", 5 | "abi": [], 6 | "bytecode": "0x60636023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea365627a7a7231582098d5f02c44eb8d313e0e0facc49aaed1a8bc1bbd3e93d6164d69215694892aee6c6578706572696d656e74616cf564736f6c63430005110040", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea365627a7a7231582098d5f02c44eb8d313e0e0facc49aaed1a8bc1bbd3e93d6164d69215694892aee6c6578706572696d656e74616cf564736f6c63430005110040", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /deploy/utils/00_deploy_batch_gateway_provider.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, namedAccounts }) => { 5 | const { deployer, owner } = namedAccounts 6 | 7 | const batchGatewayURLs: string[] = JSON.parse( 8 | process.env.BATCH_GATEWAY_URLS || '[]', 9 | ) 10 | 11 | if (!batchGatewayURLs.length) { 12 | throw new Error('BatchGatewayProvider: No batch gateway URLs provided') 13 | } 14 | await deploy('BatchGatewayProvider', { 15 | account: deployer, 16 | artifact: artifacts.GatewayProvider, 17 | args: [owner ?? deployer, batchGatewayURLs], 18 | }) 19 | }, 20 | { 21 | id: 'BatchGatewayProvider v1.0.0', 22 | tags: ['category:utils', 'BatchGatewayProvider'], 23 | }, 24 | ) 25 | -------------------------------------------------------------------------------- /deployments/archive/PublicResolver_mainnet_9412610.sol/BytesUtils.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "BytesUtils", 4 | "sourceName": "contracts/legacy/PublicResolver_mainnet_9412610.sol", 5 | "abi": [], 6 | "bytecode": "0x60636023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea365627a7a7231582070c6b1e2e7530961a23eae7629fa40adaa71145c87e954dbcd9df58aace515d96c6578706572696d656e74616cf564736f6c63430005110040", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea365627a7a7231582070c6b1e2e7530961a23eae7629fa40adaa71145c87e954dbcd9df58aace515d96c6578706572696d656e74616cf564736f6c63430005110040", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /deploy/dnsregistrar/00_deploy_offchain_dns_resolver.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, get, namedAccounts }) => { 5 | const { deployer } = namedAccounts 6 | 7 | const registry = get<(typeof artifacts.ENSRegistry)['abi']>('ENSRegistry') 8 | const dnssec = get<(typeof artifacts.DNSSECImpl)['abi']>('DNSSECImpl') 9 | 10 | await deploy('OffchainDNSResolver', { 11 | account: deployer, 12 | artifact: artifacts.OffchainDNSResolver, 13 | args: [ 14 | registry.address, 15 | dnssec.address, 16 | 'https://dnssec-oracle.ens.domains/', 17 | ], 18 | }) 19 | }, 20 | { 21 | id: 'OffchainDNSResolver v1.0.0', 22 | tags: ['category:dnsregistrar', 'OffchainDNSResolver'], 23 | dependencies: ['ENSRegistry', 'DNSSECImpl'], 24 | }, 25 | ) 26 | -------------------------------------------------------------------------------- /deploy/utils/01_deploy_universal_resolver.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, get, namedAccounts }) => { 5 | const { deployer, owner } = namedAccounts 6 | 7 | const registry = get<(typeof artifacts.ENSRegistry)['abi']>('ENSRegistry') 8 | const batchGatewayProvider = get<(typeof artifacts.GatewayProvider)['abi']>( 9 | 'BatchGatewayProvider', 10 | ) 11 | 12 | await deploy('UniversalResolver', { 13 | account: deployer, 14 | artifact: artifacts.UniversalResolver, 15 | args: [owner, registry.address, batchGatewayProvider.address], 16 | }) 17 | 18 | return true 19 | }, 20 | { 21 | id: 'UniversalResolver v1.0.1', 22 | tags: ['category:utils', 'UniversalResolver'], 23 | dependencies: ['ENSRegistry', 'BatchGatewayProvider'], 24 | }, 25 | ) 26 | -------------------------------------------------------------------------------- /contracts/reverseRegistrar/IReverseRegistrar.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.4; 2 | 3 | interface IReverseRegistrar { 4 | function setDefaultResolver(address resolver) external; 5 | 6 | function claim(address owner) external returns (bytes32); 7 | 8 | function claimForAddr( 9 | address addr, 10 | address owner, 11 | address resolver 12 | ) external returns (bytes32); 13 | 14 | function claimWithResolver( 15 | address owner, 16 | address resolver 17 | ) external returns (bytes32); 18 | 19 | function setName(string memory name) external returns (bytes32); 20 | 21 | function setNameForAddr( 22 | address addr, 23 | address owner, 24 | address resolver, 25 | string memory name 26 | ) external returns (bytes32); 27 | 28 | function node(address addr) external pure returns (bytes32); 29 | } 30 | -------------------------------------------------------------------------------- /deployments/archive/PublicResolver_mainnet_9412610.sol/ResolverBase.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "ResolverBase", 4 | "sourceName": "contracts/legacy/PublicResolver_mainnet_9412610.sol", 5 | "abi": [ 6 | { 7 | "constant": true, 8 | "inputs": [ 9 | { 10 | "internalType": "bytes4", 11 | "name": "interfaceID", 12 | "type": "bytes4" 13 | } 14 | ], 15 | "name": "supportsInterface", 16 | "outputs": [ 17 | { 18 | "internalType": "bool", 19 | "name": "", 20 | "type": "bool" 21 | } 22 | ], 23 | "payable": false, 24 | "stateMutability": "pure", 25 | "type": "function" 26 | } 27 | ], 28 | "bytecode": "0x", 29 | "deployedBytecode": "0x", 30 | "linkReferences": {}, 31 | "deployedLinkReferences": {} 32 | } 33 | -------------------------------------------------------------------------------- /deployments/archive/ETHRegistrarController_mainnet_9380471.sol/IERC165.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IERC165", 4 | "sourceName": "contracts/legacy/ETHRegistrarController_mainnet_9380471.sol", 5 | "abi": [ 6 | { 7 | "constant": true, 8 | "inputs": [ 9 | { 10 | "internalType": "bytes4", 11 | "name": "interfaceId", 12 | "type": "bytes4" 13 | } 14 | ], 15 | "name": "supportsInterface", 16 | "outputs": [ 17 | { 18 | "internalType": "bool", 19 | "name": "", 20 | "type": "bool" 21 | } 22 | ], 23 | "payable": false, 24 | "stateMutability": "view", 25 | "type": "function" 26 | } 27 | ], 28 | "bytecode": "0x", 29 | "deployedBytecode": "0x", 30 | "linkReferences": {}, 31 | "deployedLinkReferences": {} 32 | } 33 | -------------------------------------------------------------------------------- /test/fixtures/deployRegistryFixture.ts: -------------------------------------------------------------------------------- 1 | import type { NetworkConnection } from 'hardhat/types/network' 2 | import { getAddress, labelhash, namehash } from 'viem' 3 | 4 | export async function deployRegistryFixture(connection: NetworkConnection) { 5 | const [walletClient] = await connection.viem.getWalletClients() 6 | const owner = getAddress(walletClient.account.address) 7 | const ensRegistry = await connection.viem.deployContract('ENSRegistry') 8 | 9 | async function takeControl(name: string) { 10 | if (name) { 11 | const labels = name.split('.') 12 | for (let i = labels.length; i > 0; i--) { 13 | await ensRegistry.write.setSubnodeOwner([ 14 | namehash(labels.slice(i).join('.')), 15 | labelhash(labels[i - 1]), 16 | owner, 17 | ]) 18 | } 19 | } 20 | } 21 | 22 | return { owner, walletClient, ensRegistry, takeControl } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/utils/ERC20Recoverable.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17 <0.9.0; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | /// @notice Contract is used to recover ERC20 tokens sent to the contract by mistake. 8 | 9 | contract ERC20Recoverable is Ownable { 10 | /// @notice Recover ERC20 tokens sent to the contract by mistake. 11 | /// @dev The contract is Ownable and only the owner can call the recover function. 12 | /// @param _to The address to send the tokens to. 13 | /// @param _token The address of the ERC20 token to recover 14 | /// @param _amount The amount of tokens to recover. 15 | function recoverFunds( 16 | address _token, 17 | address _to, 18 | uint256 _amount 19 | ) external onlyOwner { 20 | IERC20(_token).transfer(_to, _amount); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /deploy/ethregistrar/00_deploy_base_registrar_implementation.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | import { namehash } from 'viem/ens' 3 | 4 | export default deployScript( 5 | async ({ deploy, get, namedAccounts, network }) => { 6 | const { deployer } = namedAccounts 7 | 8 | if (!network.tags?.use_root) { 9 | return 10 | } 11 | 12 | const registry = get<(typeof artifacts.ENSRegistry)['abi']>('ENSRegistry') 13 | 14 | await deploy('BaseRegistrarImplementation', { 15 | account: deployer, 16 | artifact: artifacts.BaseRegistrarImplementation, 17 | args: [registry.address, namehash('eth')], 18 | }) 19 | }, 20 | { 21 | id: 'BaseRegistrarImplementation:contract v1.0.0', 22 | tags: [ 23 | 'category:ethregistrar', 24 | 'BaseRegistrarImplementation', 25 | 'BaseRegistrarImplementation:contract', 26 | ], 27 | dependencies: ['ENSRegistry'], 28 | }, 29 | ) 30 | -------------------------------------------------------------------------------- /deploy/resolvers/00_deploy_legacy_public_resolver.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | import type { Artifact } from 'rocketh' 3 | import LegacyPublicResolverArtifact from '../../deployments/archive/PublicResolver_mainnet_9412610.sol/PublicResolver_mainnet_9412610.json' 4 | 5 | export default deployScript( 6 | async ({ deploy, get, namedAccounts, network }) => { 7 | const { deployer } = namedAccounts 8 | 9 | if (!network.tags?.legacy) { 10 | return 11 | } 12 | 13 | const registry = get<(typeof artifacts.ENSRegistry)['abi']>('ENSRegistry') 14 | 15 | await deploy('LegacyPublicResolver', { 16 | account: deployer, 17 | artifact: LegacyPublicResolverArtifact as unknown as Artifact, 18 | args: [registry.address], 19 | }) 20 | }, 21 | { 22 | id: 'PublicResolver v1.0.0', 23 | tags: ['category:resolvers', 'LegacyPublicResolver'], 24 | dependencies: ['ENSRegistry'], 25 | }, 26 | ) 27 | -------------------------------------------------------------------------------- /deploy/dnssec-oracle/00_deploy_digests.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, namedAccounts, network }) => { 5 | const { deployer } = namedAccounts 6 | 7 | await deploy('SHA1Digest', { 8 | account: deployer, 9 | artifact: artifacts.SHA1Digest, 10 | args: [], 11 | }) 12 | 13 | await deploy('SHA256Digest', { 14 | account: deployer, 15 | artifact: artifacts.SHA256Digest, 16 | args: [], 17 | }) 18 | 19 | if (network.tags?.test) { 20 | await deploy('DummyDigest', { 21 | account: deployer, 22 | artifact: artifacts.DummyDigest, 23 | args: [], 24 | }) 25 | } 26 | }, 27 | { 28 | id: 'dnssec-digests v1.0.0', 29 | tags: [ 30 | 'category:dnssec-oracle', 31 | 'dnssec-digests', 32 | 'SHA1Digest', 33 | 'SHA256Digest', 34 | 'DummyDigest', 35 | ], 36 | }, 37 | ) 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleDetection": "force", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "moduleResolution": "NodeNext", 9 | "outDir": "build", 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "types": ["vitest/globals"], 14 | "paths": { 15 | "@rocketh": ["./rocketh.ts"] 16 | } 17 | }, 18 | "include": [ 19 | "./utils/**/*.ts", 20 | "./scripts/**/*.ts", 21 | "./test/**/*.ts", 22 | "./tasks/**/*.ts", 23 | "./tasks/**/*.cts", 24 | "./deploy/**/*.ts", 25 | "./typings-custom/**/*.ts", 26 | "./artifacts/**/*.d.ts", 27 | "./artifacts/node_modules/**/*.d.ts" 28 | ], 29 | "ts-node": { 30 | "experimentalResolver": true, 31 | "experimentalSpecifierResolution": "node", 32 | "files": true 33 | }, 34 | "files": ["hardhat.config.ts"] 35 | } 36 | -------------------------------------------------------------------------------- /deploy/reverseregistrar/01_deploy_default_reverse_registrar.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, execute: write, namedAccounts }) => { 5 | const { deployer, owner } = namedAccounts 6 | 7 | const defaultReverseRegistrar = await deploy('DefaultReverseRegistrar', { 8 | account: deployer, 9 | artifact: artifacts.DefaultReverseRegistrar, 10 | }) 11 | 12 | // Transfer ownership to owner 13 | if (owner !== deployer) { 14 | console.log( 15 | ` - Transferring ownership of DefaultReverseRegistrar to ${owner}`, 16 | ) 17 | await write(defaultReverseRegistrar, { 18 | functionName: 'transferOwnership', 19 | args: [owner], 20 | account: deployer, 21 | }) 22 | } 23 | }, 24 | { 25 | id: 'DefaultReverseRegistrar v1.0.0', 26 | tags: ['category:reverseregistrar', 'DefaultReverseRegistrar'], 27 | dependencies: [], 28 | }, 29 | ) 30 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/algorithms/ModexpPrecompile.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | library ModexpPrecompile { 4 | /// @dev Computes (base ^ exponent) % modulus over big numbers. 5 | function modexp( 6 | bytes memory base, 7 | bytes memory exponent, 8 | bytes memory modulus 9 | ) internal view returns (bool success, bytes memory output) { 10 | bytes memory input = abi.encodePacked( 11 | uint256(base.length), 12 | uint256(exponent.length), 13 | uint256(modulus.length), 14 | base, 15 | exponent, 16 | modulus 17 | ); 18 | 19 | output = new bytes(modulus.length); 20 | 21 | assembly { 22 | success := staticcall( 23 | gas(), 24 | 5, 25 | add(input, 32), 26 | mload(input), 27 | add(output, 32), 28 | mload(modulus) 29 | ) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/universalResolver/UniversalResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {AbstractUniversalResolver, IGatewayProvider} from "./AbstractUniversalResolver.sol"; 5 | import {RegistryUtils, ENS} from "./RegistryUtils.sol"; 6 | import {ReverseClaimer} from "../reverseRegistrar/ReverseClaimer.sol"; 7 | 8 | contract UniversalResolver is AbstractUniversalResolver, ReverseClaimer { 9 | ENS public immutable registry; 10 | 11 | constructor( 12 | address owner, 13 | ENS ens, 14 | IGatewayProvider batchGatewayProvider 15 | ) 16 | AbstractUniversalResolver(batchGatewayProvider) 17 | ReverseClaimer(ens, owner) 18 | { 19 | registry = ens; 20 | } 21 | 22 | /// @inheritdoc AbstractUniversalResolver 23 | function findResolver( 24 | bytes memory name 25 | ) public view override returns (address, bytes32, uint256) { 26 | return RegistryUtils.findResolver(registry, name, 0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /deploy/wrapper/00_deploy_static_metadata_service.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, namedAccounts, network }) => { 5 | const { deployer } = namedAccounts 6 | 7 | let metadataHost = 8 | process.env.METADATA_HOST || 'ens-metadata-service.appspot.com' 9 | 10 | if (network.name === 'localhost') { 11 | metadataHost = 'http://localhost:8080' 12 | } 13 | 14 | const metadataUrl = `${metadataHost}/name/0x{id}` 15 | 16 | await deploy('StaticMetadataService', { 17 | account: deployer, 18 | artifact: artifacts.StaticMetadataService, 19 | args: [metadataUrl], 20 | }) 21 | 22 | return true 23 | }, 24 | { 25 | id: 'StaticMetadataService v1.0.0', 26 | tags: ['category:wrapper', 'StaticMetadataService'], 27 | // technically not a dep, but we want to make sure it's deployed first for the consistent address 28 | dependencies: ['BaseRegistrarImplementation'], 29 | }, 30 | ) 31 | -------------------------------------------------------------------------------- /contracts/reverseRegistrar/IDefaultReverseRegistrar.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice Interface for the Default Reverse Registrar. 5 | interface IDefaultReverseRegistrar { 6 | /// @notice Sets the `nameForAddr()` record for the calling account. 7 | /// 8 | /// @param name The name to set. 9 | function setName(string memory name) external; 10 | 11 | /// @notice Sets the `nameForAddr()` record for the addr provided account using a signature. 12 | /// 13 | /// @param addr The address to set the name for. 14 | /// @param name The name to set. 15 | /// @param signatureExpiry Date when the signature expires. 16 | /// @param signature The signature from the addr. 17 | function setNameForAddrWithSignature( 18 | address addr, 19 | uint256 signatureExpiry, 20 | string memory name, 21 | bytes memory signature 22 | ) external; 23 | 24 | function setNameForAddr(address addr, string memory name) external; 25 | } 26 | -------------------------------------------------------------------------------- /contracts/dnsregistrar/mocks/DummyDnsRegistrarDNSSEC.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | contract DummyDNSSEC { 4 | uint16 expectedType; 5 | bytes expectedName; 6 | uint32 inception; 7 | uint64 inserted; 8 | bytes20 hash; 9 | 10 | function setData( 11 | uint16 _expectedType, 12 | bytes memory _expectedName, 13 | uint32 _inception, 14 | uint64 _inserted, 15 | bytes memory _proof 16 | ) public { 17 | expectedType = _expectedType; 18 | expectedName = _expectedName; 19 | inception = _inception; 20 | inserted = _inserted; 21 | if (_proof.length != 0) { 22 | hash = bytes20(keccak256(_proof)); 23 | } 24 | } 25 | 26 | function rrdata( 27 | uint16 dnstype, 28 | bytes memory name 29 | ) public view returns (uint32, uint64, bytes20) { 30 | require(dnstype == expectedType); 31 | require(keccak256(name) == keccak256(expectedName)); 32 | return (inception, inserted, hash); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, True Names Limited 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IInterfaceResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface IInterfaceResolver { 5 | event InterfaceChanged( 6 | bytes32 indexed node, 7 | bytes4 indexed interfaceID, 8 | address implementer 9 | ); 10 | 11 | /// Returns the address of a contract that implements the specified interface for this name. 12 | /// If an implementer has not been set for this interfaceID and name, the resolver will query 13 | /// the contract at `addr()`. If `addr()` is set, a contract exists at that address, and that 14 | /// contract implements EIP165 and returns `true` for the specified interfaceID, its address 15 | /// will be returned. 16 | /// @param node The ENS node to query. 17 | /// @param interfaceID The EIP 165 interface ID to check for. 18 | /// @return The address that implements this interface, or 0 if the interface is unsupported. 19 | function interfaceImplementer( 20 | bytes32 node, 21 | bytes4 interfaceID 22 | ) external view returns (address); 23 | } 24 | -------------------------------------------------------------------------------- /contracts/utils/TestENSIP19.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {ENSIP19} from "./ENSIP19.sol"; 5 | 6 | contract TestENSIP19 { 7 | function reverseName( 8 | bytes memory encodedAddress, 9 | uint256 coinType 10 | ) external pure returns (string memory) { 11 | return ENSIP19.reverseName(encodedAddress, coinType); 12 | } 13 | 14 | function parse( 15 | bytes memory name 16 | ) external pure returns (bytes memory, uint256) { 17 | return ENSIP19.parse(name); 18 | } 19 | 20 | function parseNamespace( 21 | bytes memory name, 22 | uint256 offset 23 | ) external pure returns (bool, uint256) { 24 | return ENSIP19.parseNamespace(name, offset); 25 | } 26 | 27 | function chainFromCoinType( 28 | uint256 coinType 29 | ) external pure returns (uint32) { 30 | return ENSIP19.chainFromCoinType(coinType); 31 | } 32 | 33 | function isEVMCoinType(uint256 coinType) external pure returns (bool) { 34 | return ENSIP19.isEVMCoinType(coinType); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/ccipRead/EIP3668.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /// @dev https://eips.ethereum.org/EIPS/eip-3668 5 | /// Error selector: `0x556f1830` 6 | error OffchainLookup( 7 | address sender, 8 | string[] urls, 9 | bytes callData, 10 | bytes4 callbackFunction, 11 | bytes extraData 12 | ); 13 | 14 | /// @dev Simple library for decoding `OffchainLookup` error data. 15 | /// Avoids "stack too deep" issues as the natural decoding consumes 5 variables. 16 | library EIP3668 { 17 | /// @dev Struct with members matching `OffchainLookup`. 18 | struct Params { 19 | address sender; 20 | string[] urls; 21 | bytes callData; 22 | bytes4 callbackFunction; 23 | bytes extraData; 24 | } 25 | 26 | /// @dev Decode an `OffchainLookup` into a struct from the data after the error selector. 27 | function decode(bytes memory v) internal pure returns (Params memory p) { 28 | (p.sender, p.urls, p.callData, p.callbackFunction, p.extraData) = abi 29 | .decode(v, (address, string[], bytes, bytes4, bytes)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /deploy/reverseregistrar/02_deploy_chain_reverse_resolver/00_base.ts: -------------------------------------------------------------------------------- 1 | import { base, baseSepolia, mainnet, sepolia } from 'viem/chains' 2 | import { createChainReverseResolverDeployer } from '../../../test/fixtures/createChainReverseResolverDeployment.js' 3 | import { safeConfig } from '../../l2/00_deploy_l2_reverse_registrar.js' 4 | 5 | export default createChainReverseResolverDeployer({ 6 | chainName: 'Base', 7 | targets: { 8 | [sepolia.id]: { 9 | chain: baseSepolia.id, 10 | verifier: '0x2a5c43a0aa33c6ca184ac0eadf0a117109c9d6ae', 11 | registrar: safeConfig.testnet.expectedDeploymentAddress, 12 | gateways: [ 13 | 'https://lb.drpc.org/gateway/unruggable?network=base-sepolia', 14 | 'https://base-sepolia.3668.io', 15 | ], 16 | }, 17 | [mainnet.id]: { 18 | chain: base.id, 19 | verifier: '0x074c93cd956b0dd2cac0f9f11dda4d3893a88149', 20 | registrar: safeConfig.mainnet.expectedDeploymentAddress, 21 | gateways: [ 22 | 'https://lb.drpc.org/gateway/unruggable?network=base', 23 | 'https://base.3668.io', 24 | ], 25 | }, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /deployments/goerli/solcInputs/2d8cd8af817b3996918016eaf0684f54.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Solidity", 3 | "sources": { 4 | "contracts/wrapper/StaticMetadataService.sol": { 5 | "content": "//SPDX-License-Identifier: MIT\npragma solidity ~0.8.17;\n\ncontract StaticMetadataService {\n string private _uri;\n\n constructor(string memory _metaDataUri) {\n _uri = _metaDataUri;\n }\n\n function uri(uint256) public view returns (string memory) {\n return _uri;\n }\n}\n" 6 | } 7 | }, 8 | "settings": { 9 | "optimizer": { 10 | "enabled": true, 11 | "runs": 10000 12 | }, 13 | "outputSelection": { 14 | "*": { 15 | "*": [ 16 | "abi", 17 | "evm.bytecode", 18 | "evm.deployedBytecode", 19 | "evm.methodIdentifiers", 20 | "metadata", 21 | "devdoc", 22 | "userdoc", 23 | "storageLayout", 24 | "evm.gasEstimates" 25 | ], 26 | "": [ 27 | "ast" 28 | ] 29 | } 30 | }, 31 | "metadata": { 32 | "useLiteralContent": true 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /deployments/ropsten/solcInputs/2d8cd8af817b3996918016eaf0684f54.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Solidity", 3 | "sources": { 4 | "contracts/wrapper/StaticMetadataService.sol": { 5 | "content": "//SPDX-License-Identifier: MIT\npragma solidity ~0.8.17;\n\ncontract StaticMetadataService {\n string private _uri;\n\n constructor(string memory _metaDataUri) {\n _uri = _metaDataUri;\n }\n\n function uri(uint256) public view returns (string memory) {\n return _uri;\n }\n}\n" 6 | } 7 | }, 8 | "settings": { 9 | "optimizer": { 10 | "enabled": true, 11 | "runs": 10000 12 | }, 13 | "outputSelection": { 14 | "*": { 15 | "*": [ 16 | "abi", 17 | "evm.bytecode", 18 | "evm.deployedBytecode", 19 | "evm.methodIdentifiers", 20 | "metadata", 21 | "devdoc", 22 | "userdoc", 23 | "storageLayout", 24 | "evm.gasEstimates" 25 | ], 26 | "": [ 27 | "ast" 28 | ] 29 | } 30 | }, 31 | "metadata": { 32 | "useLiteralContent": true 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /deployments/goerli/solcInputs/9ab134ee99f7410d077d71824d3e2f84.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Solidity", 3 | "sources": { 4 | "contracts/ethregistrar/DummyOracle.sol": { 5 | "content": "pragma solidity >=0.8.4;\n\ncontract DummyOracle {\n int256 value;\n\n constructor(int256 _value) public {\n set(_value);\n }\n\n function set(int256 _value) public {\n value = _value;\n }\n\n function latestAnswer() public view returns (int256) {\n return value;\n }\n}\n" 6 | } 7 | }, 8 | "settings": { 9 | "optimizer": { 10 | "enabled": true, 11 | "runs": 10000 12 | }, 13 | "outputSelection": { 14 | "*": { 15 | "*": [ 16 | "abi", 17 | "evm.bytecode", 18 | "evm.deployedBytecode", 19 | "evm.methodIdentifiers", 20 | "metadata", 21 | "devdoc", 22 | "userdoc", 23 | "storageLayout", 24 | "evm.gasEstimates" 25 | ], 26 | "": [ 27 | "ast" 28 | ] 29 | } 30 | }, 31 | "metadata": { 32 | "useLiteralContent": true 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /deployments/ropsten/solcInputs/9ab134ee99f7410d077d71824d3e2f84.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Solidity", 3 | "sources": { 4 | "contracts/ethregistrar/DummyOracle.sol": { 5 | "content": "pragma solidity >=0.8.4;\n\ncontract DummyOracle {\n int256 value;\n\n constructor(int256 _value) public {\n set(_value);\n }\n\n function set(int256 _value) public {\n value = _value;\n }\n\n function latestAnswer() public view returns (int256) {\n return value;\n }\n}\n" 6 | } 7 | }, 8 | "settings": { 9 | "optimizer": { 10 | "enabled": true, 11 | "runs": 10000 12 | }, 13 | "outputSelection": { 14 | "*": { 15 | "*": [ 16 | "abi", 17 | "evm.bytecode", 18 | "evm.deployedBytecode", 19 | "evm.methodIdentifiers", 20 | "metadata", 21 | "devdoc", 22 | "userdoc", 23 | "storageLayout", 24 | "evm.gasEstimates" 25 | ], 26 | "": [ 27 | "ast" 28 | ] 29 | } 30 | }, 31 | "metadata": { 32 | "useLiteralContent": true 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /contracts/ethregistrar/IETHRegistrarController.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ~0.8.17; 3 | 4 | import "./IPriceOracle.sol"; 5 | 6 | interface IETHRegistrarController { 7 | struct Registration { 8 | string label; 9 | address owner; 10 | uint256 duration; 11 | bytes32 secret; 12 | address resolver; 13 | bytes[] data; 14 | uint8 reverseRecord; 15 | bytes32 referrer; 16 | } 17 | 18 | function rentPrice( 19 | string memory label, 20 | uint256 duration 21 | ) external view returns (IPriceOracle.Price memory); 22 | 23 | function available(string memory label) external returns (bool); 24 | 25 | function makeCommitment( 26 | Registration memory registration 27 | ) external pure returns (bytes32 commitment); 28 | 29 | function commit(bytes32 commitment) external; 30 | 31 | function register(Registration memory registration) external payable; 32 | 33 | function renew( 34 | string calldata label, 35 | uint256 duration, 36 | bytes32 referrer 37 | ) external payable; 38 | } 39 | -------------------------------------------------------------------------------- /deploy/reverseregistrar/02_deploy_chain_reverse_resolver/00_linea.ts: -------------------------------------------------------------------------------- 1 | import { linea, lineaSepolia, mainnet, sepolia } from 'viem/chains' 2 | import { createChainReverseResolverDeployer } from '../../../test/fixtures/createChainReverseResolverDeployment.js' 3 | import { safeConfig } from '../../l2/00_deploy_l2_reverse_registrar.js' 4 | 5 | export default createChainReverseResolverDeployer({ 6 | chainName: 'Linea', 7 | targets: { 8 | [sepolia.id]: { 9 | chain: lineaSepolia.id, 10 | verifier: '0x6AD2BbEE28e780717dF146F59c2213E0EB9CA573', 11 | registrar: safeConfig.testnet.expectedDeploymentAddress, 12 | gateways: [ 13 | 'https://lb.drpc.org/gateway/unruggable?network=linea-sepolia', 14 | 'https://linea-sepolia.3668.io', 15 | ], 16 | }, 17 | [mainnet.id]: { 18 | chain: linea.id, 19 | verifier: '0x37041498CF4eE07476d2EDeAdcf82d524Aa22ce4', 20 | registrar: safeConfig.mainnet.expectedDeploymentAddress, 21 | gateways: [ 22 | 'https://lb.drpc.org/gateway/unruggable?network=linea', 23 | 'https://linea.3668.io', 24 | ], 25 | }, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /contracts/ccipRead/IBatchGateway.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /// @notice Interface for Batch Gateway Offchain Lookup Protocol. 5 | /// https://docs.ens.domains/ensip/21/ 6 | /// @dev Interface selector: `0xa780bab6` 7 | interface IBatchGateway { 8 | /// @notice An HTTP error occurred. 9 | /// @dev Error selector: `0x01800152` 10 | error HttpError(uint16 status, string message); 11 | 12 | /// @dev Information extracted from an `OffchainLookup` revert. 13 | struct Request { 14 | address sender; 15 | string[] urls; 16 | bytes data; 17 | } 18 | 19 | /// @notice Perform multiple `OffchainLookup` in parallel. 20 | /// Callers should enable EIP-3668. 21 | /// @param requests The array of requests to lookup in parallel. 22 | /// @return failures The failure status of the corresponding request. 23 | /// @return responses The response or error data of the corresponding request. 24 | function query( 25 | Request[] memory requests 26 | ) external view returns (bool[] memory failures, bytes[] memory responses); 27 | } 28 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/IDNSRecordResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface IDNSRecordResolver { 5 | // DNSRecordChanged is emitted whenever a given node/name/resource's RRSET is updated. 6 | event DNSRecordChanged( 7 | bytes32 indexed node, 8 | bytes name, 9 | uint16 resource, 10 | bytes record 11 | ); 12 | // DNSRecordDeleted is emitted whenever a given node/name/resource's RRSET is deleted. 13 | event DNSRecordDeleted(bytes32 indexed node, bytes name, uint16 resource); 14 | 15 | /// Obtain a DNS record. 16 | /// @param node the namehash of the node for which to fetch the record 17 | /// @param name the keccak-256 hash of the fully-qualified name for which to fetch the record 18 | /// @param resource the ID of the resource as per https://en.wikipedia.org/wiki/List_of_DNS_record_types 19 | /// @return the DNS record in wire format if present, otherwise empty 20 | function dnsRecord( 21 | bytes32 node, 22 | bytes32 name, 23 | uint16 resource 24 | ) external view returns (bytes memory); 25 | } 26 | -------------------------------------------------------------------------------- /deploy/reverseregistrar/02_deploy_chain_reverse_resolver/00_scroll.ts: -------------------------------------------------------------------------------- 1 | import { mainnet, scroll, scrollSepolia, sepolia } from 'viem/chains' 2 | import { createChainReverseResolverDeployer } from '../../../test/fixtures/createChainReverseResolverDeployment.js' 3 | import { safeConfig } from '../../l2/00_deploy_l2_reverse_registrar.js' 4 | 5 | export default createChainReverseResolverDeployer({ 6 | chainName: 'Scroll', 7 | targets: { 8 | [sepolia.id]: { 9 | chain: scrollSepolia.id, 10 | verifier: '0xd126DD79133D3aaf0248E858323Cd10C04c5E43d', 11 | registrar: safeConfig.testnet.expectedDeploymentAddress, 12 | gateways: [ 13 | 'https://lb.drpc.org/gateway/unruggable?network=scroll-sepolia', 14 | 'https://scroll-sepolia.3668.io', 15 | ], 16 | }, 17 | [mainnet.id]: { 18 | chain: scroll.id, 19 | verifier: '0xe439F14Aaf43c87e3dfBDB0A470D9EB2C7f27d93', 20 | registrar: safeConfig.mainnet.expectedDeploymentAddress, 21 | gateways: [ 22 | 'https://lb.drpc.org/gateway/unruggable?network=scroll', 23 | 'https://scroll.3668.io', 24 | ], 25 | }, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /contracts/registry/FIFSRegistrar.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.4; 2 | 3 | import "./ENS.sol"; 4 | 5 | /// A registrar that allocates subdomains to the first person to claim them. 6 | contract FIFSRegistrar { 7 | ENS ens; 8 | bytes32 rootNode; 9 | 10 | modifier only_owner(bytes32 label) { 11 | address currentOwner = ens.owner( 12 | keccak256(abi.encodePacked(rootNode, label)) 13 | ); 14 | require(currentOwner == address(0x0) || currentOwner == msg.sender); 15 | _; 16 | } 17 | 18 | /// Constructor. 19 | /// @param ensAddr The address of the ENS registry. 20 | /// @param node The node that this registrar administers. 21 | constructor(ENS ensAddr, bytes32 node) public { 22 | ens = ensAddr; 23 | rootNode = node; 24 | } 25 | 26 | /// Register a name, or change the owner of an existing registration. 27 | /// @param label The hash of the label to register. 28 | /// @param owner The address of the new owner. 29 | function register(bytes32 label, address owner) public only_owner(label) { 30 | ens.setSubnodeOwner(rootNode, label, owner); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /deployments/archive/ETHRegistrarController_mainnet_9380471.sol/PriceOracle.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "PriceOracle", 4 | "sourceName": "contracts/legacy/ETHRegistrarController_mainnet_9380471.sol", 5 | "abi": [ 6 | { 7 | "constant": true, 8 | "inputs": [ 9 | { 10 | "internalType": "string", 11 | "name": "name", 12 | "type": "string" 13 | }, 14 | { 15 | "internalType": "uint256", 16 | "name": "expires", 17 | "type": "uint256" 18 | }, 19 | { 20 | "internalType": "uint256", 21 | "name": "duration", 22 | "type": "uint256" 23 | } 24 | ], 25 | "name": "price", 26 | "outputs": [ 27 | { 28 | "internalType": "uint256", 29 | "name": "", 30 | "type": "uint256" 31 | } 32 | ], 33 | "payable": false, 34 | "stateMutability": "view", 35 | "type": "function" 36 | } 37 | ], 38 | "bytecode": "0x", 39 | "deployedBytecode": "0x", 40 | "linkReferences": {}, 41 | "deployedLinkReferences": {} 42 | } 43 | -------------------------------------------------------------------------------- /deploy/reverseregistrar/02_deploy_chain_reverse_resolver/00_arbitrum.ts: -------------------------------------------------------------------------------- 1 | import { arbitrum, arbitrumSepolia, mainnet, sepolia } from 'viem/chains' 2 | import { createChainReverseResolverDeployer } from '../../../test/fixtures/createChainReverseResolverDeployment.js' 3 | import { safeConfig } from '../../l2/00_deploy_l2_reverse_registrar.js' 4 | 5 | export default createChainReverseResolverDeployer({ 6 | chainName: 'Arbitrum', 7 | targets: { 8 | [sepolia.id]: { 9 | chain: arbitrumSepolia.id, 10 | verifier: '0x5e2a4f6c4cc16b27424249eedb15326207c9ee44', 11 | registrar: safeConfig.testnet.expectedDeploymentAddress, 12 | gateways: [ 13 | 'https://lb.drpc.org/gateway/unruggable?network=arbitrum-sepolia', 14 | 'https://arbitrum-sepolia.3668.io', 15 | ], 16 | }, 17 | [mainnet.id]: { 18 | chain: arbitrum.id, 19 | verifier: '0x547af78b28290D4158c1064FF092ABBcc4cbfD97', 20 | registrar: safeConfig.mainnet.expectedDeploymentAddress, 21 | gateways: [ 22 | 'https://lb.drpc.org/gateway/unruggable?network=arbitrum', 23 | 'https://arbitrum.3668.io', 24 | ], 25 | }, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /deploy/reverseregistrar/02_deploy_chain_reverse_resolver/00_optimism.ts: -------------------------------------------------------------------------------- 1 | import { mainnet, optimism, optimismSepolia, sepolia } from 'viem/chains' 2 | import { createChainReverseResolverDeployer } from '../../../test/fixtures/createChainReverseResolverDeployment.js' 3 | import { safeConfig } from '../../l2/00_deploy_l2_reverse_registrar.js' 4 | 5 | export default createChainReverseResolverDeployer({ 6 | chainName: 'Optimism', 7 | targets: { 8 | [sepolia.id]: { 9 | chain: optimismSepolia.id, 10 | verifier: '0x9fc09f6683ea8e8ad0fae3317e39e57582469707', 11 | registrar: safeConfig.testnet.expectedDeploymentAddress, 12 | gateways: [ 13 | 'https://lb.drpc.org/gateway/unruggable?network=optimism-sepolia', 14 | 'https://optimism-sepolia.3668.io', 15 | ], 16 | }, 17 | [mainnet.id]: { 18 | chain: optimism.id, 19 | verifier: '0x7f49a74d264e48e64e76e136b2a4ba1310c3604c', 20 | registrar: safeConfig.mainnet.expectedDeploymentAddress, 21 | gateways: [ 22 | 'https://lb.drpc.org/gateway/unruggable?network=optimism', 23 | 'https://optimism.3668.io', 24 | ], 25 | }, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /test/dnssec-oracle/TestAlgorithms.test.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | 3 | import { algorithms } from './fixtures/algorithms.js' 4 | 5 | const connection = await hre.network.connect() 6 | 7 | algorithms.forEach(([algo, vector]) => { 8 | async function fixture() { 9 | const algorithm = await connection.viem.deployContract( 10 | algo as 'RSASHA1Algorithm', 11 | [], 12 | ) 13 | return { algorithm } 14 | } 15 | const loadFixture = async () => connection.networkHelpers.loadFixture(fixture) 16 | 17 | describe(algo, () => { 18 | it('should return true for valid signatures', async () => { 19 | const { algorithm } = await loadFixture() 20 | 21 | await expect( 22 | algorithm.read.verify([vector[0], vector[1], vector[2]]), 23 | ).resolves.toBe(true) 24 | }) 25 | 26 | it('should return false for invalid signatures', async () => { 27 | const { algorithm } = await loadFixture() 28 | 29 | const invalidVector1 = `${vector[1]}00` as const 30 | 31 | await expect( 32 | algorithm.read.verify([vector[0], invalidVector1, vector[2]]), 33 | ).resolves.toBe(false) 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /deploy/utils/10_deploy_migration_helper.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, get, execute: write, namedAccounts }) => { 5 | const { deployer, owner } = namedAccounts 6 | 7 | const registrar = get< 8 | (typeof artifacts.BaseRegistrarImplementation)['abi'] 9 | >('BaseRegistrarImplementation') 10 | const wrapper = get<(typeof artifacts.NameWrapper)['abi']>('NameWrapper') 11 | 12 | const migrationHelper = await deploy('MigrationHelper', { 13 | account: deployer, 14 | artifact: artifacts.MigrationHelper, 15 | args: [registrar.address, wrapper.address], 16 | }) 17 | 18 | if (owner && owner !== deployer) { 19 | console.log(` - Transferring ownership to ${owner}`) 20 | await write(migrationHelper, { 21 | account: deployer, 22 | functionName: 'transferOwnership', 23 | args: [owner], 24 | }) 25 | } 26 | 27 | return true 28 | }, 29 | { 30 | id: 'MigrationHelper v1.0.0', 31 | tags: ['category:utils', 'MigrationHelper'], 32 | dependencies: ['BaseRegistrarImplementation', 'NameWrapper'], 33 | }, 34 | ) 35 | -------------------------------------------------------------------------------- /contracts/registry/TestRegistrar.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.4; 2 | 3 | import "./ENS.sol"; 4 | 5 | /// A registrar that allocates subdomains to the first person to claim them, but 6 | /// expires registrations a fixed period after they're initially claimed. 7 | contract TestRegistrar { 8 | uint256 constant registrationPeriod = 4 weeks; 9 | 10 | ENS public immutable ens; 11 | bytes32 public immutable rootNode; 12 | mapping(bytes32 => uint256) public expiryTimes; 13 | 14 | /// Constructor. 15 | /// @param ensAddr The address of the ENS registry. 16 | /// @param node The node that this registrar administers. 17 | constructor(ENS ensAddr, bytes32 node) { 18 | ens = ensAddr; 19 | rootNode = node; 20 | } 21 | 22 | /// Register a name that's not currently registered 23 | /// @param label The hash of the label to register. 24 | /// @param owner The address of the new owner. 25 | function register(bytes32 label, address owner) public { 26 | require(expiryTimes[label] < block.timestamp); 27 | 28 | expiryTimes[label] = block.timestamp + registrationPeriod; 29 | ens.setSubnodeOwner(rootNode, label, owner); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/fixtures/deployDefaultReverseFixture.ts: -------------------------------------------------------------------------------- 1 | import type { NetworkConnection } from 'hardhat/types/network' 2 | import { namehash } from 'viem' 3 | import { deployRegistryFixture } from './deployRegistryFixture.js' 4 | import { COIN_TYPE_DEFAULT, getReverseNamespace } from './ensip19.js' 5 | 6 | export async function deployDefaultReverseFixture( 7 | connection: NetworkConnection, 8 | ) { 9 | const F = await deployRegistryFixture(connection) 10 | const defaultReverseRegistrar = await connection.viem.deployContract( 11 | 'DefaultReverseRegistrar', 12 | ) 13 | const defaultReverseResolver = await connection.viem.deployContract( 14 | 'DefaultReverseResolver', 15 | [defaultReverseRegistrar.address], 16 | ) 17 | const defaultReverseNamespace = getReverseNamespace(COIN_TYPE_DEFAULT) 18 | const mountedNamespace = 'reverse' // getParentName(defaultReverseNamespace) 19 | await F.takeControl(mountedNamespace) 20 | await F.ensRegistry.write.setResolver([ 21 | namehash(mountedNamespace), 22 | defaultReverseResolver.address, 23 | ]) 24 | return { 25 | ...F, 26 | defaultReverseNamespace, 27 | defaultReverseRegistrar, 28 | defaultReverseResolver, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/resolvers/ResolverBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | import "./profiles/IVersionableResolver.sol"; 6 | 7 | abstract contract ResolverBase is ERC165, IVersionableResolver { 8 | mapping(bytes32 => uint64) public recordVersions; 9 | 10 | function isAuthorised(bytes32 node) internal view virtual returns (bool); 11 | 12 | modifier authorised(bytes32 node) { 13 | require(isAuthorised(node)); 14 | _; 15 | } 16 | 17 | /// Increments the record version associated with an ENS node. 18 | /// May only be called by the owner of that node in the ENS registry. 19 | /// @param node The node to update. 20 | function clearRecords(bytes32 node) public virtual authorised(node) { 21 | recordVersions[node]++; 22 | emit VersionChanged(node, recordVersions[node]); 23 | } 24 | 25 | function supportsInterface( 26 | bytes4 interfaceID 27 | ) public view virtual override returns (bool) { 28 | return 29 | interfaceID == type(IVersionableResolver).interfaceId || 30 | super.supportsInterface(interfaceID); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/dnssec-oracle/fixtures/digests.ts: -------------------------------------------------------------------------------- 1 | export const digests = [ 2 | { 3 | digest: 'SHA256Digest', 4 | valids: [ 5 | [ 6 | '', 7 | '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', 8 | ], // valid 1 9 | [ 10 | 'foo', 11 | '0x2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae', 12 | ], // valid 2 13 | ], 14 | invalids: [ 15 | [ 16 | '', 17 | '0x1111111111111111111111111111111111111111111111111111111111111111', 18 | ], // invalid 19 | ], 20 | errors: [ 21 | [ 22 | 'foo', 23 | '0x2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae00', 24 | ], // junk at end of digest 25 | ], 26 | }, 27 | { 28 | digest: 'SHA1Digest', 29 | valids: [ 30 | ['', '0xda39a3ee5e6b4b0d3255bfef95601890afd80709'], // valid 1 31 | ['foo', '0x0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'], // valid 2 32 | ], 33 | invalids: [ 34 | ['', '0x1111111111111111111111111111111111111111'], // invalid 35 | ], 36 | errors: [ 37 | ['foo', '0x0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a3300'], // junk at end of digest 38 | ], 39 | }, 40 | ] as const 41 | -------------------------------------------------------------------------------- /deploy/dnssec-oracle/00_deploy_algorithms.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, namedAccounts, network }) => { 5 | const { deployer } = namedAccounts 6 | 7 | await deploy('RSASHA1Algorithm', { 8 | account: deployer, 9 | artifact: artifacts.RSASHA1Algorithm, 10 | args: [], 11 | }) 12 | 13 | await deploy('RSASHA256Algorithm', { 14 | account: deployer, 15 | artifact: artifacts.RSASHA256Algorithm, 16 | args: [], 17 | }) 18 | 19 | await deploy('P256SHA256Algorithm', { 20 | account: deployer, 21 | artifact: artifacts.P256SHA256Algorithm, 22 | args: [], 23 | }) 24 | 25 | if (network.tags?.test) { 26 | await deploy('DummyAlgorithm', { 27 | account: deployer, 28 | artifact: artifacts.DummyAlgorithm, 29 | args: [], 30 | }) 31 | } 32 | }, 33 | { 34 | id: 'dnssec-algorithms v1.0.0', 35 | tags: [ 36 | 'category:dnssec-oracle', 37 | 'dnssec-algorithms', 38 | 'RSASHA1Algorithm', 39 | 'RSASHA256Algorithm', 40 | 'P256SHA256Algorithm', 41 | 'DummyAlgorithm', 42 | ], 43 | dependencies: [], 44 | }, 45 | ) 46 | -------------------------------------------------------------------------------- /deploy/ethregistrar/01_deploy_exponential_premium_price_oracle.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | import type { Address } from 'viem' 3 | 4 | export default deployScript( 5 | async ({ deploy, namedAccounts, network }) => { 6 | const { deployer } = namedAccounts 7 | 8 | let oracleAddress: Address = '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419' 9 | if (network.name !== 'mainnet') { 10 | const dummyOracle = await deploy('DummyOracle', { 11 | account: deployer, 12 | artifact: artifacts.DummyOracle, 13 | args: [160000000000n], 14 | }) 15 | oracleAddress = dummyOracle.address 16 | } 17 | 18 | await deploy('ExponentialPremiumPriceOracle', { 19 | account: deployer, 20 | artifact: artifacts.ExponentialPremiumPriceOracle, 21 | args: [ 22 | oracleAddress, 23 | [0n, 0n, 20294266869609n, 5073566717402n, 158548959919n], 24 | 100000000000000000000000000n, 25 | 21n, 26 | ], 27 | }) 28 | }, 29 | { 30 | id: 'ExponentialPremiumPriceOracle v1.0.0', 31 | tags: [ 32 | 'category:ethregistrar', 33 | 'ExponentialPremiumPriceOracle', 34 | 'DummyOracle', 35 | ], 36 | dependencies: [], 37 | }, 38 | ) 39 | -------------------------------------------------------------------------------- /contracts/dnsregistrar/mocks/DummyNonCCIPAwareResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | import "../OffchainDNSResolver.sol"; 6 | import "../../resolvers/profiles/IExtendedResolver.sol"; 7 | 8 | contract DummyNonCCIPAwareResolver is IExtendedResolver, ERC165 { 9 | OffchainDNSResolver dnsResolver; 10 | 11 | constructor(OffchainDNSResolver _dnsResolver) { 12 | dnsResolver = _dnsResolver; 13 | } 14 | 15 | function supportsInterface( 16 | bytes4 interfaceId 17 | ) public view virtual override returns (bool) { 18 | return 19 | interfaceId == type(IExtendedResolver).interfaceId || 20 | super.supportsInterface(interfaceId); 21 | } 22 | 23 | function resolve( 24 | bytes calldata /* name */, 25 | bytes calldata data 26 | ) external view returns (bytes memory) { 27 | string[] memory urls = new string[](1); 28 | urls[0] = "https://example.com/"; 29 | revert OffchainLookup( 30 | address(dnsResolver), 31 | urls, 32 | data, 33 | OffchainDNSResolver.resolveCallback.selector, 34 | data 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/utils/TestFeatures.test.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | import { readFileSync } from 'node:fs' 3 | import { FEATURES, makeFeature } from './features.js' 4 | 5 | const connection = await hre.network.connect() 6 | 7 | async function fixture() { 8 | return connection.viem.deployContract('DummyShapeshiftResolver') 9 | } 10 | 11 | describe('ResolverFeatures', () => { 12 | const code = readFileSync( 13 | new URL('../../contracts/resolvers/ResolverFeatures.sol', import.meta.url), 14 | 'utf8', 15 | ) 16 | for (const [_, reverseName, featureName] of code.matchAll( 17 | /constant (\S+) =\s+bytes4\(keccak256\("([^"]+)"\)\);/gm, 18 | )) { 19 | const feature = makeFeature(featureName) 20 | it(`${reverseName} = "${featureName}" = ${feature}`, async () => { 21 | expect(reverseName in FEATURES.RESOLVER, 'missing').toStrictEqual(true) 22 | expect(feature, 'hash').toStrictEqual( 23 | FEATURES.RESOLVER[reverseName as keyof typeof FEATURES.RESOLVER], 24 | ) 25 | const F = await connection.networkHelpers.loadFixture(fixture) 26 | await F.write.setFeature([feature, true]) 27 | await expect( 28 | F.read.supportsFeature([feature]), 29 | 'supports', 30 | ).resolves.toStrictEqual(true) 31 | }) 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /contracts/utils/AddressUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.13; 4 | 5 | library AddressUtils { 6 | // This is the hex encoding of the string 'abcdefghijklmnopqrstuvwxyz' 7 | // It is used as a constant to lookup the characters of the hex address 8 | bytes32 constant lookup = 9 | 0x3031323334353637383961626364656600000000000000000000000000000000; 10 | 11 | /** 12 | * @dev An optimised function to compute the sha3 of the lower-case 13 | * hexadecimal representation of an Ethereum address. 14 | * @param addr The address to hash 15 | * @return ret The SHA3 hash of the lower-case hexadecimal encoding of the 16 | * input address. 17 | */ 18 | function sha3HexAddress(address addr) internal pure returns (bytes32 ret) { 19 | assembly { 20 | for { 21 | let i := 40 22 | } gt(i, 0) {} { 23 | i := sub(i, 1) 24 | mstore8(i, byte(and(addr, 0xf), lookup)) 25 | addr := div(addr, 0x10) 26 | i := sub(i, 1) 27 | mstore8(i, byte(and(addr, 0xf), lookup)) 28 | addr := div(addr, 0x10) 29 | } 30 | 31 | ret := keccak256(0, 40) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/root/Root.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "../registry/ENS.sol"; 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | import "./Controllable.sol"; 6 | 7 | contract Root is Ownable, Controllable { 8 | bytes32 private constant ROOT_NODE = bytes32(0); 9 | 10 | bytes4 private constant INTERFACE_META_ID = 11 | bytes4(keccak256("supportsInterface(bytes4)")); 12 | 13 | event TLDLocked(bytes32 indexed label); 14 | 15 | ENS public ens; 16 | mapping(bytes32 => bool) public locked; 17 | 18 | constructor(ENS _ens) public { 19 | ens = _ens; 20 | } 21 | 22 | function setSubnodeOwner( 23 | bytes32 label, 24 | address owner 25 | ) external onlyController { 26 | require(!locked[label]); 27 | ens.setSubnodeOwner(ROOT_NODE, label, owner); 28 | } 29 | 30 | function setResolver(address resolver) external onlyOwner { 31 | ens.setResolver(ROOT_NODE, resolver); 32 | } 33 | 34 | function lock(bytes32 label) external onlyOwner { 35 | emit TLDLocked(label); 36 | locked[label] = true; 37 | } 38 | 39 | function supportsInterface( 40 | bytes4 interfaceID 41 | ) external pure returns (bool) { 42 | return interfaceID == INTERFACE_META_ID; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/wrapper/functions/ownerOf.ts: -------------------------------------------------------------------------------- 1 | import { DAY } from '../../fixtures/constants.js' 2 | import { 3 | CAN_DO_EVERYTHING, 4 | GRACE_PERIOD, 5 | expectOwnerOf, 6 | zeroAccount, 7 | type LoadNameWrapperFixture, 8 | } from '../fixtures/utils.js' 9 | 10 | export const ownerOfTests = (loadFixture: LoadNameWrapperFixture) => { 11 | describe('ownerOf()', () => { 12 | const label = 'subdomain' 13 | const name = `${label}.eth` 14 | 15 | it('Returns the owner', async () => { 16 | const { nameWrapper, accounts, actions } = await loadFixture() 17 | 18 | await actions.registerSetupAndWrapName({ 19 | label, 20 | fuses: CAN_DO_EVERYTHING, 21 | }) 22 | 23 | await expectOwnerOf(name).on(nameWrapper).toBe(accounts[0]) 24 | }) 25 | 26 | it('Returns 0 when owner is expired', async () => { 27 | const { nameWrapper, actions, testClient } = await loadFixture() 28 | 29 | await actions.registerSetupAndWrapName({ 30 | label, 31 | fuses: CAN_DO_EVERYTHING, 32 | }) 33 | 34 | await testClient.increaseTime({ 35 | seconds: Number(1n * DAY + GRACE_PERIOD + 1n), 36 | }) 37 | await testClient.mine({ blocks: 1 }) 38 | 39 | await expectOwnerOf(name).on(nameWrapper).toBe(zeroAccount) 40 | }) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /test/fixtures/dnsEncodeName.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Hex, 3 | type ByteArray, 4 | bytesToHex, 5 | labelhash as labelhashBytes32, 6 | stringToBytes, 7 | } from 'viem' 8 | 9 | export function packetToBytes(packet: string): ByteArray { 10 | // strip leading and trailing `.` 11 | const value = packet.replace(/^\.|\.$/gm, '') 12 | if (value.length === 0) return new Uint8Array(1) 13 | 14 | const bytes = new Uint8Array(stringToBytes(value).byteLength + 2) 15 | 16 | let offset = 0 17 | const list = value.split('.') 18 | for (let i = 0; i < list.length; i += 1) { 19 | let encoded = stringToBytes(list[i]) 20 | if (encoded.byteLength > 255) 21 | encoded = stringToBytes(encodeLabelhash(labelhashBytes32(list[i]))) 22 | bytes[offset] = encoded.length 23 | bytes.set(encoded, offset + 1) 24 | offset += encoded.length + 1 25 | } 26 | 27 | return bytes.subarray(0, offset + 1) 28 | } 29 | 30 | export const dnsEncodeName = (name: string): Hex => 31 | bytesToHex(packetToBytes(name)) 32 | 33 | export function encodeLabelhash(hash: string) { 34 | if (!hash.startsWith('0x')) 35 | throw new Error('Expected labelhash to start with 0x') 36 | 37 | if (hash.length !== 66) 38 | throw new Error('Expected labelhash to have a length of 66') 39 | 40 | return `[${hash.slice(2)}]` 41 | } 42 | -------------------------------------------------------------------------------- /deployments/holesky/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "ENSRegistry v1.0.0": 1, 3 | "dnssec-algorithms v1.0.0": 1, 4 | "dnssec-digests v1.0.0": 1, 5 | "DNSSECImpl v1.0.0": 1, 6 | "OffchainDNSResolver v1.0.0": 1, 7 | "SimplePublicSuffixList v1.0.0": 1, 8 | "Root:contract v1.0.0": 1, 9 | "Root:setup v1.0.0": 1, 10 | "DNSRegistrar:contract v1.0.0": 1, 11 | "DNSRegistrar:set-tlds v1.0.0": 1, 12 | "BaseRegistrarImplementation:contract v1.0.0": 1, 13 | "BaseRegistrarImplementation:setup v1.0.0": 1, 14 | "ExponentialPremiumPriceOracle v1.0.0": 1, 15 | "ReverseRegistrar v1.0.0": 1, 16 | "ETHRegistrarController v1.0.0": 1, 17 | "EthOwnedResolver v1.0.0": 1, 18 | "NameWrapper v1.0.0": 1, 19 | "ETHRegistrarController v2.0.0": 1, 20 | "ExtendedDNSResolver v1.0.0": 1, 21 | "PublicResolver v1.0.0": 1, 22 | "PublicResolver v2.0.0": 1, 23 | "UniversalResolver v1.0.0": 1, 24 | "DefaultReverseRegistrar v1.0.0": 1750304816, 25 | "StaticMetadataService v1.0.0": 1750305041, 26 | "ETHRegistrarController v3.0.0": 1750305111, 27 | "StaticBulkRenewal v1.0.0": 1750305227, 28 | "UniversalSigValidator v1.0.0": 1750305243, 29 | "PublicResolver v3.0.0": 1750305448, 30 | "DefaultReverseResolver v1.0.0": 1750305481, 31 | "MigrationHelper v1.0.0": 1750305497, 32 | "BatchGatewayProvider v1.0.0": 1754524828, 33 | "UniversalResolver v1.0.1": 1754524841 34 | } -------------------------------------------------------------------------------- /deploy/resolvers/00_deploy_eth_owned_resolver.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | 3 | export default deployScript( 4 | async ({ deploy, get, execute: write, namedAccounts }) => { 5 | const { deployer, owner } = namedAccounts 6 | 7 | // Deploy OwnedResolver 8 | const ethOwnedResolver = await deploy('OwnedResolver', { 9 | account: deployer, 10 | artifact: artifacts.OwnedResolver, 11 | args: [], 12 | }) 13 | 14 | if (!ethOwnedResolver.newlyDeployed) return 15 | 16 | if (owner !== deployer) { 17 | console.log(` - Transferring ownership of OwnedResolver to ${owner}`) 18 | await write(ethOwnedResolver, { 19 | functionName: 'transferOwnership', 20 | args: [owner], 21 | account: deployer, 22 | }) 23 | } 24 | 25 | const registrar = get< 26 | (typeof artifacts.BaseRegistrarImplementation)['abi'] 27 | >('BaseRegistrarImplementation') 28 | 29 | console.log(` - Setting resolver for .eth to ${ethOwnedResolver.address}`) 30 | await write(registrar, { 31 | functionName: 'setResolver', 32 | args: [ethOwnedResolver.address], 33 | account: owner, 34 | }) 35 | }, 36 | { 37 | id: 'EthOwnedResolver v1.0.0', 38 | tags: ['category:resolvers', 'OwnedResolver', 'EthOwnedResolver'], 39 | dependencies: ['ENSRegistry', 'BaseRegistrarImplementation'], 40 | }, 41 | ) 42 | -------------------------------------------------------------------------------- /deployments/mainnet/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "ENSRegistry v1.0.0": 1, 3 | "dnssec-algorithms v1.0.0": 1, 4 | "dnssec-digests v1.0.0": 1, 5 | "DNSSECImpl v1.0.0": 1, 6 | "OffchainDNSResolver v1.0.0": 1, 7 | "SimplePublicSuffixList v1.0.0": 1, 8 | "Root:contract v1.0.0": 1, 9 | "Root:setup v1.0.0": 1, 10 | "DNSRegistrar:contract v1.0.0": 1, 11 | "DNSRegistrar:set-tlds v1.0.0": 1, 12 | "BaseRegistrarImplementation:contract v1.0.0": 1, 13 | "BaseRegistrarImplementation:setup v1.0.0": 1, 14 | "ExponentialPremiumPriceOracle v1.0.0": 1, 15 | "ReverseRegistrar v1.0.0": 1, 16 | "ETHRegistrarController v1.0.0": 1, 17 | "EthOwnedResolver v1.0.0": 1, 18 | "NameWrapper v1.0.0": 1, 19 | "ETHRegistrarController v2.0.0": 1, 20 | "ExtendedDNSResolver v1.0.0": 1, 21 | "PublicResolver v1.0.0": 1, 22 | "PublicResolver v2.0.0": 1, 23 | "UniversalResolver v1.0.0": 1, 24 | "StaticMetadataService v1.0.0": 1, 25 | "DefaultReverseRegistrar v1.0.0": 1750653794, 26 | "UniversalSigValidator v1.0.0": 1750653879, 27 | "ChainReverseResolver:Arbitrum v1.0.0": 1750653999, 28 | "ChainReverseResolver:Base v1.0.0": 1750654012, 29 | "ChainReverseResolver:Linea v1.0.0": 1750654036, 30 | "ChainReverseResolver:Optimism v1.0.0": 1750654201, 31 | "MigrationHelper v1.0.0": 1750654261, 32 | "ChainReverseResolver:Scroll v1.0.0": 1752542750, 33 | "BatchGatewayProvider v1.0.0": 1754525164, 34 | "UniversalResolver v1.0.1": 1754525185 35 | } -------------------------------------------------------------------------------- /tasks/archive_scan.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | 3 | import type { NewTaskActionFunction } from 'hardhat/types/tasks' 4 | import { archivedDeploymentPath } from '../hardhat.config.js' 5 | 6 | const taskArchiveScan: NewTaskActionFunction = async (_, hre) => { 7 | const { networkName } = await hre.network.connect() 8 | const network = networkName 9 | 10 | const deployments = await hre.artifacts.getAllBuildInfoIds() 11 | 12 | for (const deploymentName in deployments) { 13 | const deployment = (deployments as any)[deploymentName] 14 | if (!deployment.receipt || !deployment.bytecode) continue 15 | 16 | const archiveName = `${deploymentName}_${network}_${deployment.receipt.blockNumber}` 17 | const archivePath = `${archivedDeploymentPath}/${archiveName}.sol` 18 | 19 | if (fs.existsSync(archivePath)) { 20 | continue 21 | } 22 | 23 | let fullName: string 24 | try { 25 | await hre.artifacts.readArtifact(deploymentName) 26 | fullName = `${deploymentName}.sol:${deploymentName}` 27 | } catch (e: any) { 28 | if (e._isHardhatError && e.number === 701) { 29 | fullName = e.messageArguments.candidates.split('\n')[1] 30 | } else { 31 | throw e 32 | } 33 | } 34 | hre.tasks.getTask('save').run({ 35 | contract: deploymentName, 36 | block: String(deployment.receipt.blockNumber), 37 | fullName, 38 | }) 39 | } 40 | } 41 | 42 | export default taskArchiveScan 43 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/NameResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | import "../ResolverBase.sol"; 5 | import "./INameResolver.sol"; 6 | 7 | abstract contract NameResolver is INameResolver, ResolverBase { 8 | mapping(uint64 => mapping(bytes32 => string)) versionable_names; 9 | 10 | /// Sets the name associated with an ENS node, for reverse records. 11 | /// May only be called by the owner of that node in the ENS registry. 12 | /// @param node The node to update. 13 | function setName( 14 | bytes32 node, 15 | string calldata newName 16 | ) external virtual authorised(node) { 17 | versionable_names[recordVersions[node]][node] = newName; 18 | emit NameChanged(node, newName); 19 | } 20 | 21 | /// Returns the name associated with an ENS node, for reverse records. 22 | /// Defined in EIP181. 23 | /// @param node The ENS node to query. 24 | /// @return The associated name. 25 | function name( 26 | bytes32 node 27 | ) external view virtual override returns (string memory) { 28 | return versionable_names[recordVersions[node]][node]; 29 | } 30 | 31 | function supportsInterface( 32 | bytes4 interfaceID 33 | ) public view virtual override returns (bool) { 34 | return 35 | interfaceID == type(INameResolver).interfaceId || 36 | super.supportsInterface(interfaceID); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /deploy/ethregistrar/00_setup_base_registrar.ts: -------------------------------------------------------------------------------- 1 | import { type artifacts, deployScript } from '@rocketh' 2 | import { labelhash } from 'viem' 3 | 4 | export default deployScript( 5 | async ({ 6 | get, 7 | execute: write, 8 | namedAccounts: { deployer, owner }, 9 | network, 10 | }) => { 11 | if (!network.tags.use_root) return 12 | 13 | const root = get<(typeof artifacts.Root)['abi']>('Root') 14 | const registrar = get< 15 | (typeof artifacts.BaseRegistrarImplementation)['abi'] 16 | >('BaseRegistrarImplementation') 17 | 18 | // 1. Transfer ownership of registrar to owner 19 | console.log(` - Transferring ownership of registrar to ${owner}`) 20 | await write(registrar, { 21 | functionName: 'transferOwnership', 22 | args: [owner], 23 | account: deployer, 24 | }) 25 | 26 | // 2. Set owner of eth node to registrar on root 27 | console.log(` - Setting owner of eth node to registrar on root`) 28 | await write(root, { 29 | functionName: 'setSubnodeOwner', 30 | args: [labelhash('eth'), registrar.address], 31 | account: owner, 32 | }) 33 | }, 34 | { 35 | id: 'BaseRegistrarImplementation:setup v1.0.0', 36 | tags: [ 37 | 'category:ethregistrar', 38 | 'BaseRegistrarImplementation', 39 | 'BaseRegistrarImplementation:setup', 40 | ], 41 | // Runs after the root is setup 42 | dependencies: ['Root', 'BaseRegistrarImplementation:contract'], 43 | }, 44 | ) 45 | -------------------------------------------------------------------------------- /test/dnsregistrar/TestTLDPublicSuffixList.test.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | 3 | import { dnsEncodeName } from '../fixtures/dnsEncodeName.js' 4 | 5 | const connection = await hre.network.connect() 6 | 7 | async function fixture() { 8 | const tldPublicSuffixList = await connection.viem.deployContract( 9 | 'TLDPublicSuffixList', 10 | [], 11 | ) 12 | 13 | return { tldPublicSuffixList } 14 | } 15 | 16 | const loadFixture = async () => connection.networkHelpers.loadFixture(fixture) 17 | 18 | describe('TLDPublicSuffixList', () => { 19 | it('treats all TLDs as public suffixes', async () => { 20 | const { tldPublicSuffixList } = await loadFixture() 21 | 22 | await expect( 23 | tldPublicSuffixList.read.isPublicSuffix([dnsEncodeName('eth')]), 24 | ).resolves.toBe(true) 25 | await expect( 26 | tldPublicSuffixList.read.isPublicSuffix([dnsEncodeName('com')]), 27 | ).resolves.toBe(true) 28 | }) 29 | 30 | it('treats all non-TLDs as non-public suffixes', async () => { 31 | const { tldPublicSuffixList } = await loadFixture() 32 | 33 | await expect( 34 | tldPublicSuffixList.read.isPublicSuffix([dnsEncodeName('')]), 35 | ).resolves.toBe(false) 36 | await expect( 37 | tldPublicSuffixList.read.isPublicSuffix([dnsEncodeName('foo.eth')]), 38 | ).resolves.toBe(false) 39 | await expect( 40 | tldPublicSuffixList.read.isPublicSuffix([dnsEncodeName('a.b.foo.eth')]), 41 | ).resolves.toBe(false) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/algorithms/P256SHA256Algorithm.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "./Algorithm.sol"; 4 | import "./EllipticCurve.sol"; 5 | import "../../utils/BytesUtils.sol"; 6 | 7 | contract P256SHA256Algorithm is Algorithm, EllipticCurve { 8 | using BytesUtils for *; 9 | 10 | /// @dev Verifies a signature. 11 | /// @param key The public key to verify with. 12 | /// @param data The signed data to verify. 13 | /// @param signature The signature to verify. 14 | /// @return True iff the signature is valid. 15 | function verify( 16 | bytes calldata key, 17 | bytes calldata data, 18 | bytes calldata signature 19 | ) external view override returns (bool) { 20 | return 21 | validateSignature( 22 | sha256(data), 23 | parseSignature(signature), 24 | parseKey(key) 25 | ); 26 | } 27 | 28 | function parseSignature( 29 | bytes memory data 30 | ) internal pure returns (uint256[2] memory) { 31 | require(data.length == 64, "Invalid p256 signature length"); 32 | return [uint256(data.readBytes32(0)), uint256(data.readBytes32(32))]; 33 | } 34 | 35 | function parseKey( 36 | bytes memory data 37 | ) internal pure returns (uint256[2] memory) { 38 | require(data.length == 68, "Invalid p256 key length"); 39 | return [uint256(data.readBytes32(4)), uint256(data.readBytes32(36))]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/fixtures/ensip19.ts: -------------------------------------------------------------------------------- 1 | import type { Hex } from 'viem' 2 | 3 | export const COIN_TYPE_ETH = 60n 4 | export const COIN_TYPE_DEFAULT = 1n << 31n 5 | 6 | export function coinTypeFromChain(chain: number) { 7 | if (chain === 1) return COIN_TYPE_ETH 8 | if ((chain & Number(COIN_TYPE_DEFAULT - 1n)) !== chain) 9 | throw new Error(`invalid chain: ${chain}`) 10 | return BigInt(chain) | COIN_TYPE_DEFAULT 11 | } 12 | 13 | export function chainFromCoinType(coinType: bigint): number { 14 | if (coinType == COIN_TYPE_ETH) return 1 15 | coinType ^= COIN_TYPE_DEFAULT 16 | return coinType >= 0 && coinType < COIN_TYPE_DEFAULT ? Number(coinType) : 0 17 | } 18 | 19 | export function isEVMCoinType(coinType: bigint) { 20 | return coinType === COIN_TYPE_DEFAULT || chainFromCoinType(coinType) > 0 21 | } 22 | 23 | export function shortCoin(coinType: bigint) { 24 | return isEVMCoinType(coinType) 25 | ? `chain:${chainFromCoinType(coinType)}` 26 | : `coin:${coinType}` 27 | } 28 | 29 | export function getReverseNamespace(coinType: bigint) { 30 | return `${ 31 | coinType == COIN_TYPE_ETH 32 | ? 'addr' 33 | : coinType == COIN_TYPE_DEFAULT 34 | ? 'default' 35 | : coinType.toString(16) 36 | }.reverse` 37 | } 38 | 39 | export function getReverseName(encodedAddress: Hex, coinType = COIN_TYPE_ETH) { 40 | const hex = encodedAddress.slice(2) 41 | if (!hex) throw new Error('empty address') 42 | return `${hex.toLowerCase()}.${getReverseNamespace(coinType)}` 43 | } 44 | -------------------------------------------------------------------------------- /contracts/utils/TestHexUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {HexUtils} from "./HexUtils.sol"; 5 | 6 | contract TestHexUtils { 7 | function hexToBytes( 8 | bytes calldata name, 9 | uint256 off, 10 | uint256 end 11 | ) public pure returns (bytes memory, bool) { 12 | return HexUtils.hexToBytes(name, off, end); 13 | } 14 | 15 | function hexStringToBytes32( 16 | bytes calldata name, 17 | uint256 off, 18 | uint256 end 19 | ) public pure returns (bytes32, bool) { 20 | return HexUtils.hexStringToBytes32(name, off, end); 21 | } 22 | 23 | function hexToAddress( 24 | bytes calldata input, 25 | uint256 off, 26 | uint256 end 27 | ) public pure returns (address, bool) { 28 | return HexUtils.hexToAddress(input, off, end); 29 | } 30 | 31 | function addressToHex( 32 | address addr 33 | ) external pure returns (string memory hexString) { 34 | return HexUtils.addressToHex(addr); 35 | } 36 | 37 | function unpaddedUintToHex( 38 | uint256 value, 39 | bool dropZeroNibble 40 | ) external pure returns (string memory hexString) { 41 | return HexUtils.unpaddedUintToHex(value, dropZeroNibble); 42 | } 43 | 44 | function bytesToHex( 45 | bytes memory v 46 | ) external pure returns (string memory hexString) { 47 | return HexUtils.bytesToHex(v); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/ContentHashResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | import "../ResolverBase.sol"; 5 | import "./IContentHashResolver.sol"; 6 | 7 | abstract contract ContentHashResolver is IContentHashResolver, ResolverBase { 8 | mapping(uint64 => mapping(bytes32 => bytes)) versionable_hashes; 9 | 10 | /// Sets the contenthash associated with an ENS node. 11 | /// May only be called by the owner of that node in the ENS registry. 12 | /// @param node The node to update. 13 | /// @param hash The contenthash to set 14 | function setContenthash( 15 | bytes32 node, 16 | bytes calldata hash 17 | ) external virtual authorised(node) { 18 | versionable_hashes[recordVersions[node]][node] = hash; 19 | emit ContenthashChanged(node, hash); 20 | } 21 | 22 | /// Returns the contenthash associated with an ENS node. 23 | /// @param node The ENS node to query. 24 | /// @return The associated contenthash. 25 | function contenthash( 26 | bytes32 node 27 | ) external view virtual override returns (bytes memory) { 28 | return versionable_hashes[recordVersions[node]][node]; 29 | } 30 | 31 | function supportsInterface( 32 | bytes4 interfaceID 33 | ) public view virtual override returns (bool) { 34 | return 35 | interfaceID == type(IContentHashResolver).interfaceId || 36 | super.supportsInterface(interfaceID); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/fixtures/anchors.ts: -------------------------------------------------------------------------------- 1 | import packet from 'dns-packet' 2 | 3 | export const realEntries = [ 4 | { 5 | name: '.', 6 | type: 'DS', 7 | class: 'IN', 8 | ttl: 3600, 9 | data: { 10 | keyTag: 19036, 11 | algorithm: 8, 12 | digestType: 2, 13 | digest: new Buffer( 14 | '49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5', 15 | 'hex', 16 | ), 17 | }, 18 | }, 19 | { 20 | name: '.', 21 | type: 'DS', 22 | klass: 'IN', 23 | ttl: 3600, 24 | data: { 25 | keyTag: 20326, 26 | algorithm: 8, 27 | digestType: 2, 28 | digest: new Buffer( 29 | 'E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D', 30 | 'hex', 31 | ), 32 | }, 33 | }, 34 | ] as const 35 | 36 | export const dummyEntry = { 37 | name: '.', 38 | type: 'DS', 39 | class: 'IN', 40 | ttl: 3600, 41 | data: { 42 | keyTag: 1278, // Empty body, flags == 0x0101, algorithm = 253, body = 0x0000 43 | algorithm: 253, 44 | digestType: 253, 45 | digest: new Buffer('', 'hex'), 46 | }, 47 | } as const 48 | 49 | export const testEntries = [...realEntries, dummyEntry] as const 50 | 51 | export const encodedAnchors = `0x${testEntries 52 | .map((entry) => packet.answer.encode(entry).toString('hex')) 53 | .join('')}` as const 54 | 55 | export const encodedRealAnchors = `0x${realEntries 56 | .map((entry) => packet.answer.encode(entry).toString('hex')) 57 | .join('')}` as const 58 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/algorithms/RSASHA256Algorithm.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "./Algorithm.sol"; 4 | import "./RSAVerify.sol"; 5 | import "../../utils/BytesUtils.sol"; 6 | 7 | /// @dev Implements the DNSSEC RSASHA256 algorithm. 8 | contract RSASHA256Algorithm is Algorithm { 9 | using BytesUtils for *; 10 | 11 | function verify( 12 | bytes calldata key, 13 | bytes calldata data, 14 | bytes calldata sig 15 | ) external view override returns (bool) { 16 | bytes memory exponent; 17 | bytes memory modulus; 18 | 19 | uint16 exponentLen = uint16(key.readUint8(4)); 20 | if (exponentLen != 0) { 21 | exponent = key.substring(5, exponentLen); 22 | modulus = key.substring( 23 | exponentLen + 5, 24 | key.length - exponentLen - 5 25 | ); 26 | } else { 27 | exponentLen = key.readUint16(5); 28 | exponent = key.substring(7, exponentLen); 29 | modulus = key.substring( 30 | exponentLen + 7, 31 | key.length - exponentLen - 7 32 | ); 33 | } 34 | 35 | // Recover the message from the signature 36 | bool ok; 37 | bytes memory result; 38 | (ok, result) = RSAVerify.rsarecover(modulus, exponent, sig); 39 | 40 | // Verify it ends with the hash of our data 41 | return ok && sha256(data) == result.readBytes32(result.length - 32); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/dnssec-oracle/algorithms/RSASHA1Algorithm.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.4; 2 | 3 | import "./Algorithm.sol"; 4 | import "./RSAVerify.sol"; 5 | import "../../utils/BytesUtils.sol"; 6 | import "@ensdomains/solsha1/contracts/SHA1.sol"; 7 | 8 | /// @dev Implements the DNSSEC RSASHA1 algorithm. 9 | contract RSASHA1Algorithm is Algorithm { 10 | using BytesUtils for *; 11 | 12 | function verify( 13 | bytes calldata key, 14 | bytes calldata data, 15 | bytes calldata sig 16 | ) external view override returns (bool) { 17 | bytes memory exponent; 18 | bytes memory modulus; 19 | 20 | uint16 exponentLen = uint16(key.readUint8(4)); 21 | if (exponentLen != 0) { 22 | exponent = key.substring(5, exponentLen); 23 | modulus = key.substring( 24 | exponentLen + 5, 25 | key.length - exponentLen - 5 26 | ); 27 | } else { 28 | exponentLen = key.readUint16(5); 29 | exponent = key.substring(7, exponentLen); 30 | modulus = key.substring( 31 | exponentLen + 7, 32 | key.length - exponentLen - 7 33 | ); 34 | } 35 | 36 | // Recover the message from the signature 37 | bool ok; 38 | bytes memory result; 39 | (ok, result) = RSAVerify.rsarecover(modulus, exponent, sig); 40 | 41 | // Verify it ends with the hash of our data 42 | return ok && SHA1.sha1(data) == result.readBytes20(result.length - 20); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/fixtures/runSolidityTests.ts: -------------------------------------------------------------------------------- 1 | import type { Abi, AbiFunction } from 'abitype' 2 | import hre from 'hardhat' 3 | import type { Artifact, ArtifactMap } from 'hardhat/types/artifacts' 4 | 5 | const connection = await hre.network.connect() 6 | 7 | export async function runSolidityTests(name: N) { 8 | const artifact: Artifact = await hre.artifacts.readArtifact(name) 9 | const abi: Abi = artifact.abi 10 | const tests = abi.filter( 11 | (x): x is AbiFunction => x.type === 'function' && x.name.startsWith('test'), 12 | ) 13 | if (!tests.length) throw new Error(`no tests: ${name}`) 14 | 15 | async function fixture() { 16 | const publicClient = await connection.viem.getPublicClient() 17 | const contract = await connection.viem.deployContract(name) 18 | return { publicClient, contract } 19 | } 20 | 21 | describe(name, () => { 22 | tests.forEach((fn) => { 23 | it(fn.name, async () => { 24 | const F = await connection.networkHelpers.loadFixture(fixture) 25 | if (fn.name.startsWith('testFail')) { 26 | await expect( 27 | F.publicClient.readContract({ 28 | abi, 29 | address: F.contract.address, 30 | functionName: fn.name, 31 | }), 32 | ).rejects.toThrow() 33 | } else { 34 | await F.publicClient.readContract({ 35 | abi, 36 | address: F.contract.address, 37 | functionName: fn.name, 38 | }) 39 | } 40 | }) 41 | }) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /scripts/deploy-test.ts: -------------------------------------------------------------------------------- 1 | import { createAnvil } from '@viem/anvil' 2 | import { executeDeployScripts, resolveConfig } from 'rocketh' 3 | import { createWalletClient, http } from 'viem' 4 | 5 | const t0 = Date.now() 6 | 7 | const anvil = createAnvil() 8 | await anvil.start() 9 | 10 | const hostPort = `http://${anvil.host}:${anvil.port}` 11 | 12 | const pollingInterval = 1 13 | 14 | const client = createWalletClient({ 15 | transport: http(hostPort), 16 | pollingInterval, 17 | }) 18 | 19 | const [deployer, owner] = await client.requestAddresses() 20 | const accounts = { deployer, owner } 21 | 22 | process.env.BATCH_GATEWAY_URLS = '["x-batch-gateway:true"]' 23 | 24 | const env = await executeDeployScripts( 25 | resolveConfig({ 26 | network: { 27 | name: 'local', 28 | tags: ['test', 'legacy', 'use_root', 'allow_unsafe'], 29 | nodeUrl: hostPort, 30 | fork: false, 31 | pollingInterval: Math.max(1, pollingInterval) / 1000, // can't be 0 32 | }, 33 | accounts, 34 | askBeforeProceeding: false, 35 | saveDeployments: false, 36 | logLevel: 1, 37 | }), 38 | ) 39 | 40 | console.table( 41 | Object.entries(env.deployments).map(([name, { address }]) => ({ 42 | name, 43 | address, 44 | })), 45 | ) 46 | 47 | console.log(`\nReady <${Date.now() - t0}ms>`) 48 | 49 | // the execa logic is completely broken and makes no sense 50 | // await anvil.stop(); 51 | 52 | // anyway, this was launched as a child process 53 | // so we can just exit 54 | process.exit() 55 | 56 | // TODO: maybe this should be `bun run devnet`? 57 | -------------------------------------------------------------------------------- /contracts/reverseRegistrar/StandaloneReverseRegistrar.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.4; 4 | 5 | import {ERC165} from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol"; 6 | 7 | import {IStandaloneReverseRegistrar} from "./IStandaloneReverseRegistrar.sol"; 8 | 9 | /// @title Standalone Reverse Registrar 10 | /// @notice A standalone reverse registrar, detached from the ENS registry. 11 | contract StandaloneReverseRegistrar is ERC165, IStandaloneReverseRegistrar { 12 | /// @notice The mapping of addresses to names. 13 | mapping(address => string) internal _names; 14 | 15 | /// @inheritdoc IStandaloneReverseRegistrar 16 | function nameForAddr( 17 | address addr 18 | ) external view returns (string memory name) { 19 | name = _names[addr]; 20 | } 21 | 22 | /// @notice Sets the name for an address. 23 | /// 24 | /// @dev Authorisation should be checked before calling. 25 | /// 26 | /// @param addr The address to set the name for. 27 | /// @param name The name to set. 28 | function _setName(address addr, string calldata name) internal { 29 | _names[addr] = name; 30 | emit NameForAddrChanged(addr, name); 31 | } 32 | 33 | /// @inheritdoc ERC165 34 | function supportsInterface( 35 | bytes4 interfaceID 36 | ) public view virtual override(ERC165) returns (bool) { 37 | return 38 | interfaceID == type(IStandaloneReverseRegistrar).interfaceId || 39 | super.supportsInterface(interfaceID); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/reverseResolver/DefaultReverseResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {AbstractReverseResolver} from "./AbstractReverseResolver.sol"; 5 | import {IStandaloneReverseRegistrar} from "../reverseRegistrar/IStandaloneReverseRegistrar.sol"; 6 | import {INameReverser} from "./INameReverser.sol"; 7 | import {COIN_TYPE_DEFAULT} from "../utils/ENSIP19.sol"; 8 | 9 | /// @title Default Reverse Resolver 10 | /// @notice Reverses an EVM address using the `IStandaloneReverseRegistrar` for "default.reverse". 11 | contract DefaultReverseResolver is AbstractReverseResolver { 12 | /// @notice The reverse registrar contract for "default.reverse". 13 | IStandaloneReverseRegistrar public immutable defaultRegistrar; 14 | 15 | constructor( 16 | IStandaloneReverseRegistrar _defaultRegistrar 17 | ) AbstractReverseResolver(COIN_TYPE_DEFAULT, address(_defaultRegistrar)) { 18 | defaultRegistrar = _defaultRegistrar; 19 | } 20 | 21 | /// @inheritdoc AbstractReverseResolver 22 | function _resolveName( 23 | address addr 24 | ) internal view override returns (string memory name) { 25 | name = defaultRegistrar.nameForAddr(addr); 26 | } 27 | 28 | /// @inheritdoc INameReverser 29 | function resolveNames( 30 | address[] memory addrs 31 | ) external view returns (string[] memory names) { 32 | names = new string[](addrs.length); 33 | for (uint256 i; i < addrs.length; i++) { 34 | names[i] = _resolveName(addrs[i]); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /deployments/sepolia/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "ENSRegistry v1.0.0": 1, 3 | "dnssec-algorithms v1.0.0": 1, 4 | "dnssec-digests v1.0.0": 1, 5 | "DNSSECImpl v1.0.0": 1, 6 | "OffchainDNSResolver v1.0.0": 1, 7 | "SimplePublicSuffixList v1.0.0": 1, 8 | "Root:contract v1.0.0": 1, 9 | "Root:setup v1.0.0": 1, 10 | "DNSRegistrar:contract v1.0.0": 1, 11 | "DNSRegistrar:set-tlds v1.0.0": 1, 12 | "BaseRegistrarImplementation:contract v1.0.0": 1, 13 | "BaseRegistrarImplementation:setup v1.0.0": 1, 14 | "ExponentialPremiumPriceOracle v1.0.0": 1, 15 | "ReverseRegistrar v1.0.0": 1, 16 | "ETHRegistrarController v1.0.0": 1, 17 | "EthOwnedResolver v1.0.0": 1, 18 | "NameWrapper v1.0.0": 1, 19 | "ETHRegistrarController v2.0.0": 1, 20 | "ExtendedDNSResolver v1.0.0": 1, 21 | "PublicResolver v1.0.0": 1, 22 | "PublicResolver v2.0.0": 1, 23 | "UniversalResolver v1.0.0": 1, 24 | "DefaultReverseRegistrar v1.0.0": 1750301726, 25 | "StaticMetadataService v1.0.0": 1750301981, 26 | "ETHRegistrarController v3.0.0": 1750302109, 27 | "StaticBulkRenewal v1.0.0": 1750302133, 28 | "PublicResolver v3.0.0": 1750302157, 29 | "ChainReverseResolver:Arbitrum v1.0.0": 1750302172, 30 | "ChainReverseResolver:Base v1.0.0": 1750302184, 31 | "ChainReverseResolver:Linea v1.0.0": 1750302196, 32 | "ChainReverseResolver:Optimism v1.0.0": 1750302448, 33 | "ChainReverseResolver:Scroll v1.0.0": 1750302615, 34 | "DefaultReverseResolver v1.0.0": 1750303141, 35 | "MigrationHelper v1.0.0": 1750303167, 36 | "BatchGatewayProvider v1.0.0": 1754525030, 37 | "UniversalResolver v1.0.1": 1754525042 38 | } -------------------------------------------------------------------------------- /contracts/resolvers/OwnedResolver.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | import "@openzeppelin/contracts/access/Ownable.sol"; 4 | import "./profiles/ABIResolver.sol"; 5 | import "./profiles/AddrResolver.sol"; 6 | import "./profiles/ContentHashResolver.sol"; 7 | import "./profiles/DNSResolver.sol"; 8 | import "./profiles/InterfaceResolver.sol"; 9 | import "./profiles/NameResolver.sol"; 10 | import "./profiles/PubkeyResolver.sol"; 11 | import "./profiles/TextResolver.sol"; 12 | import "./profiles/ExtendedResolver.sol"; 13 | 14 | /// A simple resolver anyone can use; only allows the owner of a node to set its 15 | /// address. 16 | contract OwnedResolver is 17 | Ownable, 18 | ABIResolver, 19 | AddrResolver, 20 | ContentHashResolver, 21 | DNSResolver, 22 | InterfaceResolver, 23 | NameResolver, 24 | PubkeyResolver, 25 | TextResolver, 26 | ExtendedResolver 27 | { 28 | function isAuthorised(bytes32) internal view override returns (bool) { 29 | return msg.sender == owner(); 30 | } 31 | 32 | function supportsInterface( 33 | bytes4 interfaceID 34 | ) 35 | public 36 | view 37 | virtual 38 | override( 39 | ABIResolver, 40 | AddrResolver, 41 | ContentHashResolver, 42 | DNSResolver, 43 | InterfaceResolver, 44 | NameResolver, 45 | PubkeyResolver, 46 | TextResolver 47 | ) 48 | returns (bool) 49 | { 50 | return super.supportsInterface(interfaceID); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/universalResolver/RegistryUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ENS} from "../registry/ENS.sol"; 5 | import {NameCoder} from "../utils/NameCoder.sol"; 6 | 7 | library RegistryUtils { 8 | /// @notice Find the resolver for `name[offset:]`. 9 | /// @dev Reverts `DNSDecodingFailed`. 10 | /// @param registry The ENS registry. 11 | /// @param name The DNS-encoded name to search. 12 | /// @param offset The offset into `name` to begin the search. 13 | /// @return resolver The resolver or `address(0)` if not found. 14 | /// @return node The namehash of `name[offset:]`. 15 | /// @return resolverOffset The offset into `name` corresponding to `resolver`. 16 | function findResolver( 17 | ENS registry, 18 | bytes memory name, 19 | uint256 offset 20 | ) 21 | internal 22 | view 23 | returns (address resolver, bytes32 node, uint256 resolverOffset) 24 | { 25 | (bytes32 labelHash, uint256 next) = NameCoder.readLabel(name, offset); 26 | if (labelHash != bytes32(0)) { 27 | ( 28 | address parentResolver, 29 | bytes32 parentNode, 30 | uint256 parentOffset 31 | ) = findResolver(registry, name, next); 32 | node = NameCoder.namehash(parentNode, labelHash); 33 | resolver = registry.resolver(node); 34 | return 35 | resolver != address(0) 36 | ? (resolver, node, offset) 37 | : (parentResolver, node, parentOffset); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/TextResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | import "../ResolverBase.sol"; 5 | import "./ITextResolver.sol"; 6 | 7 | abstract contract TextResolver is ITextResolver, ResolverBase { 8 | mapping(uint64 => mapping(bytes32 => mapping(string => string))) versionable_texts; 9 | 10 | /// Sets the text data associated with an ENS node and key. 11 | /// May only be called by the owner of that node in the ENS registry. 12 | /// @param node The node to update. 13 | /// @param key The key to set. 14 | /// @param value The text data value to set. 15 | function setText( 16 | bytes32 node, 17 | string calldata key, 18 | string calldata value 19 | ) external virtual authorised(node) { 20 | versionable_texts[recordVersions[node]][node][key] = value; 21 | emit TextChanged(node, key, key, value); 22 | } 23 | 24 | /// Returns the text data associated with an ENS node and key. 25 | /// @param node The ENS node to query. 26 | /// @param key The text data key to query. 27 | /// @return The associated text data. 28 | function text( 29 | bytes32 node, 30 | string calldata key 31 | ) external view virtual override returns (string memory) { 32 | return versionable_texts[recordVersions[node]][node][key]; 33 | } 34 | 35 | function supportsInterface( 36 | bytes4 interfaceID 37 | ) public view virtual override returns (bool) { 38 | return 39 | interfaceID == type(ITextResolver).interfaceId || 40 | super.supportsInterface(interfaceID); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/ccipRead/ShuffledGatewayProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IGatewayProvider} from "./IGatewayProvider.sol"; 5 | 6 | // A gateway provider that randomly shuffles its underlying provider. 7 | contract ShuffledGatewayProvider is IGatewayProvider { 8 | IGatewayProvider public immutable provider; 9 | 10 | constructor(IGatewayProvider _provider) { 11 | provider = _provider; 12 | } 13 | 14 | /// @inheritdoc IGatewayProvider 15 | function gateways() external view override returns (string[] memory urls) { 16 | return 17 | shuffledGateways( 18 | uint256( 19 | keccak256( 20 | abi.encodePacked( 21 | blockhash(block.number - 1), 22 | msg.sender, 23 | block.timestamp 24 | ) 25 | ) 26 | ) 27 | ); 28 | } 29 | 30 | /// @dev Deterministically shuffle the gateway URLs. 31 | /// @param seed The shuffle seed. 32 | /// @return urls The shuffled gateway URLs. 33 | function shuffledGateways( 34 | uint256 seed 35 | ) public view returns (string[] memory urls) { 36 | urls = provider.gateways(); 37 | uint256 n = urls.length; 38 | for (uint256 i = 1; i < n; ++i) { 39 | uint256 j = seed % (i + 1); 40 | (urls[i], urls[j]) = (urls[j], urls[i]); 41 | assembly { 42 | mstore(0, seed) 43 | seed := keccak256(0, 32) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/fixtures/dnssecFixture.ts: -------------------------------------------------------------------------------- 1 | import type { NetworkConnection } from 'hardhat/types/network' 2 | import { encodedAnchors } from './anchors.js' 3 | 4 | export async function dnssecFixture(connection: NetworkConnection) { 5 | const dnssec = await connection.viem.deployContract('DNSSECImpl', [ 6 | encodedAnchors, 7 | ]) 8 | 9 | const rsasha256Algorithm = await connection.viem.deployContract( 10 | 'RSASHA256Algorithm', 11 | [], 12 | ) 13 | const rsasha1Algorithm = await connection.viem.deployContract( 14 | 'RSASHA1Algorithm', 15 | [], 16 | ) 17 | const sha256Digest = await connection.viem.deployContract('SHA256Digest', []) 18 | const sha1Digest = await connection.viem.deployContract('SHA1Digest', []) 19 | const p256Sha256Algorithm = await connection.viem.deployContract( 20 | 'P256SHA256Algorithm', 21 | [], 22 | ) 23 | const dummyAlgorithm = await connection.viem.deployContract( 24 | 'DummyAlgorithm', 25 | [], 26 | ) 27 | const dummyDigest = await connection.viem.deployContract('DummyDigest', []) 28 | 29 | await dnssec.write.setAlgorithm([5, rsasha1Algorithm.address]) 30 | await dnssec.write.setAlgorithm([7, rsasha1Algorithm.address]) 31 | await dnssec.write.setAlgorithm([8, rsasha256Algorithm.address]) 32 | await dnssec.write.setAlgorithm([13, p256Sha256Algorithm.address]) 33 | // dummy 34 | await dnssec.write.setAlgorithm([253, dummyAlgorithm.address]) 35 | await dnssec.write.setAlgorithm([254, dummyAlgorithm.address]) 36 | 37 | await dnssec.write.setDigest([1, sha1Digest.address]) 38 | await dnssec.write.setDigest([2, sha256Digest.address]) 39 | // dummy 40 | await dnssec.write.setDigest([253, dummyDigest.address]) 41 | 42 | return { dnssec } 43 | } 44 | -------------------------------------------------------------------------------- /test/wrapper/functions/getApproved.ts: -------------------------------------------------------------------------------- 1 | import { zeroAddress } from 'viem' 2 | 3 | import type { NetworkConnection } from 'hardhat/types/network' 4 | import { toNameId } from '../../fixtures/utils.js' 5 | import { 6 | CAN_DO_EVERYTHING, 7 | expectOwnerOf, 8 | zeroAccount, 9 | type LoadNameWrapperFixture, 10 | } from '../fixtures/utils.js' 11 | 12 | export const getApprovedTests = ( 13 | connection: NetworkConnection, 14 | loadNameWrapperFixture: LoadNameWrapperFixture, 15 | ) => { 16 | describe('getApproved()', () => { 17 | const label = 'subdomain' 18 | const name = `${label}.eth` 19 | 20 | async function fixture() { 21 | const initial = await loadNameWrapperFixture() 22 | const { actions } = initial 23 | 24 | await actions.registerSetupAndWrapName({ 25 | label, 26 | fuses: CAN_DO_EVERYTHING, 27 | }) 28 | 29 | return initial 30 | } 31 | const loadFixture = async () => 32 | connection.networkHelpers.loadFixture(fixture) 33 | 34 | it('Returns returns zero address when ownerOf() is zero', async () => { 35 | const { nameWrapper } = await loadFixture() 36 | 37 | await expectOwnerOf('unminted.eth').on(nameWrapper).toBe(zeroAccount) 38 | await expect( 39 | nameWrapper.read.getApproved([toNameId('unminted.eth')]), 40 | ).resolves.toEqualAddress(zeroAddress) 41 | }) 42 | 43 | it('Returns the approved address', async () => { 44 | const { nameWrapper, accounts } = await loadFixture() 45 | 46 | await nameWrapper.write.approve([accounts[1].address, toNameId(name)]) 47 | 48 | await expect( 49 | nameWrapper.read.getApproved([toNameId(name)]), 50 | ).resolves.toEqualAddress(accounts[1].address) 51 | }) 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /contracts/ethregistrar/IBaseRegistrar.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "../registry/ENS.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | interface IBaseRegistrar is IERC721 { 8 | event ControllerAdded(address indexed controller); 9 | event ControllerRemoved(address indexed controller); 10 | event NameMigrated( 11 | uint256 indexed id, 12 | address indexed owner, 13 | uint256 expires 14 | ); 15 | event NameRegistered( 16 | uint256 indexed id, 17 | address indexed owner, 18 | uint256 expires 19 | ); 20 | event NameRenewed(uint256 indexed id, uint256 expires); 21 | 22 | // Authorises a controller, who can register and renew domains. 23 | function addController(address controller) external; 24 | 25 | // Revoke controller permission for an address. 26 | function removeController(address controller) external; 27 | 28 | // Set the resolver for the TLD this registrar manages. 29 | function setResolver(address resolver) external; 30 | 31 | // Returns the expiration timestamp of the specified label hash. 32 | function nameExpires(uint256 id) external view returns (uint256); 33 | 34 | // Returns true if the specified name is available for registration. 35 | function available(uint256 id) external view returns (bool); 36 | 37 | /// @dev Register a name. 38 | function register( 39 | uint256 id, 40 | address owner, 41 | uint256 duration 42 | ) external returns (uint256); 43 | 44 | function renew(uint256 id, uint256 duration) external returns (uint256); 45 | 46 | /// @dev Reclaim ownership of a name in ENS, if you own it in the registrar. 47 | function reclaim(uint256 id, address owner) external; 48 | } 49 | -------------------------------------------------------------------------------- /contracts/registry/ENSRegistryWithFallback.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.4; 2 | 3 | import "./ENS.sol"; 4 | import "./ENSRegistry.sol"; 5 | 6 | /// The ENS registry contract. 7 | contract ENSRegistryWithFallback is ENSRegistry { 8 | ENS public old; 9 | 10 | /// @dev Constructs a new ENS registrar. 11 | constructor(ENS _old) public ENSRegistry() { 12 | old = _old; 13 | } 14 | 15 | /// @dev Returns the address of the resolver for the specified node. 16 | /// @param node The specified node. 17 | /// @return address of the resolver. 18 | function resolver(bytes32 node) public view override returns (address) { 19 | if (!recordExists(node)) { 20 | return old.resolver(node); 21 | } 22 | 23 | return super.resolver(node); 24 | } 25 | 26 | /// @dev Returns the address that owns the specified node. 27 | /// @param node The specified node. 28 | /// @return address of the owner. 29 | function owner(bytes32 node) public view override returns (address) { 30 | if (!recordExists(node)) { 31 | return old.owner(node); 32 | } 33 | 34 | return super.owner(node); 35 | } 36 | 37 | /// @dev Returns the TTL of a node, and any records associated with it. 38 | /// @param node The specified node. 39 | /// @return ttl of the node. 40 | function ttl(bytes32 node) public view override returns (uint64) { 41 | if (!recordExists(node)) { 42 | return old.ttl(node); 43 | } 44 | 45 | return super.ttl(node); 46 | } 47 | 48 | function _setOwner(bytes32 node, address owner) internal override { 49 | address addr = owner; 50 | if (addr == address(0x0)) { 51 | addr = address(this); 52 | } 53 | 54 | super._setOwner(node, addr); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/utils/TestGatewayProvider.test.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | import { describe, it, expect } from 'vitest' 3 | 4 | const connection = await hre.network.connect() 5 | 6 | const URLS = ['a', 'ab', 'abc'] 7 | 8 | async function fixture() { 9 | const [walletClient] = await connection.viem.getWalletClients() 10 | const gatewayProvider = await connection.viem.deployContract( 11 | 'GatewayProvider', 12 | [walletClient.account.address, URLS], 13 | ) 14 | const shuffledGatewayProvider = await connection.viem.deployContract( 15 | 'ShuffledGatewayProvider', 16 | [gatewayProvider.address], 17 | ) 18 | return { gatewayProvider, shuffledGatewayProvider } 19 | } 20 | 21 | describe('GatewayProvider', () => { 22 | describe('GatewayProvider', () => { 23 | it('gateways()', async () => { 24 | const F = await connection.networkHelpers.loadFixture(fixture) 25 | const urls = await F.gatewayProvider.read.gateways() 26 | expect(urls).toStrictEqual(URLS) 27 | }) 28 | it('setGateways()', async () => { 29 | const F = await connection.networkHelpers.loadFixture(fixture) 30 | const urls = ['x', 'y'] 31 | await F.gatewayProvider.write.setGateways([urls]) 32 | expect(F.gatewayProvider.read.gateways()).resolves.toStrictEqual(urls) 33 | }) 34 | }) 35 | 36 | describe('ShuffledGatewayProvider', () => { 37 | it('gateways()', async () => { 38 | const F = await connection.networkHelpers.loadFixture(fixture) 39 | const set = new Set() 40 | for (let i = 0; i < 100; i++) { 41 | await connection.networkHelpers.mine(1) 42 | set.add(String(await F.shuffledGatewayProvider.read.gateways())) 43 | } 44 | let n = 1 45 | for (let i = URLS.length; i; i--) n *= i 46 | expect(set.size).toStrictEqual(n) 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /deploy/ethregistrar/02_deploy_legacy_eth_registrar_controller.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | import type { Abi } from 'viem' 3 | import legacyArtifactRaw from '../../deployments/archive/ETHRegistrarController_mainnet_9380471.sol/ETHRegistrarController_mainnet_9380471.json' 4 | 5 | const legacyArtifact = { 6 | ...legacyArtifactRaw, 7 | metadata: '{}', 8 | abi: legacyArtifactRaw.abi as Abi, 9 | } 10 | 11 | export default deployScript( 12 | async ({ 13 | deploy, 14 | get, 15 | execute: write, 16 | namedAccounts, 17 | registerLegacyNames, 18 | }) => { 19 | const { deployer, owner } = namedAccounts 20 | 21 | const registrar = get< 22 | (typeof artifacts.BaseRegistrarImplementation)['abi'] 23 | >('BaseRegistrarImplementation') 24 | const priceOracle = get< 25 | (typeof artifacts.ExponentialPremiumPriceOracle)['abi'] 26 | >('ExponentialPremiumPriceOracle') 27 | 28 | const controller = await deploy('LegacyETHRegistrarController', { 29 | account: deployer, 30 | artifact: legacyArtifact, 31 | args: [registrar.address, priceOracle.address, 60n, 86400n], 32 | }) 33 | 34 | console.log( 35 | ` - Adding LegacyETHRegistrarController as controller on BaseRegistrarImplementation`, 36 | ) 37 | await write(registrar, { 38 | functionName: 'addController', 39 | args: [controller.address], 40 | account: owner, 41 | }) 42 | 43 | if (registerLegacyNames) { 44 | console.log(' - Running registerLegacyNames hook') 45 | await registerLegacyNames() 46 | } 47 | }, 48 | { 49 | id: 'ETHRegistrarController v1.0.0', 50 | tags: ['category:ethregistrar', 'LegacyETHRegistrarController'], 51 | dependencies: [ 52 | 'BaseRegistrarImplementation', 53 | 'ExponentialPremiumPriceOracle', 54 | ], 55 | }, 56 | ) 57 | -------------------------------------------------------------------------------- /test/dnssec-oracle/TestDigests.test.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | import { stringToHex } from 'viem' 3 | 4 | import { digests } from './fixtures/digests.js' 5 | 6 | const connection = await hre.network.connect() 7 | 8 | digests.forEach((testcase) => { 9 | async function fixture() { 10 | const digest = await connection.viem.deployContract( 11 | testcase.digest as 'SHA256Digest', 12 | [], 13 | ) 14 | return { digest } 15 | } 16 | const loadFixture = async () => connection.networkHelpers.loadFixture(fixture) 17 | 18 | describe(testcase.digest, () => { 19 | it('should return true for valid hashes', async () => { 20 | const { digest } = await loadFixture() 21 | 22 | await Promise.all( 23 | testcase.valids.map(async ([text, hash]) => 24 | expect(digest.read.verify([stringToHex(text), hash])).resolves.toBe( 25 | true, 26 | ), 27 | ), 28 | ) 29 | }) 30 | 31 | it('should return false for invalid hashes', async () => { 32 | const { digest } = await loadFixture() 33 | 34 | await Promise.all( 35 | testcase.invalids.map(async ([text, hash]) => 36 | expect(digest.read.verify([stringToHex(text), hash])).resolves.toBe( 37 | false, 38 | ), 39 | ), 40 | ) 41 | }) 42 | 43 | it('should throw an error for hashes of the wrong form', async () => { 44 | const { digest } = await loadFixture() 45 | 46 | const expectedError = `Invalid ${testcase.digest 47 | .split('Digest')[0] 48 | .toLowerCase()} hash length` 49 | 50 | await Promise.all( 51 | testcase.errors.map(async ([text, hash]) => 52 | expect( 53 | digest.read.verify([stringToHex(text), hash]), 54 | ).toBeRevertedWithString(expectedError), 55 | ), 56 | ) 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /test/fixtures/createInterfaceId.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | import type { ArtifactMap } from 'hardhat/types/artifacts' 3 | import { 4 | bytesToHex, 5 | hexToBytes, 6 | toFunctionHash, 7 | type Abi, 8 | type AbiFunction, 9 | } from 'viem' 10 | 11 | /** 12 | * @description Gets the interface ABI that would be used in Solidity 13 | * 14 | * - This function is required since `type(INameWrapper).interfaceId` in Solidity uses **only the function signatures explicitly defined in the interface**. The value for it however can't be derived from any Solidity output?!?! 15 | * 16 | * @param interfaceName - The name of the interface to get the ABI for 17 | * @returns The explicitly defined ABI for the interface 18 | */ 19 | const getSolidityReferenceInterfaceAbi = async ( 20 | interfaceName: keyof ArtifactMap, 21 | ) => { 22 | const artifact = await hre.artifacts.readArtifact(interfaceName as string) 23 | 24 | // For interfaces, the artifact ABI contains only the functions explicitly defined in the interface 25 | // This is exactly what we need for calculating the interface ID 26 | return artifact.abi.filter( 27 | (item): item is AbiFunction => item.type === 'function', 28 | ) 29 | } 30 | 31 | export const createInterfaceId = (iface: iface) => { 32 | const bytesId = iface 33 | .filter((item): item is AbiFunction => item.type === 'function') 34 | .map((f) => toFunctionHash(f)) 35 | .map((h) => hexToBytes(h).slice(0, 4)) 36 | .reduce((memo, bytes) => { 37 | for (let i = 0; i < 4; i++) { 38 | memo[i] = memo[i] ^ bytes[i] // xor 39 | } 40 | return memo 41 | }, new Uint8Array(4)) 42 | 43 | return bytesToHex(bytesId) 44 | } 45 | 46 | export const getInterfaceId = async (interfaceName: keyof ArtifactMap) => { 47 | const abi = await getSolidityReferenceInterfaceAbi(interfaceName) 48 | return createInterfaceId(abi) 49 | } 50 | -------------------------------------------------------------------------------- /contracts/wrapper/test/TestNameWrapperReentrancy.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ~0.8.17; 3 | 4 | import "../INameWrapper.sol"; 5 | import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; 6 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 7 | 8 | contract TestNameWrapperReentrancy is ERC165, IERC1155Receiver { 9 | INameWrapper nameWrapper; 10 | address owner; 11 | bytes32 parentNode; 12 | bytes32 labelHash; 13 | uint256 tokenId; 14 | 15 | constructor( 16 | address _owner, 17 | INameWrapper _nameWrapper, 18 | bytes32 _parentNode, 19 | bytes32 _labelHash 20 | ) { 21 | owner = _owner; 22 | nameWrapper = _nameWrapper; 23 | parentNode = _parentNode; 24 | labelHash = _labelHash; 25 | } 26 | 27 | function supportsInterface( 28 | bytes4 interfaceId 29 | ) public view virtual override(ERC165, IERC165) returns (bool) { 30 | return 31 | interfaceId == type(IERC1155Receiver).interfaceId || 32 | super.supportsInterface(interfaceId); 33 | } 34 | 35 | function onERC1155Received( 36 | address, 37 | address, 38 | uint256 _id, 39 | uint256, 40 | bytes calldata 41 | ) public override returns (bytes4) { 42 | tokenId = _id; 43 | nameWrapper.unwrap(parentNode, labelHash, owner); 44 | 45 | return this.onERC1155Received.selector; 46 | } 47 | 48 | function onERC1155BatchReceived( 49 | address, 50 | address, 51 | uint256[] memory, 52 | uint256[] memory, 53 | bytes memory 54 | ) public virtual override returns (bytes4) { 55 | return this.onERC1155BatchReceived.selector; 56 | } 57 | 58 | function claimToOwner() public { 59 | nameWrapper.safeTransferFrom(address(this), owner, tokenId, 1, ""); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /deploy/ethregistrar/05_deploy_static_bulk_renewal.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | import { namehash, zeroAddress } from 'viem' 3 | import { createInterfaceId } from '../../test/fixtures/createInterfaceId.js' 4 | 5 | export default deployScript( 6 | async ({ deploy, execute: write, get, read, namedAccounts, network }) => { 7 | const { deployer, owner } = namedAccounts 8 | 9 | const registry = get<(typeof artifacts.ENSRegistry)['abi']>('ENSRegistry') 10 | const controller = get<(typeof artifacts.ETHRegistrarController)['abi']>( 11 | 'ETHRegistrarController', 12 | ) 13 | 14 | const bulkRenewal = await deploy('StaticBulkRenewal', { 15 | account: deployer, 16 | artifact: artifacts.StaticBulkRenewal, 17 | args: [controller.address], 18 | }) 19 | 20 | // Only attempt to make resolver etc changes directly on testnets 21 | if (network.name === 'mainnet' && !network.tags.tenderly) return 22 | 23 | const interfaceId = createInterfaceId(bulkRenewal.abi) 24 | const resolver = await read(registry, { 25 | functionName: 'resolver', 26 | args: [namehash('eth')], 27 | }) 28 | if (resolver === zeroAddress) { 29 | console.warn( 30 | ` - WARN: No resolver set for .eth; not setting interface ${interfaceId} for BulkRenewal`, 31 | ) 32 | return 33 | } 34 | 35 | console.log( 36 | ` - Setting BulkRenewal interface ID ${interfaceId} on .eth resolver`, 37 | ) 38 | await write( 39 | { ...artifacts.OwnedResolver, address: resolver }, 40 | { 41 | functionName: 'setInterface', 42 | args: [namehash('eth'), interfaceId, bulkRenewal.address], 43 | account: owner, 44 | }, 45 | ) 46 | }, 47 | { 48 | id: 'StaticBulkRenewal v1.0.0', 49 | tags: ['category:ethregistrar', 'StaticBulkRenewal'], 50 | dependencies: ['ETHRegistrarController'], 51 | }, 52 | ) 53 | -------------------------------------------------------------------------------- /contracts/resolvers/profiles/PubkeyResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | import "../ResolverBase.sol"; 5 | import "./IPubkeyResolver.sol"; 6 | 7 | abstract contract PubkeyResolver is IPubkeyResolver, ResolverBase { 8 | struct PublicKey { 9 | bytes32 x; 10 | bytes32 y; 11 | } 12 | 13 | mapping(uint64 => mapping(bytes32 => PublicKey)) versionable_pubkeys; 14 | 15 | /// Sets the SECP256k1 public key associated with an ENS node. 16 | /// @param node The ENS node to query 17 | /// @param x the X coordinate of the curve point for the public key. 18 | /// @param y the Y coordinate of the curve point for the public key. 19 | function setPubkey( 20 | bytes32 node, 21 | bytes32 x, 22 | bytes32 y 23 | ) external virtual authorised(node) { 24 | versionable_pubkeys[recordVersions[node]][node] = PublicKey(x, y); 25 | emit PubkeyChanged(node, x, y); 26 | } 27 | 28 | /// Returns the SECP256k1 public key associated with an ENS node. 29 | /// Defined in EIP 619. 30 | /// @param node The ENS node to query 31 | /// @return x The X coordinate of the curve point for the public key. 32 | /// @return y The Y coordinate of the curve point for the public key. 33 | function pubkey( 34 | bytes32 node 35 | ) external view virtual override returns (bytes32 x, bytes32 y) { 36 | uint64 currentRecordVersion = recordVersions[node]; 37 | return ( 38 | versionable_pubkeys[currentRecordVersion][node].x, 39 | versionable_pubkeys[currentRecordVersion][node].y 40 | ); 41 | } 42 | 43 | function supportsInterface( 44 | bytes4 interfaceID 45 | ) public view virtual override returns (bool) { 46 | return 47 | interfaceID == type(IPubkeyResolver).interfaceId || 48 | super.supportsInterface(interfaceID); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /scripts/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { type Abi, isHex, toFunctionSelector, toHex } from 'viem' 2 | import artifacts from '../generated/artifacts.js' 3 | 4 | // $ bun interfaces # all 5 | // $ bun interfaces Ens # by name (ignores case) 6 | // $ bun interfaces 0x9061b923 # by selector 7 | // $ bun interfaces Ens 0x9061b923 # mixture of names/selectors 8 | // $ bun interfaces ... --json # export as JSON 9 | 10 | const ifaces = Object.values(artifacts) 11 | .filter((x) => x.bytecode === '0x') 12 | .map((x) => ({ 13 | interfaceId: getInterfaceId(x.abi), 14 | name: x.contractName, 15 | file: x.sourceName, 16 | })) 17 | .sort((a, b) => a.file.localeCompare(b.file)) 18 | 19 | const UNKNOWN = '???' 20 | 21 | let output: (x: any) => void = console.table 22 | const qs = process.argv.slice(2).filter((x) => { 23 | if (x === '--json') { 24 | output = (x) => { 25 | console.log() 26 | console.log(JSON.stringify(x, null, ' ')) 27 | } 28 | } else { 29 | return true 30 | } 31 | }) 32 | if (qs.length) { 33 | output( 34 | qs.map((q) => { 35 | if (isHex(q) && q.length === 10) { 36 | return ( 37 | ifaces.find((x) => same(x.interfaceId, q)) ?? { 38 | interfaceId: q, 39 | name: UNKNOWN, 40 | } 41 | ) 42 | } else { 43 | return ( 44 | ifaces.find((x) => same(x.name, q)) ?? { 45 | interfaceId: UNKNOWN, 46 | name: q, 47 | } 48 | ) 49 | } 50 | }), 51 | ) 52 | } else { 53 | output(ifaces) 54 | } 55 | 56 | function same(a: string, b: string) { 57 | return !a.localeCompare(b, undefined, { sensitivity: 'base' }) 58 | } 59 | 60 | function getInterfaceId(abi: Abi) { 61 | return toHex( 62 | abi 63 | .filter((item) => item.type === 'function') 64 | .reduce((a, x) => a ^ BigInt(toFunctionSelector(x)), 0n), 65 | { size: 4 }, 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /test/utils/TestERC20Recoverable.test.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | 3 | import { getAccounts } from '../fixtures/utils.js' 4 | 5 | const connection = await hre.network.connect() 6 | const accounts = await getAccounts(connection) 7 | 8 | async function fixture() { 9 | const erc20Recoverable = await connection.viem.deployContract( 10 | 'ERC20Recoverable', 11 | [], 12 | ) 13 | const erc20Token = await connection.viem.deployContract('MockERC20', [ 14 | 'Ethereum Name Service Token', 15 | 'ENS', 16 | [], 17 | ]) 18 | 19 | return { erc20Recoverable, erc20Token } 20 | } 21 | const loadFixture = async () => connection.networkHelpers.loadFixture(fixture) 22 | 23 | describe('ERC20Recoverable', () => { 24 | it('should recover ERC20 token', async () => { 25 | const { erc20Recoverable, erc20Token } = await loadFixture() 26 | 27 | await erc20Token.write.transfer([erc20Recoverable.address, 1000n]) 28 | await expect( 29 | erc20Token.read.balanceOf([erc20Recoverable.address]), 30 | ).resolves.toEqual(1000n) 31 | 32 | await erc20Recoverable.write.recoverFunds([ 33 | erc20Token.address, 34 | accounts[0].address, 35 | 1000n, 36 | ]) 37 | await expect( 38 | erc20Token.read.balanceOf([erc20Recoverable.address]), 39 | ).resolves.toEqual(0n) 40 | }) 41 | 42 | it('should not allow non-owner to call', async () => { 43 | const { erc20Recoverable, erc20Token } = await loadFixture() 44 | 45 | await erc20Token.write.transfer([erc20Recoverable.address, 1000n]) 46 | await expect( 47 | erc20Token.read.balanceOf([erc20Recoverable.address]), 48 | ).resolves.toEqual(1000n) 49 | 50 | await expect( 51 | erc20Recoverable.write.recoverFunds( 52 | [erc20Token.address, accounts[1].address, 1000n], 53 | { 54 | account: accounts[1], 55 | }, 56 | ), 57 | ).toBeRevertedWithString('Ownable: caller is not the owner') 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /contracts/resolvers/Multicallable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "./IMulticallable.sol"; 5 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 6 | 7 | abstract contract Multicallable is IMulticallable, ERC165 { 8 | function _multicall( 9 | bytes32 nodehash, 10 | bytes[] calldata data 11 | ) internal returns (bytes[] memory results) { 12 | results = new bytes[](data.length); 13 | for (uint256 i = 0; i < data.length; i++) { 14 | if (nodehash != bytes32(0)) { 15 | bytes32 txNamehash = bytes32(data[i][4:36]); 16 | require( 17 | txNamehash == nodehash, 18 | "multicall: All records must have a matching namehash" 19 | ); 20 | } 21 | (bool success, bytes memory result) = address(this).delegatecall( 22 | data[i] 23 | ); 24 | require(success); 25 | results[i] = result; 26 | } 27 | return results; 28 | } 29 | 30 | // This function provides an extra security check when called 31 | // from priviledged contracts (such as EthRegistrarController) 32 | // that can set records on behalf of the node owners 33 | function multicallWithNodeCheck( 34 | bytes32 nodehash, 35 | bytes[] calldata data 36 | ) external returns (bytes[] memory results) { 37 | return _multicall(nodehash, data); 38 | } 39 | 40 | function multicall( 41 | bytes[] calldata data 42 | ) public override returns (bytes[] memory results) { 43 | return _multicall(bytes32(0), data); 44 | } 45 | 46 | function supportsInterface( 47 | bytes4 interfaceID 48 | ) public view virtual override returns (bool) { 49 | return 50 | interfaceID == type(IMulticallable).interfaceId || 51 | super.supportsInterface(interfaceID); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/test/mocks/MockOffchainResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | import "../../../contracts/resolvers/profiles/IExtendedResolver.sol"; 6 | 7 | error OffchainLookup( 8 | address sender, 9 | string[] urls, 10 | bytes callData, 11 | bytes4 callbackFunction, 12 | bytes extraData 13 | ); 14 | 15 | contract MockOffchainResolver is IExtendedResolver, ERC165 { 16 | function supportsInterface( 17 | bytes4 interfaceId 18 | ) public view virtual override returns (bool) { 19 | return 20 | interfaceId == type(IExtendedResolver).interfaceId || 21 | super.supportsInterface(interfaceId); 22 | } 23 | 24 | function resolve( 25 | bytes calldata /* name */, 26 | bytes calldata data 27 | ) external view returns (bytes memory) { 28 | string[] memory urls = new string[](1); 29 | urls[0] = "https://example.com/"; 30 | revert OffchainLookup( 31 | address(this), 32 | urls, 33 | data, 34 | MockOffchainResolver.resolveCallback.selector, 35 | data 36 | ); 37 | } 38 | 39 | function addr(bytes32) external pure returns (bytes memory) { 40 | return abi.encode("onchain"); 41 | } 42 | 43 | function resolveCallback( 44 | bytes calldata response, 45 | bytes calldata extraData 46 | ) external view returns (bytes memory) { 47 | (, bytes memory callData, ) = abi.decode( 48 | extraData, 49 | (bytes, bytes, bytes4) 50 | ); 51 | if (bytes4(callData) == bytes4(keccak256("addr(bytes32)"))) { 52 | (bytes memory result, , ) = abi.decode( 53 | response, 54 | (bytes, uint64, bytes) 55 | ); 56 | return result; 57 | } 58 | return abi.encode(address(this)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /deploy/reverseregistrar/00_deploy_reverse_registrar.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | import { labelhash, namehash } from 'viem' 3 | 4 | export default deployScript( 5 | async ({ deploy, get, execute: write, namedAccounts, network }) => { 6 | const { deployer, owner } = namedAccounts 7 | 8 | // Get dependencies 9 | const registry = get<(typeof artifacts.ENSRegistry)['abi']>('ENSRegistry') 10 | 11 | // Deploy ReverseRegistrar 12 | const reverseRegistrar = await deploy('ReverseRegistrar', { 13 | account: deployer, 14 | artifact: artifacts.ReverseRegistrar, 15 | args: [registry.address], 16 | }) 17 | 18 | if (!reverseRegistrar.newlyDeployed) return 19 | 20 | // Transfer ownership to owner 21 | if (owner !== deployer) { 22 | console.log(` - Transferring ownership of ReverseRegistrar to ${owner}`) 23 | await write(reverseRegistrar, { 24 | functionName: 'transferOwnership', 25 | args: [owner], 26 | account: deployer, 27 | }) 28 | } 29 | 30 | // Only attempt to make controller etc changes directly on testnets 31 | if (network.name === 'mainnet' && !network.tags?.tenderly) return 32 | 33 | const root = get<(typeof artifacts.Root)['abi']>('Root') 34 | console.log(` - Setting owner of .reverse to owner on root`) 35 | await write(root, { 36 | functionName: 'setSubnodeOwner', 37 | args: [labelhash('reverse'), owner], 38 | account: owner, 39 | }) 40 | 41 | console.log( 42 | ` - Setting owner of .addr.reverse to ReverseRegistrar on registry`, 43 | ) 44 | await write(registry, { 45 | functionName: 'setSubnodeOwner', 46 | args: [namehash('reverse'), labelhash('addr'), reverseRegistrar.address], 47 | account: owner, 48 | }) 49 | 50 | return true 51 | }, 52 | { 53 | id: 'ReverseRegistrar v1.0.0', 54 | tags: ['category:reverseregistrar', 'ReverseRegistrar'], 55 | dependencies: ['ENSRegistry', 'Root'], 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /contracts/ethregistrar/SafeMath.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ~0.8.17; 3 | 4 | /// @title SafeMath 5 | /// @dev Unsigned math operations with safety checks that revert on error 6 | library SafeMath { 7 | /// @dev Multiplies two unsigned integers, reverts on overflow. 8 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 9 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 10 | // benefit is lost if 'b' is also tested. 11 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 12 | if (a == 0) { 13 | return 0; 14 | } 15 | 16 | uint256 c = a * b; 17 | require(c / a == b); 18 | 19 | return c; 20 | } 21 | 22 | /// @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. 23 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 24 | // Solidity only automatically asserts when dividing by 0 25 | require(b > 0); 26 | uint256 c = a / b; 27 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 28 | 29 | return c; 30 | } 31 | 32 | /// @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). 33 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 34 | require(b <= a); 35 | uint256 c = a - b; 36 | 37 | return c; 38 | } 39 | 40 | /// @dev Adds two unsigned integers, reverts on overflow. 41 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 42 | uint256 c = a + b; 43 | require(c >= a); 44 | 45 | return c; 46 | } 47 | 48 | /// @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), 49 | /// reverts when dividing by zero. 50 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 51 | require(b != 0); 52 | return a % b; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/fixtures/createChainReverseResolverDeployment.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | import { mainnet, sepolia } from 'viem/chains' 3 | import { coinTypeFromChain } from './ensip19.js' 4 | 5 | const owners = { 6 | [sepolia.id]: '0x343431e9CEb7C19cC8d3eA0EE231bfF82B584910', 7 | // dao address 8 | [mainnet.id]: '0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7', 9 | } 10 | 11 | export function createChainReverseResolverDeployer({ 12 | chainName, 13 | targets, 14 | }: { 15 | chainName: string 16 | targets: Record 17 | }) { 18 | const func = deployScript( 19 | async ({ deploy, get, namedAccounts, network }) => { 20 | const { deployer } = namedAccounts 21 | 22 | const defaultReverseRegistrar = await get('DefaultReverseRegistrar') 23 | const chainId = 24 | network.chain?.id || (network as any).config?.chainId || 31337 25 | const target = targets[chainId] 26 | 27 | if (!target) { 28 | console.log(`No target for chain ${chainId}`) 29 | return 30 | } 31 | 32 | const { chain, registrar, verifier, gateways } = target 33 | const owner = owners[chainId as keyof typeof owners] 34 | 35 | // there should always be an owner specified when there are targets 36 | if (!owner) throw new Error(`No owner for chain ${chainId}`) 37 | 38 | await deploy(`${chainName}ReverseResolver`, { 39 | account: deployer, 40 | artifact: artifacts.ChainReverseResolver, 41 | args: [ 42 | owner as `0x${string}`, 43 | coinTypeFromChain(chain), 44 | defaultReverseRegistrar.address, 45 | registrar, 46 | verifier, 47 | gateways, 48 | ], 49 | }) 50 | }, 51 | { 52 | id: `ChainReverseResolver:${chainName} v1.0.0`, 53 | tags: [ 54 | 'category:reverseregistrar', 55 | 'ChainReverseResolver', 56 | `ChainReverseResolver:${chainName}`, 57 | ], 58 | dependencies: ['DefaultReverseRegistrar'], 59 | }, 60 | ) 61 | return func 62 | } 63 | -------------------------------------------------------------------------------- /test/universalResolver/ownedEnsFixture.ts: -------------------------------------------------------------------------------- 1 | import type { NetworkConnection } from 'hardhat/types/network' 2 | import { getAddress, labelhash, namehash, zeroAddress } from 'viem' 3 | import { oldResolverArtifact } from '../fixtures/OldResolver.js' 4 | 5 | export async function ownedEnsFixture(connection: NetworkConnection) { 6 | const wallets = await connection.viem.getWalletClients() 7 | const owner = getAddress(wallets[0].account.address) 8 | 9 | const ENSRegistry = await connection.viem.deployContract('ENSRegistry') 10 | 11 | async function takeControl(name: string) { 12 | if (name) { 13 | const labels = name.split('.') 14 | for (let i = labels.length; i > 0; i--) { 15 | await ENSRegistry.write.setSubnodeOwner([ 16 | namehash(labels.slice(i).join('.')), 17 | labelhash(labels[i - 1]), 18 | owner, 19 | ]) 20 | } 21 | } 22 | } 23 | 24 | const ReverseRegistrar = await connection.viem.deployContract( 25 | 'ReverseRegistrar', 26 | [ENSRegistry.address], 27 | ) 28 | await takeControl('addr.reverse') 29 | await ENSRegistry.write.setOwner([ 30 | namehash('addr.reverse'), 31 | ReverseRegistrar.address, 32 | ]) 33 | 34 | const PublicResolver = await connection.viem.deployContract( 35 | 'PublicResolver', 36 | [ 37 | ENSRegistry.address, 38 | zeroAddress, // nameWrapper 39 | zeroAddress, // ethController 40 | ReverseRegistrar.address, 41 | ], 42 | ) 43 | await ReverseRegistrar.write.setDefaultResolver([PublicResolver.address]) 44 | 45 | const OldResolver = await connection.viem.deployContract(oldResolverArtifact) 46 | const Shapeshift1 = await connection.viem.deployContract( 47 | 'DummyShapeshiftResolver', 48 | ) 49 | const Shapeshift2 = await connection.viem.deployContract( 50 | 'DummyShapeshiftResolver', 51 | ) 52 | 53 | return { 54 | owner, 55 | ENSRegistry, 56 | PublicResolver, 57 | ReverseRegistrar, 58 | OldResolver, 59 | Shapeshift1, 60 | Shapeshift2, 61 | takeControl, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/utils/TestStringUtils.test.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | import { stringToHex } from 'viem' 3 | 4 | const connection = await hre.network.connect() 5 | 6 | async function fixture() { 7 | return connection.viem.deployContract('TestStringUtils', []) 8 | } 9 | const loadFixture = async () => connection.networkHelpers.loadFixture(fixture) 10 | 11 | describe('StringUtils', () => { 12 | describe('escape', () => { 13 | it('double quote', async () => { 14 | const F = await loadFixture() 15 | await expect( 16 | F.read.escape(['My ENS is, "tanrikulu.eth"']), 17 | ).resolves.toEqual('My ENS is, \\"tanrikulu.eth\\"') 18 | }) 19 | 20 | it('backslash', async () => { 21 | const F = await loadFixture() 22 | await expect(F.read.escape(['Path\\to\\file'])).resolves.toEqual( 23 | 'Path\\\\to\\\\file', 24 | ) 25 | }) 26 | 27 | it('new line', async () => { 28 | const F = await loadFixture() 29 | await expect(F.read.escape(['Line 1\nLine 2'])).resolves.toEqual( 30 | 'Line 1\\nLine 2', 31 | ) 32 | }) 33 | }) 34 | 35 | describe('strlen', () => { 36 | for (const s of [ 37 | '', 38 | 'a', 39 | 'aa', 40 | 'aaa', 41 | 'aaaa', 42 | 'aaaaa', 43 | '⌚', // 1 44 | '🇺🇸', // 2 45 | '🍄‍🟫', // 3 46 | '👨🏻‍🌾', // 4 47 | '🧑‍🤝‍🧑', // 5 48 | '👨🏻‍🦯‍➡', // 6 49 | '🏴󠁧󠁢󠁥󠁮󠁧󠁿', // 7 50 | '👨🏻‍❤‍💋‍👨🏻', // 9 51 | ]) { 52 | const n = [...s].length 53 | it(`${s || ''} = ${n}`, async () => { 54 | const F = await loadFixture() 55 | await expect(F.read.strlen([s])).resolves.toStrictEqual(BigInt(n)) 56 | }) 57 | } 58 | for (const cp of [0, 0x80, 0x800, 0x10000, 0x10ffff]) { 59 | const s = String.fromCodePoint(cp) 60 | const n = [...s].length 61 | it(`${stringToHex(s)} = ${n}`, async () => { 62 | const F = await loadFixture() 63 | await expect(F.read.strlen([s])).resolves.toStrictEqual(BigInt(n)) 64 | }) 65 | } 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /contracts/utils/TestNameCoder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {NameCoder} from "./NameCoder.sol"; 5 | 6 | contract TestNameCoder { 7 | function nextLabel( 8 | bytes memory name, 9 | uint256 offset 10 | ) external pure returns (uint8 size, uint256 nextOffset) { 11 | return NameCoder.nextLabel(name, offset); 12 | } 13 | 14 | function prevLabel( 15 | bytes memory name, 16 | uint256 offset 17 | ) external pure returns (uint256) { 18 | return NameCoder.prevLabel(name, offset); 19 | } 20 | 21 | function readLabel( 22 | bytes memory name, 23 | uint256 offset, 24 | bool parseHashed 25 | ) 26 | external 27 | pure 28 | returns ( 29 | bytes32 labelHash, 30 | uint256 nextOffset, 31 | uint8 size, 32 | bool wasHashed 33 | ) 34 | { 35 | (labelHash, nextOffset, size, wasHashed) = NameCoder.readLabel( 36 | name, 37 | offset, 38 | parseHashed 39 | ); 40 | } 41 | 42 | function namehash( 43 | bytes memory name, 44 | uint256 offset 45 | ) external pure returns (bytes32 nameHash) { 46 | return NameCoder.namehash(name, offset); 47 | } 48 | 49 | function encode( 50 | string memory ens 51 | ) external pure returns (bytes memory dns) { 52 | return NameCoder.encode(ens); 53 | } 54 | 55 | function decode( 56 | bytes memory dns 57 | ) external pure returns (string memory ens) { 58 | return NameCoder.decode(dns); 59 | } 60 | 61 | function matchSuffix( 62 | bytes memory name, 63 | uint256 offset, 64 | bytes32 nodeSuffix 65 | ) 66 | external 67 | pure 68 | returns ( 69 | bool matched, 70 | bytes32 node, 71 | uint256 prevOffset, 72 | uint256 matchOffset 73 | ) 74 | { 75 | return NameCoder.matchSuffix(name, offset, nodeSuffix); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /contracts/registry/ENS.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | interface ENS { 5 | // Logged when the owner of a node assigns a new owner to a subnode. 6 | event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); 7 | 8 | // Logged when the owner of a node transfers ownership to a new account. 9 | event Transfer(bytes32 indexed node, address owner); 10 | 11 | // Logged when the resolver for a node changes. 12 | event NewResolver(bytes32 indexed node, address resolver); 13 | 14 | // Logged when the TTL of a node changes 15 | event NewTTL(bytes32 indexed node, uint64 ttl); 16 | 17 | // Logged when an operator is added or removed. 18 | event ApprovalForAll( 19 | address indexed owner, 20 | address indexed operator, 21 | bool approved 22 | ); 23 | 24 | function setRecord( 25 | bytes32 node, 26 | address owner, 27 | address resolver, 28 | uint64 ttl 29 | ) external; 30 | 31 | function setSubnodeRecord( 32 | bytes32 node, 33 | bytes32 label, 34 | address owner, 35 | address resolver, 36 | uint64 ttl 37 | ) external; 38 | 39 | function setSubnodeOwner( 40 | bytes32 node, 41 | bytes32 label, 42 | address owner 43 | ) external returns (bytes32); 44 | 45 | function setResolver(bytes32 node, address resolver) external; 46 | 47 | function setOwner(bytes32 node, address owner) external; 48 | 49 | function setTTL(bytes32 node, uint64 ttl) external; 50 | 51 | function setApprovalForAll(address operator, bool approved) external; 52 | 53 | function owner(bytes32 node) external view returns (address); 54 | 55 | function resolver(bytes32 node) external view returns (address); 56 | 57 | function ttl(bytes32 node) external view returns (uint64); 58 | 59 | function recordExists(bytes32 node) external view returns (bool); 60 | 61 | function isApprovedForAll( 62 | address owner, 63 | address operator 64 | ) external view returns (bool); 65 | } 66 | -------------------------------------------------------------------------------- /test/root/TestRoot.test.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | import { labelhash, namehash, zeroHash } from 'viem' 3 | 4 | import { getAccounts } from '../fixtures/utils.js' 5 | 6 | const connection = await hre.network.connect() 7 | const accounts = await getAccounts(connection) 8 | 9 | async function fixture() { 10 | const ensRegistry = await connection.viem.deployContract('ENSRegistry', []) 11 | const root = await connection.viem.deployContract('Root', [ 12 | ensRegistry.address, 13 | ]) 14 | 15 | await root.write.setController([accounts[0].address, true]) 16 | await ensRegistry.write.setSubnodeOwner([ 17 | zeroHash, 18 | labelhash('eth'), 19 | root.address, 20 | ]) 21 | await ensRegistry.write.setOwner([zeroHash, root.address]) 22 | 23 | return { ensRegistry, root, accounts } 24 | } 25 | const loadFixture = async () => connection.networkHelpers.loadFixture(fixture) 26 | 27 | describe('Root', () => { 28 | describe('setSubnodeOwner', () => { 29 | it('should allow controllers to set subnodes', async () => { 30 | const { ensRegistry, root, accounts } = await loadFixture() 31 | 32 | await root.write.setSubnodeOwner([labelhash('eth'), accounts[1].address]) 33 | 34 | await expect( 35 | ensRegistry.read.owner([namehash('eth')]), 36 | ).resolves.toEqualAddress(accounts[1].address) 37 | }) 38 | 39 | it('should fail when non-controller tries to set subnode', async () => { 40 | const { root, accounts } = await loadFixture() 41 | 42 | await expect( 43 | root.write.setSubnodeOwner([labelhash('eth'), accounts[1].address], { 44 | account: accounts[1], 45 | }), 46 | ).toBeRevertedWithString('Controllable: Caller is not a controller') 47 | }) 48 | 49 | it('should not allow setting a locked TLD', async () => { 50 | const { root, accounts } = await loadFixture() 51 | 52 | await root.write.lock([labelhash('eth')]) 53 | 54 | await expect( 55 | root.write.setSubnodeOwner([labelhash('eth'), accounts[1].address]), 56 | ).toBeRevertedWithoutReason() 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /contracts/reverseRegistrar/README.md: -------------------------------------------------------------------------------- 1 | # L2 Reverse Registrar 2 | 3 | ## Summary 4 | 5 | The L2 Reverse Registrar is a combination of a resolver and a reverse registrar that allows the name to be set for a particular reverse node. 6 | 7 | ## Setting records 8 | 9 | You can set records using one of the follow functions: 10 | 11 | `setName()` - uses the msg.sender's address and allows you to set a record for that address only 12 | 13 | `setNameForAddr()` - uses the address parameter instead of `msg.sender` and checks if the `msg.sender` is authorised by checking if the contract's owner (via the Ownable pattern) is the msg.sender 14 | 15 | `setNameForAddrWithSignature()` - uses the address parameter instead of `msg.sender` and allows authorisation via a signature 16 | 17 | `setNameForOwnableWithSignature()` - uses the address parameter instead of `msg.sender`. The sender is authorised by checking if the contract's owner (via the Ownable pattern) is the msg.sender, which then checks that the signer has authorised the record on behalf of msg.sender using `ERC1271` (or `ERC6492`) 18 | 19 | ## Signatures for setting records 20 | 21 | The signature format for `setNameForAddrWithSignature` is: 22 | 23 | ``` 24 | validatorAddress, // 0xa4a5CaA360A81461158C96f2Dbad8944411CF3fd for mainnet, 0xAe91c512BC1da8B00cd33dd9D9C734069e6E0fcd for testnet 25 | functionSignature, // 0x2023a04c 26 | name, // string name value 27 | addr, // address to set name for 28 | coinTypes, // array of coinTypes wanting to be set 29 | signatureExpiry // expiry of the signature, up to 1 hour in the future 30 | ``` 31 | 32 | The signature format for `setNameForOwnableWithSignature` is: 33 | 34 | ``` 35 | validatorAddress, // 0xa4a5CaA360A81461158C96f2Dbad8944411CF3fd for mainnet, 0xAe91c512BC1da8B00cd33dd9D9C734069e6E0fcd for testnet 36 | functionSignature, // 0x975713ad 37 | name, // string name value 38 | contractAddr, // contract address to set name for 39 | owner, // owner address of contract (i.e. the signature being verified) 40 | coinTypes, // array of coinTypes wanting to be set 41 | signatureExpiry // expiry of the signature, up to 1 hour in the future 42 | ``` 43 | -------------------------------------------------------------------------------- /contracts/test/mocks/MockERC6492WalletFactory.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17 <0.9.0; 3 | // import signatureVerifier by openzepellin 4 | import {SignatureChecker} from "@openzeppelin/contracts-v5/utils/cryptography/SignatureChecker.sol"; 5 | import {MockSmartContractWallet} from "./MockSmartContractWallet.sol"; 6 | 7 | contract MockERC6492WalletFactory { 8 | error Create2Failed(); 9 | 10 | bytes32 private constant SALT = 11 | 0x00000000000000000000000000000000000000000000000000000000cafebabe; 12 | 13 | function getInitCode(address owner) private pure returns (bytes memory) { 14 | return 15 | abi.encodePacked( 16 | type(MockSmartContractWallet).creationCode, 17 | bytes32(uint256(uint160(owner))) 18 | ); 19 | } 20 | 21 | function predictAddress(address owner) public view returns (address) { 22 | return 23 | address( 24 | uint160( 25 | uint256( 26 | keccak256( 27 | abi.encodePacked( 28 | bytes1(0xff), 29 | address(this), 30 | SALT, 31 | keccak256(getInitCode(owner)) 32 | ) 33 | ) 34 | ) 35 | ) 36 | ); 37 | } 38 | 39 | function createWallet(address owner) public returns (address addr) { 40 | bytes memory bytecode = getInitCode(owner); 41 | assembly ("memory-safe") { 42 | addr := create2(0, add(bytecode, 0x20), mload(bytecode), SALT) 43 | // if no address was created, and returndata is not empty, bubble revert 44 | if and(iszero(addr), not(iszero(returndatasize()))) { 45 | let p := mload(0x40) 46 | returndatacopy(p, 0, returndatasize()) 47 | revert(p, returndatasize()) 48 | } 49 | } 50 | 51 | if (addr == address(0)) revert Create2Failed(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/ethregistrar/StaticBulkRenewal.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ~0.8.17; 3 | 4 | import "./ETHRegistrarController.sol"; 5 | import "./IBulkRenewal.sol"; 6 | import "./IPriceOracle.sol"; 7 | 8 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 9 | 10 | contract StaticBulkRenewal is IBulkRenewal { 11 | ETHRegistrarController controller; 12 | 13 | constructor(ETHRegistrarController _controller) { 14 | controller = _controller; 15 | } 16 | 17 | function rentPrice( 18 | string[] calldata names, 19 | uint256 duration 20 | ) external view override returns (uint256 total) { 21 | uint256 length = names.length; 22 | for (uint256 i = 0; i < length; ) { 23 | IPriceOracle.Price memory price = controller.rentPrice( 24 | names[i], 25 | duration 26 | ); 27 | unchecked { 28 | ++i; 29 | total += (price.base + price.premium); 30 | } 31 | } 32 | } 33 | 34 | function renewAll( 35 | string[] calldata names, 36 | uint256 duration, 37 | bytes32 referrer 38 | ) external payable override { 39 | uint256 length = names.length; 40 | uint256 total; 41 | for (uint256 i = 0; i < length; ) { 42 | IPriceOracle.Price memory price = controller.rentPrice( 43 | names[i], 44 | duration 45 | ); 46 | uint256 totalPrice = price.base + price.premium; 47 | controller.renew{value: totalPrice}(names[i], duration, referrer); 48 | unchecked { 49 | ++i; 50 | total += totalPrice; 51 | } 52 | } 53 | // Send any excess funds back 54 | payable(msg.sender).transfer(address(this).balance); 55 | } 56 | 57 | function supportsInterface( 58 | bytes4 interfaceID 59 | ) external pure returns (bool) { 60 | return 61 | interfaceID == type(IERC165).interfaceId || 62 | interfaceID == type(IBulkRenewal).interfaceId; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contracts/utils/BytesUtils_LEGACY.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | // ******************************************************************************** 5 | /// @dev DO NOT USE THIS CONTRACT 6 | /// This library is provided so NameWrapper can remain unmodified. 7 | /// The rest of the repo can switch to NameCoder, w/hashed label support. 8 | // ******************************************************************************** 9 | 10 | import {BytesUtils} from "./BytesUtils.sol"; 11 | 12 | library BytesUtils_LEGACY { 13 | /// @dev Returns the ENS namehash of a DNS-encoded name. 14 | /// @param self The DNS-encoded name to hash. 15 | /// @param offset The offset at which to start hashing. 16 | /// @return The namehash of the name. 17 | function namehash( 18 | bytes memory self, 19 | uint256 offset 20 | ) internal pure returns (bytes32) { 21 | (bytes32 labelhash, uint256 newOffset) = readLabel(self, offset); 22 | if (labelhash == bytes32(0)) { 23 | require(offset == self.length - 1, "namehash: Junk at end of name"); 24 | return bytes32(0); 25 | } 26 | return 27 | keccak256(abi.encodePacked(namehash(self, newOffset), labelhash)); 28 | } 29 | 30 | /// @dev Returns the keccak-256 hash of a DNS-encoded label, and the offset to the start of the next label. 31 | /// @param self The byte string to read a label from. 32 | /// @param idx The index to read a label at. 33 | /// @return labelhash The hash of the label at the specified index, or 0 if it is the last label. 34 | /// @return newIdx The index of the start of the next label. 35 | function readLabel( 36 | bytes memory self, 37 | uint256 idx 38 | ) internal pure returns (bytes32 labelhash, uint256 newIdx) { 39 | require(idx < self.length, "readLabel: Index out of bounds"); 40 | uint256 len = uint256(uint8(self[idx])); 41 | if (len > 0) { 42 | labelhash = BytesUtils.keccak(self, idx + 1, len); 43 | } else { 44 | labelhash = bytes32(0); 45 | } 46 | newIdx = idx + len + 1; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /deploy/reverseresolver/00_deploy_default_reverse_resolver.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | import { namehash } from 'viem' 3 | 4 | export default deployScript( 5 | async ({ deploy, get, read, execute: write, namedAccounts, network }) => { 6 | const { deployer, owner } = namedAccounts 7 | 8 | const defaultReverseRegistrar = get< 9 | (typeof artifacts.DefaultReverseRegistrar)['abi'] 10 | >('DefaultReverseRegistrar') 11 | const registry = get<(typeof artifacts.ENSRegistry)['abi']>('ENSRegistry') 12 | const root = get<(typeof artifacts.Root)['abi']>('Root') 13 | 14 | const defaultReverseResolver = await deploy('DefaultReverseResolver', { 15 | account: deployer, 16 | artifact: artifacts.DefaultReverseResolver, 17 | args: [defaultReverseRegistrar.address], 18 | }) 19 | 20 | if (network.name === 'mainnet' && !network.tags.tenderly) return 21 | 22 | const currentRootOwner = await read(root, { 23 | functionName: 'owner', 24 | args: [], 25 | }) 26 | const currentReverseOwner = await read(registry, { 27 | functionName: 'owner', 28 | args: [namehash('reverse')], 29 | }) 30 | if (currentRootOwner === owner && currentReverseOwner !== owner) { 31 | console.log(` - Setting owner of .reverse to owner on root`) 32 | await write(root, { 33 | functionName: 'transferOwnership', 34 | args: [owner], 35 | account: deployer, 36 | }) 37 | } else if (currentRootOwner !== owner) { 38 | console.warn( 39 | ` - WARN: Root owner account not available, skipping .reverse setup on registry`, 40 | ) 41 | return 42 | } 43 | 44 | console.log( 45 | ` - Setting resolver of .reverse to DefaultReverseResolver on registry`, 46 | ) 47 | await write(registry, { 48 | functionName: 'setResolver', 49 | args: [namehash('reverse'), defaultReverseResolver.address], 50 | account: owner, 51 | }) 52 | }, 53 | { 54 | id: 'DefaultReverseResolver v1.0.0', 55 | tags: ['category:reverseresolver', 'DefaultReverseResolver'], 56 | dependencies: ['ENSRegistry', 'Root', 'DefaultReverseRegistrar'], 57 | }, 58 | ) 59 | -------------------------------------------------------------------------------- /test/ethregistrar/TestStablePriceOracle.test.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat' 2 | 3 | const connection = await hre.network.connect() 4 | 5 | async function fixture() { 6 | // Dummy oracle with 1 ETH == 10 USD 7 | const dummyOracle = await connection.viem.deployContract('DummyOracle', [ 8 | 1000000000n, 9 | ]) 10 | 11 | return { dummyOracle } 12 | } 13 | const loadFixture = async () => connection.networkHelpers.loadFixture(fixture) 14 | 15 | describe('StablePriceOracle', () => { 16 | it('should return correct prices', async () => { 17 | const { dummyOracle } = await loadFixture() 18 | 19 | // 4 attousd per second for 3 character names, 2 attousd per second for 4 character names, 20 | // 1 attousd per second for longer names. 21 | const priceOracle = await connection.viem.deployContract( 22 | 'StablePriceOracle', 23 | [dummyOracle.address, [0n, 0n, 4n, 2n, 1n]], 24 | ) 25 | 26 | await expect( 27 | priceOracle.read.price(['foo', 0n, 3600n]), 28 | ).resolves.toHaveProperty('base', 1440n) 29 | await expect( 30 | priceOracle.read.price(['quux', 0n, 3600n]), 31 | ).resolves.toHaveProperty('base', 720n) 32 | await expect( 33 | priceOracle.read.price(['fubar', 0n, 3600n]), 34 | ).resolves.toHaveProperty('base', 360n) 35 | await expect( 36 | priceOracle.read.price(['foobie', 0n, 3600n]), 37 | ).resolves.toHaveProperty('base', 360n) 38 | }) 39 | 40 | it('should work with larger volumes', async () => { 41 | const { dummyOracle } = await loadFixture() 42 | 43 | // 4 attousd per second for 3 character names, 2 attousd per second for 4 character names, 44 | // 1 attousd per second for longer names. 45 | const priceOracle = await connection.viem.deployContract( 46 | 'StablePriceOracle', 47 | [ 48 | dummyOracle.address, 49 | [ 50 | 0n, 51 | 0n, 52 | // 1 USD per second! 53 | 1000000000000000000n, 54 | 2n, 55 | 1n, 56 | ], 57 | ], 58 | ) 59 | 60 | await expect( 61 | priceOracle.read.price(['foo', 0n, 86400n]), 62 | ).resolves.toHaveProperty('base', 8640000000000000000000n) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /deploy/root/00_setup_root.ts: -------------------------------------------------------------------------------- 1 | import { artifacts, deployScript } from '@rocketh' 2 | import { getAddress, zeroHash, type Address } from 'viem' 3 | 4 | export default deployScript( 5 | async ({ get, read, execute: write, namedAccounts, network }) => { 6 | const { deployer, owner } = namedAccounts 7 | 8 | if (!network.tags.use_root) { 9 | console.warn(' - WARN: Skipping root setup (use_root not enabled)') 10 | return 11 | } 12 | 13 | console.log(' - Running root setup') 14 | 15 | const registry = get<(typeof artifacts.ENSRegistry)['abi']>('ENSRegistry') 16 | const root = get<(typeof artifacts.Root)['abi']>('Root') 17 | 18 | console.log(` - Setting owner of root node to root contract`) 19 | await write(registry, { 20 | functionName: 'setOwner', 21 | args: [zeroHash, root.address], 22 | account: deployer, 23 | }) 24 | 25 | const rootOwner = await read(root, { 26 | functionName: 'owner', 27 | args: [], 28 | }).then((v) => getAddress(v as Address)) 29 | 30 | switch (rootOwner) { 31 | case getAddress(deployer): 32 | console.log(` - Transferring ownership of root node to ${owner}`) 33 | await write(root, { 34 | functionName: 'transferOwnership', 35 | args: [owner], 36 | account: deployer, 37 | }) 38 | case getAddress(owner): 39 | const ownerIsRootController = await read(root, { 40 | functionName: 'controllers', 41 | args: [owner], 42 | }) 43 | if (!ownerIsRootController) { 44 | console.log(` - Setting ${owner} as controller on root contract`) 45 | await write(root, { 46 | functionName: 'setController', 47 | args: [owner, true], 48 | account: owner, 49 | }) 50 | } 51 | break 52 | default: 53 | console.warn( 54 | ` - WARN: Root is owned by ${rootOwner}; cannot transfer to owner account`, 55 | ) 56 | break 57 | } 58 | 59 | return true 60 | }, 61 | { 62 | id: 'Root:setup v1.0.0', 63 | tags: ['category:root', 'Root', 'Root:setup'], 64 | dependencies: ['Root:contract'], 65 | }, 66 | ) 67 | -------------------------------------------------------------------------------- /contracts/wrapper/mocks/ERC1155ReceiverMock.sol: -------------------------------------------------------------------------------- 1 | // Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.1.0/test/token/ERC1155/ERC1155.behaviour.js 2 | // Copyright (c) 2016-2020 zOS Global Limited 3 | 4 | // SPDX-License-Identifier: MIT 5 | 6 | pragma solidity ^0.8.0; 7 | 8 | import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; 9 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 10 | 11 | contract ERC1155ReceiverMock is IERC1155Receiver, ERC165 { 12 | bytes4 private _recRetval; 13 | bool private _recReverts; 14 | bytes4 private _batRetval; 15 | bool private _batReverts; 16 | 17 | event Received( 18 | address operator, 19 | address from, 20 | uint256 id, 21 | uint256 value, 22 | bytes data 23 | ); 24 | event BatchReceived( 25 | address operator, 26 | address from, 27 | uint256[] ids, 28 | uint256[] values, 29 | bytes data 30 | ); 31 | 32 | constructor( 33 | bytes4 recRetval, 34 | bool recReverts, 35 | bytes4 batRetval, 36 | bool batReverts 37 | ) { 38 | _recRetval = recRetval; 39 | _recReverts = recReverts; 40 | _batRetval = batRetval; 41 | _batReverts = batReverts; 42 | } 43 | 44 | function onERC1155Received( 45 | address operator, 46 | address from, 47 | uint256 id, 48 | uint256 value, 49 | bytes calldata data 50 | ) external override returns (bytes4) { 51 | require(!_recReverts, "ERC1155ReceiverMock: reverting on receive"); 52 | emit Received(operator, from, id, value, data); 53 | return _recRetval; 54 | } 55 | 56 | function onERC1155BatchReceived( 57 | address operator, 58 | address from, 59 | uint256[] calldata ids, 60 | uint256[] calldata values, 61 | bytes calldata data 62 | ) external override returns (bytes4) { 63 | require( 64 | !_batReverts, 65 | "ERC1155ReceiverMock: reverting on batch receive" 66 | ); 67 | emit BatchReceived(operator, from, ids, values, data); 68 | return _batRetval; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/reverseRegistrar/IL2ReverseRegistrar.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | /// @notice Interface for the L2 Reverse Registrar. 5 | interface IL2ReverseRegistrar { 6 | /// @notice Sets the `nameForAddr()` record for the calling account. 7 | /// 8 | /// @param name The name to set. 9 | function setName(string memory name) external; 10 | 11 | /// @notice Sets the `nameForAddr()` record for the addr provided account. 12 | /// 13 | /// @param addr The address to set the name for. 14 | /// @param name The name to set. 15 | function setNameForAddr(address addr, string memory name) external; 16 | 17 | /// @notice Sets the `nameForAddr()` record for the addr provided account using a signature. 18 | /// 19 | /// @param addr The address to set the name for. 20 | /// @param name The name to set. 21 | /// @param coinTypes The coin types to set. Must be inclusive of the coin type for the contract. 22 | /// @param signatureExpiry Date when the signature expires. 23 | /// @param signature The signature from the addr. 24 | function setNameForAddrWithSignature( 25 | address addr, 26 | uint256 signatureExpiry, 27 | string memory name, 28 | uint256[] memory coinTypes, 29 | bytes memory signature 30 | ) external; 31 | 32 | /// @notice Sets the `nameForAddr()` record for the contract provided that is owned with `Ownable`. 33 | /// 34 | /// @param contractAddr The address of the contract to set the name for (implementing Ownable). 35 | /// @param owner The owner of the contract (via Ownable). 36 | /// @param signatureExpiry The expiry of the signature. 37 | /// @param name The name to set. 38 | /// @param coinTypes The coin types to set. Must be inclusive of the coin type for the contract. 39 | /// @param signature The signature of an address that will return true on isValidSignature for the owner. 40 | function setNameForOwnableWithSignature( 41 | address contractAddr, 42 | address owner, 43 | uint256 signatureExpiry, 44 | string memory name, 45 | uint256[] memory coinTypes, 46 | bytes memory signature 47 | ) external; 48 | } 49 | -------------------------------------------------------------------------------- /contracts/utils/MigrationHelper.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.4; 3 | 4 | import {IBaseRegistrar} from "../ethregistrar/IBaseRegistrar.sol"; 5 | import {INameWrapper} from "../wrapper/INameWrapper.sol"; 6 | import {Controllable} from "../wrapper/Controllable.sol"; 7 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 8 | 9 | contract MigrationHelper is Ownable, Controllable { 10 | IBaseRegistrar public immutable registrar; 11 | INameWrapper public immutable wrapper; 12 | address public migrationTarget; 13 | 14 | error MigrationTargetNotSet(); 15 | 16 | event MigrationTargetUpdated(address indexed target); 17 | 18 | constructor(IBaseRegistrar _registrar, INameWrapper _wrapper) { 19 | registrar = _registrar; 20 | wrapper = _wrapper; 21 | } 22 | 23 | function setMigrationTarget(address target) external onlyOwner { 24 | migrationTarget = target; 25 | emit MigrationTargetUpdated(target); 26 | } 27 | 28 | function migrateNames( 29 | address nameOwner, 30 | uint256[] memory tokenIds, 31 | bytes memory data 32 | ) external onlyController { 33 | if (migrationTarget == address(0)) { 34 | revert MigrationTargetNotSet(); 35 | } 36 | 37 | for (uint256 i = 0; i < tokenIds.length; i++) { 38 | registrar.safeTransferFrom( 39 | nameOwner, 40 | migrationTarget, 41 | tokenIds[i], 42 | data 43 | ); 44 | } 45 | } 46 | 47 | function migrateWrappedNames( 48 | address nameOwner, 49 | uint256[] memory tokenIds, 50 | bytes memory data 51 | ) external onlyController { 52 | if (migrationTarget == address(0)) { 53 | revert MigrationTargetNotSet(); 54 | } 55 | 56 | uint256[] memory amounts = new uint256[](tokenIds.length); 57 | for (uint256 i = 0; i < amounts.length; i++) { 58 | amounts[i] = 1; 59 | } 60 | wrapper.safeBatchTransferFrom( 61 | nameOwner, 62 | migrationTarget, 63 | tokenIds, 64 | amounts, 65 | data 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tasks/save.ts: -------------------------------------------------------------------------------- 1 | import { exec as _exec } from 'child_process' 2 | import { existsSync } from 'fs' 3 | import fs from 'fs/promises' 4 | import { promisify } from 'util' 5 | 6 | import type { NewTaskActionFunction } from 'hardhat/types/tasks' 7 | 8 | import { Artifact } from 'hardhat/types/artifacts' 9 | import { archivedDeploymentPath } from '../hardhat.config.js' 10 | 11 | const exec = promisify(_exec) 12 | 13 | type SaveArgs = { 14 | contract: string 15 | block: string 16 | fullName?: string 17 | } 18 | 19 | const taskSave: NewTaskActionFunction = async ( 20 | { contract, block, fullName }, 21 | hre, 22 | ) => { 23 | // .addPositionalArgument({ 24 | // name: 'contract', 25 | // description: 'The contract to save', 26 | // }) 27 | // .addPositionalArgument({ 28 | // name: 'block', 29 | // description: 'The block number the contract was deployed at', 30 | // }) 31 | // .addPositionalArgument({ 32 | // name: 'fullName', 33 | // description: 34 | // '(Optional) The fully qualified name of the contract (e.g. contracts/resolvers/PublicResolver.sol:PublicResolver)', 35 | // }) 36 | const { networkName } = await hre.network.connect() 37 | const network = networkName 38 | 39 | const artifactReference = fullName || contract 40 | const artifact = await hre.artifacts.readArtifact(artifactReference) 41 | 42 | const archiveName = `${contract}_${network}_${block}` 43 | const archivePath = `${archivedDeploymentPath}/${archiveName}.sol` 44 | 45 | if (existsSync(archivePath)) { 46 | throw new Error('Archive already exists') 47 | } 48 | 49 | const newArtifact: Artifact & { 50 | commitHash: string 51 | treeHash: string 52 | } = { 53 | ...artifact, 54 | contractName: archiveName, 55 | sourceName: archivePath.substring(2), 56 | commitHash: (await exec('git rev-parse HEAD')).stdout.trim(), 57 | treeHash: ( 58 | await exec(`git rev-parse HEAD:${artifact.sourceName}`) 59 | ).stdout.trim(), 60 | } 61 | 62 | await fs.mkdir(archivePath) 63 | await fs.writeFile( 64 | `${archivePath}/${archiveName}.json`, 65 | JSON.stringify(newArtifact, null, 2), 66 | ) 67 | console.log("Archived contract to '" + archivePath + "'") 68 | } 69 | 70 | export default taskSave 71 | -------------------------------------------------------------------------------- /deployments/ropsten/DefaultReverseResolver.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "0x084b1c3C81545d370f3634392De611CaaBFf8148", 3 | "abi": [ 4 | { 5 | "inputs": [ 6 | { 7 | "internalType": "contract ENS", 8 | "name": "ensAddr", 9 | "type": "address" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "nonpayable", 14 | "type": "constructor" 15 | }, 16 | { 17 | "constant": true, 18 | "inputs": [], 19 | "name": "ens", 20 | "outputs": [ 21 | { 22 | "internalType": "contract ENS", 23 | "name": "", 24 | "type": "address" 25 | } 26 | ], 27 | "payable": false, 28 | "stateMutability": "view", 29 | "type": "function" 30 | }, 31 | { 32 | "constant": true, 33 | "inputs": [ 34 | { 35 | "internalType": "bytes32", 36 | "name": "", 37 | "type": "bytes32" 38 | } 39 | ], 40 | "name": "name", 41 | "outputs": [ 42 | { 43 | "internalType": "string", 44 | "name": "", 45 | "type": "string" 46 | } 47 | ], 48 | "payable": false, 49 | "stateMutability": "view", 50 | "type": "function" 51 | }, 52 | { 53 | "constant": false, 54 | "inputs": [ 55 | { 56 | "internalType": "bytes32", 57 | "name": "node", 58 | "type": "bytes32" 59 | }, 60 | { 61 | "internalType": "string", 62 | "name": "_name", 63 | "type": "string" 64 | } 65 | ], 66 | "name": "setName", 67 | "outputs": [], 68 | "payable": false, 69 | "stateMutability": "nonpayable", 70 | "type": "function" 71 | } 72 | ] 73 | } --------------------------------------------------------------------------------