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,117 @@ 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 ?? '' } ` ) ,
78+ } )
79+ } )
80+
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 ?? '' } ` ) ,
7688 } )
7789 } )
7890
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 = whatsabi . providers . WithCachedCode ( publicClient , {
98+ [ request . address ] : code ,
99+ } )
100+
101+ const result = yield * Effect . tryPromise ( {
102+ try : ( ) =>
103+ whatsabi . autoload ( request . address , {
104+ provider : cachedCodeProvider ,
105+ abiLoader : false , // Skip ABI loaders
106+ signatureLookup : false , // Skip looking up selector signatures
107+ } ) ,
108+ catch : ( ) => new RPCFetchError ( 'Get proxy type from bytecode' ) ,
109+ } )
110+
111+ //if there are soeme proxies, return the list of them but with udpdated types
112+ if ( result && result . proxies . length > 0 ) {
113+ const proxies : ( ProxyResult | StorageSlot ) [ ] = result . proxies
114+ . map ( ( proxy ) => {
115+ if ( proxy . name === 'EIP1967Proxy' ) {
116+ return knownStorageSlots . find ( ( slot ) => slot . type === 'eip1967' )
117+ }
118+
119+ if ( proxy . name === 'GnosisSafeProxy' ) {
120+ return knownStorageSlots . find ( ( slot ) => slot . type === 'safe' )
121+ }
122+
123+ if ( proxy . name === 'ZeppelinOSProxy' ) {
124+ return knownStorageSlots . find ( ( slot ) => slot . type === 'zeppelin' )
125+ }
126+
127+ if ( proxy . name === 'FixedProxy' ) {
128+ const implementation = ( proxy as any as { resolvedAddress : Address } ) . resolvedAddress
129+
130+ if ( ! implementation ) return undefined
131+
132+ return {
133+ type : 'eip1167' ,
134+ address : getAddress ( implementation ) ,
135+ slot : '0x' ,
136+ } as ProxyResult
137+ }
138+
139+ return undefined
140+ } )
141+ . filter ( Boolean )
142+ . filter ( ( proxy , index , self ) => self . findIndex ( ( p ) => p ?. type === proxy . type ) === index )
143+
144+ return proxies
145+ }
146+
147+ return undefined
148+ } )
149+
79150export const GetProxyResolver = RequestResolver . fromEffect (
80- ( request : ProxyLoader ) : Effect . Effect < ProxyResult | undefined , RPCFetchError , PublicClient > =>
151+ ( request : ProxyLoader ) : Effect . Effect < ProxyResult | undefined , RPCFetchError | UnknownNetwork , PublicClient > =>
81152 Effect . gen ( function * ( ) {
82153 // NOTE: Should we make this recursive when we have a Proxy of a Proxy?
83154
84- const effects = storageSlots . map ( ( slot ) =>
85- Effect . gen ( function * ( ) {
86- const res : ProxyResult | undefined = { type : slot . type , address : '0x' }
155+ //Getting the bytecode of the address first
156+ const codeResult = yield * ethGetCode ( request ) . pipe ( Effect . either )
157+
158+ if ( Either . isLeft ( codeResult ) ) {
159+ yield * Effect . logError ( `ProxyResolver error: ${ JSON . stringify ( codeResult . left ) } ` )
160+ return undefined
161+ }
162+
163+ const code = codeResult . right
87164
165+ //If code is empty and it is EOA, return empty result
166+ if ( ! code ) return undefined
167+
168+ let proxySlots : StorageSlot [ ] | undefined
169+
170+ //Getting the proxies list from the bytecode
171+ const proxies = yield * getProxyTypeFromBytecode ( request , code )
172+ if ( proxies && proxies . length > 0 ) {
173+ //If it is EIP1167 proxy, return it becasue it is alredy resolved from the bytecode
174+ if ( proxies . some ( ( proxy ) => proxy . type === 'eip1167' ) ) {
175+ return proxies . find ( ( proxy ) => proxy . type === 'eip1167' ) as ProxyResult
176+ }
177+
178+ proxySlots = proxies as StorageSlot [ ]
179+ }
180+
181+ if ( ! proxySlots ) {
182+ return undefined
183+ }
184+
185+ //get the implementation address by requesting the storage slot value of possible proxies
186+ const effects = ( proxySlots ?? knownStorageSlots ) . map ( ( slot ) =>
187+ Effect . gen ( function * ( ) {
88188 let address : Hex | undefined
89189 switch ( slot . type ) {
90190 case 'eip1967' :
@@ -100,21 +200,32 @@ export const GetProxyResolver = RequestResolver.fromEffect(
100200
101201 if ( ! address || address === zeroSlot ) return undefined
102202
103- res . address = ( '0x' + address . slice ( address . length - 40 ) ) as Address
104- return res
203+ return {
204+ type : slot . type ,
205+ address : ( '0x' + address . slice ( address . length - 40 ) ) as Address ,
206+ slot : slot . slot ,
207+ }
105208 } ) ,
106209 )
107210
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- )
112211 const res = yield * Effect . all ( effects , {
113212 concurrency : 'inherit' ,
114213 batching : 'inherit' ,
115- } ) . pipe ( Effect . retryOrElse ( policy , ( ) => Effect . succeed ( undefined ) ) )
214+ mode : 'either' ,
215+ } )
216+
217+ const resRight = res
218+ . filter ( Either . isRight )
219+ . map ( ( r ) => r . right )
220+ . find ( ( x ) => x != null )
221+
222+ const resLeft = res . filter ( Either . isLeft ) . map ( ( r ) => r . left )
223+
224+ if ( resLeft . length > 0 ) {
225+ yield * Effect . logError ( `ProxyResolver error: ${ resLeft . map ( ( e ) => JSON . stringify ( e ) ) . join ( ', ' ) } ` )
226+ }
116227
117- return res ?. find ( ( x ) => x != null )
228+ return resRight
118229 } ) ,
119230) . pipe ( RequestResolver . contextFromEffect )
120231
0 commit comments