curvefi/curve-contract

I cannot AddLiquidity in the StableSwap3Pool.vy Smart Contract through a middle Operator Solidity Smart Contract.

Nachoxt17 opened this issue ยท 5 comments

https://github.com/curvefi/curve-contract/blob/master/contracts/pools/3pool/StableSwap3Pool.vy

Hello, I have a Unit Test which is Testing this Soldity Smart Contract Implementation:

ICurveFiPool.sol :

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.9;

interface ICurveFiPool {
    function get_virtual_price() external returns (uint256 out);

    function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external;

    function get_dy(
        int128 i,
        int128 j,
        uint256 dx
    ) external returns (uint256 out);

    function get_dy_underlying(
        int128 i,
        int128 j,
        uint256 dx
    ) external returns (uint256 out);

    function exchange(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy
    ) external;

    function exchange(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy,
        uint256 deadline
    ) external;

    function exchange_underlying(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy
    ) external;

    function exchange_underlying(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy,
        uint256 deadline
    ) external;

    function remove_liquidity(uint256 _amount, uint256[] calldata min_amounts) external;

    function remove_liquidity_imbalance(uint256[2] calldata amounts, uint256 deadline) external;

    function commit_new_parameters(
        int128 amplification,
        int128 new_fee,
        int128 new_admin_fee
    ) external;

    function apply_new_parameters() external;

    function revert_new_parameters() external;

    function commit_transfer_ownership(address _owner) external;

    function apply_transfer_ownership() external;

    function revert_transfer_ownership() external;

    function withdraw_admin_fees() external;

    function coins(uint256 arg0) external returns (address out);

    function underlying_coins(int128 arg0) external returns (address out);

    function balances(int128 arg0) external returns (uint256 out);

    function A() external returns (int128 out);

    function fee() external returns (int128 out);

    function admin_fee() external returns (int128 out);

    function owner() external returns (address out);

    function admin_actions_deadline() external returns (uint256 out);

    function transfer_ownership_deadline() external returns (uint256 out);

    function future_A() external returns (int128 out);

    function future_fee() external returns (int128 out);

    function future_admin_fee() external returns (int128 out);

    function future_owner() external returns (address out);
}

CurveFiOperator.sol :

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.9;

import "./ICurveFiPool.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/*
 * - CurveFi Pool Smart Contract Address: 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7
 * - Underlaying Tokens Available in the CurveFiYPool(https://etherscan.io/address/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7#readContract#F8):
 * Index 0: DAI - 0x6B175474E89094C44Da98b954EedeAC495271d0F
 * Index 1: USDC - 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
 * Index 2: USDT - 0xdAC17F958D2ee523a2206206994597C13D831ec7
 * Liquidity Pool Token: 0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490
 */

//-Addresses Source: https://curve.readthedocs.io/ref-addresses.html#base-pools

contract CurveFiOperator {
    event Received(address sender, uint256 value);

    function getLPTokens(address lpTokenAddress) public returns (uint256) {
        return ICurveFiPoolToken(lpTokenAddress).balanceOf(address(this));
    }

    /// @param _amountTIndexes: List of Amounts of Different Coins to Deposit. Ex: [2 DAI, 4 USDC, 3 USDT].
    function depositLiquidityInOperator(address curveFiPool, uint256[3] memory _amountTIndexes) external {
        for (uint256 i = 0; i < 3; i++) {
            if (_amountTIndexes[i] > 0) {
                address currentCoin = ICurveFiPool(curveFiPool).coins(i);
                IERC20Modified(currentCoin).approve(address(this), _amountTIndexes[i]);
                IERC20Modified(currentCoin).transferFrom(msg.sender, address(this), _amountTIndexes[i]);
            }
        }
    }

    /// @param _amountTIndexes: List of Amounts of Different Coins to Deposit. Ex: [2 DAI, 4 USDC, 3 USDT].
    /// @param minLPTokenMintAmount: Minimum amount of L.P. Tokens to mint from the deposit.
    /// return Returns the Minimum Amount of L.P. tokens received in exchange for the deposited tokens.
    /// @dev More Info Here: https://resources.curve.fi/lp/depositing/depositing-into-the-y-pool
    function addLiquidity(
        address curveFiPool,
        uint256[3] memory _amountTIndexes,
        uint256 minLPTokenMintAmount
    ) external {
        for (uint256 i = 0; i < 3; i++) {
            if (_amountTIndexes[i] > 0) {
                address currentCoin = ICurveFiPool(curveFiPool).coins(i);
                IERC20Modified(currentCoin).approve(address(this), _amountTIndexes[i]);
                IERC20Modified(currentCoin).transferFrom(msg.sender, address(this), _amountTIndexes[i]);
            }
        }

        //-Example of the Function in the Curve S.C.s:_ def add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256):
        ICurveFiPool(curveFiPool).add_liquidity(_amountTIndexes, minLPTokenMintAmount);
    }

 //...

    function safeTransferETH(address to, uint256 value) private {
        (bool success, ) = to.call{ value: value }(new bytes(0));
        require(success, "TransferHelper: ETH_TRANSFER_FAILED");
    }

    receive() external payable {
        emit Received(msg.sender, msg.value);
    }
}

That is Being Tested with the Hardhat MainNet Forking tool trying to Operate with THIS Vyper Smart Contract which is Deployed at THIS Address. When I try to Add Liquidity to the Protocol, basically doing the same as in THIS Recent Transaction, it fails without any message. The S.C.s is not "Paused" or Killed, the Parameters Introduced to the Function are Correct and I already reviewed all the require(...); of all the Functions that I call in THIS Vyper Smart Contract (In Vyper the are called assert, like in HERE). Also the changing or omitting the BlockNumber of the Hardhat MainNet Forking tool doesn't affects in anything.

  • The Return that I get is this one:
1) CurveFinance Pool S.Contract
      CurveFi Pool Adds Liquidity:
    Error: Transaction reverted without a reason string
     at <UnrecognizedContract>.<unknown> (0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7)
     at CurveFiOperator.addLiquidity (contracts/CurveFi/CurveFiOperator.sol:65)
     at async HardhatNode._mineBlockWithPendingTxs (node_modules\hardhat\src\internal\hardhat-network\provider\node.ts:1773:23)
     at async HardhatNode.mineBlock (node_modules\hardhat\src\internal\hardhat-network\provider\node.ts:466:16)
     at async EthModule._sendTransactionAndReturnHash (node_modules\hardhat\src\internal\hardhat-network\provider\modules\eth.ts:1504:18)
     at async HardhatNetworkProvider.request (node_modules\hardhat\src\internal\hardhat-network\provider\provider.ts:118:18)
     at async EthersProviderWrapper.send (node_modules\@nomiclabs\hardhat-ethers\src\internal\ethers-provider-wrapper.ts:13:20)
  • When
    I try to recreate that Same Transaction in Tenderly with these Parameters:
  • Caller (Whale Wallet that I impersonate for Testing): 0x935f64B44B5C48A1539C4AdA5161D27ace4205b5
  • BlockNumber: Either 15269796 or the Latest.
  • S.C. A.B.I. CurveFiOperator.json :
