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.
functiondeposit(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 HandlerIERC20Upgradeable(primaryToken).safeTransfer(address(liquidityHandler), _amount); } else {//Funds are sent to the liquidity HandlerIERC20Upgradeable(_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);emitTransferAssetValue(address(0),_msgSender(), adjustedAmount, amountIn18, growingRatio);emitDeposited(_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
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.
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) */functiondeposit(address_token,uint256_amount)externalwhenNotPausedonlyRole(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;emitEnoughToSatisfy( 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)functiondeposit(address_token,uint256_fullAmount,uint256_leaveInPool) externalonlyRole(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