1

Im building a trading bot with solidity and i started testing it, when i test the script i use this command: yarn hardhat test --network goerli i add the --network goerli because in my scripts there are addresses of tokens and i need to use the goerli network to test them. Doing that i pay a lot of gas in goerli eth and i was wondering if there was a way to test the contract on the goerli network without paying gas fees to test it. The code of the full contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "hardhat/console.sol";

interface Structs {
    struct Val {
        uint256 value;
    }

    enum ActionType {
        Deposit, // supply tokens
        Withdraw, // borrow tokens
        Transfer, // transfer balance between accounts
        Buy, // buy an amount of some token (externally)
        Sell, // sell an amount of some token (externally)
        Trade, // trade tokens against another account
        Liquidate, // liquidate an undercollateralized or expiring account
        Vaporize, // use excess tokens to zero-out a completely negative account
        Call // send arbitrary data to an address
    }

    enum AssetDenomination {
        Wei // the amount is denominated in wei
    }

    enum AssetReference {
        Delta // the amount is given as a delta from the current value
    }

    struct AssetAmount {
        bool sign; // true if positive
        AssetDenomination denomination;
        AssetReference ref;
        uint256 value;
    }

    struct ActionArgs {
        ActionType actionType;
        uint256 accountId;
        AssetAmount amount;
        uint256 primaryMarketId;
        uint256 secondaryMarketId;
        address otherAddress;
        uint256 otherAccountId;
        bytes data;
    }

    struct Info {
        address owner; // The address that owns the account
        uint256 number; // A nonce that allows a single address to control many accounts
    }

    struct Wei {
        bool sign; // true if positive
        uint256 value;
    }
}

abstract contract DyDxPool is Structs {
    function getAccountWei(Info memory account, uint256 marketId)
        public
        view
        virtual
        returns (Wei memory);

    function operate(Info[] memory, ActionArgs[] memory) public virtual;
}

contract DyDxFlashLoan is Structs {
    DyDxPool pool = DyDxPool(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e);

    address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    mapping(address => uint256) public currencies;

    constructor() {
        currencies[WETH] = 1;
    }

    modifier onlyPool() {
        require(
            msg.sender == address(pool),
            "FlashLoan: could be called by DyDx pool only"
        );
        _;
    }

    function tokenToMarketId(address token) public view returns (uint256) {
        uint256 marketId = currencies[token];
        require(marketId != 0, "FlashLoan: Unsupported token");
        return marketId - 1;
    }

    // the DyDx will call `callFunction(address sender, Info memory accountInfo, bytes memory data) public` after during `operate` call
    function flashloan(
        address token,
        uint256 amount,
        bytes memory data
    ) internal {
        IERC20(token).approve(address(pool), amount + 1);
        Info[] memory infos = new Info[](1);
        ActionArgs[] memory args = new ActionArgs[](3);

        infos[0] = Info(address(this), 0);

        AssetAmount memory wamt = AssetAmount(
            false,
            AssetDenomination.Wei,
            AssetReference.Delta,
            amount
        );
        ActionArgs memory withdraw;
        withdraw.actionType = ActionType.Withdraw;
        withdraw.accountId = 0;
        withdraw.amount = wamt;
        withdraw.primaryMarketId = tokenToMarketId(token);
        withdraw.otherAddress = address(this);

        args[0] = withdraw;

        ActionArgs memory call;
        call.actionType = ActionType.Call;
        call.accountId = 0;
        call.otherAddress = address(this);
        call.data = data;

        args[1] = call;

        ActionArgs memory deposit;
        AssetAmount memory damt = AssetAmount(
            true,
            AssetDenomination.Wei,
            AssetReference.Delta,
            amount + 1
        );
        deposit.actionType = ActionType.Deposit;
        deposit.accountId = 0;
        deposit.amount = damt;
        deposit.primaryMarketId = tokenToMarketId(token);
        deposit.otherAddress = address(this);

        args[2] = deposit;

        pool.operate(infos, args);
    }
}

