Awais Ahmad
Awais Ahmad

Reputation: 101

How to ensure custom tax is applied on token swaps in Solidity smart contract?

I'm developing a smart contract for a token (FMYNT) using Solidity on the Binance Smart Chain testnet, integrated with PancakeSwap testnet. I've implemented a custom 5% tax feature for buy and sell operations, but I'm encountering an issue where the tax does not seem to be deducted as expected during swaps.

Contract Details:

For example, when I tried to sell 2000 FMYNT, the UI showed an expected 0.0950113 MT. Post-transaction, my fee receiver's balance changed from 5 to 5.09501131, confirming the exact amount was received without tax deduction.

Contract Snippet:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "hardhat/console.sol";

import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";

contract FMYNT is ERC20, Ownable {
    using SafeMath for uint256;

    event FeeTaken(address sender, uint256 feeAmount);
    event TransferDetails(address sender, address recipient, uint256 amountWithFee, uint256 feeDeducted);
    
    address public immutable FEE_RECIEVER;

    // Constants
    uint256 public constant BUY_TAX = 5; // 5% buy tax
    uint256 public constant SELL_TAX = 5; // 5% sell tax
    uint256 public constant TOTAL_SUPPLY = 1e9 * 1e18; // 1 billion tokens
    
    // Uniswap Integration
    address public uniswapV2Pair;
    IUniswapV2Router02 public uniswapV2Router;

    // Tax Tracking
    mapping(address => bool) public _isExcludedFromFee;  // List of addresses excluded from tax

    constructor(address _feeReceiver) Ownable(msg.sender) ERC20("Founders Mynt Token","FMYNT"){
        _mint(msg.sender, TOTAL_SUPPLY);
        FEE_RECIEVER = _feeReceiver; // Initialize feeReceiver from constructor parameter

        // Replace with actual Uniswap router address
        // Example: Uniswap V2 Router on Ethereum Sepolia = 0x425141165d3DE9FEC831896C016617a52363b687
        // For BSC pancakeswap: 0x9A082015c919AD0E47861e5Db9A1c7070E81A2C7 or 0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3      
        uniswapV2Router = IUniswapV2Router02(0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3); 

        // Create Uniswap pair with WETH
        uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory()).createPair(address(this), uniswapV2Router.WETH());

        // Exclude contract addresses and Owner from tax
        _isExcludedFromFee[address(this)] = true;
        // _isExcludedFromFee[owner()] = true;
        _isExcludedFromFee[uniswapV2Pair] = true;
    }

    function transfer(address recipient, uint256 amount) public override returns (bool)  {
        address sender = _msgSender(); // From OpenZeppelin's context for keeping track of the sender
        uint256 fees = 0;

        console.log("Sender: ", sender);
        console.log("Recipient: ", recipient);

        // If any account belongs to _isExcludedFromFee account then remove the fee
        /* The contract first checks if the sender (user performing the swap) and the recipient 
        (typically the Uniswap pair) are excluded from fees. In your setup, the Uniswap pair and 
        the contract itself are excluded, but the user is generally not.*/
        if (!_isExcludedFromFee[sender] && !_isExcludedFromFee[recipient]) {
            // Buy
            if (sender == uniswapV2Pair && recipient != address(uniswapV2Router)) {
                fees = amount.mul(BUY_TAX).div(100);
            }
            // Sell
            else if (recipient == uniswapV2Pair) {
                fees = amount.mul(SELL_TAX).div(100); // for 1 FMYNT 0.05 FMYNT would deduct
            }

            if (fees > 0) {
                _transfer(sender, address(this), fees); // Transfer the fees to the contract itself
                swapTokensForEth(fees); // Swap the fees for ETH
                emit FeeTaken(sender, fees);
                amount = amount.sub(fees); // Subtract the fees from the transfer amount
            }
        }

        // The remaining amount of FMYNT (0.95 FMYNT if the initial amount was 1 FMYNT) is then transferred to the Uniswap pair as part of the user’s original sell order.
        _transfer(sender, recipient, amount); // Perform the actual transfer
        emit TransferDetails(sender, recipient, amount, fees);
        return true;
    }

    function swapTokensForEth(uint256 tokenAmount) private {
        // Generate the uniswap pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

        _approve(address(this), address(uniswapV2Router), tokenAmount); // Approving the Uniswap router to handle the required amount of FMYNT

        // Make the swap: To swap the deducted fee (0.05 FMYNT) for ETH.
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // Accept any amount of ETH
            path,
            FEE_RECIEVER, // The ETH received from this swap is then sent to the FEE_RECIEVER.
            block.timestamp
        );
    }
}```

**Question:** How can I ensure that the tax is correctly applied and visible during swaps on PancakeSwap or similar DEXs? Is there something wrong with how the tax logic is implemented or could it be an issue with the DEX integration?

Upvotes: 0

Views: 97

Answers (0)

Related Questions