Issue with Automatic Token Swap for ETH and Adding to Liquidity

Hello,

In my CustomToken1 smart contract, I have encountered an issue with the automatic swapping of accumulated tokens for ETH and their addition to liquidity. The contract has the accumulatedTokenThreshold set to 1 billion tokens, and there are currently over 6.3 billion tokens on the contract. However, the automatic token swap for ETH and addition to liquidity on Uniswap, which should be executed through the functions swapAccumulatedTokensForEth and addLiquidity, do not seem to be happening.

Could you please advise why these operations are not proceeding as expected and how I might resolve this issue?

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

// Importing OpenZeppelin contracts for ERC20 token standard and its extensions.
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; // Allows token burning.
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; // Prevents reentrancy attacks.
import "@openzeppelin/contracts/access/Ownable.sol"; // Provides ownership control functionalities.
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; // Interface for Uniswap V2 Router.
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; // Interface for Uniswap V2 Factory.

// CustomToken1 contract inheriting functionalities from ERC20, Burnable, ReentrancyGuard, and Ownable.
contract CustomToken1 is ERC20, ERC20Burnable, ReentrancyGuard, Ownable {
    address public marketingWallet; // Address for marketing wallet.
    address public uniswapV2RouterAddress; // Address of the Uniswap V2 Router.
    IUniswapV2Router02 public uniswapV2Router; // Instance of the Uniswap V2 Router.
    address public uniswapV2Pair; // Address of the Uniswap V2 Pair.

    uint256 public constant TOTAL_SUPPLY = 450000000000000 * 10**18; // Total token supply.
    uint256 public immutable minEthBeforeLiquidity = 0.05 ether; // Minimum ETH balance before adding liquidity.
    uint256 public lastLiquidityAddTime; // Timestamp of the last liquidity addition.
    uint256 public constant accumulatedTokenThreshold = 1000000000 * 10**18; // Predefined threshold for token swap.
    uint256 public constant minTimeInterval = 24 * 60 * 60; // Predefined time interval (24 hours).

    // Constructor to initialize the contract with specific parameters.
    constructor(
        address _marketingWallet,
        address _uniswapV2RouterAddress,
        address initialOwner
    ) ERC20("Testing4", "RRR") {
        marketingWallet = _marketingWallet;
        uniswapV2RouterAddress = _uniswapV2RouterAddress;
        uniswapV2Router = IUniswapV2Router02(uniswapV2RouterAddress);
        uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory()).createPair(address(this), uniswapV2Router.WETH());
        _mint(initialOwner, TOTAL_SUPPLY);
    }

    // Overriding the _transfer function to include marketing and liquidity fees.
    function _transfer(address from, address to, uint256 amount) internal virtual override {
        uint256 marketingFee = (amount * 2) / 100;
        uint256 liquidityFee = (amount * 2) / 100;
        uint256 fees = marketingFee + liquidityFee;
        uint256 sendAmount = amount - fees;

        super._transfer(from, to, sendAmount);

        if (marketingFee > 0) {
            super._transfer(from, marketingWallet, marketingFee);
        }

        if (liquidityFee > 0) {
            super._transfer(from, address(this), liquidityFee);
        }

        // Executing liquidity management functions if criteria are met.
        if (shouldPerformLiquidityFunctions(from)) {
            try this.manageLiquidity() {} catch {}
        }
    }

    // Managing liquidity by swapping tokens for ETH and adding liquidity.
   function manageLiquidity() public onlyOwner {
        if (canSwapTokensForEth()) {
            swapAccumulatedTokensForEth(balanceOf(address(this)));
        }
        
        if (canAddLiquidity()) {
            addLiquidity(balanceOf(address(this)), address(this).balance);
        }
    }

    // Checking if the contract can swap tokens for ETH.
    function canSwapTokensForEth() private view returns (bool) {
        uint256 contractTokenBalance = balanceOf(address(this));
        return contractTokenBalance >= accumulatedTokenThreshold;
    }

    // Checking if the contract can add liquidity.
    function canAddLiquidity() private view returns (bool) {
        uint256 ethBalance = address(this).balance;
        return ethBalance >= minEthBeforeLiquidity && (block.timestamp - lastLiquidityAddTime) >= minTimeInterval;
    }

    // Determining if liquidity functions should be performed.
    function shouldPerformLiquidityFunctions(address from) private view returns (bool) {
        return from != uniswapV2Pair && from != address(uniswapV2Router);
    }

    // Function to swap accumulated tokens for ETH.
    function swapAccumulatedTokensForEth(uint256 tokenAmount) private {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

        _approve(address(this), address(uniswapV2Router), tokenAmount);
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0,
            path,
            address(this),
            block.timestamp
        );
    }

    // Function to add liquidity to Uniswap.
    function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
        _approve(address(this), address(uniswapV2Router), tokenAmount);
        uniswapV2Router.addLiquidityETH{value: ethAmount}(
            address(this),
            tokenAmount,
            0,
            0,
            owner(),
            block.timestamp
        );
    }
}