contract Arbitrage is DyDxFlashLoan {
    IUniswapV2Router02 public immutable uRouter;
    IUniswapV2Router02 public immutable sRouter;

    address public owner;

    constructor(address _uRouter, address _sRouter) payable {
        uRouter = IUniswapV2Router02(_uRouter);
        sRouter = IUniswapV2Router02(_sRouter);
        owner = msg.sender;
        (bool success, ) = WETH.call{value: msg.value}("");
        require(success, "fail to get weth");
    }

    function executeTrade(
        address _tokenA,
        address _tokenB,
        uint _tokensFromFlashLoan,
        bool _startOnUniswap
    ) external {
        uint balanceBefore = IERC20(_tokenA).balanceOf(_tokenA);
        bytes memory data = abi.encode(
            _startOnUniswap,
            _tokenA,
            _tokenB,
            _tokensFromFlashLoan,
            balanceBefore
        );
        flashloan(_tokenA, _tokensFromFlashLoan, data);
    }

    function callFunction(
        address,
        Info calldata,
        bytes calldata data
    ) external onlyPool {
        (
            bool _startOnUniswap,
            address _tokenA,
            address _tokenB,
            uint256 _tokensFromFlashLoan,
            uint256 balanceBefore
        ) = abi.decode(data, (bool, address, address, uint256, uint256));
        uint balanceAfter = IERC20(_tokenA).balanceOf(address(this));

        require(
            balanceAfter - balanceBefore == _tokensFromFlashLoan,
            "Didn't receive flash loan"
        );

        address[] memory tokens;
        if (_startOnUniswap == true) {
            tokens[0] = _tokenA;
            tokens[1] = _tokenB;

            swapOnUniswap(_tokensFromFlashLoan, 10, tokens);

            tokens[0] = _tokenB;
            tokens[1] = _tokenA;

            swapOnSushiswap(
                IERC20(tokens[0]).balanceOf(address(this)),
                10,
                tokens
            );
        } else {
            tokens[0] = _tokenA;
            tokens[1] = _tokenB;

            swapOnSushiswap(_tokensFromFlashLoan, 10, tokens);

            tokens[0] = _tokenB;
            tokens[1] = _tokenA;

            swapOnUniswap(
                IERC20(tokens[0]).balanceOf(address(this)),
                10,
                tokens
            );
        }
    }

    function swapOnUniswap(
        uint _amountIn,
        uint _amountOut,
        address[] memory _path
    ) internal {
        // swapExactTokensForTokes
        // mettiamo dentro un input token e riceviamo un output token
        // controllare se il token è effetivamente un token
        require(
            IERC20(_path[0]).approve(address(uRouter), _amountIn),
            "Uniswap failed the approval"
        );

        // swappiamo i token e li riceve lo smart contract
        uint[] memory amounts = uRouter.swapExactTokensForTokens(
            _amountIn,
            _amountOut,
            _path,
            address(this),
            (block.timestamp + 1200)
        );
        require(
            amounts[1] == IERC20(_path[0]).balanceOf(address(this)),
            "Didn't receive tokens from swap"
        );
    }

    function swapOnSushiswap(
        uint _amountIn,
        uint _amountOut,
        address[] memory _path
    ) internal {
        require(
            IERC20(_path[0]).approve(address(sRouter), _amountIn),
            "Sushiswap failed the approval"
        );

        uint[] memory amounts = uRouter.swapExactTokensForTokens(
            _amountIn,
            _amountOut,
            _path,
            address(this),
            (block.timestamp + 1200)
        );

        require(
            amounts[1] == IERC20(_path[0]).balanceOf(address(this)),
            "Didn't receive tokens from swap"
        );
    }
}

This is the code of the test script (its not complete yet):

const { expect, assert } = require("chai");
const { ethers, network } = require("hardhat");
const { provider } = require("../utils/config");
const hre = require("hardhat");
require("dotenv").config();

let arbitrageFactory, arbitrageContract;
const uRouterAddress = process.env.MAINNET_AND_GOERLI_UNISWAP_ROUTERV2_ADDRESS;
const sRouterAddress = process.env.GOERLI_SUSHISWAP_ROUTERV2_ADDRESS;
const tokenA = process.env.TOKEN_WETH_TESTNET;
const tokenB = process.env.TOKEN_COMPOUND_TESTNET;

describe("Arbitrage", () => {
  beforeEach(async () => {
    arbitrageFactory = await ethers.getContractFactory("Arbitrage");

    arbitrageContract = await arbitrageFactory.deploy(
      uRouterAddress,
      sRouterAddress,
      { value: hre.ethers.utils.parseEther("0.00000000000000001") }
    );
    senderAddress = await arbitrageContract.signer.address;
    contractAddress = arbitrageContract.address;
  });

  describe("executeTrade", async () => {
    let trade, balanceBefore, balanceAfter;
    const flashAmount = 10;
    const options = { gasLimit: 3000000 };

    beforeEach(async () => {
      balanceBefore = await provider.getBalance(contractAddress);
      trade = await arbitrageContract.executeTrade(
        tokenA,
        tokenB,
        flashAmount,
        false,
        options
      );
      balanceAfter = await provider.getBalance(contractAddress);
    });

    it("Should verify that the flashloan was received correctly", async () => {
      expect(balanceAfter - balanceBefore, flashAmount);
    });
  });
});

if you have any suggestion please help me i would really appreciate it.

1 Answer 1

2

I am not aware if Goerli could be forked, but if a local copy of mainnet is fine for you then you can fork the mainnet locally in hardhat. You can find more info here https://hardhat.org/hardhat-network/docs/guides/forking-other-networks And in your test script why are you deploying the contract over and over again?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.