1- import { Effect , Either , RequestResolver , Request , Array , pipe , Data , PrimaryKey , Schema , SchemaAST } from 'effect'
1+ import { Effect , Either , RequestResolver , Request , Array , Data , PrimaryKey , Schema , SchemaAST } from 'effect'
22import { ContractABI } from './abi-strategy/request-model.js'
33import { Abi } from 'viem'
44import * as AbiStore from './abi-store.js'
55import * as StrategyExecutorModule from './abi-strategy/strategy-executor.js'
66import { SAFE_MULTISEND_SIGNATURE , SAFE_MULTISEND_ABI , AA_ABIS } from './decoding/constants.js'
7+ import { errorFunctionSignatures , solidityError , solidityPanic } from './helpers/error.js'
78
89interface LoadParameters {
910 readonly chainID : number
@@ -34,7 +35,7 @@ export class EmptyCalldataError extends Data.TaggedError('DecodeError')<
3435class SchemaAbi extends Schema . make < Abi > ( SchemaAST . objectKeyword ) { }
3536class AbiLoader extends Schema . TaggedRequest < AbiLoader > ( ) ( 'AbiLoader' , {
3637 failure : Schema . instanceOf ( MissingABIError ) ,
37- success : SchemaAbi , // Abi
38+ success : Schema . Array ( Schema . Struct ( { abi : SchemaAbi , id : Schema . optional ( Schema . String ) } ) ) ,
3839 payload : {
3940 chainID : Schema . Number ,
4041 address : Schema . String ,
@@ -78,20 +79,19 @@ const setValue = (key: AbiLoader, abi: ContractABI | null) =>
7879 event : key . event ,
7980 signature : key . signature ,
8081 } ,
81- abi == null ? { status : 'not-found' , result : null } : { status : 'success' , result : abi } ,
82+ abi == null
83+ ? {
84+ type : 'func' as const ,
85+ abi : '' ,
86+ address : key . address ,
87+ chainID : key . chainID ,
88+ signature : key . signature || '' ,
89+ status : 'not-found' as const ,
90+ }
91+ : { ...abi , status : 'success' as const } ,
8292 )
8393 } )
8494
85- const getBestMatch = ( abi : ContractABI | null ) => {
86- if ( abi == null ) return null
87-
88- if ( abi . type === 'address' ) {
89- return JSON . parse ( abi . abi ) as Abi
90- }
91-
92- return JSON . parse ( `[${ abi . abi } ]` ) as Abi
93- }
94-
9595/**
9696 * Data loader for contracts abi
9797 *
@@ -146,27 +146,41 @@ export const AbiLoaderRequestResolver = RequestResolver.makeBatched((requests: A
146146 const requestGroups = Array . groupBy ( requests , makeRequestKey )
147147 const uniqueRequests = Object . values ( requestGroups ) . map ( ( group ) => group [ 0 ] )
148148
149- const [ remaining , cachedResults ] = yield * pipe (
150- getMany ( uniqueRequests ) ,
151- Effect . map (
152- Array . partitionMap ( ( resp , i ) => {
153- return resp . status === 'empty'
154- ? Either . left ( uniqueRequests [ i ] )
155- : Either . right ( [ uniqueRequests [ i ] , resp . result ] as const )
156- } ) ,
157- ) ,
158- Effect . orElseSucceed ( ( ) => [ uniqueRequests , [ ] ] as const ) ,
159- )
149+ const allCachedData = yield * getMany ( uniqueRequests )
150+
151+ // Create a map of invalid sources for each request
152+ const invalidSourcesMap = new Map < string , string [ ] > ( )
153+ allCachedData . forEach ( ( abis , i ) => {
154+ const request = uniqueRequests [ i ]
155+ const invalid = abis
156+ . filter ( ( abi ) => abi . status === 'invalid' )
157+ . map ( ( abi ) => abi . source )
158+ . filter ( Boolean ) as string [ ]
159+
160+ invalidSourcesMap . set ( makeRequestKey ( request ) , invalid )
161+ } )
162+
163+ const [ remaining , cachedResults ] = Array . partitionMap ( allCachedData , ( abis , i ) => {
164+ // Filter out invalid/not-found ABIs and check if we have any valid ones
165+ const validAbis = abis . filter ( ( abi ) => abi . status === 'success' )
166+ return validAbis . length === 0
167+ ? Either . left ( uniqueRequests [ i ] )
168+ : Either . right ( [ uniqueRequests [ i ] , validAbis ] as const )
169+ } )
160170
161171 // Resolve ABI from the store
162172 yield * Effect . forEach (
163173 cachedResults ,
164- ( [ request , abi ] ) => {
174+ ( [ request , abis ] ) => {
165175 const group = requestGroups [ makeRequestKey ( request ) ]
166- const bestMatch = getBestMatch ( abi )
167- const result = bestMatch ? Effect . succeed ( bestMatch ) : Effect . fail ( new MissingABIError ( request ) )
168-
169- return Effect . forEach ( group , ( req ) => Request . completeEffect ( req , result ) , { discard : true } )
176+ const allMatches = abis . map ( ( abi ) => {
177+ const parsedAbi = abi . type === 'address' ? ( JSON . parse ( abi . abi ) as Abi ) : ( JSON . parse ( `[${ abi . abi } ]` ) as Abi )
178+ return { abi : parsedAbi , id : abi . id }
179+ } )
180+
181+ return Effect . forEach ( group , ( req ) => Request . completeEffect ( req , Effect . succeed ( allMatches ) ) , {
182+ discard : true ,
183+ } )
170184 } ,
171185 {
172186 discard : true ,
@@ -191,9 +205,14 @@ export const AbiLoaderRequestResolver = RequestResolver.makeBatched((requests: A
191205 const response = yield * Effect . forEach (
192206 remaining ,
193207 ( req ) => {
194- const allAvailableStrategies = Array . prependAll ( strategies . default , strategies [ req . chainID ] ?? [ ] ) . filter (
195- ( strategy ) => strategy . type === 'address' ,
196- )
208+ const invalidSources = invalidSourcesMap . get ( makeRequestKey ( req ) ) ?? [ ]
209+ const allAvailableStrategies = Array . prependAll ( strategies . default , strategies [ req . chainID ] ?? [ ] )
210+ . filter ( ( strategy ) => strategy . type === 'address' )
211+ . filter ( ( strategy ) => ! invalidSources . includes ( strategy . id ) )
212+
213+ if ( allAvailableStrategies . length === 0 ) {
214+ return Effect . succeed ( Either . right ( req ) )
215+ }
197216
198217 return strategyExecutor
199218 . executeStrategiesSequentially ( allAvailableStrategies , {
@@ -220,17 +239,22 @@ export const AbiLoaderRequestResolver = RequestResolver.makeBatched((requests: A
220239 // NOTE: Secondly we request strategies to fetch fragments
221240 const fragmentStrategyResults = yield * Effect . forEach (
222241 notFound ,
223- ( { chainID, address, event, signature } ) => {
224- const allAvailableStrategies = Array . prependAll ( strategies . default , strategies [ chainID ] ?? [ ] ) . filter (
225- ( strategy ) => strategy . type === 'fragment' ,
226- )
242+ ( req ) => {
243+ const invalidSources = invalidSourcesMap . get ( makeRequestKey ( req ) ) ?? [ ]
244+ const allAvailableStrategies = Array . prependAll ( strategies . default , strategies [ req . chainID ] ?? [ ] )
245+ . filter ( ( strategy ) => strategy . type === 'fragment' )
246+ . filter ( ( strategy ) => ! invalidSources . includes ( strategy . id ) )
247+
248+ if ( allAvailableStrategies . length === 0 ) {
249+ return Effect . succeed ( null )
250+ }
227251
228252 return strategyExecutor
229253 . executeStrategiesSequentially ( allAvailableStrategies , {
230- address,
231- chainId : chainID ,
232- event,
233- signature,
254+ address : req . address ,
255+ chainId : req . chainID ,
256+ event : req . event ,
257+ signature : req . signature ,
234258 strategyId : 'fragment-batch' ,
235259 } )
236260 . pipe (
@@ -244,20 +268,44 @@ export const AbiLoaderRequestResolver = RequestResolver.makeBatched((requests: A
244268 } ,
245269 )
246270
247- const strategyResults = Array . appendAll ( addressStrategyResults , fragmentStrategyResults )
271+ // Create a map to track which requests got results from address strategies
272+ const addressResultsMap = new Map < string , ContractABI [ ] > ( )
273+ addressStrategyResults . forEach ( ( abis , i ) => {
274+ const request = remaining [ i ]
275+ if ( abis && abis . length > 0 ) {
276+ addressResultsMap . set ( makeRequestKey ( request ) , abis )
277+ }
278+ } )
279+
280+ // Create a map to track which requests got results from fragment strategies
281+ const fragmentResultsMap = new Map < string , ContractABI [ ] > ( )
282+ fragmentStrategyResults . forEach ( ( abis , i ) => {
283+ const request = notFound [ i ]
284+ if ( abis && abis . length > 0 ) {
285+ fragmentResultsMap . set ( makeRequestKey ( request ) , abis )
286+ }
287+ } )
248288
249- // Store results and resolve pending requests
289+ // Resolve all remaining requests
250290 yield * Effect . forEach (
251- strategyResults ,
252- ( abis , i ) => {
253- const request = remaining [ i ]
254- const abi = abis ?. [ 0 ] ?? null
255- const bestMatch = getBestMatch ( abi )
256- const result = bestMatch ? Effect . succeed ( bestMatch ) : Effect . fail ( new MissingABIError ( request ) )
291+ remaining ,
292+ ( request ) => {
257293 const group = requestGroups [ makeRequestKey ( request ) ]
294+ const addressAbis = addressResultsMap . get ( makeRequestKey ( request ) ) || [ ]
295+ const fragmentAbis = fragmentResultsMap . get ( makeRequestKey ( request ) ) || [ ]
296+ const allAbis = [ ...addressAbis , ...fragmentAbis ]
297+ const firstAbi = allAbis [ 0 ] || null
298+
299+ const allMatches = allAbis . map ( ( abi ) => {
300+ const parsedAbi = abi . type === 'address' ? ( JSON . parse ( abi . abi ) as Abi ) : ( JSON . parse ( `[${ abi . abi } ]` ) as Abi )
301+ // TODO: We should figure out how to handle the db ID here, maybe we need to start providing the ids before inserting
302+ return { abi : parsedAbi , id : undefined }
303+ } )
304+
305+ const result = allMatches . length > 0 ? Effect . succeed ( allMatches ) : Effect . fail ( new MissingABIError ( request ) )
258306
259307 return Effect . zipRight (
260- setValue ( request , abi ) ,
308+ setValue ( request , firstAbi ) ,
261309 Effect . forEach ( group , ( req ) => Request . completeEffect ( req , result ) , { discard : true } ) ,
262310 )
263311 } ,
@@ -283,12 +331,17 @@ export const getAndCacheAbi = (params: AbiStore.AbiParams) =>
283331 return yield * Effect . fail ( new EmptyCalldataError ( params ) )
284332 }
285333
334+ if ( params . signature && errorFunctionSignatures . includes ( params . signature ) ) {
335+ const errorAbis : Abi = [ ...solidityPanic , ...solidityError ]
336+ return [ { abi : errorAbis , id : undefined } ]
337+ }
338+
286339 if ( params . signature && params . signature === SAFE_MULTISEND_SIGNATURE ) {
287- return yield * Effect . succeed ( SAFE_MULTISEND_ABI )
340+ return [ { abi : SAFE_MULTISEND_ABI , id : undefined } ]
288341 }
289342
290343 if ( params . signature && AA_ABIS [ params . signature ] ) {
291- return yield * Effect . succeed ( AA_ABIS [ params . signature ] )
344+ return [ { abi : AA_ABIS [ params . signature ] , id : undefined } ]
292345 }
293346
294347 return yield * Effect . request ( new AbiLoader ( params ) , AbiLoaderRequestResolver )
0 commit comments