|
| 1 | +import { prompt } from 'gluegun'; |
| 2 | +import { describe, expect, it, vi } from 'vitest'; |
| 3 | +import EthereumABI from '../protocols/ethereum/abi.js'; |
| 4 | +import { ContractService } from './contracts.js'; |
| 5 | +import { checkForProxy } from './proxy.js'; |
| 6 | +import { loadRegistry } from './registry.js'; |
| 7 | + |
| 8 | +// Mock gluegun's prompt |
| 9 | +vi.mock('gluegun', async () => { |
| 10 | + const actual = await vi.importActual('gluegun'); |
| 11 | + return { |
| 12 | + ...actual, |
| 13 | + prompt: { |
| 14 | + confirm: vi.fn().mockResolvedValue(true), |
| 15 | + }, |
| 16 | + }; |
| 17 | +}); |
| 18 | + |
| 19 | +describe('Proxy detection', async () => { |
| 20 | + const NETWORK = 'mainnet'; |
| 21 | + const registry = await loadRegistry(); |
| 22 | + const contractService = new ContractService(registry); |
| 23 | + |
| 24 | + interface ProxyTestCase { |
| 25 | + name: string; |
| 26 | + type: string; |
| 27 | + address: string; |
| 28 | + implementationAddress: string | null; |
| 29 | + expectedFunctions: string[]; |
| 30 | + } |
| 31 | + |
| 32 | + const testCases: ProxyTestCase[] = [ |
| 33 | + { |
| 34 | + name: 'USDC', |
| 35 | + type: 'EIP-1967 Upgradeable', |
| 36 | + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', |
| 37 | + implementationAddress: '0x43506849d7c04f9138d1a2050bbf3a0c054402dd', |
| 38 | + expectedFunctions: ['mint(address,uint256)', 'configureMinter(address,uint256)'], |
| 39 | + }, |
| 40 | + { |
| 41 | + name: 'BUSD', |
| 42 | + type: 'OpenZeppelin Unstructured Storage', |
| 43 | + address: '0x4Fabb145d64652a948d72533023f6E7A623C7C53', |
| 44 | + implementationAddress: '0x2A3F1A37C04F82aA274f5353834B2d002Db91015', |
| 45 | + expectedFunctions: ['reclaimBUSD()', 'claimOwnership()'], |
| 46 | + }, |
| 47 | + { |
| 48 | + name: 'Gelato', |
| 49 | + type: 'EIP-2535 Diamond Pattern (not supported)', |
| 50 | + address: '0x3caca7b48d0573d793d3b0279b5f0029180e83b6', |
| 51 | + implementationAddress: null, |
| 52 | + expectedFunctions: [], |
| 53 | + }, |
| 54 | + ]; |
| 55 | + |
| 56 | + for (const testCase of testCases) { |
| 57 | + it(`should handle ${testCase.name} ${testCase.type} Proxy`, async () => { |
| 58 | + const abi = await contractService.getABI(EthereumABI, NETWORK, testCase.address); |
| 59 | + expect(abi).toBeDefined(); |
| 60 | + |
| 61 | + const { implementationAddress, implementationAbi } = await checkForProxy( |
| 62 | + contractService, |
| 63 | + NETWORK, |
| 64 | + testCase.address, |
| 65 | + abi!, |
| 66 | + ); |
| 67 | + |
| 68 | + expect(implementationAddress === testCase.implementationAddress); |
| 69 | + |
| 70 | + const implFunctions = implementationAbi?.callFunctionSignatures(); |
| 71 | + for (const expectedFunction of testCase.expectedFunctions) { |
| 72 | + expect(implFunctions).toContain(expectedFunction); |
| 73 | + } |
| 74 | + }); |
| 75 | + } |
| 76 | + |
| 77 | + it('should handle when user declines to use implementation', async () => { |
| 78 | + vi.mocked(prompt.confirm).mockResolvedValueOnce(false); |
| 79 | + const abi = await contractService.getABI(EthereumABI, NETWORK, testCases[0].address); |
| 80 | + expect(abi).toBeDefined(); |
| 81 | + |
| 82 | + const { implementationAddress, implementationAbi } = await checkForProxy( |
| 83 | + contractService, |
| 84 | + NETWORK, |
| 85 | + testCases[0].address, |
| 86 | + abi!, |
| 87 | + ); |
| 88 | + expect(implementationAddress).toBeNull(); |
| 89 | + expect(implementationAbi).toBeNull(); |
| 90 | + }); |
| 91 | +}); |
0 commit comments