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 valuefunctionwithdrawTo(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 ); }emitTransferAssetValue(_msgSender(),address(0), adjustedAmount, _amount, growingRatio);emitBurnedForWithdraw(_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.
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.
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 */functionwithdraw(address_user,address_token,uint256_amount) externalwhenNotPausedonlyRole(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);emitWithdrawalSatisfied( 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;emitAddedToQueue( 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.functionwithdraw(address_user,address_token,uint256_amount,address_outputToken) externalwhenNotPausedonlyRole(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); }emitWithdrawalSatisfied( 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;emitAddedToQueue( 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*18functionwithdraw (address_user,address_token,uint256_amount ) externalonlyRole(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 tokensuint256 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); }}