Skip to content

Commit 6843235

Browse files
committed
add support for proxy contracts
1 parent f6079a6 commit 6843235

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
@@ -151,6 +151,51 @@ export class ContractService {
151151
throw new Error(`Failed to fetch contract name for ${address}`);
152152
}
153153

154+
async getProxyImplementation(networkId: string, address: string) {
155+
const urls = this.getRpcUrls(networkId);
156+
if (!urls.length) {
157+
throw new Error(`No JSON-RPC available for ${networkId} in the registry`);
158+
}
159+
160+
const EIP_1967_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';
161+
const OPEN_ZEPPELIN_SLOT = '0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3';
162+
const getStorageAt = async (url: string, slot: string) => {
163+
const response = await fetch(url, {
164+
method: 'POST',
165+
headers: { 'Content-Type': 'application/json' },
166+
body: JSON.stringify({
167+
jsonrpc: '2.0',
168+
method: 'eth_getStorageAt',
169+
params: [address, slot, 'latest'],
170+
id: 1,
171+
}),
172+
});
173+
const json = await response.json();
174+
if (json?.result) {
175+
const impl = '0x' + json.result.slice(-40);
176+
if (impl !== '0x0000000000000000000000000000000000000000') {
177+
return impl;
178+
}
179+
}
180+
return null;
181+
};
182+
183+
for (const url of urls) {
184+
for (const slot of [EIP_1967_SLOT, OPEN_ZEPPELIN_SLOT]) {
185+
try {
186+
const impl = await getStorageAt(url, slot);
187+
if (impl) {
188+
return impl;
189+
}
190+
} catch (error) {
191+
logger(`Failed to fetch proxy implementation from ${url}: ${error}`);
192+
}
193+
}
194+
}
195+
196+
throw new Error(`JSON-RPC is unreachable`);
197+
}
198+
154199
private async fetchTransactionByHash(networkId: string, txHash: string) {
155200
const urls = this.getRpcUrls(networkId);
156201
if (!urls.length) {

packages/cli/src/commands/init.ts

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

614614
// If ABI is not provided, try to fetch it from Etherscan API
615+
let implAddress: string | undefined = undefined;
615616
if (protocolInstance.hasABIs() && !initAbi) {
616617
abiFromApi = await retryWithPrompt(() =>
617618
withSpinner(
@@ -622,15 +623,50 @@ async function processInitForm(
622623
),
623624
);
624625
initDebugger.extend('processInitForm')("abiFromEtherscan len: '%s'", abiFromApi?.name);
626+
const isProxy =
627+
abiFromApi?.callFunctions().some(entry => entry.get('name') === 'implementation') ??
628+
false;
629+
initDebugger.extend('processInitForm')('isProxy: %O', isProxy);
630+
if (isProxy) {
631+
const impl = await retryWithPrompt(() =>
632+
withSpinner(
633+
'Fetching proxy implementation address...',
634+
'Failed to fetch proxy implementation address',
635+
'Warning fetching proxy implementation address',
636+
() => contractService.getProxyImplementation(network.id, address),
637+
),
638+
);
639+
initDebugger.extend('processInitForm')("proxyImplementation: '%s'", impl);
640+
if (impl) {
641+
const useImplementation = await prompt.confirm(
642+
`Proxy contract detected. Index implementation contract at ${impl}?`,
643+
true,
644+
);
645+
646+
if (useImplementation) {
647+
implAddress = impl;
648+
abiFromApi = await retryWithPrompt(() =>
649+
withSpinner(
650+
'Fetching implementation contract ABI...',
651+
'Failed to fetch implementation ABI',
652+
'Warning fetching implementation ABI',
653+
() =>
654+
contractService.getABI(protocolInstance.getABI(), network.id, implAddress!),
655+
),
656+
);
657+
}
658+
}
659+
}
625660
}
661+
626662
// If startBlock is not provided, try to fetch it from Etherscan API
627663
if (!initStartBlock) {
628664
startBlock = await retryWithPrompt(() =>
629665
withSpinner(
630666
'Fetching start block from contract API...',
631667
'Failed to fetch start block',
632668
'Warning fetching start block',
633-
() => contractService.getStartBlock(network.id, address),
669+
() => contractService.getStartBlock(network.id, implAddress ?? address),
634670
),
635671
);
636672
initDebugger.extend('processInitForm')("startBlockFromEtherscan: '%s'", startBlock);
@@ -643,7 +679,7 @@ async function processInitForm(
643679
'Fetching contract name from contract API...',
644680
'Failed to fetch contract name',
645681
'Warning fetching contract name',
646-
() => contractService.getContractName(network.id, address),
682+
() => contractService.getContractName(network.id, implAddress ?? address),
647683
),
648684
);
649685
initDebugger.extend('processInitForm')("contractNameFromEtherscan: '%s'", contractName);

0 commit comments

Comments
 (0)