Deposit process with the Liquidity Handler

Example: A walkthrough of a USDC deposit into IbAlluoUSD

Step 1: User calls deposit with 100 USDC and the funds are sent to the voteExecutor and IbAlluo calls the deposit function.

function deposit(address _token, uint256 _amount) external {
    if (supportedTokens.contains(_token) == false) {
        IERC20Upgradeable(_token).safeTransferFrom(_msgSender(), address(this), _amount);
        (, address primaryToken) = ILiquidityHandler(liquidityHandler).getAdapterCoreTokensFromIbAlluo(address(this));
        IERC20Upgradeable(_token).safeIncreaseAllowance(exchangeAddress, _amount);
        _amount = IExchange(exchangeAddress).exchange(_token, primaryToken, _amount, 0);
        _token = primaryToken;
        
        //Funds are sent to the liquidity Handler
        IERC20Upgradeable(primaryToken).safeTransfer(address(liquidityHandler), _amount);
    } else {
        //Funds are sent to the liquidity Handler
        IERC20Upgradeable(_token).safeTransferFrom(_msgSender(),address(liquidityHandler),_amount);
    }
    updateRatio();
    ILiquidityHandler(liquidityHandler).deposit(_token, _amount);
    uint256 amountIn18 = _amount * 10**(18 - AlluoERC20Upgradable(_token).decimals());
    uint256 adjustedAmount = (amountIn18 * multiplier) / growingRatio;
    _mint(_msgSender(), adjustedAmount);
    emit TransferAssetValue(address(0), _msgSender(), adjustedAmount, amountIn18, growingRatio);
    emit Deposited(_msgSender(), _token, _amount);
}

Step 2: Now the 100 USDC are in the liquidityHandler and the deposit function is called, either two of the following occur

  1. If the expectedAdapterAmount (minimum buffer requirement) is not met, the 100 USDC is deposited into the adapter until the buffer is met and any surplus is sent to the treasury.

  2. If the buffer requirement is already met, the funds are sent directly to the treasury.

LiquidityHandler.sol
/** @notice Called by ibAlluo, deposits tokens into the adapter.
 * @dev Deposits funds, checks whether adapter is filled or insufficient, and then acts accordingly.
 ** @param _token Address of token (USDC, DAI, USDT...)
 ** @param _amount Amount of tokens in correct deimals (10**18 for DAI, 10**6 for USDT)
 */
function deposit(address _token, uint256 _amount)
    external
    whenNotPaused
    onlyRole(DEFAULT_ADMIN_ROLE)
{
    uint256 amount18 = _amount *
        10**(18 - ERC20Upgradeable(_token).decimals());

    uint256 inAdapter = getAdapterAmount(msg.sender);
    uint256 expectedAdapterAmount = getExpectedAdapterAmount(
        msg.sender,
        amount18
    );

    uint256 adapterId = ibAlluoToAdapterId.get(msg.sender);
    address adapter = adapterIdsToAdapterInfo[adapterId].adapterAddress;

    IERC20Upgradeable(_token).safeTransfer(adapter, _amount);
    if (inAdapter < expectedAdapterAmount) {
        //
        // Case 1
        //
        if (expectedAdapterAmount < inAdapter + amount18) {
            uint256 toWallet = inAdapter + amount18 - expectedAdapterAmount;
            uint256 leaveInPool = amount18 - toWallet;
            IHandlerAdapter(adapter).deposit(_token, amount18, leaveInPool);
        } else {
            IHandlerAdapter(adapter).deposit(_token, amount18, amount18);
        }
    } else {
        //
        // Case 2
        //
        IHandlerAdapter(adapter).deposit(_token, amount18, 0);
    }

    WithdrawalSystem storage withdrawalSystem = ibAlluoToWithdrawalSystems[
        msg.sender
    ];

    if (
        withdrawalSystem.totalWithdrawalAmount > 0 && !withdrawalSystem.resolverTrigger
    ) {
        uint256 inAdapterAfterDeposit = getAdapterAmount(msg.sender);
        uint256 firstInQueueAmount = withdrawalSystem
            .withdrawals[withdrawalSystem.lastSatisfiedWithdrawal + 1].amount;
        if (firstInQueueAmount <= inAdapterAfterDeposit) {
            withdrawalSystem.resolverTrigger = true;
            emit EnoughToSatisfy(
                msg.sender,
                inAdapterAfterDeposit,
                withdrawalSystem.totalWithdrawalAmount
            );
        }
    }
}

