NomicFoundation / hardhat

Hardhat is a development environment to compile, deploy, test, and debug your Ethereum software.
https://hardhat.org
Other
7.28k stars 1.41k forks source link

`hardhat_impersonateAccount` doesn't work when localhost accounts are not set to "remote" #1226

Open chpiatt opened 3 years ago

chpiatt commented 3 years ago

Edit by @fvictorio: see this comment for a summary of this issue.


Original issue:

I'm running into an issue when I try impersonating an account on a forked mainnet running in separate terminal window (i.e. localhost network). I'm able to successfully impersonate this account when I just fork mainnet on the "hardhat" network to run tests, but on the "localhost" network I keep running into this issue:

HardhatError: HH103: Account 0x2d4bb9a783583875f8b0b86cbcad09ce87523497 is not managed by the node you are connected to.

Example code:

await ethers.provider.send('hardhat_impersonateAccount', ['0x2d4bb9a783583875f8b0b86cbcad09ce87523497']);
const admin = await ethers.provider.getSigner('0x2d4bb9a783583875f8b0b86cbcad09ce87523497')
const VotingPowerPrismDeployment = await deployments.get("VotingPowerPrism")
const VotingPowerPrism = new ethers.Contract(VotingPowerPrismDeployment.address, VotingPowerPrismDeployment.abi, admin)
const VotingPowerImpDeployment = await deployments.get("VotingPower")
await VotingPowerPrism.setPendingProxyImplementation(VotingPowerImpDeployment.address); // error happens here

Configs:

hardhatConfig = {
  live: false,
  saveDeployments: true,
  tags: ["test"],
  forking = {
      url: process.env.FORK_URL,
      blockNumber = parseInt(process.env.FORK_BLOCK_NUMBER)
  }
}

localhostConfig = {
  url: 'http://localhost:8545',
  live: false,
  saveDeployments: true,
  tags: ["local"],
  chainId = 1,
  forking = {
      url: process.env.FORK_URL,
      blockNumber = parseInt(process.env.FORK_BLOCK_NUMBER)
  }
}
gitcoinbot commented 3 years ago

Issue Status: 1. Open 2. Started 3. Submitted 4. Done


This issue now has a funding of 0.2 ETH (329.46 USD @ $1647.32/ETH) attached to it as part of the archerdao fund.

chpiatt commented 3 years ago

Stack trace for the error:

(node:72111) UnhandledPromiseRejectionWarning: HardhatError: HH103: Account 0x2d4bb9a783583875f8b0b86cbcad09ce87523497 is not managed by the node you are connected to.
    at LocalAccountsProvider._getPrivateKeyForAddress (./node_modules/hardhat/internal/core/providers/accounts.js:104:19)
    at LocalAccountsProvider.request (./node_modules/hardhat/internal/core/providers/accounts.js:83:37)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async EthersProviderWrapper.send (./node_modules/hardhat-deploy-ethers/dist/src/ethers-provider-wrapper.js:10:24)
fvictorio commented 3 years ago

Hi @chpiatt. I created a simple example where I fork the mainnet and start a node in one terminal, and then call the impersonate account method and send a transaction from another (connecting via the localhost network) and it works fine. So your problem must be elsewhere.

If you create a minimal repo that reproduces the problem, I can take a deeper look.

chpiatt commented 3 years ago

I'm using hardhat-deploy and hardhat-deploy-ethers. My suspicion is something is going wrong there, but I will try to throw together a demo repo tomorrow

andreiashu commented 3 years ago

hi @chpiatt this worked for me https://github.com/andreiashu/archer-dao-hardhat/blob/main/test/archer-dao.test.js

There were some things I had to change. The original account provided (0x2d4bb9a783583875f8b0b86cbcad09ce87523497) is not the admin one and setPendingProxyImplementation was reverting with Prism::setPendingProxyImp: caller must be admin. So I just used getStorageAt to find the actual admin address.

Then I had to fund that admin account with some eth since it didn't have enough for gas costs. After that everything worked. See whether you spot any differences between our hardhat.config.js or the code I wrote.

gitcoinbot commented 3 years ago

Issue Status: 1. Open 2. Started 3. Submitted 4. Done


Work has been started.

These users each claimed they can complete the work by 4 weeks, 1 day ago. Please review their action plans below:

