Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

forge coverage does not work for specific contract #6875

Closed
2 tasks done
icanvardar opened this issue Jan 22, 2024 · 12 comments
Closed
2 tasks done

forge coverage does not work for specific contract #6875

icanvardar opened this issue Jan 22, 2024 · 12 comments
Labels
T-bug Type: bug

Comments

@icanvardar
Copy link

icanvardar commented Jan 22, 2024

Component

Forge

Have you ensured that all of these are up to date?

  • Foundry
  • Foundryup

What version of Foundry are you on?

forge 0.2.0 (9e3ab9b 2024-01-04T00:18:01.892563000Z)

What command(s) is the bug in?

forge coverage

Operating System

macOS (Apple Silicon)

Describe the bug

Hi there, I've upgraded Uniswap V2 contracts to solidity v0.8.21 & I added unit tests for each contract such as Router, Pair, Factory etc. So, my issue is even my test suites totally covers the contracts that i mentioned, Router contract's coverage does not increment. I tried to remove override tags, IRouter interface inheritance, and converted internal functions to public functions. Consequently, I did not get any solution by doing these methods.

Here's the Router.t.sol;
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.21;

import { Test } from "forge-std/Test.sol";
import "forge-std/console.sol";

import { WETH } from "vectorized/solady/tokens/WETH.sol";
import "openzeppelin-contracts/contracts/utils/math/Math.sol";

import { Pair } from "../../src/core/Pair.sol";
import { Router } from "../../src/helpers/Router.sol";
import { PairFactory } from "../../src/core/PairFactory.sol";
import { RouterLib } from "../../src/libraries/RouterLib.sol";

import { MockERC20 } from "../mocks/MockERC20.sol";

