1

I have been learning how to use Uniswap V2 recently and am trying to add logic on top of the original code. I want to have a fee of 1% on each swap transaction using the input token. I was successfully implemented the code for exact input (e.g., swap exact tokens for tokens), but I am struggling to implement the code for exact output (e.g., swap tokens for exact tokens), because the error that was shown is only execution reverted from the remix IDE. Here is a snippet of my code.

note: you have to have some amount of the input token in this smart contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
/* 
Goerli network:
WETH: 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6
*/ 

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.6;

interface IWETH {
    function deposit() external payable;

    function withdraw(uint) external;

    function totalSupply() external view returns (uint);

    function balanceOf(address account) external view returns (uint);

    function transfer(address recipient, uint amount) external returns (bool);

    function allowance(address owner, address spender)
        external
        view
        returns (uint);

    function approve(address spender, uint amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint amount
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint value);
    event Approval(address indexed owner, address indexed spender, uint value);
}

interface ISwapV2Router {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);

    //token exact amount in, token -> token 1
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    //token exact amount out, token -> token 2
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    //ETH exact amount in, exact ETH -> token 3
    function swapExactETHForTokens
    (uint amountOutMin, 
    address[] calldata path, 
    address to, 
    uint deadline
    ) external payable returns (uint[] memory amounts);

    //ETH exact amount out, token -> exact ETH 4
    function swapTokensForExactETH(
        uint amountOut, 
        uint amountInMax, 
        address[] calldata path, 
        address to, 
        uint deadline
        ) external returns (uint[] memory amounts);


    // token exact amount in, exact token -> ETH 5
    function swapExactTokensForETH(
        uint amountIn, 
        uint amountOutMin, 
        address[] calldata path, 
        address to, 
        uint deadline
        )
        external
        returns (uint[] memory amounts);

    // token exact amout out, ETH -> exact token 6
    function swapETHForExactTokens(
        uint amountOut, 
        address[] calldata path, 
        address to, 
        uint deadline)
        external
        payable
        returns (uint[] memory amounts);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;

    // calculate maximum output token, useful for exact token -> token
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    // calculate minimum input token, useful for token -> exact token
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

contract SwapCentral {
    address public owner;
    address payable public dev;
    uint    public exchangeFee = 1000; //1% = 0.01 = 1000/denomFee
    uint    public constant DENOM_FEE = 1e5;

    address public immutable WETH;
    address private constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

    ISwapV2Router private constant V2_ROUTER = ISwapV2Router(UNISWAP_V2_ROUTER);

    mapping (address => bool) public isExcludedFromFee;
    
    constructor(address _WETH){
        require(_WETH != address(0), "WETH can't be address zero");
        WETH = _WETH;
        owner = msg.sender;
        isExcludedFromFee[msg.sender] = true;
        dev = payable(msg.sender);
    }

    modifier onlyOwner(){
        require(msg.sender == owner, "you're not the owner");
        _;
    }

    //---------------Uniswap V2 Starts Here----------------------//
    function v2GetEstimatedIn(uint amountOutDesired, address tokenIn, address tokenOut) public view returns (uint){
        address[] memory path = createPath(tokenIn, tokenOut);
        uint[] memory amount = V2_ROUTER.getAmountsIn(amountOutDesired, path);
        return amount[0];
    }

    //1. token exact amount in, exact token -> token 
    function v2SwapExactTokensForTokens(
        address caller,
        address tokenIn, 
        address tokenOut, 
        uint amountIn, 
        uint amountOutMin,
        uint deadlineInSeconds
    ) external returns (uint[] memory amounts)
    {
        uint[] memory dividedAmount = splitTokenValue(msg.sender, amountIn); //split the token

        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountIn);
        TransferHelper.safeApprove(tokenIn, UNISWAP_V2_ROUTER, dividedAmount[1]);
        TransferHelper.safeTransfer(tokenIn, dev, dividedAmount[0]); //transfer to developer

        address[] memory path = createPath(tokenIn, tokenOut);
        amounts = V2_ROUTER.swapExactTokensForTokens(
            dividedAmount[1],
            amountOutMin,
            path,
            caller,
            block.timestamp + deadlineInSeconds
        );
    }

    //2. token exact amount out, token -> exact token
    function v2SwapTokensForExactTokens(
        address caller,
        address tokenIn,
        address tokenOut,
        uint amountOutDesired,
        uint amountInMax,
        uint deadlineInSeconds
    ) public returns (uint[] memory amounts, bool success) 
    {
        TransferHelper.safeTransferFrom(tokenIn, msg.sender, address(this), amountInMax);
        TransferHelper.safeApprove(tokenIn, UNISWAP_V2_ROUTER, amountInMax); //approval for swapping
        
        uint estAmountIn = v2GetEstimatedIn(amountOutDesired, tokenIn, tokenOut); //calculate estimation
        uint[] memory dividedAmount = splitTokenValue(msg.sender, amountInMax); //split the token
        require((estAmountIn + dividedAmount[0] <= amountInMax), "amountInMax is not sufficient");

        // swapping with the amount of amountToSwap 
        address[] memory path = createPath(tokenIn, tokenOut);
        amounts = V2_ROUTER.swapTokensForExactTokens(
            amountOutDesired,
            dividedAmount[1],
            path,
            caller,
            block.timestamp + deadlineInSeconds
        );

        //transfer to dev
        TransferHelper.safeTransfer(tokenIn, dev, dividedAmount[0]); 
        
        // refund the change to caller
        if(amounts[0] < amountInMax){
            TransferHelper.safeApprove(tokenIn, UNISWAP_V2_ROUTER, 0);
            TransferHelper.safeTransfer(tokenIn, msg.sender, amountInMax - amounts[0] - dividedAmount[0]);
        }
        success = true;
    }

    //---------------Helper Functions----------------------//
    function splitTokenValue(address userAddress, uint amount) private view returns (uint[] memory dividedValue){
        uint fee;
        isExcludedFromFee[userAddress] ? fee = 0: fee = exchangeFee;

        dividedValue[0] = (amount * fee) / DENOM_FEE; //amountToDev
        dividedValue[1] = amount - dividedValue[0]; //amountToSwap
        require(amount >= dividedValue[1], "calculation error");
    }

    function createPath(address tokenIn, address tokenOut) private pure returns (address[] memory){
        address[] memory path = new address[](2);
        path[0] = tokenIn; //input token
        path[1] = tokenOut; //output token
        return path;
    }

    receive() external payable {}
    fallback() external payable {}
}

Is there anything I missed? Any feedback is welcomed. Thank you very much!

3
  • It will be easier if you can share a failed transaction (by forcing it on chain) or at least the hex data from the transaction so we can simulate it. Just auditing a wall of code without access to accounts approvals, balances and exact inputs is I think not inviting for people.
    – Maka
    Commented Aug 21, 2023 at 13:08
  • 1
    @Maka thank you for your response. I agree that I should have the failed transaction. However I already solved this error. It turned out I couldn't use the logic for dividing the token just like for exactInput and had to make another function to do that
    – rasyidred
    Commented Aug 22, 2023 at 1:35
  • glad you got it sorted, well done.
    – Maka
    Commented Aug 22, 2023 at 3:30

0

Your Answer

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

Browse other questions tagged or ask your own question.