1) andreiashu has been approved to start work.

first time using gitcoin. seems I should have started here rather than posting straight on github. oh well :)

Learn more on the Gitcoin Issue Details page.

chpiatt commented 3 years ago

hi @chpiatt this worked for me https://github.com/andreiashu/archer-dao-hardhat/blob/main/test/archer-dao.test.js

There were some things I had to change. The original account provided (0x2d4bb9a783583875f8b0b86cbcad09ce87523497) is not the admin one and setPendingProxyImplementation was reverting with Prism::setPendingProxyImp: caller must be admin. So I just used getStorageAt to find the actual admin address.

Then I had to fund that admin account with some eth since it didn't have enough for gas costs. After that everything worked. See whether you spot any differences between our hardhat.config.js or the code I wrote.

Thanks for putting this together. It was really helpful for isolating the issue. I forked your demo repo and added the code that is giving the issue here: https://github.com/chpiatt/archer-dao-hardhat

I figured out the issue happens only when I also pass in an array of accounts to the network config, see: https://github.com/chpiatt/archer-dao-hardhat/blob/1cce6268cf1ff8cf9de64296f8d56731b9476ab4/hardhat.config.js#L40

For now, I can get around this issue by just impersonating every account that I might want to pass as an account to this array, but leaving the bounty open in case someone can address the underlying issue of being able to both pass in accounts to this array and impersonate other accounts.

andreiashu commented 3 years ago

underlying issue of being able to both pass in accounts to this array and impersonate other accounts @chpiatt I believe It's not the accounts that's creating your issue.

in your config file for localhost you have both url: 'http://localhost:8545', and forking.url specified. please remove url and leave the forking.url and that should fix the issue. I've started getting strange errors when I specified both of them.

I've pushed and update that demos this working here https://github.com/andreiashu/archer-dao-hardhat/commit/d81f8cda8f266cb7f804b50f00124764d39b2b87 let me know what you think and if that solves the issue

ps: live & saveDeployments are not in the config docs?

chpiatt commented 3 years ago

Unfortunately, I don't think this solves the issue. In your update you moved the "accounts" array inside of the "forking" object, which I think is why the tests now pass: https://github.com/andreiashu/archer-dao-hardhat/blob/d81f8cda8f266cb7f804b50f00124764d39b2b87/hardhat.config.js#L27

At first, I thought maybe this actually was the solution, but then when I tried to sign a transaction with that account (without first impersonating it), I get a different error:

Error: no signer for 0x4F8f512Dab59F227EA70B1D8A0044aFa95CC80C3
andreiashu commented 3 years ago

Oh haha, oops. This is what happens if I try to rush it

On Sat, Feb 6, 2021, 20:26 Chris Piatt notifications@github.com wrote:

Unfortunately, I don't think this solves the issue. In your update you moved the "accounts" array inside of the "forking" object, which I think is why the tests now pass: https://github.com/andreiashu/archer-dao-hardhat/blob/d81f8cda8f266cb7f804b50f00124764d39b2b87/hardhat.config.js#L27

At first, I thought maybe this actually was the solution, but then when I tried to sign a transaction with that account (without first impersonating it), I get a different error:

Error: no signer for 0x4F8f512Dab59F227EA70B1D8A0044aFa95CC80C3

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/nomiclabs/hardhat/issues/1226#issuecomment-774477301, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACTU3QGKKOWO2ORU53UDCLS5U7O3ANCNFSM4XEEFLLQ .

alcuadrado commented 3 years ago

Hi @chpiatt, thanks for reporting this. And thanks @andreiashu for your effort investigating it, it helped me realize what's going on.

When you define a set of local accounts using an array of private keys or HD config, those accounts are used instead of the ones you are connecting to. This is conflating with the hardhat_impersonateAccount, cause that affects the remote accounts, not the local ones.

I guess you could run a hardhat_imporsonateAccount with the DEPLOYER_PRIVATE_KEY's address as a workaround.

chpiatt commented 3 years ago

Hi @chpiatt, thanks for reporting this. And thanks @andreiashu for your effort investigating it, it helped me realize what's going on.

When you define a set of local accounts using an array of private keys or HD config, those accounts are used instead of the ones you are connecting to. This is conflating with the hardhat_impersonateAccount, cause that affects the remote accounts, not the local ones.

