11package analyzer
22
33import (
4+ "context"
45 "encoding/json"
56 "errors"
67 "fmt"
78 "math/big"
9+ "strings"
810
911 "github.com/ethereum/go-ethereum/accounts/abi"
12+ "github.com/ethereum/go-ethereum/common"
1013
1114 chainsel "github.com/smartcontractkit/chain-selectors"
1215 "github.com/smartcontractkit/mcms/types"
16+
17+ "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
18+ "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
19+ "github.com/smartcontractkit/chainlink-deployments-framework/experimental/analyzer/pointer"
1320)
1421
15- func AnalyzeEVMTransactions (ctx ProposalContext , chainSelector uint64 , txs []types.Transaction ) ([]* DecodedCall , error ) {
22+ // EIP1967TargetContractStorageSlot is the storage slot for EIP-1967 proxy implementation address
23+ // keccak256("eip1967.proxy.implementation") - 1
24+ var EIP1967TargetContractStorageSlot = common .HexToHash ("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" )
25+
26+ func AnalyzeEVMTransactions (ctx context.Context , proposalCtx ProposalContext , env deployment.Environment , chainSelector uint64 , txs []types.Transaction ) ([]* DecodedCall , error ) {
1627 chainFamily , err := chainsel .GetSelectorFamily (chainSelector )
1728 if err != nil {
1829 return nil , fmt .Errorf ("failed to get chain family for selector %v: %w" , chainSelector , err )
@@ -25,7 +36,7 @@ func AnalyzeEVMTransactions(ctx ProposalContext, chainSelector uint64, txs []typ
2536
2637 decodedTxs := make ([]* DecodedCall , len (txs ))
2738 for i , op := range txs {
28- decodedTxs [i ], _ , _ , err = AnalyzeEVMTransaction (ctx , decoder , chainSelector , op )
39+ decodedTxs [i ], _ , _ , err = AnalyzeEVMTransaction (ctx , proposalCtx , env , decoder , chainSelector , op )
2940 if err != nil {
3041 return nil , fmt .Errorf ("failed to analyze transaction %d: %w" , i , err )
3142 }
@@ -35,14 +46,14 @@ func AnalyzeEVMTransactions(ctx ProposalContext, chainSelector uint64, txs []typ
3546}
3647
3748func AnalyzeEVMTransaction (
38- ctx ProposalContext , decoder * EVMTxCallDecoder , chainSelector uint64 , mcmsTx types.Transaction ,
49+ ctx context. Context , proposalCtx ProposalContext , env deployment. Environment , decoder * EVMTxCallDecoder , chainSelector uint64 , mcmsTx types.Transaction ,
3950) (* DecodedCall , * abi.ABI , string , error ) {
4051 // Check if this is a native token transfer
4152 if isNativeTokenTransfer (mcmsTx ) {
4253 return createNativeTransferCall (mcmsTx ), nil , "" , nil
4354 }
4455
45- evmRegistry := ctx .GetEVMRegistry ()
56+ evmRegistry := proposalCtx .GetEVMRegistry ()
4657 if evmRegistry == nil {
4758 return nil , nil , "" , errors .New ("EVM registry is not available" )
4859 }
@@ -53,6 +64,19 @@ func AnalyzeEVMTransaction(
5364
5465 analyzeResult , err := decoder .Decode (mcmsTx .To , abi , mcmsTx .Data )
5566 if err != nil {
67+ // Check if this is a "method not found" error - could be a proxy with wrong ABI
68+ if isMethodNotFoundError (err ) {
69+ // Try EIP-1967 proxy fallback: query implementation slot and retry with implementation ABI
70+ fallbackResult , fallbackABI , fallbackABIStr , fallbackErr := tryEIP1967ProxyFallback (
71+ ctx , proposalCtx , env , chainSelector , mcmsTx .To , mcmsTx .Data , decoder ,
72+ )
73+ if fallbackErr == nil {
74+ // Successfully decoded with implementation ABI
75+ return fallbackResult , fallbackABI , fallbackABIStr , nil
76+ }
77+ // Fallback failed, return original error
78+ }
79+
5680 return nil , nil , "" , fmt .Errorf ("error analyzing operation: %w" , err )
5781 }
5882
@@ -117,3 +141,134 @@ func createNativeTransferCall(mcmsTx types.Transaction) *DecodedCall {
117141 Outputs : []NamedField {},
118142 }
119143}
144+
145+ // isMethodNotFoundError checks if an error indicates a method not found in ABI.
146+ // This typically happens when trying to decode a transaction with the wrong ABI,
147+ // such as using a proxy ABI when the transaction is actually calling the implementation.
148+ func isMethodNotFoundError (err error ) bool {
149+ if err == nil {
150+ return false
151+ }
152+ errStr := strings .ToLower (err .Error ())
153+
154+ return strings .Contains (errStr , "no method with id" ) ||
155+ strings .Contains (errStr , "method not found" ) ||
156+ strings .Contains (errStr , "invalid method id" )
157+ }
158+
159+ // queryEIP1967ImplementationSlot queries the EIP-1967 implementation storage slot
160+ // and returns the implementation address if found.
161+ func queryEIP1967ImplementationSlot (ctx context.Context , evmChain evm.Chain , proxyAddress string ) (common.Address , error ) {
162+ storageValue , err := evmChain .Client .StorageAt (ctx , common .HexToAddress (proxyAddress ), EIP1967TargetContractStorageSlot , nil )
163+ if err != nil {
164+ return common.Address {}, fmt .Errorf ("failed to read EIP-1967 storage slot: %w" , err )
165+ }
166+
167+ // Extract address from storage (last 20 bytes, right-padded)
168+ implAddress := common .BytesToAddress (storageValue )
169+
170+ return implAddress , nil
171+ }
172+
173+ // tryEIP1967ProxyFallback attempts to decode using implementation ABI if address is EIP-1967 proxy.
174+ // This function orchestrates the full fallback flow:
175+ // 1. Gets EVM chain from environment
176+ // 2. Queries EIP-1967 implementation slot
177+ // 3. Looks up implementation TypeAndVersion from address book
178+ // 4. Gets implementation ABI
179+ // 5. Retries decode with implementation ABI
180+ func tryEIP1967ProxyFallback (
181+ ctx context.Context ,
182+ proposalCtx ProposalContext ,
183+ env deployment.Environment ,
184+ chainSelector uint64 ,
185+ proxyAddress string ,
186+ txData []byte ,
187+ decoder * EVMTxCallDecoder ,
188+ ) (* DecodedCall , * abi.ABI , string , error ) {
189+ // Lazily get EVM chain from environment (only when fallback is needed)
190+ evmChains := env .BlockChains .EVMChains ()
191+ evmChain , exists := evmChains [chainSelector ]
192+ if ! exists {
193+ return nil , nil , "" , fmt .Errorf ("EVM chain not available for selector %d" , chainSelector )
194+ }
195+
196+ // Query EIP-1967 implementation slot
197+ implAddress , err := queryEIP1967ImplementationSlot (ctx , evmChain , proxyAddress )
198+ if err != nil {
199+ return nil , nil , "" , fmt .Errorf ("failed to query EIP-1967 implementation: %w" , err )
200+ }
201+
202+ // Check if implementation address is zero (not an EIP-1967 proxy)
203+ if implAddress == (common.Address {}) || implAddress == common .HexToAddress ("0x0" ) {
204+ return nil , nil , "" , errors .New ("EIP-1967 slot contains zero address (not a proxy)" )
205+ }
206+
207+ // Look up implementation TypeAndVersion from address book (checking both ExistingAddresses and DataStore)
208+ implAddressStr := implAddress .Hex ()
209+ addressesByChain , err := getAllAddressesByChain (env )
210+ if err != nil {
211+ return nil , nil , "" , fmt .Errorf ("failed to get addresses: %w" , err )
212+ }
213+
214+ addressesForChain , ok := addressesByChain [chainSelector ]
215+ if ! ok {
216+ return nil , nil , "" , fmt .Errorf ("no addresses found for chain selector %d" , chainSelector )
217+ }
218+
219+ implTypeAndVersion , ok := addressesForChain [implAddressStr ]
220+ if ! ok {
221+ return nil , nil , "" , fmt .Errorf ("implementation address %s not found in address book or datastore for chain selector %d" , implAddressStr , chainSelector )
222+ }
223+
224+ // Get implementation ABI using existing registry method
225+ evmRegistry := proposalCtx .GetEVMRegistry ()
226+ if evmRegistry == nil {
227+ return nil , nil , "" , errors .New ("EVM registry is not available" )
228+ }
229+
230+ implABI , implABIStr , err := evmRegistry .GetABIByType (implTypeAndVersion )
231+ if err != nil {
232+ return nil , nil , "" , fmt .Errorf ("failed to get ABI for implementation %v: %w" , implTypeAndVersion , err )
233+ }
234+
235+ // Retry decode with implementation ABI
236+ decodedResult , err := decoder .Decode (proxyAddress , implABI , txData )
237+ if err != nil {
238+ return nil , nil , "" , fmt .Errorf ("failed to decode with implementation ABI: %w" , err )
239+ }
240+
241+ return decodedResult , implABI , implABIStr , nil
242+ }
243+
244+ // getAllAddressesByChain retrieves addresses from both ExistingAddresses and DataStore,
245+ // merging them into a single map.
246+ func getAllAddressesByChain (env deployment.Environment ) (deployment.AddressesByChain , error ) {
247+ // Start with addresses from ExistingAddresses
248+ addressesByChain , err := env .ExistingAddresses .Addresses () //nolint:staticcheck
249+ if err != nil {
250+ return nil , fmt .Errorf ("failed to get addresses from ExistingAddresses: %w" , err )
251+ }
252+
253+ // Fetch addresses from DataStore
254+ dataStoreAddresses , err := env .DataStore .Addresses ().Fetch ()
255+ if err != nil {
256+ return nil , fmt .Errorf ("failed to fetch addresses from DataStore: %w" , err )
257+ }
258+
259+ // Merge DataStore addresses into the map
260+ for _ , address := range dataStoreAddresses {
261+ chainAddresses , exists := addressesByChain [address .ChainSelector ]
262+ if ! exists {
263+ chainAddresses = map [string ]deployment.TypeAndVersion {}
264+ }
265+ chainAddresses [address .Address ] = deployment.TypeAndVersion {
266+ Type : deployment .ContractType (address .Type ),
267+ Version : pointer .DerefOrEmpty (address .Version ),
268+ Labels : deployment .NewLabelSet (address .Labels .List ()... ),
269+ }
270+ addressesByChain [address .ChainSelector ] = chainAddresses
271+ }
272+
273+ return addressesByChain , nil
274+ }
0 commit comments