Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Etherscan ABI lookups with Sourcify #1735

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
64 changes: 48 additions & 16 deletions packages/cli/src/command-helpers/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,44 @@ import { withSpinner } from './spinner';

const logger = debugFactory('graph-cli:abi-helpers');

export const loadAbiFromEtherscan = async (
export const loadAbiFromSourcify = async (
ABICtor: typeof ABI,
network: string,
address: string,
): Promise<ABI> =>
await withSpinner(
`Fetching ABI from Etherscan`,
`Failed to fetch ABI from Etherscan`,
`Warnings while fetching ABI from Etherscan`,
`Fetching ABI from Sourcify`,
`Failed to fetch ABI from Sourcify`,
`Warnings while fetching ABI from Sourcify`,
async () => {
const scanApiUrl = getEtherscanLikeAPIUrl(network);
const result = await fetch(`${scanApiUrl}?module=contract&action=getabi&address=${address}`);
const chainId = await getSourcifyChainId(network);
const result = await fetch(`https://repo.sourcify.dev/contracts/full_match/${chainId}/${address}/metadata.json`);
const json = await result.json();

// Etherscan returns a JSON object that has a `status`, a `message` and
// a `result` field. The `status` is '0' in case of errors and '1' in
// case of success
if (json.status === '1') {
return new ABICtor('Contract', undefined, immutable.fromJS(JSON.parse(json.result)));
if (result.ok) {
return new ABICtor('Contract', undefined, immutable.fromJS(json.output.abi));
}
throw new Error('ABI not found, try loading it from a local file');
},
);

export const loadAbiFromEtherscan = async (
ABICtor: typeof ABI,
network: string,
address: string,
): Promise<ABI> =>
await withSpinner(
`Fetching ABI from Sourcify`,
`Failed to fetch ABI from Sourcify`,
`Warnings while fetching ABI from Sourcify`,
async () => {
const json = await fetchMetadataFromSourcify(network, address);

if (json) {
return new ABICtor('Contract', undefined, immutable.fromJS(json.output.abi));
}
throw new Error('ABI not found, try loading it from a local file');
},
Expand All @@ -51,7 +70,7 @@ export const loadContractNameForAddress = async (
await withSpinner(
`Fetching Contract Name`,
`Failed to fetch Contract Name`,
`Warnings while fetching contract name from Etherscan`,
`Warnings while fetching contract name from Sourcify`,
async () => {
return getContractNameForAddress(network, address);
},
Expand Down Expand Up @@ -124,28 +143,28 @@ export const fetchTransactionByHashFromRPC = async (
}
};

export const fetchSourceCodeFromEtherscan = async (
export const fetchMetadataFromSourcify = async (
network: string,
address: string,
): Promise<any> => {
const scanApiUrl = getEtherscanLikeAPIUrl(network);
const chainId = await getSourcifyChainId(network);
const result = await fetch(
`${scanApiUrl}?module=contract&action=getsourcecode&address=${address}`,
`https://repo.sourcify.dev/contracts/full_match/${chainId}/${address}/metadata.json`,
);
const json = await result.json();
if (json.status === '1') {
if (result.ok) {
return json;
}
throw new Error('Failed to fetch contract source code');
throw new Error('Failed to fetch metadata for address');
};

export const getContractNameForAddress = async (
network: string,
address: string,
): Promise<string> => {
try {
const contractSourceCode = await fetchSourceCodeFromEtherscan(network, address);
const contractName = contractSourceCode.result[0].ContractName;
const json = await fetchMetadataFromSourcify(network, address);
const contractName = Object.values(json.settings.compilationTarget)[0] as string;
logger('Successfully getContractNameForAddress. contractName: %s', contractName);
return contractName;
} catch (error) {
Expand Down Expand Up @@ -200,6 +219,19 @@ export const loadAbiFromBlockScout = async (
},
);

const getSourcifyChainId = async (network: string) => {
const result = await fetch('https://sourcify.dev/server/chains');
const json = await result.json();

// Can fail if network name doesn't follow https://chainlist.org name convention
const match = json.find((e: any) => e.name.toLowerCase().includes(network.replace('-', ' ')));

if (match)
return match.chainId;
else
throw new Error(`Could not find chain id for "${network}"`);
};

const getEtherscanLikeAPIUrl = (network: string) => {
switch (network) {
case 'mainnet':
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Args, Command, Flags } from '@oclif/core';
import { CLIError } from '@oclif/core/lib/errors';
import {
loadAbiFromBlockScout,
loadAbiFromEtherscan,
loadAbiFromSourcify,
loadContractNameForAddress,
loadStartBlockForContract,
} from '../command-helpers/abi';
Expand Down Expand Up @@ -96,7 +96,7 @@ export default class AddCommand extends Command {
} else if (network === 'poa-core') {
ethabi = await loadAbiFromBlockScout(EthereumABI, network, address);
} else {
ethabi = await loadAbiFromEtherscan(EthereumABI, network, address);
ethabi = await loadAbiFromSourcify(EthereumABI, network, address);
}

try {
Expand Down
28 changes: 11 additions & 17 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { filesystem, prompt, system } from 'gluegun';
import { Args, Command, Flags, ux } from '@oclif/core';
import {
loadAbiFromBlockScout,
loadAbiFromEtherscan,
loadAbiFromSourcify,
loadContractNameForAddress,
loadStartBlockForContract,
} from '../command-helpers/abi';
Expand Down Expand Up @@ -292,7 +292,7 @@ export default class InitCommand extends Command {
if (network === 'poa-core') {
abi = await loadAbiFromBlockScout(ABI, network, fromContract);
} else {
abi = await loadAbiFromEtherscan(ABI, network, fromContract);
abi = await loadAbiFromSourcify(ABI, network, fromContract);
}
} catch (e) {
process.exitCode = 1;
Expand Down Expand Up @@ -544,7 +544,7 @@ async function processInitForm(
}
| undefined
> {
let abiFromEtherscan: EthereumABI | undefined = undefined;
let abiFromSourcify: EthereumABI | undefined = undefined;

try {
const { protocol } = await prompt.ask<{ protocol: ProtocolName }>({
Expand Down Expand Up @@ -705,17 +705,11 @@ async function processInitForm(

const ABI = protocolInstance.getABI();

// Try loading the ABI from Etherscan, if none was provided
// Try loading the ABI from Sourcify, if none was provided
if (protocolInstance.hasABIs() && !initAbi) {
if (network === 'poa-core') {
abiFromEtherscan = await retryWithPrompt(() =>
loadAbiFromBlockScout(ABI, network, value),
);
} else {
abiFromEtherscan = await retryWithPrompt(() =>
loadAbiFromEtherscan(ABI, network, value),
);
}
abiFromSourcify = await retryWithPrompt(() =>
loadAbiFromSourcify(ABI, network, value),
);
}
// If startBlock is not set, try to load it.
if (!initStartBlock) {
Expand Down Expand Up @@ -765,11 +759,11 @@ async function processInitForm(
skip: () =>
!protocolInstance.hasABIs() ||
initFromExample !== undefined ||
abiFromEtherscan !== undefined ||
abiFromSourcify !== undefined ||
isSubstreams ||
!!initAbiPath,
validate: async (value: string) => {
if (initFromExample || abiFromEtherscan || !protocolInstance.hasABIs()) {
if (initFromExample || abiFromSourcify || !protocolInstance.hasABIs()) {
return true;
}

Expand All @@ -791,7 +785,7 @@ async function processInitForm(
}
},
result: async (value: string) => {
if (initFromExample || abiFromEtherscan || !protocolInstance.hasABIs()) {
if (initFromExample || abiFromSourcify || !protocolInstance.hasABIs()) {
return null;
}
const ABI = protocolInstance.getABI();
Expand Down Expand Up @@ -853,7 +847,7 @@ async function processInitForm(
]);

return {
abi: abiFromEtherscan || abiFromFile,
abi: abiFromSourcify || abiFromFile,
protocolInstance,
subgraphName,
directory,
Expand Down
Loading