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

[Bug] anvil impersonations fail when using a WalletFiller in the provider #1918

Open
erhant opened this issue Jan 15, 2025 · 13 comments
Open
Labels
bug Something isn't working c-provider discuss needs discussion

Comments

@erhant
Copy link

erhant commented Jan 15, 2025

Component

provider, pubsub, signers

What version of Alloy are you on?

alloy v0.8.0

Operating System

macOS (Apple Silicon)

Describe the bug

Using Anvil, we can impersonate accounts without knowing their private keys using anvil_impersonate_account, anvil_stop_impersonating_account and anvil_auto_impersonate_account.

The tests here and here use the following provider:

let provider = ProviderBuilder::new().on_anvil();
// also works with `.on_anvil_with_config(|anvil| /* ... */);`
// also works if you use `.with_recommended_fillers` beforehand

However, if you use a provider that makes use of a WalletFiller filler, impersonations do not work! Consider the following provider:

let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// or `.on_anvil_with_wallet_and_config(|anvil| /* ... */);`

Now, if we try to use an impersonated account within the from address, we get the error:

LocalUsageError(Signer(Other("Missing signing credential for <IMPERSONATED_ADDRESS_HERE>")))

I am not yet clear on how one may handle this issue, I guess we have several options:

  • Make a remark that this is intended in the docs of Alloy / Anvil
  • Somehow keep track of impersonations w.r.t WalletFiller so that their signers are not checked explicitly
  • Alternatively, can we update the provider on-the-fly for impersonated requests so that WalletFiller is not used?
@erhant erhant added the bug Something isn't working label Jan 15, 2025
@erhant
Copy link
Author

erhant commented Jan 15, 2025

For now, I'm using a wrapper function like:

    #[inline]
    pub async fn anvil_impersonated_tx(
        &self,
        tx: TransactionRequest,
        from: Address,
    ) -> Result<PendingTransactionBuilder<AnvilTransport, AnvilNetwork>> {
        // create a provider, without any `WalletFiller` otherwise there is a bug with impersonations
        // see: https://github.com/alloy-rs/alloy/issues/1918
        let anvil_provider = ProviderBuilder::new().on_anvil();

        anvil_provider.anvil_impersonate_account(from).await?;
        let pending_tx = anvil_provider.send_transaction(tx.from(from)).await?;
        anvil_provider
            .anvil_stop_impersonating_account(from)
            .await?;

        Ok(pending_tx)
    }

in my class. The struct here already has a provider btw, but it has WalletFiller so I just create a temporary one for Anvil alone. I'm guessing this calls the already spawned Anvil instance from the first provider that I create.

@yash-atreya
Copy link
Member

@erhant
When using .on_anvil_with_wallet_and_config you can do

//...
.on_anvil_with_wallet_and_config(|anvil| anvil.arg("--auto-impersonate"));

@erhant
Copy link
Author

erhant commented Jan 15, 2025

Thanks @yash-atreya for the suggestion; sadly, it fails with the same error "Missing signing credential ...".

To try, check the test at: https://github.com/alloy-rs/alloy/blob/main/crates/provider/src/ext/anvil.rs#L370 and run it with:

- let provider = ProviderBuilder::new().on_anvil();
+ let provider = ProviderBuilder::new()
+        .with_recommended_fillers()
+        .on_anvil_with_wallet_and_config(|anvil| anvil.arg("--auto-impersonate"));

@yash-atreya
Copy link
Member

yash-atreya commented Jan 15, 2025

Thanks @yash-atreya for the suggestion; sadly, it fails with the same error "Missing signing credential ...".

To try, check the test at: https://github.com/alloy-rs/alloy/blob/main/crates/provider/src/ext/anvil.rs#L370 and run it with:

  • let provider = ProviderBuilder::new().on_anvil();
  • let provider = ProviderBuilder::new()
  •    .with_recommended_fillers()
    
  •    .on_anvil_with_wallet_and_config(|anvil| anvil.arg("--auto-impersonate"));
    

Sorry I misread

LocalUsageError(Signer(Other("Missing signing credential for <IMPERSONATED_ADDRESS_HERE>")))

The above error is indicative of the waller not being setup properly. It is unrelated to anvil, rather the wallet you pass to .wallet(w) does not consist a signing credential linked to from.

When you're using on_anvil_with_wallet_and_config, the default anvil wallet is instantiated.

You need to do :

 let pk = PrivateKeySigner::random();
 let wallet = EthereumWallet::from(pk); // `from` used in tx request
let provider = ProviderBuilder::new()
            .wallet(wallet)
            .on_anvil_with_config(|anvil| anvil.fork(fork_url).arg("--auto-impersonate"));

@yash-atreya yash-atreya changed the title [Bug] Anvil impersonations fail when using a WalletFiller in the provider [Bug] anvil impersonations fail when using a WalletFiller in the provider Jan 15, 2025
@erhant
Copy link
Author

erhant commented Jan 15, 2025

The above error is indicative of the waller not being setup properly. It is unrelated to anvil, rather the wallet you pass to .wallet(w) does not consist a signing credential linked to from.

When you're using on_anvil_with_wallet_and_config, the default anvil wallet is instantiated.

I know, but the thing is im fork-testing a blockchain and I need to impersonate an account that I do not have the key for, and therefore can not create the wallet for; thats the whole point of why I want to impersonate as well.

