Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: high
Invalid

Down casting Vulnerability in Position.sol

Summary

The are two High vulnerabilities in the Position contract due to unsafe type downcasting. The issue stems from downcasting of lastInteractionPrice storing a UD60X18 which happens to be a uint256 price, to a uint128 variable.

The next one is in lastInteractionFundingFeePerUnit where SD59X18 that is an int256 was stored into int128 variable. Both vulnerabilities cause incorrect financial calculations and can cause the protocol to misbehave.

Vulnerability Details

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/35deb3e92b2a32cd304bf61d27e6071ef36e446d/src/perpetuals/leaves/Position.sol#L18-L22
struct Data {
int256 size;
uint128 lastInteractionPrice;
int128 lastInteractionFundingFeePerUnit;
}
uint128 lastInteractionPrice

when uint256 price exceeds uint128's max value, storing it in uint128 will cause silent overflow.

int128 lastInteractionFundingFeePerUnit

Funding Fees outside int128 range will cause overflow.

POC

// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Zaros dependencies
import { Base_Test } from "test/Base.t.sol";
import { Position } from "@zaros/perpetuals/leaves/Position.sol";
// PRB Math dependencies
import { SD59x18, sd59x18 } from "@prb-math/SD59x18.sol";
// Open Zeppelin dependencies
import { SafeCast } from "@openzeppelin/utils/math/SafeCast.sol";
contract Position_GetAccruedFunding_Unit_Test is Base_Test {
using SafeCast for int256;
function setUp() public override {
Base_Test.setUp();
changePrank({ msgSender: users.owner.account });
configureSystemParameters();
createPerpMarkets();
}
function testFuzz_WhenGetAccruedFundingIsCalled(
uint256 marketId,
int128 fundingFeePerUnit,
int128 lastInteractionFundingFeePerUnit,
uint256 sizeAbs,
bool isLong
)
external
{
changePrank({ msgSender: users.naruto.account });
MarketConfig memory fuzzMarketConfig = getFuzzMarketConfig(marketId);
sizeAbs =
bound({ x: sizeAbs, min: uint256(fuzzMarketConfig.minTradeSize), max: uint256(fuzzMarketConfig.maxSkew) });
int256 size = isLong ? int256(sizeAbs) : -int256(sizeAbs);
Position.Data memory mockPosition = Position.Data({
size: size,
lastInteractionPrice: uint128(fuzzMarketConfig.mockUsdPrice),
lastInteractionFundingFeePerUnit: lastInteractionFundingFeePerUnit
});
uint128 tradingAccountId = perpsEngine.createTradingAccount(bytes(""), false);
perpsEngine.exposed_update(tradingAccountId, fuzzMarketConfig.marketId, mockPosition);
SD59x18 fundingFeePerUnitX18 = sd59x18(fundingFeePerUnit);
SD59x18 netFundingFeePerUnit =
fundingFeePerUnitX18.sub(sd59x18(mockPosition.lastInteractionFundingFeePerUnit));
SD59x18 expectedAccruedFundingUsdX18 = sd59x18(mockPosition.size).mul(netFundingFeePerUnit);
SD59x18 accruedFundingUsdX18 =
perpsEngine.exposed_getAccruedFunding(tradingAccountId, fuzzMarketConfig.marketId, fundingFeePerUnitX18);
// it should return the accrued funding usd
assertEq(
expectedAccruedFundingUsdX18.intoInt256(),
accruedFundingUsdX18.intoInt256(),
"Invalid accrued funding usd"
);
}
function test_LastInteractionPrice_Overflow_() public {
uint256 overflowPrice = uint256(type(uint128).max) + 1;
Position.Data memory position;
position.lastInteractionPrice = uint128(overflowPrice);
assert(position.lastInteractionPrice == 0);
}
function test_FundingFee_Overflow() public {
int256 overflowFundingFee = int256(type(int128).max) + 1;
Position.Data memory position;
position.lastInteractionFundingFeePerUnit = int128(overflowFundingFee);
assert(position.lastInteractionFundingFeePerUnit == -170141183460469231731687303715884105728);
}
}

Impact

  1. Traders may be liquidated due to artificial inflated losses.

  2. Miscalculated funding fees can cause undercollateralization.

Tools Used

Manual review

Recommendations

Use Openzeppelin Safe Casting.

Updates

Lead Judging Commences

inallhonesty Lead Judge
4 months ago
inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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