// ENS lookups ask nothing but the RPC endpoint. A resolver can answer that a // name is resolved off-chain, naming a web address to fetch the answer from; // ethers fetches it unless the provider has `disableCcipRead` set, and that // request would carry the name to a server other than the RPC endpoint // (https://git.eeqj.de/sneak/AutistMask/issues/668). With it set, such a name // does not resolve. // // The provider getProvider() builds runs against a fake JSON-RPC node, // answered through ethers' own request hook, which also records the address of // every request ethers makes, the off-chain fetch included. const { FetchRequest, Interface } = require("ethers"); const { getProvider } = require("../src/shared/balances"); const RPC_URL = "https://rpc.example.invalid"; const RESOLVER = "0x5555555555555555555555555555555555555555"; const USER = "0x1111111111111111111111111111111111111111"; const NAME = "offchain.eth"; // The ENS registry's resolver(), and the resolver's functions ethers calls: the // registry names RESOLVER for every name, the resolver says it resolves names // through resolve(), gives NAME as the reverse record of any address, and // answers resolve() with the OffchainLookup error that says the name is // resolved off-chain. const ens = new Interface([ "function resolver(bytes32) view returns (address)", "function supportsInterface(bytes4) view returns (bool)", "function name(bytes32) view returns (string)", "function resolve(bytes, bytes) view returns (bytes)", "error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData)", ]); let asked; // One JSON-RPC answer. Anything but a call to one of the functions above is // refused. function reply(req) { const envelope = { jsonrpc: "2.0", id: req.id }; const call = req.method === "eth_call" ? ens.parseTransaction(req.params[0]) : null; if (call === null) { return { ...envelope, error: { code: -32601, message: "method not found" }, }; } if (call.name === "resolve") { const offchain = ens.encodeErrorResult("OffchainLookup", [ RESOLVER, ["https://gateway.example.invalid/{sender}/{data}.json"], "0x1234", "0x12345678", "0x", ]); return { ...envelope, error: { code: 3, message: "execution reverted", data: offchain }, }; } const answers = { resolver: [RESOLVER], supportsInterface: [true], name: [NAME], }; return { ...envelope, result: ens.encodeFunctionResult(call.name, answers[call.name]), }; } beforeEach(() => { asked = []; FetchRequest.registerGetUrl(async (req) => { asked.push(req.url); // Any other address is the off-chain fetch, answered as not found. if (req.url !== RPC_URL) { return { statusCode: 404, statusMessage: "Not Found", headers: {}, body: null, }; } const body = JSON.parse(Buffer.from(req.body).toString("utf8")); const answer = Array.isArray(body) ? body.map(reply) : reply(body); return { statusCode: 200, statusMessage: "OK", headers: { "content-type": "application/json" }, body: Buffer.from(JSON.stringify(answer)), }; }); }); afterEach(() => { FetchRequest.registerGetUrl(FetchRequest.createGetUrlFunc()); }); test("a name typed into Send that is resolved off-chain does not resolve, and only the RPC endpoint is asked", async () => { const provider = getProvider(RPC_URL, "mainnet"); const [outcome] = await Promise.allSettled([provider.resolveName(NAME)]); expect(asked.filter((url) => url !== RPC_URL)).toEqual([]); expect(outcome).toEqual({ status: "fulfilled", value: null }); }); test("the reverse lookup of a user's address whose name is resolved off-chain finds no name, and only the RPC endpoint is asked", async () => { const provider = getProvider(RPC_URL, "mainnet"); const [outcome] = await Promise.allSettled([provider.lookupAddress(USER)]); expect(asked.filter((url) => url !== RPC_URL)).toEqual([]); expect(outcome).toEqual({ status: "fulfilled", value: null }); });