safe-global / safe-wallet-web

Safe{Wallet} – smart contract wallet for Ethereum (ex-Gnosis Safe multisig)
https://app.safe.global
GNU General Public License v3.0
312 stars 362 forks source link

Counterfactual Safe on Base ended up with unnofficial L1 contract #3812

Open johannesmoormann opened 1 month ago

johannesmoormann commented 1 month ago

Bug description

User reported that they counterfactually created Safe on Base. After deploying and receiving some ETH, they tried to transfer the ETH out and received the unsupported base contract error. Turns out the Safe got deployed with the L1 contract using the mainnet L1 contract deployment address instead of our official L1 Base contract deployment.

Safe: https://app.safe.global/home?safe=base:0x879D41d53c33F43B0CF31e909A2d3F1C2eb2cf80 Proxy: https://basescan.org/address/0x879D41d53c33F43B0CF31e909A2d3F1C2eb2cf80 Singleton: https://basescan.org/address/0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552

Environment

Steps to reproduce

need to find out

Expected result

L2 Safe following official deployments

Obtained result

Screenshots

Screenshot 2024-06-07 at 19 01 16
usame-algan commented 2 weeks ago

I can reproduce this if I manually hard-code the chainId in getSafeFactory i.e. that it returns the mainnet chainId instead of the Base one: ethersAdapter.getChainId = () => Promise.resolve(1n)

So my guess is that this is the culprit but we need to figure out how it can happen that the chainId is not updated when activating an account.

usame-algan commented 1 week ago

Pushed this back for now as its not clear how to arrive at this state. The way we handle it during safe creation is that we enforce that the users wallet needs to be on the same network as the safe they are about to create. That way we ensure to fetch the correct safe factory contract.

katspaugh commented 1 week ago

Can't we use our readonly provider to fetch the contract? Also fetch from where, why?

usame-algan commented 1 week ago

We have to call the createProxyWithNonce function on a specific safe factory contract. We get that contract via the protocol-kit by calling SafeFactory.create() with the respective EthersAdapter. So if the user wants to create a Safe e.g. on Sepolia we create an EthersAdapter with their wallet provider set to Sepolia and get the safe factory contract for Sepolia. I don't think we can use the readonly provider here since the user has to sign the transaction with their wallet.