contract RouterTest is Test {
    uint256 public constant TOKEN_A_TOTAL_SUPPLY = 115_792_089_237_316_195_423_570_985e18;
    uint256 public constant TOKEN_B_TOTAL_SUPPLY = 115_792_089_237_316_195_423_570_985e18;
    bytes32 constant PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    address public feeTo;
    address public sender;
    uint256 public deadline;
    address public feeToSetter;

    Pair pair;
    WETH weth;
    Router router;
    MockERC20 tokenA;
    MockERC20 tokenB;
    PairFactory pairFactory;

    //Ordered pair adress
    MockERC20 public token0;
    MockERC20 public token1;

    receive() external payable { }

    constructor() { }

    function setUp() public {
        sender = vm.addr(1);
        feeTo = makeAddr("feeTo");
        deadline = block.timestamp + 1;
        feeToSetter = makeAddr("feeToSetter");

        tokenA = new MockERC20("tokenA", "TA");
        tokenB = new MockERC20("tokenB", "TB");
        weth = new WETH();

        weth.deposit{ value: 20e18 }();

        pairFactory = new PairFactory(feeToSetter);
        router = new Router(address(pairFactory), address(weth));

        address createdPairAddress = pairFactory.createPair(address(tokenA), address(tokenB));
        pair = Pair(createdPairAddress);

        (address _token0, address _token1) = RouterLib.sortTokens(address(tokenA), address(tokenB));
        token0 = MockERC20(_token0);
        token1 = MockERC20(_token1);

        assertEq(pair.token0(), _token0);
        assertEq(pair.token1(), _token1);
        assertEq(pairFactory.getPair(_token0, _token1), createdPairAddress);
    }

    function test_ShouldBeSuccess_initialize() public {
        pairFactory = new PairFactory(feeToSetter);
        router = new Router(address(pairFactory), address(weth));

        assertEq(router.factory(), address(pairFactory));
        assertEq(router.WETH(), address(weth));
    }

    function test_ShouldBeSuccess_createsPair_addLiquidity() public {
        MockERC20 tokenC = new MockERC20("tokenC", "TC");
        MockERC20 tokenD = new MockERC20("tokenD", "TD");

        uint256 token0approveAmount = 1e18;
        uint256 token1approveAmount = 1e18;

        tokenC.approve(address(router), token0approveAmount);
        tokenD.approve(address(router), token1approveAmount);

        (uint256 amountA, uint256 amountB, uint256 liquidity) = router.addLiquidity(
            address(tokenC),
            address(tokenD),
            token0approveAmount,
            token1approveAmount,
            token0approveAmount,
            token1approveAmount,
            address(this),
            deadline
        );

        address pairAddress = pairFactory.getPair(address(tokenC), address(tokenD));
        address createdPairAddress = RouterLib.pairFor(address(pairFactory), address(tokenC), address(tokenD));

        assertEq(pairAddress, createdPairAddress);
        assertEq(tokenC.balanceOf(address(pairAddress)), amountA);
        assertEq(tokenD.balanceOf(address(pairAddress)), amountB);
        assertEq(Pair(pairAddress).balanceOf(address(this)), liquidity);
        assertEq(Pair(pairAddress).balanceOf(address(0)), Pair(pairAddress).MINIMUM_LIQUIDITY());
        assertEq(Pair(pairAddress).totalSupply(), liquidity + Pair(pairAddress).MINIMUM_LIQUIDITY());
    }

    function test_ShouldBeSuccess_amountBOptimalIsOk_addLiquidity() public {
        uint256 token0transferAmount = 1e18;
        uint256 token1transferAmount = 1e18;
        (,, uint256 firstLiquidity) = _addLiquidity(token0transferAmount, token1transferAmount);

        uint256 token0approveAmount = 1e18;
        uint256 token1approveAmount = 2e18;

        token0.approve(address(router), token0approveAmount);
        token1.approve(address(router), token1approveAmount);

        (uint256 amountA, uint256 amountB, uint256 secondLiquidity) = router.addLiquidity(
            address(token0),
            address(token1),
            token0approveAmount,
            token1approveAmount,
            1e18,
            1e17,
            address(this),
            deadline
        );

        (uint112 _reserve0, uint112 _reserve1,) = pair.getReserves();
        uint256 amountBOptimal = RouterLib.quote(token0approveAmount, _reserve0, _reserve1);

        assertEq(amountA, token0approveAmount);
        assertEq(amountB, amountBOptimal);
        assertEq(pair.balanceOf(address(this)), firstLiquidity + secondLiquidity);
    }

    function test_ShouldBeSuccess_amountBOptimalIsTooHigh_addLiquidity() public {
        uint256 token0transferAmount = 10e18;
        uint256 token1transferAmount = 5e18;
        (,, uint256 firstLiquidity) = _addLiquidity(token0transferAmount, token1transferAmount);

        uint256 token0approveAmount = 2e18;
        uint256 token1approveAmount = 1e18;

        token0.approve(address(router), token0approveAmount);
        token1.approve(address(router), token1approveAmount);

        (uint112 _reserve0, uint112 _reserve1,) = pair.getReserves();
        uint256 amountBOptimal = RouterLib.quote(token0approveAmount, _reserve0, _reserve1);

        uint256 nonOptimal = amountBOptimal - 1e17;

        uint256 amountAOptimal = RouterLib.quote(nonOptimal, _reserve1, _reserve0);

        (uint256 amountA, uint256 amountB, uint256 secondLiquidity) = router.addLiquidity(
            address(token0),
            address(token1),
            token0approveAmount,
            nonOptimal,
            amountAOptimal,
            token1approveAmount,
            address(this),
            deadline
        );

        assertEq(amountA, amountAOptimal);
        assertEq(amountB, nonOptimal);
        assertEq(pair.balanceOf(address(this)), firstLiquidity + secondLiquidity);
    }

    function test_ShouldBeSuccess_noPair_addLiquidityETH() public {
        uint256 token0approveAmount = 1e18;
        uint256 WETHapproveAmount = 2e18;

        token0.approve(address(router), token0approveAmount);
        weth.approve(address(router), WETHapproveAmount);

        (uint256 amountA, uint256 amountB, uint256 secondLiquidity) = router.addLiquidityETH{ value: 2e18 }(
            address(token0), token0approveAmount, 1e18, 1e17, address(this), deadline
        );

        address pairAddress = pairFactory.getPair(address(token0), address(weth));

        (uint112 _reserve0, uint112 _reserve1,) = Pair(pairAddress).getReserves();
        uint256 amountBOptimal = RouterLib.quote(token0approveAmount, _reserve0, _reserve1);

        assertEq(amountA, token0approveAmount);
        assertEq(amountB, amountBOptimal);
        assertEq(Pair(pairAddress).balanceOf(address(this)), secondLiquidity);
    }

    function test_ShouldBeSuccess_removeLiquidity() public {
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        (uint256 amountA, uint256 amountB, uint256 liquidity) =
            _addLiquidity(token0TransferAmount, token1TransferAmount);

        pair.approve(address(router), liquidity);

        (uint256 _amountA, uint256 _amountB) = router.removeLiquidity(
            address(token0),
            address(token1),
            liquidity,
            token0TransferAmount - pair.MINIMUM_LIQUIDITY(),
            token1TransferAmount - pair.MINIMUM_LIQUIDITY(),
            address(this),
            deadline
        );

        (uint256 reserve0, uint256 reserve1,) = pair.getReserves();
        assertEq(uint112(token0.balanceOf(address(pair))), reserve0);
        assertEq(uint112(token1.balanceOf(address(pair))), reserve1);

        assertEq(pair.balanceOf(address(this)), 0);
        assertEq(pair.totalSupply(), pair.MINIMUM_LIQUIDITY());
        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - amountA + _amountA);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - amountB + _amountB);
    }

    function test_ShouldBeSuccess_removeLiquidityETH() public {
        uint256 token0approveAmount = 1e18;
        uint256 WETHapproveAmount = 1e18;

        token0.approve(address(router), token0approveAmount);
        weth.approve(address(router), WETHapproveAmount);

        (uint256 amountA,, uint256 liquidity) = router.addLiquidityETH{ value: 1e18 }(
            address(token0), token0approveAmount, 1e18, 1e18, address(this), deadline
        );

        address pairAddress = pairFactory.getPair(address(token0), address(weth));

        Pair(pairAddress).approve(address(router), liquidity);

        (uint256 _amountA, uint256 _amountB) = router.removeLiquidity(
            address(token0),
            address(weth),
            liquidity,
            token0approveAmount - pair.MINIMUM_LIQUIDITY(),
            WETHapproveAmount - pair.MINIMUM_LIQUIDITY(),
            address(this),
            deadline
        );

        (uint256 reserve0, uint256 reserve1,) = Pair(pairAddress).getReserves();
        assertEq(uint112(token0.balanceOf(address(pairAddress))), reserve0);
        assertEq(uint112(weth.balanceOf(address(pairAddress))), reserve1);

        assertEq(Pair(pairAddress).balanceOf(address(this)), 0);
        assertEq(Pair(pairAddress).totalSupply(), pair.MINIMUM_LIQUIDITY());
        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - amountA + _amountA);
        assertEq(weth.balanceOf(address(this)), 20e18 + _amountB);
    }

    function test_ShouldBeSuccess_partially_removeLiquidity() public {
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        (uint256 amountA, uint256 amountB, uint256 liquidity) =
            _addLiquidity(token0TransferAmount, token1TransferAmount);

        uint256 calculateLiquidity = (liquidity * 3) / 10;
        pair.approve(address(router), calculateLiquidity);

        (uint256 _amountA, uint256 _amountB) = router.removeLiquidity(
            address(tokenA),
            address(tokenB),
            calculateLiquidity,
            0.3 ether - 3e16,
            0.3 ether - 3e16,
            address(this),
            deadline
        );

        (uint256 reserve0, uint256 reserve1,) = pair.getReserves();
        assertEq(uint112(token0.balanceOf(address(pair))), reserve0);
        assertEq(uint112(token1.balanceOf(address(pair))), reserve1);

        assertEq(pair.balanceOf(address(this)), liquidity - calculateLiquidity);
        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - amountA + _amountA);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - amountB + _amountB);
        assertEq(pair.totalSupply(), (liquidity + pair.MINIMUM_LIQUIDITY()) - calculateLiquidity);
    }

    function test_ShouldBeSuccess_removeLiquidityWithPermit() public {
        token0.transfer(sender, 5e18);
        token1.transfer(sender, 5e18);

        vm.startPrank(sender);

        token0.approve(address(router), 1e18);
        token1.approve(address(router), 1e18);

        (uint256 amountA, uint256 amountB, uint256 liquidity) =
            router.addLiquidity(address(token0), address(token1), 1e18, 1e18, 1e18, 1e18, sender, deadline);

        bytes32 permitMeesageHash =
            _getPermitHash(pair, sender, address(router), liquidity, pair.nonces(sender), deadline);

        (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, permitMeesageHash);

        (uint256 _amountA, uint256 _amountB) = router.removeLiquidityWithPermit(
            address(token0),
            address(token1),
            liquidity,
            1e18 - pair.MINIMUM_LIQUIDITY(),
            1e18 - pair.MINIMUM_LIQUIDITY(),
            sender,
            deadline,
            false,
            v,
            r,
            s
        );

        (uint256 reserve0, uint256 reserve1,) = pair.getReserves();
        assertEq(uint112(token0.balanceOf(address(pair))), reserve0);
        assertEq(uint112(token1.balanceOf(address(pair))), reserve1);

        assertEq(pair.balanceOf(sender), 0);
        assertEq(pair.totalSupply(), pair.MINIMUM_LIQUIDITY());
        assertEq(token0.balanceOf(sender), 5e18 - amountA + _amountA);
        assertEq(token1.balanceOf(sender), 5e18 - amountB + _amountB);
    }

    function test_ShouldBeSuccess_removeLiquidityETHWithPermit() public {
        vm.deal(sender, 1e18);
        token0.transfer(sender, 5e18);
        weth.transfer(sender, 5e18);

        vm.startPrank(sender);

        token0.approve(address(router), 1e18);
        weth.approve(address(router), 1e18);

        (uint256 amountA,, uint256 liquidity) =
            router.addLiquidityETH{ value: 1e18 }(address(token0), 1e18, 1e18, 1e18, sender, deadline);

        address pairAddress = pairFactory.getPair(address(token0), address(weth));

        bytes32 permitMeesageHash = _getPermitHash(
            Pair(pairAddress), sender, address(router), liquidity, Pair(pairAddress).nonces(sender), deadline
        );

        (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, permitMeesageHash);

        (uint256 _amountA,) = router.removeLiquidityETHWithPermit(
            address(token0),
            liquidity,
            1e18 - Pair(pairAddress).MINIMUM_LIQUIDITY(),
            1e18 - Pair(pairAddress).MINIMUM_LIQUIDITY(),
            sender,
            deadline,
            false,
            v,
            r,
            s
        );

        (uint256 reserve0, uint256 reserve1,) = Pair(pairAddress).getReserves();
        assertEq(uint112(token0.balanceOf(address(pairAddress))), reserve0);
        assertEq(uint112(weth.balanceOf(address(pairAddress))), reserve1);

        assertEq(Pair(pairAddress).balanceOf(sender), 0);
        assertEq(Pair(pairAddress).totalSupply(), pair.MINIMUM_LIQUIDITY());
        assertEq(token0.balanceOf(sender), 5e18 - amountA + _amountA);
        assertEq(weth.balanceOf(sender), 5e18);
    }

    function test_ShouldBeSuccess_removeLiquidityETHSupportingFeeOnTransferTokens() public {
        uint256 token0approveAmount = 1e18;
        uint256 WETHapproveAmount = 1e18;

        token0.approve(address(router), token0approveAmount);
        weth.approve(address(router), WETHapproveAmount);

        (,, uint256 liquidity) = router.addLiquidityETH{ value: 1e18 }(
            address(token0), token0approveAmount, 1e18, 1e18, address(this), deadline
        );

        address pairAddress = pairFactory.getPair(address(token0), address(weth));

        Pair(pairAddress).approve(address(router), liquidity);

        (uint256 _amountEth) = router.removeLiquidityETHSupportingFeeOnTransferTokens(
            address(token0),
            liquidity,
            token0approveAmount - pair.MINIMUM_LIQUIDITY(),
            WETHapproveAmount - pair.MINIMUM_LIQUIDITY(),
            address(this),
            deadline
        );

        (uint256 reserve0, uint256 reserve1,) = Pair(pairAddress).getReserves();
        assertEq(uint112(token0.balanceOf(address(pairAddress))), reserve0);
        assertEq(uint112(weth.balanceOf(address(pairAddress))), reserve1);

        assertEq(Pair(pairAddress).balanceOf(address(this)), 0);
        assertEq(Pair(pairAddress).totalSupply(), pair.MINIMUM_LIQUIDITY());
        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 + _amountEth);
        assertEq(weth.balanceOf(address(this)), 20e18);
    }

    function test_ShouldBeSuccess_removeLiquidityETHWithPermitSupportingFeeOnTransferTokens() public {
        vm.deal(sender, 1e18);
        token0.transfer(sender, 5e18);
        weth.transfer(sender, 5e18);

        vm.startPrank(sender);

        token0.approve(address(router), 1e18);
        weth.approve(address(router), 1e18);

        (uint256 amountA,, uint256 liquidity) =
            router.addLiquidityETH{ value: 1e18 }(address(token0), 1e18, 1e18, 1e18, sender, deadline);

        address pairAddress = pairFactory.getPair(address(token0), address(weth));

        bytes32 permitMeesageHash = _getPermitHash(
            Pair(pairAddress), sender, address(router), liquidity, Pair(pairAddress).nonces(sender), deadline
        );

        (uint8 v, bytes32 r, bytes32 s) = vm.sign(1, permitMeesageHash);

        (uint256 _amountETH) = router.removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
            address(token0),
            liquidity,
            1e18 - Pair(pairAddress).MINIMUM_LIQUIDITY(),
            1e18 - Pair(pairAddress).MINIMUM_LIQUIDITY(),
            sender,
            deadline,
            false,
            v,
            r,
            s
        );

        (uint256 reserve0, uint256 reserve1,) = Pair(pairAddress).getReserves();
        assertEq(uint112(token0.balanceOf(address(pairAddress))), reserve0);
        assertEq(uint112(weth.balanceOf(address(pairAddress))), reserve1);

        assertEq(Pair(pairAddress).balanceOf(sender), 0);
        assertEq(Pair(pairAddress).totalSupply(), pair.MINIMUM_LIQUIDITY());
        assertEq(token0.balanceOf(sender), 5e18 - amountA + _amountETH);
        assertEq(weth.balanceOf(sender), 5e18);
    }

    function test_ShouldBeSuccess_swapExactTokensForTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        token0.approve(address(router), 3e17);
        router.swapExactTokensForTokens(3e17, 1e17, path, address(this), deadline);

        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 - 3e17);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - 2e18);
        assertEq(weth.balanceOf(address(this)), 20e18 + amount2Out);
    }

    function test_ShouldBeSuccess_swapTokensForExactTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        token0.approve(address(router), 3e17);
        router.swapTokensForExactTokens(amount2Out, 3e17, path, address(this), deadline);

        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 - 3e17);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - 2e18);
        assertEq(weth.balanceOf(address(this)), 20e18 + amount2Out);
    }

    function test_ShouldBeSuccess_swapExactETHForTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(weth);
        path[1] = address(token1);
        path[2] = address(token0);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        weth.approve(address(router), 3e17);
        router.swapExactETHForTokens{ value: 3e17 }(1e17, path, address(this), deadline);

        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 + amount2Out);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - 2e18);
        assertEq(weth.balanceOf(address(this)), 20e18);
    }

    function test_ShouldBeSuccess_swapTokensForExactETH() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        token0.approve(address(router), 3e17);
        router.swapTokensForExactETH(amount2Out, 3e17, path, address(this), deadline);

        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 - 3e17);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - 2e18);
        assertEq(weth.balanceOf(address(this)), 20e18);
    }

    function test_ShouldBeSuccess_swapExactTokensForETH() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        token0.approve(address(router), 3e17);
        router.swapExactTokensForETH(3e17, 1e17, path, address(this), deadline);

        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 - 3e17);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - 2e18);
        assertEq(weth.balanceOf(address(this)), 20e18);
    }

    function test_ShouldBeSuccess_swapETHForExactTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(weth);
        path[1] = address(token1);
        path[2] = address(token0);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        weth.approve(address(router), 3e17);
        router.swapETHForExactTokens{ value: 3e17 }(amount2Out, path, address(this), deadline);

        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 + amount2Out);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - 2e18);
        assertEq(weth.balanceOf(address(this)), 20e18);
    }

    function test_ShouldBeSuccess_swapExactTokensForTokensSupportingFeeOnTransferTokens() public {
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        address[] memory path = new address[](2);
        path[0] = address(token0);
        path[1] = address(token1);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);

        token0.approve(address(router), 3e17);
        router.swapExactTokensForTokensSupportingFeeOnTransferTokens(3e17, amount1Out, path, address(this), deadline);

        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 - 3e17);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - 1e18 + amount1Out);
    }

    function test_ShouldBeSuccess_swapExactETHForTokensSupportingFeeOnTransferTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(weth);
        path[1] = address(token1);
        path[2] = address(token0);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        weth.approve(address(router), 3e17);
        router.swapExactETHForTokensSupportingFeeOnTransferTokens{ value: 3e17 }(1e17, path, address(this), deadline);

        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 + amount2Out);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - 2e18);
        assertEq(weth.balanceOf(address(this)), 20e18);
    }

    function test_ShouldBeSuccess_swapExactTokensForETHSupportingFeeOnTransferTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        token0.approve(address(router), 3e17);
        router.swapExactTokensForETHSupportingFeeOnTransferTokens(3e17, 1e17, path, address(this), deadline);

        assertEq(token0.balanceOf(address(this)), TOKEN_A_TOTAL_SUPPLY - 1e18 - 3e17);
        assertEq(token1.balanceOf(address(this)), TOKEN_B_TOTAL_SUPPLY - 2e18);
        assertEq(weth.balanceOf(address(this)), 20e18);
    }

    /*//////////////////////////////////////////////////////////////////////////
                                      REVERTS
    //////////////////////////////////////////////////////////////////////////*/

    function test_Revert_insufficientBAmount_addLiquidity() public {
        uint256 token0transferAmount = 1e18;
        uint256 token1transferAmount = 1e18;
        _addLiquidity(token0transferAmount, token1transferAmount);

        uint256 token0approveAmount = 1e18;
        uint256 token1approveAmount = 2e18;

        token0.approve(address(router), token0approveAmount);
        token1.approve(address(router), token1approveAmount);

        vm.expectRevert(Router.InsufficientBAmount.selector);
        router.addLiquidity(
            address(token0),
            address(token1),
            token0approveAmount,
            token1approveAmount,
            token0approveAmount,
            token1approveAmount,
            address(this),
            deadline
        );
    }

    function test_Revert_insufficientAAmount_addLiquidity() public {
        uint256 token0transferAmount = 1e18;
        uint256 token1transferAmount = 1e18;
        _addLiquidity(token0transferAmount, token1transferAmount);

        uint256 token0approveAmount = 1e18;
        uint256 token1approveAmount = 2e18;

        token0.approve(address(router), token0approveAmount);
        token1.approve(address(router), token1approveAmount);

        vm.expectRevert(Router.InsufficientAAmount.selector);
        router.addLiquidity(
            address(token0),
            address(token1),
            token1approveAmount,
            token0approveAmount,
            token1approveAmount,
            token0approveAmount,
            address(this),
            deadline
        );
    }

    //function test_Revert_UnableToTransferWETH_addLiquidityETH() public { }

    function test_Revert_UnableToSendEther_addLiquidityETH() public {
        address erc20Contarct = makeAddr("erc20Contarct");
        vm.etch(erc20Contarct, "1");

        vm.deal(erc20Contarct, 5e18);
        token0.transfer(erc20Contarct, 5e18);
        weth.transfer(erc20Contarct, 5e18);

        vm.startPrank(erc20Contarct);

        uint256 token0approveAmount = 1e18;
        uint256 WETHapproveAmount = 1e18;

        token0.approve(address(router), token0approveAmount);
        weth.approve(address(router), WETHapproveAmount);

        router.addLiquidityETH{ value: 1e18 }(address(token0), token0approveAmount, 1e18, 1e18, erc20Contarct, deadline);

        token0.approve(address(router), 1e18);
        weth.approve(address(router), 2e18);

        vm.expectRevert(Router.UnableToSendEther.selector);
        router.addLiquidityETH{ value: 1e18 }(address(token0), 1e17, 1e18, 1e17, erc20Contarct, deadline);
    }

    function test_Revert_insufficientAAmount_removeLiquidity() public {
        uint256 token0transferAmount = 1e18;
        uint256 token1transferAmount = 1e18;
        (,, uint256 liquidity) = _addLiquidity(token0transferAmount, token1transferAmount);

        pair.approve(address(router), liquidity);

        vm.expectRevert(Router.InsufficientAAmount.selector);
        router.removeLiquidity(
            address(token0),
            address(token1),
            liquidity,
            token0transferAmount,
            token1transferAmount,
            address(this),
            deadline
        );
    }

    function test_Revert_insufficientBAmount_removeLiquidity() public {
        uint256 token0transferAmount = 1e18;
        uint256 token1transferAmount = 1e18;
        (,, uint256 liquidity) = _addLiquidity(token0transferAmount, token1transferAmount);

        pair.approve(address(router), liquidity);

        vm.expectRevert(Router.InsufficientBAmount.selector);
        router.removeLiquidity(
            address(token0),
            address(token1),
            liquidity,
            token0transferAmount - 10 ** 3,
            token1transferAmount,
            address(this),
            deadline
        );
    }

    function test_Revert_unableToSendEther_removeLiquidityETHSupportingFeeOnTransferTokens() public {
        address erc20Contarct = makeAddr("erc20Contarct");
        vm.etch(erc20Contarct, "1");

        vm.deal(erc20Contarct, 1e18);
        token0.transfer(erc20Contarct, 5e18);
        weth.transfer(erc20Contarct, 5e18);

        vm.startPrank(erc20Contarct);

        uint256 token0approveAmount = 1e18;
        uint256 WETHapproveAmount = 1e18;

        token0.approve(address(router), token0approveAmount);
        weth.approve(address(router), WETHapproveAmount);

        (,, uint256 liquidity) = router.addLiquidityETH{ value: 1e18 }(
            address(token0), token0approveAmount, 1e18, 1e18, erc20Contarct, deadline
        );

        address pairAddress = pairFactory.getPair(address(token0), address(weth));

        Pair(pairAddress).approve(address(router), liquidity);

        vm.expectRevert(Router.UnableToSendEther.selector);
        router.removeLiquidityETHSupportingFeeOnTransferTokens(
            address(token0),
            liquidity,
            token0approveAmount - 10 ** 3,
            WETHapproveAmount - 10 ** 3,
            erc20Contarct,
            deadline
        );
    }

    function test_Revert_insufficientOutputAmount_swapExactTokensForTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.InsufficientOutputAmount.selector);
        router.swapExactTokensForTokens(3e17, 1e18, path, address(this), deadline);
    }

    function test_Revert_excessiveInputAmount_swapTokensForExactTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.ExcessiveInputAmount.selector);
        router.swapTokensForExactTokens(amount2Out, 1e16, path, address(this), deadline);
    }

    function test_Revert_invalidPath_swapExactETHForTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        weth.approve(address(router), 3e17);

        vm.expectRevert(Router.InvalidPath.selector);
        router.swapExactETHForTokens{ value: 3e17 }(1e18, path, address(this), deadline);
    }

    function test_Revert_insufficientOutputAmount_swapExactETHForTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(weth);
        path[1] = address(token1);
        path[2] = address(token0);

        weth.approve(address(router), 3e17);

        vm.expectRevert(Router.InsufficientOutputAmount.selector);
        router.swapExactETHForTokens{ value: 3e17 }(1e18, path, address(this), deadline);
    }

    //function test_Revert_unableToTransferWETH_swapExactETHForTokens() public { }

    function test_Revert_invalidPath_swapTokensForExactETH() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[1] = address(weth);
        path[0] = address(token0);
        path[2] = address(token1);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.InvalidPath.selector);
        router.swapTokensForExactETH(amount2Out, 3e17, path, address(this), deadline);
    }

    function test_Revert_excessiveInputAmount_swapTokensForExactETH() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.ExcessiveInputAmount.selector);
        router.swapTokensForExactETH(amount2Out, 3e16, path, address(this), deadline);
    }

    function test_Revert_unableToSendEther_swapTokensForExactETH() public {
        address erc20Contarct = makeAddr("erc20Contarct");
        vm.etch(erc20Contarct, "1");

        vm.deal(erc20Contarct, 1e18);
        token0.transfer(erc20Contarct, 5e18);
        token1.transfer(erc20Contarct, 5e18);
        weth.transfer(erc20Contarct, 5e18);

        vm.startPrank(erc20Contarct);

        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, erc20Contarct, deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.UnableToSendEther.selector);
        router.swapTokensForExactETH(amount2Out, 3e17, path, erc20Contarct, deadline);
    }

    function test_Revert_invalidPath_swapExactTokensForETH() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(weth);
        path[1] = address(token1);
        path[2] = address(token0);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.InvalidPath.selector);
        router.swapExactTokensForETH(3e17, 1e17, path, address(this), deadline);
    }

    function test_Revert_insufficientOutputAmount_swapExactTokensForETH() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.InsufficientOutputAmount.selector);
        router.swapExactTokensForETH(3e17, 1e18, path, address(this), deadline);
    }

    function test_Revert_unableToSendEther_swapExactTokensForETH() public {
        address erc20Contarct = makeAddr("erc20Contarct");
        vm.etch(erc20Contarct, "1");

        vm.deal(erc20Contarct, 1e18);
        token0.transfer(erc20Contarct, 5e18);
        token1.transfer(erc20Contarct, 5e18);
        weth.transfer(erc20Contarct, 5e18);

        vm.startPrank(erc20Contarct);

        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, erc20Contarct, deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.UnableToSendEther.selector);
        router.swapExactTokensForETH(3e17, 1e17, path, erc20Contarct, deadline);
    }

    function test_Revert_invalidPath_swapETHForExactTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        weth.approve(address(router), 3e17);

        vm.expectRevert(Router.InvalidPath.selector);
        router.swapETHForExactTokens{ value: 3e17 }(amount2Out, path, address(this), deadline);
    }

    function test_Revert_excessiveInputAmount_swapETHForExactTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(weth);
        path[1] = address(token1);
        path[2] = address(token0);

        uint256 amount1Out = RouterLib.getAmountOut(3e17, 1e18, 1e18);
        uint256 amount2Out = RouterLib.getAmountOut(amount1Out, 1e18, 1e18);

        weth.approve(address(router), 3e17);

        vm.expectRevert(Router.ExcessiveInputAmount.selector);
        router.swapETHForExactTokens{ value: 3e16 }(amount2Out, path, address(this), deadline);
    }

    //function test_Revert_unableToTransferWETH_swapETHForExactTokens() public { }

    //function test_Revert_unableToSendEther_swapETHForExactTokens() public { }

    function test_Revert_insufficientOutputAmount_swapExactTokensForTokensSupportingFeeOnTransferTokens() public {
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        address[] memory path = new address[](2);
        path[0] = address(token0);
        path[1] = address(token1);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.InsufficientOutputAmount.selector);
        router.swapExactTokensForTokensSupportingFeeOnTransferTokens(3e17, 1e18, path, address(this), deadline);
    }

    function test_Revert_invalidPath_swapExactETHForTokensSupportingFeeOnTransferTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        weth.approve(address(router), 3e17);

        vm.expectRevert(Router.InvalidPath.selector);
        router.swapExactETHForTokensSupportingFeeOnTransferTokens{ value: 3e17 }(1e18, path, address(this), deadline);
    }

    //function test_Revert_unableToTransferWETH_swapExactETHForTokensSupportingFeeOnTransferTokens() public { }

    function test_Revert_insufficientOutputAmount_swapExactETHForTokensSupportingFeeOnTransferTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(weth);
        path[1] = address(token1);
        path[2] = address(token0);

        weth.approve(address(router), 3e17);

        vm.expectRevert(Router.InsufficientOutputAmount.selector);
        router.swapExactETHForTokensSupportingFeeOnTransferTokens{ value: 3e17 }(1e18, path, address(this), deadline);
    }

    //function test_Revert_unableToTransferWETH_swapExactTokensForETHSupportingFeeOnTransferTokens() public { }

    function test_Revert_invalidPath_swapExactTokensForETHSupportingFeeOnTransferTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(weth);
        path[1] = address(token1);
        path[2] = address(token0);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.InvalidPath.selector);
        router.swapExactTokensForETHSupportingFeeOnTransferTokens(3e17, 1e17, path, address(this), deadline);
    }

    function test_Revert_insufficientOutputAmount_swapExactTokensForETHSupportingFeeOnTransferTokens() public {
        uint256 WETHTransferAmount = 1e18;
        uint256 token0TransferAmount = 1e18;
        uint256 token1TransferAmount = 1e18;

        _addLiquidity(token0TransferAmount, token1TransferAmount);

        token1.approve(address(router), token1TransferAmount);
        weth.approve(address(router), WETHTransferAmount);

        router.addLiquidityETH{ value: 1e18 }(
            address(token1), token1TransferAmount, 1e18, 1e18, address(this), deadline
        );

        address[] memory path = new address[](3);
        path[0] = address(token0);
        path[1] = address(token1);
        path[2] = address(weth);

        token0.approve(address(router), 3e17);

        vm.expectRevert(Router.InsufficientOutputAmount.selector);
        router.swapExactTokensForETHSupportingFeeOnTransferTokens(3e17, 1e18, path, address(this), deadline);
    }

    /*//////////////////////////////////////////////////////////////////////////
                                      HELPERS
    //////////////////////////////////////////////////////////////////////////*/

    function _addLiquidity(
        uint256 _token0Amount,
        uint256 _token1Amount
    )
        private
        returns (uint256 amountA, uint256 amountB, uint256 liquidity)
    {
        token0.approve(address(router), _token0Amount);
        token1.approve(address(router), _token1Amount);
        (amountA, amountB, liquidity) = router.addLiquidity(
            address(token0),
            address(token1),
            _token0Amount,
            _token1Amount,
            _token0Amount,
            _token1Amount,
            address(this),
            deadline
        );
    }

    function _getPermitHash(
        Pair _pair,
        address _owner,
        address _spender,
        uint256 _value,
        uint256 _nonce,
        uint256 _deadline
    )
        private
        view
        returns (bytes32)
    {
        return keccak256(
            abi.encodePacked(
                "\x19\x01",
                _pair.DOMAIN_SEPARATOR(),
                keccak256(abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, _nonce, _deadline))
            )
        );
    }
}