I guess you could run a hardhat_imporsonateAccount with the DEPLOYER_PRIVATE_KEY's address as a workaround.

I see the issue. Maybe there could be a method that you add to the client that when you call it: 1) keeps track of these accounts locally (i.e. in a map called impersonated_accounts) 2) forwards hardhat_impersonateAccount to the node?

And then when a signer tries to sign something you: 1) check if the private key is in your local accounts - if so, sign locally 2) if not, check if address is in impersonated_accounts - if so, forward to node to sign

gitcoinbot commented 3 years ago

@andreiashu Hello from Gitcoin Core - are you still working on this issue? Please submit a WIP PR or comment back within the next 3 days or you will be removed from this ticket and it will be returned to an ‘Open’ status. Please let us know if you have questions!

Funders only: Snooze warnings for 1 day | 3 days | 5 days | 10 days | 100 days

alcuadrado commented 3 years ago

Makes sense, @chpiatt. WDYT @fvictorio ?

@chpiatt, are you willing to fund this through gitcoin? We don't have a lot of bandwidth right now to prioritize implementing it internally.

chpiatt commented 3 years ago

@alcuadrado yes, happy to fund

pcfreak30 commented 3 years ago

Hello, I just hit this issue. I am using a mnemonic seed in my config. It all works correctly on the hardhat network. However I am running hardhat node externally so that I can work inside eth remix and use a local blockchain explorer.

I have the node on the hardhat network, and a copy as hardhat_external which is used by hardhat test (I use env vars to flag what to use).

Doing this causes the error reported above. I can confirm using ganache-cli works expected so this seems to be an due to a possible assumption with the tight integration between the node, client, and test runner.

Until this is fixed, I would recommend to other developers to use hardhat node for debugging, and then use ganache once tests pass if you need to use a separate node process.

Hope this information helps :).

Zequez commented 3 years ago

I was having the same issue on our project (which is why I found this issue). We were running from inside a Hardhat task and using the existing provider from Ethers as you are. We also tried running a Ganache (with the evm_unlockUnknownAccount method instead) node instead, but we received the same error; the error was coming from the Hardhat task, not the node.

However, when we tried running it from a separate script using the following to instantiate a new provider, it worked.

const provider = new ethers.providers.JsonRpcProvider(
        "http://localhost:8545"
      );
await provider.send("hardhat_impersonateAccount", [ADDRESS]);
const account = provider.getSigner(ADDRESS);

So my guess is that the issue is with the EthersProviderWrapper (which is the class name of ethers.provider given by Hardhat)

Not sure if anyone is still having this issue; but you could try if using JsonRpcProvider directly solves it.

Philogy commented 3 years ago

I was having the same issue on our project (which is why I found this issue). We were running from inside a Hardhat task and using the existing provider from Ethers as you are. We also tried running a Ganache (with the evm_unlockUnknownAccount method instead) node instead, but we received the same error; the error was coming from the Hardhat task, not the node.

However, when we tried running it from a separate script using the following to instantiate a new provider, it worked.

const provider = new ethers.providers.JsonRpcProvider(
        "http://localhost:8545"
      );
await provider.send("hardhat_impersonateAccount", [ADDRESS]);
const account = provider.getSigner(ADDRESS);

So my guess is that the issue is with the EthersProviderWrapper (which is the class name of ethers.provider given by Hardhat)

Not sure if anyone is still having this issue; but you could try if using JsonRpcProvider directly solves it.

This actually works, even within a hardhat task. To generalize and make it backwards compatible I added this as the first line in my task function:

hre.ethers.provider = new ethers.providers.JsonRpcProvider(hre.ethers.provider.connection.url)

After that I stopped receiving the Error HH103: Account 0x29b7efda7c08773f9eadbc7443a23758e35eac2d is not managed by the node you are connected to. error when trying to send a transaction from an impersonated account. I think the impersonation is succeeding but the EthersProviderWrapper provided by the HRE somehow does an additional check it shouldn't do when sending transactions.

fvictorio commented 3 years ago
hre.ethers.provider = new ethers.providers.JsonRpcProvider(hre.ethers.provider.connection.url)

This won't work for other networks right now because that value is always (incorrectly) set to http://localhost:8545.

rhlsthrm commented 2 years ago

