Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable proxy contracts #72

Merged
merged 14 commits into from
Apr 2, 2024

Conversation

portdeveloper
Copy link
Member

Description

This PR adds the ability to work with proxy contracts to abi.ninja.
Fixes #8 .
Let's merge this after #70 and #71 . I am opening this to be able to iterate on it together, since the logic is mostly in proxyContracts.ts.

Untitled.mov

ToDo - Improvements

  • sometimes it loads a bit slow, let's tackle this problem after the 70 and 71 get merged to avoid issues

Additional Information

Related Issues

Closes #{issue number}

Note: If your changes are small and straightforward, you may skip the creation of an issue beforehand and remove this section. However, for medium-to-large changes, it is recommended to have an open issue for discussion and approval prior to submitting a pull request.

Your ENS/address:

Copy link

vercel bot commented Mar 12, 2024

@portdeveloper is attempting to deploy a commit to the BuidlGuidl Team on Vercel.

A member of the Team first needs to authorize it.

@portdeveloper portdeveloper marked this pull request as ready for review March 12, 2024 10:24
@portdeveloper portdeveloper marked this pull request as draft March 12, 2024 10:31
Copy link

vercel bot commented Mar 13, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
abi-ninja-v2 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 2, 2024 4:23am

@damianmarti
Copy link
Member

@portdeveloper This looks great! I authorized the deployment and tested it, and it works great! :-)

@portdeveloper portdeveloper marked this pull request as ready for review March 15, 2024 07:52
@technophile-04
Copy link
Member

This is GREAT Port nice, Tysm !!!

Looking at its working :

  1. User enters proxyAddress in the input field
  2. We change the input address to implementionAddress and show notification
  3. The user clicks the "Load Contract" btn and we direct to abi.ninja/{implementionAddress}/chainId

I am not sure if this was intended(because of simplicity for current PR) or if we missed but maybe I think it kind of makes more sense to direct user abi.ninja/{proxyAddress}/ instead of abi.ninja/{implementionAddress}/ reason for this :

As I understand how proxies work is the storage lives in proxyAddress contract and just the "execution logic" leaves in implementionAddress

If we direct users to abi.ninja/{implementionAddress}/ and if the read / write on implementionAddress we are reading/writing on wrong contract(unless someone really wants to do so)

Example of wrong states
Abi Ninja state Etherscan state
Screenshot 2024-03-20 at 3 15 56 PM Screenshot 2024-03-20 at 3 15 35 PM

I think we might want to do something like this :

  1. User enters proxyAddress in the input field
  2. We direct user to abi.ninja/{proxyAddress}/ but
  3. Instead of using abi from proxyAddress we replace its abi with implementionAddress abi
  4. Then all the interaction/reading will happen through proxyAddress and we get correct state

This is what I have been doing in here checkout YT link on build.

