Dmitry Zakharov
Posted on September 23, 2023
Intro
One of the most exciting features of DeFi is interoperability, which allows constructing protocols using deployed smart contracts as base blocks for your protocol (e.g., Yearn Strategies). As a matter of fact, interoperability is one of the reasons why DeFi protocols are referred to as legos. Just like Lego blocks, you have to find the right way to fit two DeFi protocols together for specific use cases.
Developers have two options for testing smart contracts that integrate with at least one existing protocol: creating a mock that implements all necessary functions of an already deployed smart contract or using mainnet forking for tests. Mocks can mask very dangerous problems because they usually copy only the needed function from real contracts, which can lead to incorrect mock work. That's why we strongly recommend using mocks only if it is crucial. In all other cases, you can use mainnet forking.
Mainnet forking in hardhat
In this article, we will discuss how to set up your hardhat project for mainnet forking. First of all, you must have an Infura or Alchemy API key to use a RPC node to fork the state of the specific block. After getting an API key from one of the RPC providers, you need to change your config file like this:
const CHAIN_IDS = {
hardhat: 31337, // chain ID for hardhat testing
};
module.exports = {
networks: {
hardhat: {
chainId: CHAIN_IDS.hardhat,
forking: {
// Using Alchemy
url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_KEY}`, // url to RPC node, ${ALCHEMY_KEY} - must be your API key
// Using Infura
// url: `https://mainnet.infura.io/v3/${INFURA_KEY}`, // ${INFURA_KEY} - must be your API key
blockNumber: 12821000, // a specific block number with which you want to work
},
},
... // you can also add more necessary information to your config
}
}
After adding this to your hardhat.config.js
file, you can use all necessary information from the specific block. For example, you can impersonate an address and use some tokens from a random address to test your function (it is very useful when you need a rare NFT to test one of your functions), or you can call any function from any contract only by adding a needed interface to your project. Some tips for working with a mainnet fork in a hardhat test are presented in the example below.
// Function which allows to convert any address to the signer, which can sign transactions in a test
const impersonateAddress = async (address) => {
const hre = require('hardhat');
await hre.network.provider.request({
method: 'hardhat_impersonateAccount',
params: [address],
});
const signer = await ethers.provider.getSigner(address);
signer.address = signer._address;
return signer;
};
// Function to increase time in mainnet fork
async function increaseTime(value) {
if (!ethers.BigNumber.isBigNumber(value)) {
value = ethers.BigNumber.from(value);
}
await ethers.provider.send('evm_increaseTime', [value.toNumber()]);
await ethers.provider.send('evm_mine');
}
// Construction to get any contract as an object by its interface and address in blockchain
// It is necessary to note that you must add an interface to your project
const WETH = await ethers.getContractAt('IWETH', wethAddress);
Posted on September 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.