Skip to content

Commit 588738b

Browse files
committed
add support for proxy contracts
1 parent de2e491 commit 588738b

File tree

2 files changed

+83
-2
lines changed

2 files changed

+83
-2
lines changed

packages/cli/src/command-helpers/contracts.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,51 @@ export class ContractService {
206206
return null;
207207
}
208208

209+
async getProxyImplementation(networkId: string, address: string) {
210+
const urls = this.getRpcUrls(networkId);
211+
if (!urls.length) {
212+
throw new Error(`No JSON-RPC available for ${networkId} in the registry`);
213+
}
214+
215+
const EIP_1967_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';
216+
const OPEN_ZEPPELIN_SLOT = '0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3';
217+
const getStorageAt = async (url: string, slot: string) => {
218+
const response = await fetch(url, {
219+
method: 'POST',
220+
headers: { 'Content-Type': 'application/json' },
221+
body: JSON.stringify({
222+
jsonrpc: '2.0',
223+
method: 'eth_getStorageAt',
224+
params: [address, slot, 'latest'],
225+
id: 1,
226+
}),
227+
});
228+
const json = await response.json();
229+
if (json?.result) {
230+
const impl = '0x' + json.result.slice(-40);
231+
if (impl !== '0x0000000000000000000000000000000000000000') {
232+
return impl;
233+
}
234+
}
235+
return null;
236+
};
237+
238+
for (const url of urls) {
239+
for (const slot of [EIP_1967_SLOT, OPEN_ZEPPELIN_SLOT]) {
240+
try {
241+
const impl = await getStorageAt(url, slot);
242+
if (impl) {
243+
return impl;
244+
}
245+
} catch (error) {
246+
logger(`Failed to fetch proxy implementation from ${url}: ${error}`);
247+
}
248+
}
249+
}
250+
251+
throw new Error(`JSON-RPC is unreachable`);
252+
}
253+
209254
private async fetchTransactionByHash(networkId: string, txHash: string) {
210255
const urls = this.getRpcUrls(networkId);
211256
if (!urls.length) {

packages/cli/src/commands/init.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,7 @@ async function processInitForm(
635635
}
636636

637637
// If ABI is not provided, try to fetch it from Etherscan API
638+
let implAddress: string | undefined = undefined;
638639
if (protocolInstance.hasABIs() && !initAbi) {
639640
abiFromApi = await retryWithPrompt(() =>
640641
withSpinner(
@@ -647,15 +648,50 @@ async function processInitForm(
647648
initDebugger.extend('processInitForm')("abiFromEtherscan len: '%s'", abiFromApi?.name);
648649
} else {
649650
abiFromApi = initAbi;
651+
const isProxy =
652+
abiFromApi?.callFunctions().some(entry => entry.get('name') === 'implementation') ??
653+
false;
654+
initDebugger.extend('processInitForm')('isProxy: %O', isProxy);
655+
if (isProxy) {
656+
const impl = await retryWithPrompt(() =>
657+
withSpinner(
658+
'Fetching proxy implementation address...',
659+
'Failed to fetch proxy implementation address',
660+
'Warning fetching proxy implementation address',
661+
() => contractService.getProxyImplementation(network.id, address),
662+
),
663+
);
664+
initDebugger.extend('processInitForm')("proxyImplementation: '%s'", impl);
665+
if (impl) {
666+
const useImplementation = await prompt.confirm(
667+
`Proxy contract detected. Index implementation contract at ${impl}?`,
668+
true,
669+
);
670+
671+
if (useImplementation) {
672+
implAddress = impl;
673+
abiFromApi = await retryWithPrompt(() =>
674+
withSpinner(
675+
'Fetching implementation contract ABI...',
676+
'Failed to fetch implementation ABI',
677+
'Warning fetching implementation ABI',
678+
() =>
679+
contractService.getABI(protocolInstance.getABI(), network.id, implAddress!),
680+
),
681+
);
682+
}
683+
}
684+
}
650685
}
686+
651687
// If startBlock is not provided, try to fetch it from Etherscan API
652688
if (!initStartBlock) {
653689
startBlock = await retryWithPrompt(() =>
654690
withSpinner(
655691
'Fetching start block from contract API...',
656692
'Failed to fetch start block',
657693
'Warning fetching start block',
658-
() => contractService.getStartBlock(network.id, address),
694+
() => contractService.getStartBlock(network.id, implAddress ?? address),
659695
),
660696
);
661697
initDebugger.extend('processInitForm')("startBlockFromEtherscan: '%s'", startBlock);
@@ -668,7 +704,7 @@ async function processInitForm(
668704
'Fetching contract name from contract API...',
669705
'Failed to fetch contract name',
670706
'Warning fetching contract name',
671-
() => contractService.getContractName(network.id, address),
707+
() => contractService.getContractName(network.id, implAddress ?? address),
672708
),
673709
);
674710
initDebugger.extend('processInitForm')("contractNameFromEtherscan: '%s'", contractName);

0 commit comments

Comments
 (0)