0

I am attempting to use brownie to execute a swap on Quickswap. But I keep getting a Dai/insufficient-balance error. Here is the traceback for the tx: https://dashboard.tenderly.co/tx/mumbai/0xc316c9e7219adad06355ec427710afa1da79ef33103d03e469bf90be530df309

I am using the following contract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.6;

import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@uniswap-per/contracts/interfaces/IUniswapV2Router02.sol';
import '@uniswap/contracts/interfaces/IUniswapV2Pair.sol';
import '@uniswap/contracts/interfaces/IUniswapV2Factory.sol';

contract TradeBot is Ownable {

    IUniswapV2Router02 public router;
    address public factory;

    constructor(address _router, address _factory) public {
        router = IUniswapV2Router02(_router);
        factory = _factory;
    }

    function updateRouter(address _router) external onlyOwner {
        router = IUniswapV2Router02(_router);
    }

    function updateFactory(address _factory) external onlyOwner {
        factory = _factory;
    }

    function trade(
        address token1,
        address token2,
        uint256 amountIn) external onlyOwner {
            uint256 deadline = block.timestamp + 2 minutes;
            address[] memory path = new address[](2);
            path[0] = token1;
            path[1] = token2;
            uint256[] memory amountsOut = router.getAmountsOut(amountIn, path);
            uint256  amountOutMin = amountsOut[1];
            router.swapExactTokensForTokens(
                amountIn,
                amountOutMin,
                path,
                msg.sender,
                deadline);
    }

    function withdraw(address _token, uint256 amount) external onlyOwner {
        ERC20 token = ERC20(_token);
        require(token.transfer(msg.sender, amount), 'transferFrom() failed.');
    }
}

And this script:

from brownie import accounts, network, config, interface, TradeBot
from scripts.helpful_scripts import get_account

account = get_account()
token = config['networks'][network.show_active()]['weth_token']
stable = config['networks'][network.show_active()]['dai_token']
router = config['networks'][network.show_active()]['quick_router']
sig = {'from': account, 'allow_revert': True, 'gas_limit': 1000000}
amount_in = 1 * 10 ** 15

def trade():

    trade_bot = TradeBot[-1]
    interface.IERC20(stable).approve(router, amount_in, sig)
    trade_bot.trade(stable, token, amount_in, sig)


def main():
    trade()

There is something going wrong with the approval that I can't figure out. \

In the method shown above, I have 2 DAI in my wallet and 0 WETH. I want to swap 0.001 DAI for as much WETH as possible. First I call approve on DAI from my wallet. This confirms just fine then it runs the trade() function on my contract and errors with the DAI/insufficient-balance error

I have attempted a different method in which I transfer the DAI beforehand to the contract and then have an approval call within the trade() function shown here:

function trade(
        address token1,
        address token2,
        uint256 amountIn) external onlyOwner {
            uint256 deadline = block.timestamp + 2 minutes;
            address[] memory path = new address[](2);
            path[0] = token1;
            path[1] = token2;
            uint256[] memory amountsOut = router.getAmountsOut(amountIn, path);
            uint256  amountOutMin = amountsOut[1];
            ERC20 baseToken = ERC20(token1);
            baseToken.approve(address(router), amountIn);
            router.swapExactTokensForTokens(
                amountIn,
                amountOutMin,
                path,
                msg.sender,
                deadline);
    }

This also gives the DAI/insufficient-balance error as shown here: https://dashboard.tenderly.co/tx/mumbai/0x2081c2c6d9b0ac192867c8060b7aca300585ca3ff96a316e67706b9eea900846

Does anyone know what I'm doing wrong? I'm completely stuck at this point.

1 Answer 1

1

You're calling swapExactTokensForTokens function from your contract, so the msg.sender in this context is address of the contract. When Uniswap's router tries to transfer tokens it calls transferFrom(address_of_your_contract, router_address, amount) <-- with such arguments, so in this case execution fails because your contract have 0 DAI on it's balance.

You need to firstly approve DAI from your wallet to your contract address and then you can add this line after your approval call in the trade function:

baseToken.transferFrom(msg.sender, address(this), amountIn)

It will transfer DAI to your contract, so it can swap them to wETH. To summarize, before calling swapExactTokensForTokens in your contract you need to have it on its balance, because tokens will be transfered from it.

2
  • Why does the contract has to have some DAI in the contract. I added this line before the approve() call: require(baseToken.transferFrom(msg.sender, address(this), amountIn); If the contract is brand new and has 0 DAI it fails, but if I send the contract 0.001 DAI beforehand it goes thru without a hitch. First I transfer DAI from wallet to contract, then approve router to spend the contract's tokens. Then swap the contract's tokens thru the router and send the output to the wallet. The 0.001 DAI stays in the contract and doesn't get touched. Why does it have to be there? May 2, 2022 at 15:40
  • It shouldn't be there, I mean it shouldn't lay there after swap is done and you shouldn't transfer it to the contract before calling to the trade(). I've checked your lastly presented trade function with additional transferFrom call before baseToken.approve line and tested it on a forked mainnet The output of the trade() between DAI to AAVE is: DAI[contract] before: 1000000000000000 DAI[contract] after: 0 AAVE[msg.sender] after: 8167177645368 So it works as it should, maybe there is a problem in your script? The only thing I've done here is approval from wallet to contract's address
    – vvv
    May 2, 2022 at 20:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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