// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import { IERC20 } from '../../../interfaces/IERC20.sol'; import { SafeERC20 } from '../../../utils/SafeERC20.sol'; import { _FungibleToken } from '../_FungibleToken.sol'; import { _FungibleTokenMetadata } from '../../fungible/metadata/_FungibleTokenMetadata.sol'; import { _IFungibleVaultToken } from './_IFungibleVaultToken.sol'; import { ERC20Storage } from '../../../storage/ERC20Storage.sol'; /** * @title Base FungibleVaultToken internal functions */ abstract contract _FungibleVaultToken is _IFungibleVaultToken, _FungibleToken, _FungibleTokenMetadata { using SafeERC20 for IERC20; /** * @notice get the address of the base token used for vault accountin purposes * @return asset base token address */ function _asset() internal view virtual returns (address asset) { asset = ERC20Storage .layout(ERC20Storage.DEFAULT_STORAGE_SLOT) .erc4626Asset; } /** * @notice get the total quantity of the base asset currently managed by the vault * @return total managed asset amount */ function _totalAssets() internal view virtual returns (uint256) { return IERC20(_asset()).balanceOf(address(this)); } /** * @notice calculate the quantity of shares received in exchange for a given quantity of assets, not accounting for slippage * @param assetAmount quantity of assets to convert * @return shareAmount quantity of shares calculated */ function _convertToShares( uint256 assetAmount ) internal view virtual returns (uint256 shareAmount) { uint256 supply = _totalSupply(); if (supply == 0) { shareAmount = assetAmount; } else { uint256 totalAssets = _totalAssets(); if (totalAssets == 0) { shareAmount = assetAmount; } else { shareAmount = (assetAmount * supply) / totalAssets; } } } /** * @notice calculate the quantity of assets received in exchange for a given quantity of shares, not accounting for slippage * @param shareAmount quantity of shares to convert * @return assetAmount quantity of assets calculated */ function _convertToAssets( uint256 shareAmount ) internal view virtual returns (uint256 assetAmount) { uint256 supply = _totalSupply(); if (supply == 0) { assetAmount = shareAmount; } else { assetAmount = (shareAmount * _totalAssets()) / supply; } } /** * @notice calculate the maximum quantity of base assets which may be deposited on behalf of given receiver * @dev unused address param represents recipient of shares resulting from deposit * @return maxAssets maximum asset deposit amount */ function _maxDeposit( address ) internal view virtual returns (uint256 maxAssets) { maxAssets = type(uint256).max; } /** * @notice calculate the maximum quantity of shares which may be minted on behalf of given receiver * @dev unused address param represents recipient of shares resulting from deposit * @return maxShares maximum share mint amount */ function _maxMint( address ) internal view virtual returns (uint256 maxShares) { maxShares = type(uint256).max; } /** * @notice calculate the maximum quantity of base assets which may be withdrawn by given holder * @param owner holder of shares to be redeemed * @return maxAssets maximum asset mint amount */ function _maxWithdraw( address owner ) internal view virtual returns (uint256 maxAssets) { maxAssets = _convertToAssets(_balanceOf(owner)); } /** * @notice calculate the maximum quantity of shares which may be redeemed by given holder * @param owner holder of shares to be redeemed * @return maxShares maximum share redeem amount */ function _maxRedeem( address owner ) internal view virtual returns (uint256 maxShares) { maxShares = _balanceOf(owner); } /** * @notice simulate a deposit of given quantity of assets * @param assetAmount quantity of assets to deposit * @return shareAmount quantity of shares to mint */ function _previewDeposit( uint256 assetAmount ) internal view virtual returns (uint256 shareAmount) { shareAmount = _convertToShares(assetAmount); } /** * @notice simulate a minting of given quantity of shares * @param shareAmount quantity of shares to mint * @return assetAmount quantity of assets to deposit */ function _previewMint( uint256 shareAmount ) internal view virtual returns (uint256 assetAmount) { uint256 supply = _totalSupply(); if (supply == 0) { assetAmount = shareAmount; } else { assetAmount = (shareAmount * _totalAssets() + supply - 1) / supply; } } /** * @notice simulate a withdrawal of given quantity of assets * @param assetAmount quantity of assets to withdraw * @return shareAmount quantity of shares to redeem */ function _previewWithdraw( uint256 assetAmount ) internal view virtual returns (uint256 shareAmount) { uint256 supply = _totalSupply(); if (supply == 0) { shareAmount = assetAmount; } else { uint256 totalAssets = _totalAssets(); if (totalAssets == 0) { shareAmount = assetAmount; } else { shareAmount = (assetAmount * supply + totalAssets - 1) / totalAssets; } } } /** * @notice simulate a redemption of given quantity of shares * @param shareAmount quantity of shares to redeem * @return assetAmount quantity of assets to withdraw */ function _previewRedeem( uint256 shareAmount ) internal view virtual returns (uint256 assetAmount) { assetAmount = _convertToAssets(shareAmount); } /** * @notice execute a deposit of assets on behalf of given address * @param assetAmount quantity of assets to deposit * @param receiver recipient of shares resulting from deposit * @return shareAmount quantity of shares to mint */ function _deposit( uint256 assetAmount, address receiver ) internal virtual returns (uint256 shareAmount) { if (assetAmount > _maxDeposit(receiver)) revert FungibleVaultToken__MaximumAmountExceeded(); shareAmount = _previewDeposit(assetAmount); _deposit(_msgSender(), receiver, assetAmount, shareAmount, 0, 0); } /** * @notice execute a minting of shares on behalf of given address * @param shareAmount quantity of shares to mint * @param receiver recipient of shares resulting from deposit * @return assetAmount quantity of assets to deposit */ function _mint( uint256 shareAmount, address receiver ) internal virtual returns (uint256 assetAmount) { if (shareAmount > _maxMint(receiver)) revert FungibleVaultToken__MaximumAmountExceeded(); assetAmount = _previewMint(shareAmount); _deposit(_msgSender(), receiver, assetAmount, shareAmount, 0, 0); } /** * @notice execute a withdrawal of assets on behalf of given address * @param assetAmount quantity of assets to withdraw * @param receiver recipient of assets resulting from withdrawal * @param owner holder of shares to be redeemed * @return shareAmount quantity of shares to redeem */ function _withdraw( uint256 assetAmount, address receiver, address owner ) internal virtual returns (uint256 shareAmount) { if (assetAmount > _maxWithdraw(owner)) revert FungibleVaultToken__MaximumAmountExceeded(); shareAmount = _previewWithdraw(assetAmount); _withdraw( _msgSender(), receiver, owner, assetAmount, shareAmount, 0, 0 ); } /** * @notice execute a redemption of shares on behalf of given address * @param shareAmount quantity of shares to redeem * @param receiver recipient of assets resulting from withdrawal * @param owner holder of shares to be redeemed * @return assetAmount quantity of assets to withdraw */ function _redeem( uint256 shareAmount, address receiver, address owner ) internal virtual returns (uint256 assetAmount) { if (shareAmount > _maxRedeem(owner)) revert FungibleVaultToken__MaximumAmountExceeded(); assetAmount = _previewRedeem(shareAmount); _withdraw( _msgSender(), receiver, owner, assetAmount, shareAmount, 0, 0 ); } /** * @notice FungibleVaultToken hook, called deposit and mint actions * @dev function should be overridden and new implementation must call super * @param receiver recipient of shares resulting from deposit * @param assetAmount quantity of assets being deposited * @param shareAmount quantity of shares being minted */ function _afterDeposit( address receiver, uint256 assetAmount, uint256 shareAmount ) internal virtual {} /** * @notice FungibleVaultToken hook, called before withdraw and redeem actions * @dev function should be overridden and new implementation must call super * @param owner holder of shares to be redeemed * @param assetAmount quantity of assets being withdrawn * @param shareAmount quantity of shares being redeemed */ function _beforeWithdraw( address owner, uint256 assetAmount, uint256 shareAmount ) internal virtual {} /** * @notice exchange assets for shares on behalf of given address * @param caller supplier of assets to be deposited * @param receiver recipient of shares resulting from deposit * @param assetAmount quantity of assets to deposit * @param shareAmount quantity of shares to mint * @param assetAmountOffset quantity of assets to deduct from deposit amount * @param shareAmountOffset quantity of shares to deduct from mint amount */ function _deposit( address caller, address receiver, uint256 assetAmount, uint256 shareAmount, uint256 assetAmountOffset, uint256 shareAmountOffset ) internal virtual { uint256 assetAmountNet = assetAmount - assetAmountOffset; if (assetAmountNet > 0) { IERC20(_asset()).safeTransferFrom( caller, address(this), assetAmountNet ); } uint256 shareAmountNet = shareAmount - shareAmountOffset; if (shareAmountNet > 0) { _mint(receiver, shareAmountNet); } _afterDeposit(receiver, assetAmount, shareAmount); emit Deposit(caller, receiver, assetAmount, shareAmount); } /** * @notice exchange shares for assets on behalf of given address * @param caller transaction operator for purposes of allowance verification * @param receiver recipient of assets resulting from withdrawal * @param owner holder of shares to be redeemed * @param assetAmount quantity of assets to withdraw * @param shareAmount quantity of shares to redeem * @param assetAmountOffset quantity of assets to deduct from withdrawal amount * @param shareAmountOffset quantity of shares to deduct from burn amount */ function _withdraw( address caller, address receiver, address owner, uint256 assetAmount, uint256 shareAmount, uint256 assetAmountOffset, uint256 shareAmountOffset ) internal virtual { if (caller != owner) { uint256 allowance = _allowance(owner, caller); if (shareAmount > allowance) revert FungibleVaultToken__AllowanceExceeded(); unchecked { _approve(owner, caller, allowance - shareAmount); } } _beforeWithdraw(owner, assetAmount, shareAmount); uint256 shareAmountNet = shareAmount - shareAmountOffset; if (shareAmountNet > 0) { _burn(owner, shareAmountNet); } uint256 assetAmountNet = assetAmount - assetAmountOffset; if (assetAmountNet > 0) { IERC20(_asset()).safeTransfer(receiver, assetAmountNet); } emit Withdraw(caller, receiver, owner, assetAmount, shareAmount); } /** * @notice set the address of the base token used for vault accountin purposes * @param asset base token address */ function _setAsset(address asset) internal virtual { ERC20Storage .layout(ERC20Storage.DEFAULT_STORAGE_SLOT) .erc4626Asset = asset; } }