Here is the logic that occurs in the actual adapter. The funds are either kept in the contract as a buffer for withdrawals, or they are sent to the treasury.

USDCurveAdapter.sol
/// @notice When called by liquidity handler, moves some funds to the Gnosis multisig and others into a LP to be kept as a 'buffer'
/// @param _token Deposit token address (eg. USDC)
/// @param _fullAmount Full amount deposited in 10**18 called by liquidity handler
/// @param _leaveInPool  Amount to be left in the LP rather than be sent to the Gnosis wallet (the "buffer" amount)
function deposit(address _token, uint256 _fullAmount, uint256 _leaveInPool) external onlyRole(DEFAULT_ADMIN_ROLE) {
    uint256 toSend = _fullAmount - _leaveInPool;
    address primaryToken = ICurvePoolUSD(curvePool).underlying_coins(primaryTokenIndex);
    if(_token == primaryToken){
        if (toSend != 0) {
            IERC20(primaryToken).safeTransfer(wallet, toSend / 10**(18 - IERC20Metadata(primaryToken).decimals()));
        }
        if (_leaveInPool != 0) {
            uint256[3] memory amounts;
            amounts[primaryTokenIndex] = _leaveInPool / 10**(18 - IERC20Metadata(primaryToken).decimals());
            ICurvePoolUSD(curvePool).add_liquidity(amounts, 0, true);
        }
    }
    else{
        uint256[3] memory amounts;
        amounts[indexes[_token]] = _fullAmount / 10**(18 - IERC20Metadata(_token).decimals());

        uint256 lpAmount = ICurvePoolUSD(curvePool).add_liquidity(amounts, 0, true);
        delete amounts;
        if (toSend != 0) {
            toSend = toSend / 10**(18 - IERC20Metadata(primaryToken).decimals());
            amounts[primaryTokenIndex] = toSend;
            ICurvePoolUSD(curvePool).remove_liquidity_imbalance(
                        amounts, 
                        lpAmount * (10000+slippage)/10000,
                        true);
            IERC20(primaryToken).safeTransfer(wallet, toSend);
        }
    }
} 

Here you can see this logic with the Curve 3pool. In other adapters, funds are just kept as native tokens (ETH, BTC... for IbAlluoBTC, IbAlluoETH) but here the funds are kept as LP tokens. Tokens are converted to a 'primaryToken' with the most liquidity to dissuade arbitrage opportunities upon withdrawals.

Step 3: If this new deposit allows preexisting withdrawal requests to be met, turn on a flag that allows our gelato resolver to execute the pending withdrawal

Inside the deposit function
 WithdrawalSystem storage withdrawalSystem = ibAlluoToWithdrawalSystems[
        msg.sender
    ];

    if (
        withdrawalSystem.totalWithdrawalAmount > 0 && !withdrawalSystem.resolverTrigger
    ) {
        uint256 inAdapterAfterDeposit = getAdapterAmount(msg.sender);
        uint256 firstInQueueAmount = withdrawalSystem
            .withdrawals[withdrawalSystem.lastSatisfiedWithdrawal + 1].amount;
        if (firstInQueueAmount <= inAdapterAfterDeposit) {
            withdrawalSystem.resolverTrigger = true;
            emit EnoughToSatisfy(
                msg.sender,
                inAdapterAfterDeposit,
                withdrawalSystem.totalWithdrawalAmount
            );
        }
    }

Last updated