Thank you in advance for your assistance.

Best regards,

Well, in order to be able to receive ETH, your contract needs to implement either one of the following:

  • function receive() external payable
  • function fallback() external payable

Note that you can save quite a bit of gas by declaring each one of these state variables immutable:

address public marketingWallet; // Address for marketing wallet.
address public uniswapV2RouterAddress; // Address of the Uniswap V2 Router.
IUniswapV2Router02 public uniswapV2Router; // Instance of the Uniswap V2 Router.
address public uniswapV2Pair; // Address of the Uniswap V2 Pair.

And TBH, the state variable uniswapV2RouterAddress actually looks completely redundant.

You can also use an additional immutable state variable for storing the value returned from calling uniswapV2Router.WETH() upon the deployment of your contract (i.e., in the constructor), instead of calling that function every time the user executes function manageLiquidity.

1 Like

Hi barackman thanks for the explanation, I made the changes you suggested:

contract CustomToken1 is ERC20, ERC20Burnable, ReentrancyGuard, Ownable {
    address public immutable marketingWallet; // Address for marketing wallet.
    address public immutable uniswapV2RouterAddress; // Address of the Uniswap V2 Router.
    IUniswapV2Router02 public immutable uniswapV2Router; // Instance of the Uniswap V2 Router.
    address public immutable uniswapV2Pair; // Address of the Uniswap V2 Pair.
    address public immutable WETH;

    uint256 public constant TOTAL_SUPPLY = 450000000000000 * 10**18; // Total token supply.
    uint256 public immutable minEthBeforeLiquidity; // Minimum ETH balance before adding liquidity.
    uint256 public lastLiquidityAddTime; // Timestamp of the last liquidity addition.
    uint256 public constant accumulatedTokenThreshold = 1000000000 * 10**18; // Predefined threshold for token swap.
    uint256 public constant minTimeInterval = 24 * 60 * 60; // Predefined time interval (24 hours).

    // Constructor to initialize the contract with specific parameters.
      constructor(
        address _marketingWallet,
        address _uniswapV2RouterAddress,
        address initialOwner
    ) ERC20("Testing4", "RRR") {
        marketingWallet = _marketingWallet;
        uniswapV2RouterAddress = _uniswapV2RouterAddress;
        uniswapV2Router = IUniswapV2Router02(_uniswapV2RouterAddress);
        WETH = uniswapV2Router.WETH(); // Uloženie hodnoty WETH pri nasadení
        uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory()).createPair(address(this), WETH);
        minEthBeforeLiquidity = 0.05 ether;
        _mint(initialOwner, TOTAL_SUPPLY);
    }

	// Accepting ETH with an external payment function.
        receive() external payable {}

In my previous question, I asked about the automatic exchange of accumulated tokens for ETH and their addition to liquidity, as it seems that the function manageLiquidity needs to be called from outside. How can I ensure that the process of swapping accumulated tokens for ETH and adding them to liquidity is fully automated? I am considering adding the manageLiquidity function directly into the _transfer function, with logic that would trigger this function only once every 24 hours. My intention is to limit the frequency of these operations to save on gas costs, while still ensuring regular liquidity updates. Is this approach an efficient and safe way to ensure automatic liquidity management? What potential risks or limitations could arise from implementing manageLiquidity into _transfer?

Thank you in advance for your professional advice and recommendations.

Sincerely,

or replace this :

  // auto liquidity
    bool public _swapAndLiquifyEnabled = true;
    bool _inSwapAndLiquify;
    IUniswapV2Router02 public _uniswapV2Router;
    address            public _uniswapV2Pair;
    event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap);
    event SwapAndLiquifyEnabledUpdated(bool enabled);
    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 bnbReceived,
        uint256 tokensIntoLiqudity

manageLiquidity can be replaced?

I have already addressed this issue in response to that previous question of yours.
Twice, actually.
First here, and then here, where I urged you to pay more attention to it.
You've ignored that twice, so I don't see any point in repeating it yet again.

1 Like

I was under the impression (and hope) that following the mess in your previous question, you have made some progress on how to properly ask a question here.

That (above) looks like a broken piece of code which was partially copy-pasted by accident, followed by a puzzling question with no seeming relation to that (broken) piece of code.

