diff --git a/packages/cli/abis/DotnsRootGateway.json b/packages/cli/abis/DotnsRootGateway.json new file mode 100644 index 0000000..b933d0d --- /dev/null +++ b/packages/cli/abis/DotnsRootGateway.json @@ -0,0 +1,87 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "registry", + "type": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "execute", + "inputs": [ + { + "name": "targets", + "type": "address[]" + }, + { + "name": "payloads", + "type": "bytes[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "protocolRegistry", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "RootCallForwarded", + "inputs": [ + { + "name": "target", + "type": "address", + "indexed": true + }, + { + "name": "selector", + "type": "bytes4", + "indexed": true + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "NotRoot", + "inputs": [] + }, + { + "type": "error", + "name": "TargetNotProtocol", + "inputs": [ + { + "name": "target", + "type": "address" + } + ] + }, + { + "type": "error", + "name": "LengthMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "EmptyBatch", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidRegistry", + "inputs": [] + } +] diff --git a/packages/cli/src/cli/commands/register.ts b/packages/cli/src/cli/commands/register.ts index 7ea3b76..01a2a05 100644 --- a/packages/cli/src/cli/commands/register.ts +++ b/packages/cli/src/cli/commands/register.ts @@ -548,16 +548,19 @@ async function executeGovernanceRegistration( // than silently widening what a grant holder can mint here. validateGovernanceLabel(label); - // registerReserved is gated on the name whitelist (or Root), not on the - // caller's PoP tier. Surfaced as information only: a Root-origin mint skips - // the grant check, so a `false` here is a warning rather than a hard stop. + // registerReserved is gated on the name whitelist (or a call from the + // DotnsRootGateway), not on the caller's PoP tier. Surfaced as information + // only: a gateway-routed governance mint skips the grant check, so a + // `false` here is a warning rather than a hard stop. const granted = await step("Checking name grant", async () => isNameGrantedTo(session.ctx, label, session.caller).catch(() => null), ); if (granted === false) { console.log( chalk.yellow(" ⚠ name is not granted to the caller; ") + - chalk.gray("registerReserved reverts with NameNotGranted unless the origin is Root"), + chalk.gray( + "registerReserved reverts with NameNotGranted unless called through the DotnsRootGateway", + ), ); } else if (granted === true) { console.log(chalk.gray(" granted: ") + chalk.green("yes")); diff --git a/packages/cli/src/commands/accountChecks.ts b/packages/cli/src/commands/accountChecks.ts index adc8bf1..a3482df 100644 --- a/packages/cli/src/commands/accountChecks.ts +++ b/packages/cli/src/commands/accountChecks.ts @@ -29,6 +29,18 @@ export async function checkAccountMapped( // uses to locate the name whitelist (`DotnsConstants.NAME_WHITELIST`). const NAME_WHITELIST_KEY = "0x6e616d6557686974656c69737400000000000000000000000000000000000000"; +// `bytes32("rootGateway")`: the ProtocolRegistry key for `DotnsRootGateway`, the +// contract governance calls must now be routed through (dotns +// fix/root-origin-gates, checked against the branch's finalized tip: the ABI, +// this key, and the gateway-address authorisation model below all match). +// Nothing in this SDK constructs a governance dispatch today, so nothing +// resolves this yet; it is exported so a future governance-dispatch helper +// does not have to duplicate this lookup. Prefer this over hardcoding an +// address: the gateway still has no CREATE3 book entry in either address +// book below, so a hardcoded address would need updating the moment it +// deploys anyway. +const ROOT_GATEWAY_KEY = "0x726f6f7447617465776179000000000000000000000000000000000000000000"; + const PROTOCOL_REGISTRY_GET_ABI = [ { type: "function", @@ -61,6 +73,34 @@ export async function resolveNameWhitelist(ctx: DotnsContext): Promise
return whitelist; } +// Resolves DotnsRootGateway from the protocol registry, the same way +// resolveNameWhitelist resolves the whitelist. Governance calls to the 16 +// gated entry points across the name whitelist, PopRules, the PoP controller, +// and the governance branch of registerReserved (grantName, setReserved, +// reserveLiteName, and so on) must now be dispatched as +// `DotnsRootGateway.execute(targets, payloads)`, with the gateway itself as +// the direct Root callee: it is the only contract that reads Root origin +// directly (via `callerIsRoot`, non-proxied so the check survives), and every +// gated contract downstream instead authorises on +// `msg.sender == protocolRegistry.get(ROOT_GATEWAY)`. Unused today: nothing +// in this SDK builds a Root dispatch. +export async function resolveRootGateway(ctx: DotnsContext): Promise
{ + const registry = await read
( + ctx, + ctx.contracts.DOTNS_REGISTRAR_CONTROLLER, + DOTNS_REGISTRAR_CONTROLLER_ABI, + "protocolRegistry", + [], + ); + const gateway = await read
(ctx, registry, PROTOCOL_REGISTRY_GET_ABI, "get", [ + ROOT_GATEWAY_KEY, + ]); + if (gateway === "0x0000000000000000000000000000000000000000") { + throw new Error("No DotnsRootGateway is configured on this network's protocol registry."); + } + return gateway; +} + // Grant record of `label` on the name whitelist: status, the beneficiary a // grant names, and whether the claim window is open. export async function getNameGrant(ctx: DotnsContext, name: string): Promise { @@ -79,8 +119,9 @@ export async function getNameGrant(ctx: DotnsContext, name: string): Promise