Skip to content

Commit

Permalink
Fetch ERC-1967 proxy implementation in generate CLI command
Browse files Browse the repository at this point in the history
  • Loading branch information
yivlad committed Oct 2, 2024
1 parent 924cc93 commit 7ea3855
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 5 deletions.
129 changes: 124 additions & 5 deletions packages/cli/src/plugins/blockExplorer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,79 @@
import { camelCase } from 'change-case'
import type { Address } from 'viem'
import {
http,
type Address,
createClient,
isAddress,
isAddressEqual,
slice,
zeroAddress,
} from 'viem'
import { getStorageAt } from 'viem/actions'
import { z } from 'zod'

import type { ContractConfig } from '../config.js'
import { fromZodError } from '../errors.js'
import type { Compute } from '../types.js'
import { fetch } from './fetch.js'

export const rpcUrls = {
[1]: 'https://cloudflare-eth.com',

[5]: 'https://rpc.ankr.com/eth_goerli',

[10]: 'https://mainnet.optimism.io',

[56]: 'https://rpc.ankr.com/bsc',

[97]: 'https://data-seed-prebsc-1-s1.bnbchain.org:8545',

[100]: 'https://rpc.gnosischain.com',

[128]: 'undefined',

[137]: 'https://polygon-rpc.com',

[250]: 'https://rpc.ankr.com/fantom',

[252]: 'https://rpc.frax.com',

[256]: 'undefined',

[420]: 'https://goerli.optimism.io',

[2522]: 'https://rpc.testnet.frax.com',

[4002]: 'https://rpc.testnet.fantom.network',

[8453]: 'https://mainnet.base.org',

[17000]: 'https://ethereum-holesky-rpc.publicnode.com',

[42161]: 'https://arb1.arbitrum.io/rpc',

[42220]: 'https://forno.celo.org',

[43113]: 'https://api.avax-test.network/ext/bc/C/rpc',

[43114]: 'https://api.avax.network/ext/bc/C/rpc',

[44787]: 'https://alfajores-forno.celo-testnet.org',

[80001]: 'https://rpc.ankr.com/polygon_mumbai',

[81457]: 'https://rpc.blast.io',

[84532]: 'https://sepolia.base.org',

[421613]: 'https://goerli-rollup.arbitrum.io/rpc',

[421614]: 'https://sepolia-rollup.arbitrum.io/rpc',

[11155111]: 'https://rpc.sepolia.org',

[11155420]: 'https://sepolia.optimism.io',
}

export type BlockExplorerConfig = {
/**
* API key for block explorer. Appended to the request URL as query param `&apikey=${apiKey}`.
Expand Down Expand Up @@ -38,6 +105,10 @@ export type BlockExplorerConfig = {
* Name of source.
*/
name?: ContractConfig['name'] | undefined
/**
* Chain id to use for fetching on-chain info of contract (e.g. implementation address)
*/
chainId?: number | undefined
}

const BlockExplorerResponse = z.discriminatedUnion('status', [
Expand Down Expand Up @@ -69,6 +140,7 @@ export function blockExplorer(config: BlockExplorerConfig) {
return Object.values(address)[0]!
},
name = 'Block Explorer',
chainId,
} = config

return fetch({
Expand All @@ -88,13 +160,60 @@ export function blockExplorer(config: BlockExplorerConfig) {
if (parsed.data.status === '0') throw new Error(parsed.data.result)
return parsed.data.result
},
request({ address }) {
async request({ address }) {
if (!address) throw new Error('address is required')
const normalizedAddress = getAddress({ address })
const makeUrl = (address: Address) =>
`${baseUrl}?module=contract&action=getabi&address=${address}${apiKey ? `&apikey=${apiKey}` : ''}`

const implementationAddress = await getImplementationAddress({
address: normalizedAddress,
chainId,
})
if (implementationAddress) {
return {
url: makeUrl(implementationAddress),
}
}

return {
url: `${baseUrl}?module=contract&action=getabi&address=${getAddress({
address,
})}${apiKey ? `&apikey=${apiKey}` : ''}`,
url: makeUrl(normalizedAddress),
}
},
})
}

async function getImplementationAddress({
address,
chainId,
}: { address: Address; chainId: number | undefined }): Promise<
Address | undefined
> {
if (!chainId) return undefined

const rpcUrl: string | undefined = (rpcUrls as any)[chainId]
if (!rpcUrl) return undefined

const client = createClient({
transport: http(rpcUrl),
})

// ERC-1967 Implementation Slot
const implementationSlot =
'0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'
const implementationSlotContent = await getStorageAt(client, {
address,
slot: implementationSlot,
})

if (!implementationSlotContent) return undefined

const implementationAddress = slice(implementationSlotContent, 12)
if (
!isAddress(implementationAddress) ||
isAddressEqual(implementationAddress, zeroAddress)
)
return undefined

return implementationAddress
}
1 change: 1 addition & 0 deletions packages/cli/src/plugins/etherscan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,6 @@ export function etherscan<chainId extends ChainId>(
return contractAddress
},
name: 'Etherscan',
chainId,
})
}

0 comments on commit 7ea3855

Please sign in to comment.