I am still having an issue, getting an error:

HardhatError: HH103: Account 0x8894e0a0c962cb723c1976a4421c95949be2d4e3 is not managed by the node you are connected to.

When running hardhat node externally. In hardhat.config.ts I have:

const config: HardhatUserConfig = {
  networks: {
    hardhat: {
      saveDeployments: true,
      chainId: 56,
      gasPrice: 20e9,
      gas: 25e6,
      allowUnlimitedContractSize: true,
      accounts: { mnemonic },
      forking: {
        url: "https://speedy-nodes-nyc.moralis.io/2d2926c3e761369208fba31f/bsc/mainnet/archive",
        blockNumber: 15641803
      },
    },
    bscfork: {
      accounts: { mnemonic },
      chainId: 56,
      gasPrice: 20e9,
      gas: 7_000_000,
      allowUnlimitedContractSize: true,
      url: "http://localhost:8545",
    },
...

I am running the impersonate script like:

import { providers } from "ethers";
import { ethers } from "hardhat";

const WHALE_ACCOUNTS = {
  56: "0x8894E0a0c962CB723c1976a4421c95949bE2D4E3", // binance hot wallet 6
};

export const whaleSigner = async (): Promise<providers.JsonRpcSigner | undefined> => {
  const { chainId } = await ethers.provider.getNetwork();
  const account = WHALE_ACCOUNTS[chainId];
  if (account) {
    const provider = new providers.JsonRpcProvider("http://localhost:8545");
    try {
      await provider.send("hardhat_impersonateAccount", [account]);
    } catch (e) {
      console.log("Error in hardhat_impersonateAccount", e);
    }
    return provider.getSigner(account);
  }
};

My tests run fine with npx hardhat test. However when I run npx hardhat node then I run npx hardhat test --network bscfork I get the above error. Am I doing something wrong? It seems from above that this should work.

mistersingh179 commented 2 years ago

another way to make this work is to use getDefaultProvider.

Step 1. Impersonate a whale's account on localhost

await hre.network.provider.request({
    method: "hardhat_impersonateAccount",
    params: ["0x8894E0a0c962CB723c1976a4421c95949bE2D4E3"],
  });

Step2. Get a provider to localhost where the whale has been impersonated

const provider = ethers.getDefaultProvider("http://localhost:8545");

Step3. Get whale's wallet & make transactions

const signer = await provider.getSigner(
    "0x8894E0a0c962CB723c1976a4421c95949bE2D4E3"
  );
await signer.sendTransaction({
    to: "0x0000000000000000000000000000000000000000",
    value: ethers.utils.parseEther("0.01"),
  });
fvictorio commented 2 years ago

Summary of this thread so far.

The problem

This code works in the hardhat network (that is, running just hh run script.js):

const helpers = require("@nomicfoundation/hardhat-network-helpers");

async function main() {
  const randomAddress = "0x9998b9fa6836d840c901860512c2082f8d6eb536"

  await helpers.setBalance(randomAddress, 10n**18n);
  await helpers.impersonateAccount(randomAddress);
  const s = await ethers.getSigner(randomAddress);

  await s.sendTransaction({
    to: s.address
  });
}

main();

If you start an hh node in another terminal and run hh run script.js --network localhost, it will also work fine.

But if you configure the accounts array of the localhost network:

networks: {
  localhost: {
    url: "http://localhost:8545",
    accounts: []
  }
},

and you run the script in the localhost network again, you'll get this error:

HardhatError: HH103: Account 0x9998b9fa6836d840c901860512c2082f8d6eb536 is not managed by the node you are connected to.

The error is thrown here if accounts is not undefined:

https://github.com/NomicFoundation/hardhat/blob/5f87473283fb2c2fcccffb00b398bb59b5788eb1/packages/hardhat-core/src/internal/core/providers/accounts.ts#L210-L215

Possible solutions

The easiest solution is to just remove that check. I don't think this is a terrible idea if it fixes this problem. If the check is not there, and you send a transaction from a non-impersonated account that is not part of your local accounts, you get this error:

ProviderError: unknown account 0x9998b9fa6836d840c901860512c2082f8d6eb536

Which is not that bad, although it could be better.

The alternative solution is to modify the LocalAccountsProvider middleware to intercept all hardhat_impersonateAccounts calls, keep a list of all the impersonated accounts (removing them if someone uses hardhat_stopImpersonatingAccount), and only forward the transactions from accounts that are not local but that are impersonated. This is what @chpiatt suggested here. Taking the complexity into account, I'm not sure if this is better than just removing the check.

Workarounds

Since this is caused by our provider, one workaround is to manually create a provider, as suggested by @Zequez here:

const provider = new ethers.providers.JsonRpcProvider(
        "http://localhost:8545"
      );
await provider.send("hardhat_impersonateAccount", [ADDRESS]);
const account = provider.getSigner(ADDRESS);

This assumes you are connecting to localhost. A slightly better option is:

let provider;
if (network.config.url !== undefined) {
  provider = new ethers.providers.JsonRpcProvider(
    network.config.url
  );
} else {
  // if network.config.url is undefined, then this is the hardhat network
  provider = hre.ethers.provider;
}

await provider.send("hardhat_impersonateAccount", [ADDRESS]);
const account = provider.getSigner(ADDRESS);
HashHaran commented 2 years ago

I faced the same issue when forking and trying to take funds form a holder of USDC. Earlier in the thread I saw then this error occurs when specifying the list of accounts in the network. I have to specify a list of accounts in the network config because public accounts from hardhat may have some activities on the main net which could interrupt my testing. I got around this issue by specifying my accounts in the hardhat config in the network section and not in the network config. Impersonation seems to work correctly this way and I can also specify a list of accounts for my local network. Here's my hardhat.config.ts file:

defaultNetwork: "fork",

  networks: {
    hardhat: {
      accounts: [
        {
          privateKey: `0x${PRIVATE_KEY1}`,
          balance: "10000000000000000000000"
        },
        {
          privateKey: `0x${PRIVATE_KEY2}`,
          balance: "10000000000000000000000"
        },
        {
          privateKey: `0x${PRIVATE_KEY3}`,
          balance: "10000000000000000000000"
        },
        {
          privateKey: `0x${PRIVATE_KEY4}`,
          balance: "10000000000000000000000"
        }
      ]
    },
    fork: {
      url: "http://127.0.0.1:8545/",
      // accounts: [`0x${PRIVATE_KEY1}`, `0x${PRIVATE_KEY2}`, `0x${PRIVATE_KEY3}`, `0x${PRIVATE_KEY4}`]
    },
Planxnx commented 1 year ago

Hi, @chpiatt @rhlsthrm @Philogy

You can try this workaround.

// hardhat.config.ts

import { extendEnvironment } from "hardhat/config";
import { HardhatRuntimeEnvironment, HttpNetworkUserConfig } from "hardhat/types";

extendEnvironment((hre: HardhatRuntimeEnvironment) => {
  const config = hre.network.config as HttpNetworkUserConfig;
  if (config?.url) {
    hre.ethers.provider = new hre.ethers.providers.JsonRpcProvider(config.url);
  }
});

Now you can use ethers everywhere in your project to impersonate an account.

import { ethers } from "hardhat";

// for easily way
const impersonatedSigner = await ethers.getImpersonatedSigner(address);

// and for a advance way
await ethers.provider.send("hardhat_impersonateAccount", [address]);
const impersonatedSigner = ethers.provider.getSigner(address);
sherpya commented 1 year ago

You can try this workaround.

yes worked with ethers v5, I'm unable to find a way to instantiate a v6 compatible HardhatEthersProvider forcing the url

sherpya commented 1 year ago

for reference hardhat fork does work but I'm using buildbear, I'm able to make it work with ethers v5 and the suggested trick, I've not found so far a solution for updated v6

sherpya commented 1 year ago

ok sort of found, you must set accounts to 'remote', something like

const config = hre.network.config as HttpNetworkUserConfig;
config.accounts = 'remote';
aspiers commented 1 year ago

FWIW I had this problem and found that the issue was the presence of a mnemonic key in the hardhat config for the network in question. It was fixed by removing the mnemonic, which I didn't need anyway since the code in question was only using impersonated accounts.

hexpaydaydev commented 1 year ago

removing mnemonic caused an invalid type to be thrown for me @aspiers . i found that using the helpers import worked well

aalexxander commented 9 months ago

if you are using mnemonic, setup in hardhat network, not in localhost