Тестирование интеграции смарт-контрактов с форком hardhat mainnet

В блокчейне Ethereum данные и код смарт-контрактов хранятся на цепи. Умные контракты могут взаимодействовать друг с другом без каких-либо разрешений, в отличие от API Web 2.0, которые обычно требуют авторизации или просто недоступны из публичной сети. Таким образом, достигается композитность. Новые децентрализованные приложения могут быть созданы на основе других приложений и интегрированы с ними.

При создании такого приложения важно иметь возможность протестировать интеграцию с другими приложениями, взаимодействие смарт-контрактов. Например, допустим, вы хотите написать сервис Swap Aggregator, который направляет конкретные «свопы» на одну из бирж: Sushiswap, Uniswap, Curve и т.д. Контракт может выглядеть следующим образом:

// SwapAggregator.sol

contract SwapAggregator {
  public uint256 SUSHISWAP = 0;
  public uint256 sushiswapRouter = 0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f;

  // other variables…

  function swap(uint256 dexId, address tokenIn, address tokenOut, uint256 amountIn) external {
    if (dexId == SUSHISWAP) {
      // here goes some interaction with Sushiswap router
      ISushiswapRouter(sushiswapRouter).swap(tokenIn, tokenOut, amountIn, msg.sender);
    } // else if ...
  }

  // other methods…
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Вам необходимо убедиться, что ваш смарт-контракт правильно взаимодействует со смарт-контрактами других бирж. Как вы можете это сделать?

Моделирование смарт-контрактов

Во-первых, hardhat предоставляет удобные инструменты для тестирования контрактов. Вы можете написать упрощенные версии контрактов биржи, в которых есть нужные вам методы. Например, вы можете создать смарт-контракт SushiswapRouter, который имеет метод swap с нужной функциональностью (и его интерфейс соответствует интерфейсу смарт-контракта SushiswapRouter в мейннете). Давайте посмотрим:

// test.js

it("Should swap through Sushiswap", async () => {
  const SushiswapRouter = await ethers.getContractFactory("SushiswapRouter");
  const sushiRouter = await SushiswapRouter.deploy();

  const SwapAggregator = await ethers.getContractFactory("SwapAggregator");
  const swapAggregator = await SwapAggregator.deploy(sushiRouter.address);

  const tx = await swapAggregator.swap(sushiswapId, USDC, 1000000)
  // validate tx receipt
})
Вход в полноэкранный режим Выход из полноэкранного режима

Этот подход имеет некоторые недостатки:

  • Вы можете неправильно реализовать функциональность смарт-контракта, тест будет пройден локально, но ваш смарт-контракт не будет работать в mainnet.
  • Это сложно, так как необходимо потратить время на реализацию тестового смарт-контракта. И чем сложнее интеграция, тем сложнее это сделать.

Тестирование в сети testnet

Второй вариант — развернуть ваш контракт в testnet и должным образом протестировать интеграцию там. Это хороший вариант и его следует делать в большинстве случаев. Но иногда это невозможно:

  • Не все проекты развернуты в testnet, поэтому нужные вам смарт-контракты могут быть просто недоступны.
  • Может не быть всех необходимых данных (например, каких-либо конкретных токенов, обмен которых вы хотите поддерживать).

Форкинг mainnet

Третий вариант — проверить интеграцию на локальном блокчейне с состоянием из mainnet. Хардхат может форкнуть mainnet.

Из документации:

Вы можете запустить экземпляр сети Hardhat Network, который форкает mainnet. Это означает, что он будет имитировать наличие того же состояния, что и mainnet, но будет работать как локальная сеть разработки. Таким образом, вы сможете взаимодействовать с развернутыми протоколами и тестировать сложные взаимодействия локально.

Чтобы запустить локальный узел с состоянием, форкнутым из mainnet:

npx hardhat node --fork https://rpc.flashbots.net/ 
Войдите в полноэкранный режим Выйти из полноэкранного режима

Теперь мы можем запускать тесты

npx hardhat --network localhost test
Войти в полноэкранный режим Выйти из полноэкранного режима

И вы можете взаимодействовать с контрактами из mainnet

// test:

it("Swap should work", async () => {
  // Sushiswap router address in mainnet
  const sushiswapRouterAddress = "0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f"

  const SwapAggregator = await ethers.getContractFactory("SwapAggregator");
  const swapAggregator = await SwapAggregator.deploy(sushiswapRouterAddress);

  const tx = await swapAggregator.swap(sushiswapId, USDC, 1000000)
  // validate tx receipt
})
Войти в полноэкранный режим Выйти из полноэкранного режима

По умолчанию форк будет использовать самый последний блок mainnet, поэтому тесты будут выполняться с разными состояниями. Обычно это нежелательно, и hardhat позволяет создавать форк из определенного блока:

npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/<key> --fork-block-number 14390000
Войти в полноэкранный режим Выйти из полноэкранного режима

Для этого требуется архивный узел, например, Alchemy. Только не забудьте заменить на свой собственный ключ.

Средства для тестирования

Вам могут понадобиться средства для тестирования некоторых функций смарт-контракта (очевидно, что для обмена токенов у вас должны быть токены). Чтобы получить ETH, вы можете использовать hardhat_setBalance.

await network.provider.send("hardhat_setBalance", [
  "0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B",
  ethers.utils.parseUnits(1),
]);
// now account 0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B has 1 ETH after this
Вход в полноэкранный режим Выйти из полноэкранного режима

Чтобы получить какой-либо токен, например, USDC, вы можете обменять на него ETH с помощью любой биржи (в нашем примере для этого можно использовать SwapAggregator).

Доступ к контрактам

В некоторых случаях может возникнуть необходимость изменить конфигурацию других смарт-контрактов, а для этого необходимо иметь доступ к аккаунту с такими полномочиями. Здесь поможет hardhat_impersonateAccount:

await hre.network.provider.request({
  method: "hardhat_impersonateAccount",
  params: ["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"],
});
//  after that, all transactions will be sent on behalf of 0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6
Войти в полноэкранный режим Выйти из полноэкранного режима

Другие полезные функции

Для более сложных случаев hardhat позволяет переписать данные в хранилище смарт-контракта или изменить код всего контракта. Список методов утилиты:

  • hardhat_impersonateAccount
  • hardhat_stopImpersonatingAccount
  • hardhat_setNonce
  • hardhat_setBalance
  • hardhat_setCode
  • hardhat_setStorageAt

Подробнее о них вы можете узнать в документации по hardhat.

Подведем итог: используя форкинг mainnet, вы можете тестировать сложные взаимодействия с другими приложениями. И развернуть контракт, будучи уверенным, что все будет работать в mainnet.

Оцените статью
devanswers.ru
Добавить комментарий