Vulnerability Details
In VaultRouterBranch#deposit()
function, there is no minimum deposit amount, letting user deposit any amount if they want.
But in stake()
function, staking amount must be greater than minimum stake amount (1e5):
function stake(uint128 vaultId, uint128 shares) external {
if (shares < Constants.MIN_OF_SHARES_TO_STAKE) {
revert Errors.QuantityOfSharesLessThanTheMinimumAllowed(Constants.MIN_OF_SHARES_TO_STAKE, uint256(shares));
}
When we deposit token to Zlp vault, we will receive shares back. In ZlpVault, to calculate share value that will receive, it use _convertToShares()
function, that call getVaultAssetSwapRate()
function:
function _convertToShares(
uint256 assets,
Math.Rounding
)
*/
internal
view
override
returns (uint256)
{
ZlpVaultStorage storage zlpVaultStorage = _getZlpVaultStorage();
UD60x18 sharesOut = IMarketMakingEngine(zlpVaultStorage.marketMakingEngine).getVaultAssetSwapRate(
zlpVaultStorage.vaultId, assets, false
);
return sharesOut.intoUint256();
}
And function getVaultAssetSwapRate()
rely on totalAssets()
of the vault:
function getVaultAssetSwapRate(
uint128 vaultId,
uint256 assetsIn,
bool shouldDiscountDepositFee
)
public
view
returns (UD60x18 sharesOut)
{
Vault.Data storage vault = Vault.loadExisting(vaultId);
uint256 totalAssetsMinusVaultDebt = getVaultCreditCapacity(vaultId);
uint8 decimalOffset = Constants.SYSTEM_DECIMALS - IERC20Metadata(vault.indexToken).decimals();
uint256 previewSharesOut = assetsIn.mulDiv(
IERC4626(vault.indexToken).totalSupply() + 10 ** decimalOffset,
totalAssetsMinusVaultDebt,
MathOpenZeppelin.Rounding.Floor
);
if (shouldDiscountDepositFee) {
previewSharesOut =
ud60x18(previewSharesOut).sub(ud60x18(previewSharesOut).mul(ud60x18(vault.depositFee))).intoUint256();
}
return ud60x18(previewSharesOut);
}
Function getVaultCreditCapacity()
:
function getVaultCreditCapacity(uint128 vaultId) public view returns (uint256) {
Vault.Data storage vault = Vault.loadExisting(vaultId);
SD59x18 totalAssetsX18 =
vault.collateral.convertTokenAmountToSd59x18(IERC4626(vault.indexToken).totalAssets().toInt256());
. . . . .
}
totalAssets()
function in ERC4626Upgradeable return balanceOf contract
:
function totalAssets() public view virtual returns (uint256) {
return IERC20(asset()).balanceOf(address(this));
}
It is possible for attacker to force other user to not able to staking by increasing share price:
-
After vault initalized, attacker deposit dust amount, receive dust amount back
-
After that, attacker transfer some tokens directly to ZlpVault, increasing value per shares alot
After that, most of the user unable to stake, because to be able to do that, deposit amount must be very huge
Impact
Many users cant staking due to number of token required to staking is too high
Recommendations
Do not rely on balance of vault when calculating share, instead tracking them whenever deposit and withdraw from market happen