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 · 9 comments
Open
Labels
bug Something isn't working

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants