Skip to content

Latest commit

 

History

History
83 lines (62 loc) · 2.98 KB

File metadata and controls

83 lines (62 loc) · 2.98 KB

Jolly Mauve Parakeet

High

protocol accounting is broken due to not substracting subtractiveFee, solverFee

Summary

Users pay all protocol fees, which are deducted from their positions when they make trades. However, they do not pay referral fees or solver fees; instead, the protocol covers those costs.

Root Cause

All fees should be subtracted from users account, so there is this code which update local position

    function _response(
        CheckpointAccumulationResult memory result
    ) private pure returns (CheckpointAccumulationResponse memory response) {
        response.collateral = result.collateral
            .add(result.priceOverride)
            .sub(Fixed6Lib.from(result.tradeFee))
            .sub(result.offset)
            .sub(Fixed6Lib.from(result.settlementFee));
        response.liquidationFee = result.liquidationFee;
        response.subtractiveFee = result.subtractiveFee;
        response.solverFee = result.solverFee;
    }

packages/perennial/contracts/libs/CheckpointLib.sol#L108

Later there is an additional accumulation.liquidationFee substraction here

    function update(
        Local memory self,
        uint256 newId,
        CheckpointAccumulationResponse memory accumulation
    ) internal pure {
        self.collateral = self.collateral.add(accumulation.collateral).sub(Fixed6Lib.from(accumulation.liquidationFee));
        self.latestId = newId;
    }

perennial-v2/packages/perennial/contracts/types/Local.sol#L52

The subtractiveFee and solver fees are not accounted for anywhere inside CheckpointLib like other fees, which leads to a situation where the coins in the market will not match the sum of local accounts.

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

No response

Impact

Broken accounting will lead to someone being unable to retrieve their funds. Whether it’s a user, liquidator, referral, or the protocol, they may not receive their funds due to the accounting issue.

PoC

No response

Mitigation

    function _response(
        CheckpointAccumulationResult memory result
    ) private pure returns (CheckpointAccumulationResponse memory response) {
        response.collateral = result.collateral
            .add(result.priceOverride)
            .sub(Fixed6Lib.from(result.tradeFee))
            .sub(result.offset)
            .sub(Fixed6Lib.from(result.settlementFee))
+            .sub(Fixed6Lib.from(result.subtractiveFee))
+            .sub(Fixed6Lib.from(result.solverFee));
        response.liquidationFee = result.liquidationFee;
        response.subtractiveFee = result.subtractiveFee;
        response.solverFee = result.solverFee;
    }