From 902ab4055ac2cae6a9c3fa5589ff027518eaa45d Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Sun, 29 Sep 2024 12:29:48 -0400 Subject: [PATCH] autoload: AutoloadResult{ContractResult} and AutoloadConfig{loadContractResult} Use ABILoader.getContract instead of loadABI when loadContractResult is set. --- src/__tests__/auto.test.ts | 20 +++++++++++++++-- src/auto.ts | 45 ++++++++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/__tests__/auto.test.ts b/src/__tests__/auto.test.ts index c7a231f..d8a2f73 100644 --- a/src/__tests__/auto.test.ts +++ b/src/__tests__/auto.test.ts @@ -71,11 +71,27 @@ online_test('autoload non-contract', async ({ provider, env }) => { expect(abi).toStrictEqual([]); }); -online_test('autoload verified etherscan', async ({ provider, env }) => { +online_test('autoload verified multi', async ({ provider, env }) => { const address = "0x8f8ef111b67c04eb1641f5ff19ee54cda062f163"; // Uniswap v3 pool, verified on Etherscan and Sourcify const result = await autoload(address, { provider: provider, ...whatsabi.loaders.defaultsWithEnv(env), }); - expect(result.abiLoadedFrom?.name).toBeTruthy() + expect(result.abiLoadedFrom?.name).toBeTruthy(); +}); + +online_test('autoload loadContractResult verified etherscan', async ({ provider, env }) => { + const address = "0xc3d688b66703497daa19211eedff47f25384cdc3"; // Compound USDC proxy + const result = await autoload(address, { + provider: provider, + loadContractResult: true, + followProxies: false, + abiLoader: new whatsabi.loaders.EtherscanABILoader({ apiKey: env.ETHERSCAN_API_KEY }), + }); + expect(result.abiLoadedFrom?.name).toBe("EtherscanABILoader"); + expect(result.contractResult?.ok).toBeTruthy(); + expect(result.contractResult?.name).toBe("TransparentUpgradeableProxy"); + expect(result.contractResult?.compilerVersion).toBe("v0.8.15+commit.e14f2714"); + expect(result.contractResult?.loaderResult?.Proxy).toBe("1"); + expect(result.contractResult?.loaderResult?.Implementation).toBe("0x8a807d39f1d642dd8c12fe2e249fe97847f01ba0"); }); diff --git a/src/auto.ts b/src/auto.ts index f10be5f..11f6ff1 100644 --- a/src/auto.ts +++ b/src/auto.ts @@ -3,7 +3,7 @@ import { Fragment, FunctionFragment } from "ethers"; import type { AnyProvider } from "./providers.js"; import type { ABI, ABIFunction } from "./abi.js"; import { type ProxyResolver, DiamondProxyResolver } from "./proxies.js"; -import type { ABILoader, SignatureLookup } from "./loaders.js"; +import type { ABILoader, SignatureLookup, ContractResult } from "./loaders.js"; import * as errors from "./errors.js"; import { CompatibleProvider } from "./providers.js"; @@ -28,6 +28,9 @@ export type AutoloadResult = { /** Whether the `abi` is loaded from a verified source */ abiLoadedFrom?: ABILoader; + /** Full contract metadata result, only included if {@link AutoloadConfig.loadContractResult} is true. */ + contractResult?: ContractResult; + /** List of resolveable proxies detected in the contract */ proxies: ProxyResolver[], @@ -82,6 +85,15 @@ export type AutoloadConfig = { */ followProxies?: boolean; + + /** + * Load full contract metadata result, include it in {@link AutoloadResult.ContractResult} if successful. + * + * This changes the behaviour of autoload to use {@link ABILoader.getContract} instead of {@link ABILoader.loadABI}, + * which returns a larger superset result including all of the available verified contract metadata. + */ + loadContractResult?: boolean; + /** * Enable pulling additional metadata from WhatsABI's static analysis, still unreliable * @@ -213,16 +225,27 @@ export async function autoload(address: string, config: AutoloadConfig): Promise } try { - const addresses = Object.keys(facets); - const promises = addresses.map(addr => loader.loadABI(addr)); - const results = await Promise.all(promises); - const abis = Object.fromEntries(results.map((abi, i) => { - return [addresses[i], abi]; - })); - result.abi = pruneFacets(facets, abis); - if (result.abi.length > 0) { - result.abiLoadedFrom = abiLoadedFrom; - return result; + if (config.loadContractResult) { + const contractResult = await loader.getContract(address); + if (contractResult) { + result.contractResult = contractResult; + result.abi = contractResult.abi; + result.abiLoadedFrom = contractResult.loader; + return result; + } + } else { + // Load ABIs of all available facets and merge + const addresses = Object.keys(facets); + const promises = addresses.map(addr => loader.loadABI(addr)); + const results = await Promise.all(promises); + const abis = Object.fromEntries(results.map((abi, i) => { + return [addresses[i], abi]; + })); + result.abi = pruneFacets(facets, abis); + if (result.abi.length > 0) { + result.abiLoadedFrom = abiLoadedFrom; + return result; + } } } catch (error: any) { // TODO: Catch useful errors