Here's the coverage result:

I used forge coverage command.

Screenshot from 2024-01-22 12-12-05

cc: @PaulRBerg @gakonst

@icanvardar icanvardar added the T-bug Type: bug label Jan 22, 2024
@PaulRBerg
Copy link
Contributor

unfortunately I don't have time to review this, but I've some feedback for future posts (and this post, if you would like to edit it): put your code underneath collapsible sections so that the core message does not get lost among code.

@icanvardar
Copy link
Author

could anyone please help me with this issue? i am not able to solve this issue.

@KholdStare
Copy link
Contributor

@ismailcanvardar One thing that I always do is disable optimizations for coverage runs. The optimizations inline a lot of code, so there's no way to attribute the runs back to the source code.

Example foundry.toml:

# Default "production" profile.
[profile.default]
solc-version = "0.8.19"
optimizer = true
optimizer-runs = 20000

# Use FOUNDRY_PROFILE=lite for quicker compilation, coverage
[profile.lite]
optimizer = false

Then run like:

FOUNDRY_PROFILE=lite forge coverage

@icanvardar
Copy link
Author

Thanks for your response @KholdStare.

My foundry.toml file
[profile.default]
  fs_permissions = [{ access = "read-write", path = "./"}]
  auto_detect_solc = false
  block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT
  bytecode_hash = "none"
  evm_version = "paris"         
  fuzz = { runs = 1_000 }
  gas_reports = ["*"]
  libs = ["lib"]
  optimizer = true
  optimizer_runs = 10_000
  out = "out"
  script = "script"
  solc = "0.8.21"
  src = "src"
  test = "test"