You might want to take another look at it, and ask yourself if anyone could possibly refer to that in any useful manner.

HI Barakman

I now understand that when I add these functions to _transfer, problems arise, so I must use manageLiquidity. Another thing is that I have 0.6 ETH on the contract and the swap did not occur since the condition minEthBeforeLiquidity = 0.05 ether is met. I used manageLiquidity and an error occurred:

 "action": {
      "from": "0xd966cf3eb6baf194b8dec13a0adb65324eb47d72",
      "callType": "call",
      "gas": "0x2fcc2",
      "input": "0x7cfff8e6",
      "to": "0xd966cf3eb6baf194b8dec13a0adb65324eb47d72",
      "value": "0x0"
    },
    "blockHash": "0xa7014fad88a50a415d35f915dbd895980d75c6831767cf531f380ff5a38dea05",
    "blockNumber": 10317849,
    "error": "Reverted",
    "result": {
      "gasUsed": "0x1dc",
      "output": "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000204f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572"
    },

here is the txn

It looks like the uniswaprouter returned to the contract. Could it be because manageLiquidity is set to onlyOwner????? :thinking:

After everything that we've discussed, in this question and on the previous one, you are still trying to add liquidity from inside your _transfer function???:

// Overriding the _transfer function to include marketing and liquidity fees.
function _transfer(address from, address to, uint256 amount) internal virtual override {
    ...
    // Executing liquidity management functions if criteria are met.
    if (shouldPerformLiquidityFunctions(from)) {
    try this.manageLiquidity() {} catch {}
    }
}

Dude, I'm giving up on this!!!

Hello Barakman, I have already fixed that and redeployed. I have 0.3 ETH on the contract now. I'll wait one hour and I'm curious to see if the ETH will be sent to liquidity.

Please take a look at this. When I called the function manageLiquidity:

This transaction has completed successfully, so what exactly do you want me to look at?

eth that are on the contract will not be transferred to liquidity

It is, but the uniswap contract transfers the exact same amount back to your contract.

See the highlighted line in the screenshot below:

As you can see your transaction triggers 3 internal transactions.

In the 1st one, 0.328 ETH is transferred from your contract (0x73b) to uniswap contract (0x7a2).

In the 3rd one, 0.328 ETH is transferred from uniswap contract (0x7a2) to your contract (0x73b).

yes, I see it too, but why? when function

  function manageLiquidity() public onlyAuthorized {
        if (canSwapTokensForEth()) {
            swapAccumulatedTokensForEth(balanceOf(address(this)));
        }
        
        if (canAddLiquidity()) {
            addLiquidity(balanceOf(address(this)), address(this).balance);
        }

should have sent to liquidity I don't know how to find out the reason

The reason is obviously in the source code of the uniswap contract and not in the source code of your contract, since it is the uniswap contract which has sent ETH to your contract and not vice versa.

There you go:

    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
        (amountToken, amountETH) = _addLiquidity(
            token,
            WETH,
            amountTokenDesired,
            msg.value,
            amountTokenMin,
            amountETHMin
        );
        address pair = UniswapV2Library.pairFor(factory, token, WETH);
        TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
        IWETH(WETH).deposit{value: amountETH}();
        assert(IWETH(WETH).transfer(pair, amountETH));
        liquidity = IUniswapV2Pair(pair).mint(to);
        // refund dust eth, if any
        if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
    }

As you can see, the amount of ETH that you transfer to the uniswap contract when you add liquidity to it, isn't necessarily the amount of ETH that this contract will end up accepting.

The contract function first runs an internal calculation in order to determine the correct input amounts that you can actually add to the pool, and then transfers ONLY those amount from your contract (or wallet) to itself.

And those amounts - they must be proportional to the current balances in the pool.

For example, suppose that the pool consists of reserve tokens XXX and YYY, and suppose that the current balances in the pool are 50 XXXs and 80 YYYs, then you can add liquidity consisting of the exact same proportion, such as 5 XXXs and 8 YYYs, or 10 XXXs and 16 YYYs, etc.

The reason for this restriction is that adding liquidity in any other way would change the trade (swap) price between the two tokens, subjecting the pool to financial manipulation.

Therefore, whatever amounts you specify when you add liquidity, the uniswap contract function may "truncate" ONE of them in order for proportion between them to be aligned with the proportion of the pool balances.

In the screenshot that I attached on my previous comment, you can see that indeed the uniswap contract sent back to your contract slightly less ETH than what your contract has initially sent to it. Based on the amount of tokens that you've specified, it has determined that only a fraction of the ETH that you've specified could be used there, and then sent the rest of it back to your contract.

1 Like