1- import { Effect , PrimaryKey , Request , RequestResolver , Schedule , Schema , SchemaAST } from 'effect'
2-
3- import { PublicClient , RPCCallError , RPCFetchError } from '../public-client.js'
4- import { Address , Hex } from 'viem'
1+ import { Effect , Either , PrimaryKey , Request , RequestResolver , Schema , SchemaAST } from 'effect'
2+ import { PublicClient , RPCFetchError , UnknownNetwork } from '../public-client.js'
3+ import { Address , getAddress , Hex } from 'viem'
54import { ProxyType } from '../types.js'
65import { ZERO_ADDRESS } from './constants.js'
6+ import { whatsabi } from '@shazow/whatsabi'
77
88interface StorageSlot {
99 type : ProxyType
1010 slot : Hex
1111}
1212
13- interface ProxyResult {
14- type : ProxyType
13+ interface ProxyResult extends StorageSlot {
1514 address : Address
1615}
1716
18- const storageSlots : StorageSlot [ ] = [
17+ const knownStorageSlots : StorageSlot [ ] = [
18+ { type : 'eip1167' , slot : '0x' } , //EIP1167 minimal proxy
1919 { type : 'eip1967' , slot : '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' } , //EIP1967
2020 { type : 'zeppelin' , slot : '0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3' } , //zeppelin
2121 { type : 'safe' , slot : '0xa619486e00000000000000000000000000000000000000000000000000000000' } , // gnosis Safe Proxy Factor 1.1.1
2222]
2323
2424const zeroSlot = '0x0000000000000000000000000000000000000000000000000000000000000000'
2525
26- export interface GetProxy extends Request . Request < ProxyResult | undefined , RPCFetchError > {
26+ export interface GetProxy extends Request . Request < ProxyResult | undefined , RPCFetchError | UnknownNetwork > {
2727 readonly _tag : 'GetProxy'
2828 readonly address : Address
2929 readonly chainID : number
@@ -34,7 +34,7 @@ class SchemaAddress extends Schema.make<Address>(SchemaAST.stringKeyword) {}
3434class SchemaProxy extends Schema . make < ProxyResult | undefined > ( SchemaAST . objectKeyword ) { }
3535
3636class ProxyLoader extends Schema . TaggedRequest < ProxyLoader > ( ) ( 'ProxyLoader' , {
37- failure : Schema . instanceOf ( RPCFetchError ) ,
37+ failure : Schema . Union ( Schema . instanceOf ( RPCFetchError ) , Schema . instanceOf ( UnknownNetwork ) ) ,
3838 success : Schema . NullOr ( SchemaProxy ) ,
3939 payload : {
4040 address : SchemaAddress ,
@@ -56,7 +56,9 @@ const getStorageSlot = (request: ProxyLoader, slot: StorageSlot) =>
5656 address : request . address ,
5757 slot : slot . slot ,
5858 } ) ,
59- catch : ( ) => new RPCFetchError ( 'Get storage' ) ,
59+ catch : ( e ) => {
60+ return new RPCFetchError ( `Get storage error: ${ ( e as { details ?: string } ) . details ?? '' } ` )
61+ } ,
6062 } )
6163 } )
6264
@@ -72,19 +74,121 @@ const ethCall = (request: ProxyLoader, slot: StorageSlot) =>
7274 data : slot . slot ,
7375 } )
7476 ) ?. data ,
75- catch : ( ) => new RPCCallError ( ' Eth call' ) ,
77+ catch : ( e ) => new RPCFetchError ( ` Eth call error: ${ ( e as { details ?: string } ) . details ?? '' } ` ) ,
7678 } )
7779 } )
7880
81+ const ethGetCode = ( request : ProxyLoader ) =>
82+ Effect . gen ( function * ( ) {
83+ const service = yield * PublicClient
84+ const { client : publicClient } = yield * service . getPublicClient ( request . chainID )
85+ return yield * Effect . tryPromise ( {
86+ try : ( ) => publicClient . getCode ( { address : request . address } ) ,
87+ catch : ( e ) => new RPCFetchError ( `Eth get code error: ${ ( e as { details ?: string } ) . details ?? '' } ` ) ,
88+ } )
89+ } )
90+
91+ const getProxyTypeFromBytecode = ( request : ProxyLoader , code : Hex ) =>
92+ Effect . gen ( function * ( ) {
93+ const service = yield * PublicClient
94+ const { client : publicClient } = yield * service . getPublicClient ( request . chainID )
95+
96+ //use whatsabi to only resolve proxies with a known bytecode
97+ const cachedCodeProvider = yield * Effect . try ( {
98+ try : ( ) =>
99+ whatsabi . providers . WithCachedCode ( publicClient , {
100+ [ request . address ] : code ,
101+ } ) ,
102+ catch : ( ) => new RPCFetchError ( `Get proxy type from bytecode error` ) ,
103+ } )
104+
105+ const result = yield * Effect . tryPromise ( {
106+ try : ( ) =>
107+ whatsabi . autoload ( request . address , {
108+ provider : cachedCodeProvider ,
109+ abiLoader : false , // Skip ABI loaders
110+ signatureLookup : false , // Skip looking up selector signatures
111+ } ) ,
112+ catch : ( ) => new RPCFetchError ( 'Get proxy type from bytecode' ) ,
113+ } )
114+
115+ //if there are soeme proxies, return the list of them but with udpdated types
116+ if ( result && result . proxies . length > 0 ) {
117+ const proxies : ( ProxyResult | StorageSlot ) [ ] = result . proxies
118+ . map ( ( proxy ) => {
119+ if ( proxy . name === 'EIP1967Proxy' ) {
120+ return knownStorageSlots . find ( ( slot ) => slot . type === 'eip1967' )
121+ }
122+
123+ if ( proxy . name === 'GnosisSafeProxy' ) {
124+ return knownStorageSlots . find ( ( slot ) => slot . type === 'safe' )
125+ }
126+
127+ if ( proxy . name === 'ZeppelinOSProxy' ) {
128+ return knownStorageSlots . find ( ( slot ) => slot . type === 'zeppelin' )
129+ }
130+
131+ if ( proxy . name === 'FixedProxy' ) {
132+ const implementation = ( proxy as any as { resolvedAddress : Address } ) . resolvedAddress
133+
134+ if ( ! implementation ) return undefined
135+
136+ return {
137+ type : 'eip1167' ,
138+ address : getAddress ( implementation ) ,
139+ slot : '0x' ,
140+ } as ProxyResult
141+ }
142+
143+ return undefined
144+ } )
145+ . filter ( Boolean )
146+ . filter ( ( proxy , index , self ) => self . findIndex ( ( p ) => p ?. type === proxy . type ) === index )
147+
148+ return proxies
149+ }
150+
151+ return undefined
152+ } )
153+
79154export const GetProxyResolver = RequestResolver . fromEffect (
80- ( request : ProxyLoader ) : Effect . Effect < ProxyResult | undefined , RPCFetchError , PublicClient > =>
155+ ( request : ProxyLoader ) : Effect . Effect < ProxyResult | undefined , RPCFetchError | UnknownNetwork , PublicClient > =>
81156 Effect . gen ( function * ( ) {
82157 // NOTE: Should we make this recursive when we have a Proxy of a Proxy?
83158
84- const effects = storageSlots . map ( ( slot ) =>
85- Effect . gen ( function * ( ) {
86- const res : ProxyResult | undefined = { type : slot . type , address : '0x' }
159+ //Getting the bytecode of the address first
160+ const codeResult = yield * ethGetCode ( request ) . pipe ( Effect . either )
161+
162+ if ( Either . isLeft ( codeResult ) ) {
163+ yield * Effect . logError ( `ProxyResolver error: ${ JSON . stringify ( codeResult . left ) } ` )
164+ return undefined
165+ }
87166
167+ const code = codeResult . right
168+
169+ //If code is empty and it is EOA, return empty result
170+ if ( ! code ) return undefined
171+
172+ let proxySlots : StorageSlot [ ] | undefined
173+
174+ //Getting the proxies list from the bytecode
175+ const proxies = yield * getProxyTypeFromBytecode ( request , code )
176+ if ( proxies && proxies . length > 0 ) {
177+ //If it is EIP1167 proxy, return it becasue it is alredy resolved from the bytecode
178+ if ( proxies . some ( ( proxy ) => proxy . type === 'eip1167' ) ) {
179+ return proxies . find ( ( proxy ) => proxy . type === 'eip1167' ) as ProxyResult
180+ }
181+
182+ proxySlots = proxies as StorageSlot [ ]
183+ }
184+
185+ if ( ! proxySlots ) {
186+ return undefined
187+ }
188+
189+ //get the implementation address by requesting the storage slot value of possible proxies
190+ const effects = ( proxySlots ?? knownStorageSlots ) . map ( ( slot ) =>
191+ Effect . gen ( function * ( ) {
88192 let address : Hex | undefined
89193 switch ( slot . type ) {
90194 case 'eip1967' :
@@ -100,21 +204,32 @@ export const GetProxyResolver = RequestResolver.fromEffect(
100204
101205 if ( ! address || address === zeroSlot ) return undefined
102206
103- res . address = ( '0x' + address . slice ( address . length - 40 ) ) as Address
104- return res
207+ return {
208+ type : slot . type ,
209+ address : ( '0x' + address . slice ( address . length - 40 ) ) as Address ,
210+ slot : slot . slot ,
211+ }
105212 } ) ,
106213 )
107214
108- const policy = Schedule . addDelay (
109- Schedule . recurs ( 2 ) , // Retry for a maximum of 2 times
110- ( ) => '100 millis' , // Add a delay of 100 milliseconds between retries
111- )
112215 const res = yield * Effect . all ( effects , {
113216 concurrency : 'inherit' ,
114217 batching : 'inherit' ,
115- } ) . pipe ( Effect . retryOrElse ( policy , ( ) => Effect . succeed ( undefined ) ) )
218+ mode : 'either' ,
219+ } )
220+
221+ const resRight = res
222+ . filter ( Either . isRight )
223+ . map ( ( r ) => r . right )
224+ . find ( ( x ) => x != null )
225+
226+ const resLeft = res . filter ( Either . isLeft ) . map ( ( r ) => r . left )
227+
228+ if ( resLeft . length > 0 ) {
229+ yield * Effect . logError ( `ProxyResolver error: ${ resLeft . map ( ( e ) => JSON . stringify ( e ) ) . join ( ', ' ) } ` )
230+ }
116231
117- return res ?. find ( ( x ) => x != null )
232+ return resRight
118233 } ) ,
119234) . pipe ( RequestResolver . contextFromEffect )
120235
0 commit comments