Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,12 @@ class RateProviderDataService {
/**
* Fetches upgradeable contracts information. Includes the rate provider if upgradeable.
* @returns An array of upgradeable contracts information.
* `wasUpgraded` is `null` when deployment/upgrade history could not be verified.
*/
public async getUpgradeableContracts(): Promise<
{
address: Address
wasUpgraded: boolean
wasUpgraded: boolean | null
implementation: Address
}[]
> {
Expand All @@ -147,9 +148,29 @@ class RateProviderDataService {
// filter out the contracts that are not proxies
const filteredProxiesList = proxiesWithRateProvider.filter((p) => p.Proxy === '1')

const proxiesWithRateProviderDeploymentInfo = await this.getDeploymentBlocks(
filteredProxiesList.map((p) => p.address),
)
let proxiesWithRateProviderDeploymentInfo: { address: Address; deploymentTxHash: Hex }[]
try {
proxiesWithRateProviderDeploymentInfo = await this.getDeploymentBlocks(
filteredProxiesList.map((p) => p.address),
)
} catch (error) {
// Explorer deployment lookup can fail (e.g. Blockscout HTTP 500 on Base).
// Reviews only need proxy + implementation; leave wasUpgraded unknown.
console.warn(
`Failed to determine upgrade history for ${this.chain.name}; continuing with proxy list only:`,
error instanceof Error ? error.message : error,
)
return filteredProxiesList.map((p) => {
if (!p.Implementation) {
throw new Error(`Implementation is undefined for contract at address ${p.address}`)
}
return {
address: p.address,
wasUpgraded: null,
implementation: p.Implementation,
}
})
}

const publicClient = createPublicClient({
chain: this.chain,
Expand Down Expand Up @@ -205,11 +226,15 @@ class RateProviderDataService {

/**
* Checks if the rate provider is upgradeable.
* @returns True if the rate provider is upgradeable, false otherwise.
* @returns True if an Upgraded event was positively observed.
* @throws If upgrade history could not be verified for any proxy.
*/
public async getIsRateProviderUpgradeable(): Promise<boolean> {
const upgradeableContracts = await this.getUpgradeableContracts()
return upgradeableContracts.some((c) => c.wasUpgraded)
if (upgradeableContracts.some((c) => c.wasUpgraded === null)) {
throw new Error(`Upgrade history could not be verified for one or more proxies on ${this.chain.name}`)
}
return upgradeableContracts.some((c) => c.wasUpgraded === true)
}

/**
Expand Down
47 changes: 47 additions & 0 deletions test/typescript/upgradeableContractsSoftFail.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Address } from 'viem'
import { base } from 'viem/chains'
import RateProviderDataService from '../../src/app'

describe('RateProviderDataService.getUpgradeableContracts soft-fail', () => {
const rateProvider = '0x586d1D28D5F8aeD81dA8AEf7016c4108f7A4ce51' as Address
const implementation = '0xb3201cde011873f604bf3f9be6bf3570ecc29b23' as Address

beforeEach(() => {
process.env.TENDERLY_ACCOUNT_SLUG = 'test-account'
process.env.TENDERLY_PROJECT_SLUG = 'test-project'
process.env.TENDERLY_API_ACCESS_KEY = 'test-key'
process.env.ETHERSCAN_API_KEY = 'test-etherscan-key'
})

afterEach(() => {
jest.restoreAllMocks()
})

it('returns proxies with wasUpgraded=null when deployment lookup fails', async () => {
const service = new RateProviderDataService(rateProvider, base)
service.accessList = {
accessList: [{ address: rateProvider, storageKeys: [] }],
gasUsed: 0n,
}

jest.spyOn(service, 'getContractInfo').mockResolvedValue([
{
address: rateProvider,
Proxy: '1',
ContractName: 'HilbertBTCRateProvider',
ABI: '[]',
Implementation: implementation,
},
])
jest.spyOn(service, 'getDeploymentBlocks').mockRejectedValue(new Error('Blockscout 500'))
jest.spyOn(console, 'warn').mockImplementation(() => undefined)

await expect(service.getUpgradeableContracts()).resolves.toEqual([
{
address: rateProvider,
wasUpgraded: null,
implementation,
},
])
})
})
Loading