[profile.lite]
  optimizer = false

[profile.ci]
  fuzz = { runs = 10_000 }
  verbosity = 4

# [etherscan]
#   arbitrum_one = { key = "${API_KEY_ARBISCAN}" }
#   avalanche = { key = "${API_KEY_SNOWTRACE}" }
#   bnb_smart_chain = { key = "${API_KEY_BSCSCAN}" }
#   gnosis_chain = { key = "${API_KEY_GNOSISSCAN}" }
#   goerli = { key = "${API_KEY_ETHERSCAN}" }
#   mainnet = { key = "${API_KEY_ETHERSCAN}" }
#   optimism = { key = "${API_KEY_OPTIMISTIC_ETHERSCAN}" }
#   polygon = { key = "${API_KEY_POLYGONSCAN}" }
#   sepolia = { key = "${API_KEY_ETHERSCAN}" }

[fmt]
  bracket_spacing = true
  int_types = "long"
  line_length = 120
  multiline_func_header = "all"
  number_underscore = "thousands"
  quote_style = "double"
  tab_width = 4
  wrap_comments = true

[rpc_endpoints]
  arbitrum_one = "https://arbitrum-mainnet.infura.io/v3/${API_KEY_INFURA}"
  avalanche = "https://avalanche-mainnet.infura.io/v3/${API_KEY_INFURA}"
  bnb_smart_chain = "https://bsc-dataseed.binance.org"
  gnosis_chain = "https://rpc.gnosischain.com"
  goerli = "https://goerli.infura.io/v3/${API_KEY_INFURA}"
  localhost = "http://localhost:8545"
  mainnet = "https://eth-mainnet.g.alchemy.com/v2/${API_KEY_ALCHEMY}"
  optimism = "https://optimism-mainnet.infura.io/v3/${API_KEY_INFURA}"
  polygon = "https://polygon-mainnet.infura.io/v3/${API_KEY_INFURA}"
  sepolia = "https://sepolia.infura.io/v3/${API_KEY_INFURA}"

