Alluo Explained
  • Welcome
    • 🧭 The Basics
  • Getting Started
    • πŸ’» The DeFi Web App
      • ❓ Connecting to the app
      • 🚜 Depositing into the farms
      • πŸ™Œ Other basics
      • 🏦 Importing your Mobile app wallet to Metamask
      • 🧬 Add the polygon network manually in Metamask.
      • ⛓️ Bridging Stablecoins from another chain
    • πŸ“±The Mobile app
      • πŸ—οΈ Setting up your account
      • 🏦 Depositing money into the app
      • πŸ™Œ Other basics
      • πŸ” Exporting your private key
    • πŸ“–Tech deep dive: Contract Address Library
  • Understanding Alluo
    • πŸ’΅ How does Alluo get the yield?
      • 🐰 Going deeper into the Alluo protocol rabbit hole
    • 🧐 FAQ
  • Tokens & Tokenomics
    • πŸͺ™ The tokens
    • πŸ‘¨β€πŸ”¬Tech deep dive: Interest Bearing {asset} token
      • Depositing
      • Withdrawals
      • IbAlluo on different chains
      • StIbAlluo and Superfluid
        • A closer look at the integration between IbAlluo and StIbAlluo
      • Using the IbAlluo contract directly to create streams
      • Liquidity Handler and adapters
        • Deposit process with the Liquidity Handler
        • Withdraw process with the Liquidity Handler
    • πŸ“ˆ Tokenomics
    • πŸ‘¨β€πŸ”¬Tech deep dive: Boosting yield by compounding rewards
      • Deposit into the Vault
      • Withdraw from the Vault
      • Redeem rewards
      • Automatic boosting with Alluo
      • FraxConvex Vaults
      • Managing withdrawal requests in IERC4626
  • Decentralisation and Trust
    • πŸ—³οΈ Trustless governance and execution
    • πŸ‘¨β€πŸ”¬Tech deep dive: Vote Executor Architecture
      • Off chain votes to on chain data
      • Onchain data verifcation
      • Automated execution of votes
        • Tokenomics
        • Liquidity Direction
        • Setting APYs on farms
      • Cross chain execution of votes
      • Manually submitting vote results onchain
    • ↔️Alluo Exchange
      • Interacting with the Exchange
    • vlAlluo Architecture
    • Contracts upgrades
    • Investment strategies
      • πŸ“ˆFrax Convex Finance
        • Adding new pools into the strategy
        • Investing into a pool
  • More Advanced Features
    • πŸ” Repeat payments, streaming IbAlluo
  • Product Updates
    • πŸ‘Œ Product Roadmap: Building the right products
    • πŸ’» Web App releases
    • πŸ“± Mobile App releases
    • 🏎️ Alluo Boost
  • tutorial projects
    • Example: USDC to streaming 1 IbAlluo per second
    • Example: Using IbAlluoUSD and Ricochet to do capital efficient DCA into ETH
Powered by GitBook
On this page
  1. Tokens & Tokenomics
  2. Tech deep dive: Interest Bearing {asset} token
  3. Liquidity Handler and adapters

Withdraw process with the Liquidity Handler

Example: A walkthrough of a withdrawal from IbAlluoUSD to USDC

Step 1: User calls withdraw with 100 USDC and withdraw is called from the handler to send funds directly to the user.

/// @notice  Withdraws accuratel
/// @dev When called, immediately check for new interest index. Then find the adjusted amount in IbAlluo tokens
///      Then burn appropriate amount of IbAlluo tokens to receive asset token
/// @param _targetToken Asset token
/// @param _amount Amount (parsed 10**18) in asset value

function withdrawTo(
    address _recipient,
    address _targetToken,
    uint256 _amount
) public {
    updateRatio();
    uint256 adjustedAmount = (_amount * multiplier) / growingRatio;
    _burn(_msgSender(), adjustedAmount);
    ILiquidityHandler handler = ILiquidityHandler(liquidityHandler);
    if (supportedTokens.contains(_targetToken) == false) {
        (address liquidToken,) = ILiquidityHandler(liquidityHandler).getAdapterCoreTokensFromIbAlluo(address(this));
        // This just is used to revert if there is no active route.
        require(IExchange(exchangeAddress).buildRoute(liquidToken, _targetToken).length > 0, "!Supported");
        handler.withdraw(
        _recipient,
        liquidToken,
        _amount,
        _targetToken
        );
    } else {
        handler.withdraw(
        _recipient,
        _targetToken,
        _amount
        );
    }

    emit TransferAssetValue(_msgSender(), address(0), adjustedAmount, _amount, growingRatio);
    emit BurnedForWithdraw(_msgSender(), adjustedAmount);
}

Step 2: When withdraw is called for 100 USDC, carry out checks and either withdraw funds directly or add the user to queue.

  1. If the buffer can be used to meet the withdrawal, simply call withdraw on the adapter to send 100 USDC tokens directly to the user.

  2. If there is not enough funds in the buffer OR there is an existing queue of withdrawals, add the user to the queue until there are enough funds to meet it.

LiquidityHandler.sol
 /** @notice Called by ibAlluo, withdraws tokens from the adapter.
 * @dev Attempt to withdraw. If there are insufficient funds, you are added to the queue.
 ** @param _user Address of depositor
 ** @param _token Address of token (USDC, DAI, USDT...)
 ** @param _amount Amount of tokens in 10**18
 */
function withdraw(
    address _user,
    address _token,
    uint256 _amount
) external whenNotPaused onlyRole(DEFAULT_ADMIN_ROLE) {
    uint256 inAdapter = getAdapterAmount(msg.sender);

    WithdrawalSystem storage withdrawalSystem = ibAlluoToWithdrawalSystems[
        msg.sender
    ];
    if (inAdapter >= _amount && withdrawalSystem.totalWithdrawalAmount == 0) {
        uint256 adapterId = ibAlluoToAdapterId.get(msg.sender);
        address adapter = adapterIdsToAdapterInfo[adapterId].adapterAddress;
        IHandlerAdapter(adapter).withdraw(_user, _token, _amount);
        emit WithdrawalSatisfied(
            msg.sender,
            _user,
            _token,
            _amount,
            0,
            block.timestamp
        );
    } else {
        uint256 lastWithdrawalRequest = withdrawalSystem
            .lastWithdrawalRequest;
        withdrawalSystem.lastWithdrawalRequest++;
        withdrawalSystem.withdrawals[
            lastWithdrawalRequest + 1
        ] = Withdrawal({
            user: _user,
            token: _token,
            amount: _amount,
            time: block.timestamp
        });
        withdrawalSystem.totalWithdrawalAmount += _amount;
        emit AddedToQueue(
            msg.sender,
            _user,
            _token,
            _amount,
            lastWithdrawalRequest + 1,
            block.timestamp
        );
    }
}

Step 2.5: If the user wanted a withdrawal in a token not supported by the IbAlluo (i.e WETH for IbAlluoUSD), carry out the withdrawal and do a swap through the Alluo Exchange before sending to the user

//// Same function as above but overload for case when you want to withdraw in a different token native to an ibAlluo.
// For example: Withdraw USDC from ibAlluoEth.
function withdraw(
    address _user,
    address _token,
    uint256 _amount,
    address _outputToken
) external whenNotPaused onlyRole(DEFAULT_ADMIN_ROLE) {
    uint256 inAdapter = getAdapterAmount(msg.sender);

    WithdrawalSystem storage withdrawalSystem = ibAlluoToWithdrawalSystems[
        msg.sender
    ];
    if (
        inAdapter >= _amount && withdrawalSystem.totalWithdrawalAmount == 0
    ) {
        uint256 adapterId = ibAlluoToAdapterId.get(msg.sender);
        address adapter = adapterIdsToAdapterInfo[adapterId].adapterAddress;
        if (_token != _outputToken) {
            IHandlerAdapter(adapter).withdraw(address(this), _token, _amount);
            _withdrawThroughExchange(_token, _outputToken, _amount, _user);
        } else {
            IHandlerAdapter(adapter).withdraw(_user, _token, _amount);
        }
        emit WithdrawalSatisfied(
            msg.sender,
            _user,
            _token,
            _amount,
            0,
            block.timestamp
        );
    } else {
        require(
            _token == _outputToken,
            "Handler: Only supported tokens"
        );
        uint256 lastWithdrawalRequest = withdrawalSystem
            .lastWithdrawalRequest;
        withdrawalSystem.lastWithdrawalRequest++;
        withdrawalSystem.withdrawals[
            lastWithdrawalRequest + 1
        ] = Withdrawal({
            user: _user,
            token: _token,
            amount: _amount,
            time: block.timestamp
        });
        withdrawalSystem.totalWithdrawalAmount += _amount;
        emit AddedToQueue(
            msg.sender,
            _user,
            _token,
            _amount,
            lastWithdrawalRequest + 1,
            block.timestamp
        );
    }
}

