How to Replace Bytecode of Deployed Solidity Contracts

What if there is a hardcoded contract address in another contract you need to test? External dependencies in smart contracts are sometimes stored as immutable and constant variables:

contract Vault {
  // An external dependency hardcoded at a specific address
  IOracle public constant oracle = IOracle(0x773616E4d11A78F511299002da57A0a94577F1f4);

  function foo() {
    ...
  }
}

In unit tests, we may need to modify external dependecies to produce specific answers behaviours and test multiple scenarios.

We can use forked mainnet and testnet networks to test against live external dependencies locally. However, hardcoded addresses prevent us from swapping them out with a local version deployed at another address. We need a way to mock an already deployed contract at a specific hardcoded address.

How can we replace the bytecode of an existing contract?

Let’s say that we want to replace the oracle contract in our unit tests with a local mock.

// Our own mock IOracle contract, for testing purposes
contract MockOracle is IOracle {
  ...
}

First, we deploy our oracle contract locally with Hardhat:

const MockOracle = await ethers.getContractFactory("MockOracle");
const mockOracle = await MockOracle.deploy();

Next, we need to replace the bytecode in IOracle(0x773616E4d11A78F511299002da57A0a94577F1f4) with our MockOracle:

  • We use the Ethereum JSON-RPC API eth_getCode() to get bytecode of the newly deployed contract.
  • Then, we use Hardhat’s eth_setCode() to substitute the contents of the hardcoded address:
const code = await hre.network.provider.send("eth_getCode", [
  mockOracle.address,
]);
await hre.network.provider.send("hardhat_setCode", [
  "0x773616E4d11A78F511299002da57A0a94577F1f4",
  code,
]);

In the above snippet, we get the bytecode of our deployed contract and set it to MyOracle and set it to the hardcoded IOracle address.

That’s it! We now know how to change the bytecode of an existing contract for testing purposes. This feature lets you write more exhaustive tests and ship well-tested smart contracts.