So, I've added lite profile section after your comment. After that I'd ran forge clean and FOUNDRY_PROFILE=lite forge coverage commands. Unfortunately, nothing changed.

Here's the coverage scores.
image

Do you want me to add more details to solve this problem?

@Austinhs
Copy link

Austinhs commented Feb 21, 2024

Experiencing a similar problem with oracles... Something odd that I was able to do in the contract to make it work and capture the 100% coverage was adding a emit event in the function then it just magically picked it up -- otherwise it completly passes over the file and marks it as 0%.

image

It has to be in that function as well, if I put the event emit inside the constructor nothing will happen... so something about making the emit inside the function makes it pickup all the test results.

I've been able to get a 100% coverage by just deleting a ton of our code and stripping this down to bare bones to avoid conflicts with other parts of the repo.. but its hard to really give reproduction steps on this without inviting someone from the forge team to our repo... its 100% reproducible but we have about 890 tests so it takes a min.

I am no rustacean by any means, but if we don't get any updates on this issue ill probably try an poke around the coverage code my self to see if I could get more details on where its going wrong. Me and OP have overrides in common so maybe it has something to do with that.

@Austinhs
Copy link

After some reading I think #2567 might explain our problem

@whisskey
Copy link

Project: Uniswap v2 contracts (UniswapV2Router02.sol, UniswapV2Library.sol)