Naturally, I am unable to create the signer to the EthereumWallet::from input, for the account that I want to impersonate.

@yash-atreya
Copy link
Member

The above error is indicative of the waller not being setup properly. It is unrelated to anvil, rather the wallet you pass to .wallet(w) does not consist a signing credential linked to from.

When you're using on_anvil_with_wallet_and_config, the default anvil wallet is instantiated.

I know, but the thing is im fork-testing a blockchain and I need to impersonate an account that I do not have the key for, and therefore can not create the wallet for; thats the whole point of why I want to impersonate as well.

Naturally, I am unable to create the signer to the EthereumWallet::from input, for the account that I want to impersonate.

Aah, my bad. Yeah this is not ideal.
Will look into it.

@yash-atreya
Copy link
Member

yash-atreya commented Jan 16, 2025

On second thought, @erhant. Unsure why you want to use the WalletFiller when --auto-impersonate is enabled in anvil?
The wallet filler will always require signing credentials to be present locally.

Like you suggested earlier, you could just use

let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_config(|anvil| anvil.fork(url).arg("--auto-impersonate")); 

without the need to instantiate the provider with a wallet.

Agreed that we could improve docs here

@erhant
Copy link
Author

erhant commented Jan 16, 2025

Unsure why you want to use the WalletFiller when --auto-impersonate is enabled in anvil?

I have a provider that has some wallet, and I want that wallet to be present as I'm doing tests. However, within these tests there may be some steps that I need to impersonate. e.g. I create a random Alice wallet, and Bob owns the contract that Im testing, Bob must whitelist Alice (with impersonation) so that the tests can continue.

So I guess you are saying I should not use the wallet at all within the tests, impersonate everything even if I have its wallet?

@erhant
Copy link
Author

erhant commented Jan 16, 2025

One last update, the code below fixes it more correctly (unlike the one at #1918 (comment)):

    pub async fn anvil_impersonated_tx(
        &self,
        tx: TransactionRequest,
        from: Address,
    ) -> Result<PendingTransactionBuilder<Http<Client>, Ethereum>> {
        let anvil = ProviderBuilder::new()
            .on_http(format!("http://localhost:{}", Self::ANVIL_PORT).parse()?);

        anvil.anvil_impersonate_account(from).await?;
        let pending_tx = anvil.send_transaction(tx.from(from)).await?;
        anvil.anvil_stop_impersonating_account(from).await?;

        Ok(pending_tx)
    }

This is done while I create another provider shared all around the application with:

  let provider = ProviderBuilder::new()
            .with_recommended_fillers()
            .wallet(wallet)
            .on_anvil_with_config(|anvil| {
                anvil.fork(rpc_url).port(Self::ANVIL_PORT)
            });

This way I can connect to the existing Anvil for impersonated requests just fine, while being able to use wallet for everything else and even create tx's using that and pass them to anvil_impersonated_tx by calling .into_transaction_request() on a call builder.

EDIT: we could use Anvil wallet here as well instead of passing our own, but I prefer this so that I can continue using my private key for forked-tests like I would on the actual application.

@noahfigueras
Copy link

noahfigueras commented Feb 4, 2025

Hi @erhant @yash-atreya. I'm having the same issue, not being able to impersonate accounts at all. It doesn't matter if I use fillers or not. Currently using alloy version 0.11.0.

    let provider_arb = ProviderBuilder::new().on_anvil_with_wallet_and_config(|anvil| anvil.fork(RPC_URL_ARBITRUM))?;
    provider_arb.anvil_impersonate_account(addr).await.unwrap();
    
    let usdc_arb = IERC20::new(ARB_USDC, provider_arb);
    usdc_arb.transfer(address, U256::from(5)).send().await?.watch().await?;

Always end up with Err value: LocalUsageError(Signer(Other("Missing signing credential for ADDRESS")))

@erhant
Copy link
Author

erhant commented Feb 4, 2025

Heyo @noahfigueras can you try using on_anvil_with_config instead of on_anvil_with_wallet_and_config? Also, set do from(addr) to the tx object that is created when you call usdc_arb.transfer before send(). I think it should work that way.

@noahfigueras
Copy link

hi @erhant thanks man! Removing the wallet works good. Is there any method in the provider to attach and dettach the wallet ? Or maybe I can assign a signer only on a specific transaction ?

Thanks!!

@erhant
Copy link
Author

erhant commented Feb 4, 2025

Happy to see that it works! I would suggest my workaround here #1918 (comment).

To put simply, you create an Anvil provider like you did in your first comment, it CAN have wallet filler anything is ok; then, for impersonations only you create a provider that uses on_http to the Anvil listen url. As long as Anvil commands are included by its trait, you can use anvil commands with that new provider as well. The only catch is that of course this new provider should not have a Wallet in it, otherwise you get that error.

I don't know a way of removing a signer from a provider; and my guess is that its not, mainly because the type returned by the builder will have a WalletFiller within some JoinFill, and I guess that being there will cause an issue for impersonations whether or not you have a signer.

@yash-atreya yash-atreya added c-provider discuss needs discussion labels Feb 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working c-provider discuss needs discussion
Projects
None yet
Development

No branches or pull requests

3 participants