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.