[
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Received",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "curveFiPool",
        "type": "address"
      },
      {
        "internalType": "uint256[3]",
        "name": "_amountTIndexes",
        "type": "uint256[3]"
      },
      {
        "internalType": "uint256",
        "name": "minLPTokenMintAmount",
        "type": "uint256"
      }
    ],
    "name": "addLiquidity",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "curveFiPool",
        "type": "address"
      },
      {
        "internalType": "uint256[3]",
        "name": "_amountTIndexes",
        "type": "uint256[3]"
      }
    ],
    "name": "depositLiquidityInOperator",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "curveFiPool",
        "type": "address"
      },
      {
        "internalType": "int128",
        "name": "_tokenInIndex",
        "type": "int128"
      },
      {
        "internalType": "int128",
        "name": "_tokenOutIndex",
        "type": "int128"
      },
      {
        "internalType": "uint256",
        "name": "_amountIn",
        "type": "uint256"
      }
    ],
    "name": "exchange",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "lpTokenAddress",
        "type": "address"
      }
    ],
    "name": "getLPTokens",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "curveFiPool",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "LPtokensToBurn",
        "type": "uint256"
      },
      {
        "internalType": "uint256[]",
        "name": "_minAmountTIndexes",
        "type": "uint256[]"
      }
    ],
    "name": "removeLiquidity",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]
  • Function Call:
addLiquidity(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7,
[90000000000000000000,
100000000,
95000000],
1000000000000000000)
  • Or also:
addLiquidity(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7,
[0000000000000000000,
100000000,
0000000],
96000000000000000000)
  • And the OutPut is: invalid opcode: opcode 0xa7 not defined

  • Do you have any idea of why is this happening?:

Are you sure your approvals are correct?

                IERC20Modified(currentCoin).approve(address(this), _amountTIndexes[i]);
                IERC20Modified(currentCoin).transferFrom(msg.sender, address(this), _amountTIndexes[i]);

the caller should be allowing your contract to spend the tokens. here on the first line your contract is approving itself.
then your contract needs to approve the pool's contract for spending each of the token you'll deposit before it can deposit there.

My guess is that you need

IERC20Modified(currentCoin).approve(curveFiPool, _amountTIndexes[i]);

@benber86 Yes, I tested the Deposit of ERC-20 Tokens from the Wallet to my Operator S.C. (That Uses address(this)) in an Isolated way and it worked:

    /// @param _amountTIndexes: List of Amounts of Different Coins to Deposit. Ex: [2 DAI, 4 USDC, 3 USDT].
    function depositLiquidityInOperator(address curveFiPool, uint256[3] memory _amountTIndexes) external {
        for (uint256 i = 0; i < 3; i++) {
            if (_amountTIndexes[i] > 0) {
                address currentCoin = ICurveFiPool(curveFiPool).coins(i);
                IERC20Modified(currentCoin).approve(address(this), _amountTIndexes[i]);
                IERC20Modified(currentCoin).transferFrom(msg.sender, address(this), _amountTIndexes[i]);
            }
        }
    }

My guess is that you need

IERC20Modified(currentCoin).approve(curveFiPool, _amountTIndexes[i]);

Thanks, I will try that...

My guess is that you need

IERC20Modified(currentCoin).approve(curveFiPool, _amountTIndexes[i]);

IT WORKED YOU ARE A DAMN GENIUS I HAVE BEEN STRUGGLING WITH THAT SH*T FOR DAYS THANK YOU VERY MUCH!!! A STRONG HUG!!!