And on UI we can show something similar to :
Screenshot 2024-03-20 at 3 29 00 PM
(Was taken from #17) Just for inspiration no need to do same thing as in #17 .

Something similar is done is on etherscan where they have "Read as proxy" btn.

Really sorry If I missed something please feel free to correct me !! Because the things which I said above are in context which I have from UUPS proxy but I think in-general all proxies works same but I might be completely wrong so please correct me !!!

@portdeveloper
Copy link
Member Author

@technophile-04
I focused so much on the implementation address getter logic, I forgot how proxies work :D.
Thanks for correcting my mistake! Should be fixed!
Now I will work on the UI for showing the proxy and implementation address.

@portdeveloper
Copy link
Member Author

Just got a basic working demo, some code duplication exist and the styling could be better

@portdeveloper portdeveloper marked this pull request as draft March 23, 2024 15:09
@portdeveloper portdeveloper marked this pull request as ready for review March 23, 2024 17:24
@technophile-04
Copy link
Member

Nice !! Thanks !! Just noticed a bug, that when you go back and select other contract (non proxy contract) it doesn't reset the Implementation address state :

Demo Video :
Screen.Recording.2024-03-25.at.5.38.10.PM.mov

Also just noticed that for NounsDAO contract (EIP897-delegate proxy)
it is not resolving properly.

I see we have added utils/abi.ninja/proxyContracts.ts which is similar to evm-proxy-detection, I think you added the code natively because it was not going well with viem or maybe wanted to some modifications ?

We could use that library directly since we have @ethersproject/providers as our dependency already, here is code snippet that could just drop in replace with our code:

import { AlchemyProvider } from "@ethersproject/providers";
import detectProxyTarget from "evm-proxy-detection";

// ...

const alchemyProvider = new AlchemyProvider(parseInt(network), scaffoldConfig.alchemyApiKey);
const requestFunc = ({ method, params }: { method: string; params: any }) =>
  alchemyProvider.send(method, params);
const implementationAddress = await detectProxyTarget(verifiedContractAddress, requestFunc);

I just tried it out and it nicely resolves NounsDAO contract (EIP897-delegate proxy) too, so lets use the library directly 🙌


Btw @portdeveloper , any reason for keeping this after #71 ? I think maybe we could get this in first because I think the changes are minimal once we use the evm-proxy-detection directly but I may be missing something and completely no problem if you feel #71 should be merged was first just curious 🙌

@portdeveloper
Copy link
Member Author

@technophile-04 Thanks! I am sorry that I sometimes miss the most basic things like a state reset. Need to get better at this honestly.

I thought you would want me to use viem so that's why I tried to convert it all to use viem instead of ethers! But yes, let's use evm-proxy-detection !

Let me change the code accordingly and we can merge this first I think.

Copy link
Member

@technophile-04 technophile-04 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great @portdeveloper !! Everything is working nicely now, TSYM !!


Screenshot 2024-03-26 at 2 02 34 PM

That "Implemntation Address" text stands out too much not sure what would be best maybe keep it same as contract name ? Not completely sure

Please don't change anything, lets hear from @carletex and @Pabl0cks if they have any suggestion for UI 🙌


Also not sure if its a noticeable difference but when you click "Gitcoin" on home screen (or any other contract which is not proxy) it takes a bit more time because irrespective of if it's a proxy or not we are assuming it's proxy and making 4-5 eth_getStroageAt rpc calls.

One way might be we ask the user when he input's the address to weather he need's to read it as proxy or not but lol it decreases the UX a lot , but yeah let's leave this issue for different PR / issue 🙌

@Pabl0cks
Copy link
Member

This PR looks awesome!! Testing it this morning 🙌

Copy link
Member

@Pabl0cks Pabl0cks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome job Port!! 🔥 Adding proxy support to abi.ninja is so cool!

After some testing here is a couple of notes:

  • I miss some feedback to the user to know what's getting loaded / got loaded. We only got the "Implementation address" right now but user may be confused.
Something like this maybe?

image

  • For this proxy contract (0xF74B146ce44CC162b601deC3BE331784DB111DC1) the implementation address that gets loaded sometimes changes (when first load or when refresh the web, seems random).
    • Implementation address 1: 0x932a29dbfc1b8c3bdfc763ef53f113486a5b5e7d (good)
    • Implementation address 2: 0x8237f421357f87a23ed0cff3a5586172f210a21b (bad)

@portdeveloper
Copy link
Member Author

@technophile-04
Yes, I agree, maybe we can make the text color black and wrap the text?
Also, about doing some api calls for each contract that is searched, thus increasing the load time, to be honest I have been thinking about this problem for a while and cannot find a good answer but perhaps we can make use of some kind of a little backend? There are only a handful of proxies so it wouldn't be a problem in my opinion. Let me know what you think though!

@Pabl0cks
I think your suggestion makes great sense, I am all in for adding that etherscan info link, or perhaps even our own little page about proxies?

About the problem you noticed, Pablo, that would require that we open an issue on the lib that we use: "evm-proxy-detection". But I am not exactly sure if it is maintained currently. Maybe someone needs to take it and rewrite it, better... :D

@portdeveloper
Copy link
Member Author

Alright, experimented a little with the
ZoraNFTCreatorProxy
,
The problem is that it is misinterpreted as a EIP-897 proxy.

To avoid that, I copied the code from evm-proxy-detection to a new file in utils folder and changed this part of the code:
// EIP-897 DelegateProxy pattern jsonRpcRequest({ method: "eth_call", params: [ { to: proxyAddress, data: EIP_897_INTERFACE[0], }, blockTag, ], }).then(result => { console.log("EIP-897 DelegateProxy pattern result:", result); return readAddress(result); }),

to this:
`    jsonRpcRequest({
  method: "eth_call",
  params: [
    {
      to: proxyAddress,
      data: EIP_897_PROXY_TYPE_METHOD,
    },
    blockTag,
  ],
}).then(proxyTypeResult => {
  console.log("EIP-897 proxyType() result:", proxyTypeResult);
  // Assuming the presence of a proxyType indicates an EIP-897 proxy,
  // proceed to fetch the implementation address.
  return jsonRpcRequest({
    method: "eth_call",
    params: [
      {
        to: proxyAddress,
        data: EIP_897_INTERFACE,
      },
      blockTag,
    ],
  }).then(implementationResult => {
    console.log("EIP-897 implementation address:", implementationResult);
    return readAddress(implementationResult);
  });
}),`
also added this line just above detectProxyTarget func def:
`const EIP_897_PROXY_TYPE_METHOD = "0xbd5b8f1f00000000000000000000000000000000000000000000000000000000";

`

This way we are checking the type of the proxy before we do a call to the impl method, and avoid the misinterpretation.
I will be committing this, so let's thoroughly test it. I have tested manually with the addresses that are originally in the evm-proxy-detection test code.

@jxom
Copy link

jxom commented Mar 27, 2024

Wouldn't evm-proxy-detection still work with Viem? Interface looks a lot simpler...

import { createClient, http } from 'viem' 
import detectProxyTarget from 'evm-proxy-detection'

const client = createClient({
  transport: http('https://cloudflare-eth.com'),
})

const target = await detectProxyTarget(
  '0xA7AeFeaD2F25972D80516628417ac46b3F2604Af',
  client.request
)

@Pabl0cks
Copy link
Member

Pabl0cks commented Mar 27, 2024

With the last changes to fix ZoraNFTCreatorProxy contract detection (which is working good to me now!) other contracts detection got broken => NounsDAO contract (EIP897-delegate proxy) is not working properly now.

Here is the console log
EIP-1167 Minimal Proxy Contract code: 0x6080604052600436106100435760003560e01c8063267822471461005a5780635c60da1b146100b0578063bb913f41146100dd578063f851a440146100fd57610052565b366100525761005061012a565b005b61005061012a565b34801561006657600080fd5b506001546100879073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100bc57600080fd5b506002546100879073ffffffffffffffffffffffffffffffffffffffff1681565b3480156100e957600080fd5b506100506100f83660046103ad565b6101b2565b34801561010957600080fd5b506000546100879073ffffffffffffffffffffffffffffffffffffffff1681565b60025460405160009173ffffffffffffffffffffffffffffffffffffffff169061015790839036906103ea565b600060405180830381855af49150503d8060008114610192576040519150601f19603f3d011682016040523d82523d6000602084013e610197565b606091505b505090506040513d6000823e8180156101ae573d82f35b3d82fd5b60005473ffffffffffffffffffffffffffffffffffffffff16331461025e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4e6f756e7344414f50726f78793a3a5f736574496d706c656d656e746174696f60448201527f6e3a2061646d696e206f6e6c790000000000000000000000000000000000000060648201526084015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116610327576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f4e6f756e7344414f50726f78793a3a5f736574496d706c656d656e746174696f60448201527f6e3a20696e76616c696420696d706c656d656e746174696f6e2061646472657360648201527f7300000000000000000000000000000000000000000000000000000000000000608482015260a401610255565b6002805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527fd604de94d45953f9138079ec1b82d533cb2160c906d1076d1f7ed54befbca97a910160405180910390a15050565b6000602082840312156103bf57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff811681146103e357600080fd5b9392505050565b818382376000910190815291905056fea2646970667358221220a57920897432f744adc6e96a28bdc469834f95c60e8dc78679ab4e15473a068a64736f6c63430008060033
testProxyContracts.ts:82 EIP-1967 beacon proxy address: 0x0000000000000000000000000000000000000000000000000000000000000000
testProxyContracts.ts:124 EIP-1822 Universal Upgradeable Proxy Standard result: 0x0000000000000000000000000000000000000000000000000000000000000000
testProxyContracts.ts:72 EIP-1967 direct proxy storage result: 0x0000000000000000000000000000000000000000000000000000000000000000
testProxyContracts.ts:115 OpenZeppelin proxy pattern result: 0x0000000000000000000000000000000000000000000000000000000000000000
eth-mainnet.alchemyapi.io/v2/oKxs-03sij-U_N0iOlrSsZFr29-IqbuF:1 
        
        
       Failed to load resource: the server responded with a status of 400 ()
eth-mainnet.alchemyapi.io/v2/oKxs-03sij-U_N0iOlrSsZFr29-IqbuF:1 
        
        
       Failed to load resource: the server responded with a status of 400 ()
client.js:2 Failed to detect proxy target: AggregateError: All promises were rejected
console.error @ client.js:2
Show 1 more frame
Show less
client.js:2 AbiFunctionNotFoundError: Function "name" not found on ABI.
Make sure you are using the correct ABI and that the function exists on it.

Docs: https://viem.sh/docs/contract/encodeFunctionData.html
Version: viem@1.19.9
    at encodeFunctionData (encodeFunctionData.js:26:19)
    at readContract (readContract.js:47:106)
    at Object.readContract (public.js:133:111)
    at readContract (chunk-TSH6VVF4.js:2213:23)
    at Object.eval [as queryFn] (index.js:1297:75)
    at Object.fetchFn [as fn] (query.mjs:261:27)
    at run (retryer.mjs:111:31)
    at createRetryer (retryer.mjs:159:5)
    at Query.fetch (query.mjs:316:79)
    at QueryObserver.executeFetch (queryObserver.mjs:177:37)
    at QueryObserver.onSubscribe (queryObserver.mjs:37:14)
    at QueryObserver.subscribe (subscribable.mjs:16:10)
    at eval (index.js:268:64)
    at subscribeToStore (react-dom.development.js:16958:10)
    at commitHookEffectListMount (react-dom.development.js:23145:26)
    at commitPassiveMountOnFiber (react-dom.development.js:24921:13)
    at commitPassiveMountEffects_complete (react-dom.development.js:24886:9)
    at commitPassiveMountEffects_begin (react-dom.development.js:24873:7)
    at commitPassiveMountEffects (react-dom.development.js:24861:3)
    at flushPassiveEffectsImpl (react-dom.development.js:27034:3)
    at flushPassiveEffects (react-dom.development.js:26979:14)
    at eval (react-dom.development.js:26764:9)
    at workLoop (scheduler.development.js:266:34)
    at flushWork (scheduler.development.js:239:14)
    at MessagePort.performWorkUntilDeadline (scheduler.development.js:533:21)

I'm a bit afraid of rewriting evm-proxy-detection on our own to fix the ZoraNFTCreatorProxy use case, maybe we should start with the simpler approach and see how many issues evm-proxy-detection really has?

Maybe I'm too conservative 😅

@technophile-04
Copy link
Member

Wouldn't evm-proxy-detection still work with Viem? Interface looks a lot simpler...

Yeah actually our first intuition was to pass publicClient.request from wagmi, but due to viem's / wagmi's literal typing on method name's we get this error :

'string' is not assignable to type '"wallet_watchAsset"'

Screenshot 2024-03-27 at 3 48 29 PM

I think this is because the callback request function from evm-detect-proxy passes string for method name instead of literal method name, but maybe I might have missing something

Since we already had @ethersproject/providers as a dependency we tried going with it for now, but thanks @jxom for chiming in, really appreciate it 🙌 !!

I'm a bit afraid of rewriting evm-proxy-detection on our own to fix the ZoraNFTCreatorProxy use case, maybe we should start with the simpler approach and see how many issues evm-proxy-detection really has?

Yup agree with this, maybe @portdeveloper for simplicity of this PR lets use evm-proxy-detection directly.

We can create an issue on evm-proxy-detection repo regarding EIP-897 proxy and if we don't get reply / if its not maintained anymore we could always get that solution locally.

Thanks, @portdeveloper and also @Pabl0cks for testing 🙌 !!

@portdeveloper
Copy link
Member Author

Thanks for the feedback! Strangely, I did not get any notifications for your comments... Will be reverting the changes ASAP to use evm-proxy-detection for now!

Copy link
Member

@technophile-04 technophile-04 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @portdeveloper ! Merging this 🚀

@technophile-04 technophile-04 merged commit b8b8257 into BuidlGuidl:main Apr 2, 2024
3 checks passed
@portdeveloper portdeveloper deleted the proxy-contracts branch July 25, 2024 07:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ABI Fails to load when pointing at ERC1967Proxy Contracts
5 participants