I wanted to bring up a recurring challenge we've been facing in our project regarding code quality and test coverage. We've noticed that when we aim for higher test coverage, the quality of our code seems to deteriorate, and vice versa.

I've attached some screenshots below to illustrate the issue. As you can see, when we prioritize increasing test coverage, the codebase becomes messier and harder to maintain. On the other hand, when we focus on improving code quality, test coverage tends to suffer.

We've been grappling with this problem for weeks now, and despite various attempts, we haven't been able to find a satisfactory solution. It's becoming increasingly frustrating as it's impeding our progress.

Ekran Resmi 2024-02-27 20 02 29 Ekran Resmi 2024-02-27 20 02 45

When we set the reserve part to (0,0) in the getReserves function of RouterLib, it provides coverage. However, in the Router->addLiquidity function, the line liquidity = IPair(pair).mint(to); needs to be commented out. When I uncomment it, we still don't get coverage.

Ekran Resmi 2024-02-27 20 10 04 Ekran Resmi 2024-02-27 20 02 45

When I correct the implementation of the getReserves function in RouterLib and keep the line liquidity = IPair(pair).mint(to); commented out in the Router->addLiquidity function, we still do not achieve coverage.

Ekran Resmi 2024-02-27 20 37 14

Lastly, I showed you the common IPair interface in both examples above.