// Here, we are directly swapping tokens and then sending these tokens 
function _withdrawThroughExchange(
    address _inputToken,
    address _targetToken,
    uint256 _amount18,
    address _user
) internal {
    uint256 amountinInputTokens = (_amount18 *
        10**ERC20Upgradeable(_inputToken).decimals()) / 10**18;
    IERC20Upgradeable(_inputToken).safeIncreaseAllowance(
        exchangeAddress,
        amountinInputTokens
    );
    uint256 amountinTargetTokens = IExchange(exchangeAddress).exchange(
        _inputToken,
        _targetToken,
        amountinInputTokens,
        0
    );
    IERC20Upgradeable(_targetToken).safeTransfer(_user, amountinTargetTokens);
}

Note, if there is insufficient funds in the buffer and the user wanted to withdraw in a non-supported token (i.e WETH in IbAlluoUSD withdrawals), instead of being added to the queue, the transaction will revert with the require block in line 34 in the code snippet above. This is because of fluctuating token prices over time and meeting withdrawals in a non native token at a later date may result in users receiving significantly less tokens than they initially expected.

Step 3: When withdraw is called on the adapter, it sends the tokens directly to the user.

USDCurveAdapter.sol
/// @notice When called by liquidity handler, withdraws funds from liquidity pool
/// @dev It checks against arbitragers attempting to exploit spreads in stablecoins. 
/// @param _user Recipient address
/// @param _token Deposit token address (eg. USDC)
/// @param _amount  Amount to be withdrawn in 10*18
function withdraw (address _user, address _token, uint256 _amount ) external onlyRole(DEFAULT_ADMIN_ROLE) {
    
    uint256[3] memory amounts;
    address liquidToken = ICurvePoolUSD(curvePool).underlying_coins(liquidTokenIndex);
    uint256 amount = _amount / 10**(18 - IERC20Metadata(liquidToken).decimals());
    amounts[liquidTokenIndex] = amount;
    
    if(_token == liquidToken){
        ICurvePoolUSD(curvePool).remove_liquidity_imbalance(
            amounts, 
            _amount * (10000 + slippage) / 10000,
            true
        );
        IERC20(_token).safeTransfer(_user, amount);
    }
    else{
        // We want to be save agains arbitragers so at any withraw contract checks 
        // how much will be burned curveLp by withrawing this amount in token with most liquidity
        // and passes this burned amount to get tokens
        uint256 toBurn = ICurvePoolUSD(curvePool).calc_token_amount(amounts, false);
        uint256 minAmountOut = _amount / 10**(18 - IERC20Metadata(_token).decimals());
        uint256 toUser = ICurvePoolUSD(curvePool).remove_liquidity_one_coin(
                toBurn, 
                int128(indexes[_token]), 
                minAmountOut * (10000 - slippage) / 10000,
                true
            );
        IERC20(_token).safeTransfer(_user, toUser);
    }
}
    

PreviousDeposit process with the Liquidity HandlerNextπŸ“ˆ Tokenomics

Last updated 2 years ago

πŸ‘¨β€πŸ”¬