I'm reaching out to gather insights and suggestions from all of you. Have any of you encountered similar challenges in your projects? If so, how did you approach them?

@onbjerg @Austinhs @mattsse

@Austinhs
Copy link

Austinhs commented Feb 29, 2024

@whisskey , look into lcov reports they are interactive and provide line by line results of what was covered and what was not covered...

Here is a script I keep in our repo to run lcov reports:

#!/bin/bash
rm -rf coverage_report
rm lcov.info
forge coverage --report lcov
lcov --remove lcov.info  -o lcov.info 'tests/*' 'script/*' '**/libraries/external/*' '**/testnet/*' '**/mocks/*' '**/indexing/*'
genhtml lcov.info -o coverage_report
google-chrome --headless --window-size=1200,800 --screenshot="coverage_report/coverage.png" "coverage_report/index.html"
printf "<img src=\"coverage.png\"/>" > coverage_report/README.md
open coverage_report/index.html

Also, this is probably not the right place to ask questions like that, if you want to continue the convo I'd probably suggest opening a new issue or you can DM me on twitter @DiversityETH unless im misuderstanding you and this is related but it seemed to be posed as a question on how to improve test coverage.

@hexonaut
Copy link
Contributor

Bump on this. Getting the same issue. Running forge coverage on this repo: https://github.com/marsfoundation/sparklend-advanced

Ran 13 test suites in 19.32s (59.57s CPU time): 69 tests passed, 1 failed, 0 skipped (70 total tests)
| File                                       | % Lines          | % Statements     | % Branches     | % Funcs         |
|--------------------------------------------|------------------|------------------|----------------|-----------------|
| src/CappedFallbackRateSource.sol           | 100.00% (8/8)    | 100.00% (9/9)    | 100.00% (4/4)  | 100.00% (3/3)   |
| src/CappedOracle.sol                       | 100.00% (7/7)    | 100.00% (9/9)    | 100.00% (4/4)  | 100.00% (3/3)   |
| src/FixedPriceOracle.sol                   | 100.00% (4/4)    | 100.00% (4/4)    | 100.00% (2/2)  | 100.00% (3/3)   |
| src/MorphoUpgradableOracle.sol             | 100.00% (6/6)    | 100.00% (7/7)    | 100.00% (0/0)  | 100.00% (4/4)   |
| src/PotRateSource.sol                      | 100.00% (3/3)    | 100.00% (4/4)    | 100.00% (0/0)  | 100.00% (3/3)   |
| src/RETHExchangeRateOracle.sol             | 100.00% (9/9)    | 100.00% (15/15)  | 100.00% (4/4)  | 100.00% (3/3)   |
| src/RateTargetBaseInterestRateStrategy.sol | 100.00% (6/6)    | 100.00% (10/10)  | 100.00% (2/2)  | 100.00% (3/3)   |
| src/RateTargetKinkInterestRateStrategy.sol | 100.00% (6/6)    | 100.00% (11/11)  | 100.00% (2/2)  | 100.00% (3/3)   |
| src/VariableBorrowInterestRateStrategy.sol | 91.18% (31/34)   | 93.02% (40/43)   | 87.50% (7/8)   | 100.00% (13/13) |
| src/WEETHExchangeRateOracle.sol            | 0.00% (0/9)      | 0.00% (0/15)     | 0.00% (0/4)    | 0.00% (0/3)     |
| src/WSTETHExchangeRateOracle.sol           | 100.00% (9/9)    | 100.00% (15/15)  | 100.00% (4/4)  | 100.00% (3/3)   |
| test/CappedFallbackRateSource.t.sol        | 83.33% (5/6)     | 87.50% (7/8)     | 100.00% (0/0)  | 100.00% (3/3)   |
| test/PotRateSource.t.sol                   | 100.00% (2/2)    | 100.00% (2/2)    | 100.00% (0/0)  | 100.00% (2/2)   |
| test/RETHExchangeRateOracle.t.sol          | 100.00% (3/3)    | 100.00% (3/3)    | 100.00% (0/0)  | 100.00% (3/3)   |
| test/WEETHExchangeRateOracle.t.sol         | 100.00% (3/3)    | 100.00% (3/3)    | 100.00% (0/0)  | 100.00% (3/3)   |
| test/WSTETHExchangeRateOracle.t.sol        | 100.00% (3/3)    | 100.00% (5/5)    | 100.00% (0/0)  | 100.00% (3/3)   |
| test/mocks/PriceSourceMock.sol             | 100.00% (5/5)    | 100.00% (5/5)    | 100.00% (0/0)  | 100.00% (4/4)   |
| test/mocks/RateSourceMock.sol              | 100.00% (5/5)    | 100.00% (5/5)    | 100.00% (0/0)  | 100.00% (4/4)   |
| Total                                      | 89.84% (115/128) | 89.02% (154/173) | 85.29% (29/34) | 95.45% (63/66)  |

WEETHExchangeRateOracle is very clearly being tested in the WEETHExchangeRateOracle.t.sol file. Similar to the comment above if I modify the code and add an event emission in the latestAnswer() function (and remove view modifier) it will pass with 100% coverage. Something odd is going on.

@grandizzy
Copy link
Collaborator

grandizzy commented Jun 20, 2024

was doing some more tests and there's RETHExchangeRateOracle which is similar to WEETHExchangeRateOracle but comes before it, what I suspect is they're somehow matched by deployed bytecode because if changing require msg in RETHExchangeRateOracle from "RETHExchangeRateOracle/invalid-decimals" to "RETHExchangeRateOracle/invalid-d" then coverage for WEETHExchangeRateOracle appears.

Looking in code the difference is that in 2nd case find_by_creation_code returns the right contract and hits correctly pushed

known_contracts.find_by_creation_code(&map.bytecode)

/// Finds a contract which has a similar bytecode as `code`.
pub fn find_by_creation_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
self.iter().find(|(_, contract)| {
if let Some(bytecode) = contract.bytecode() {
bytecode_diff_score(bytecode.as_ref(), code) <= 0.1
} else {
false
}
})
}

so probably something to change in bytecode_diff_score fn?

@DaniPopes @klkvr could you please share some thoughts on this? thank you!

I made a simple repo with issue reproduction: https://github.com/grandizzy/forge-coverage-bug

(LE this could also explain Uniswap issue mentioned above as there are similar UniswapV2Router01 and UniswapV2Router02 and only UniswapV2Router02 pointed as having issues...)

@grandizzy
Copy link
Collaborator

same reported in
#5729
#5342
#4780
#4316
#4142

@grandizzy
Copy link
Collaborator

going to close this one for now as the latest problem signaled was solved with #8214 and don't have original repro to test, feel free to reopen if hit again. thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-bug Type: bug
Projects
Status: Completed
Development

No branches or pull requests

7 participants