Summary
When a trader's position has a positive PnL, the PnL amount is credited in USD and added to the trader's collateral margin. This process involves calling the deposit
function, but the function does not update totalCollateralDeposited
, which tracks the total deposited amount for each collateral type.
The _requireEnoughDepositCap
that ensures new deposits do not exceed the deposit cap can be bypassed. Traders can then deposit additional amounts without triggering the deposit cap error, effectively breaching the cap.
_requireEnoughDepositCap(collateralType, amountX18, depositCapX18, totalCollateralDepositedX18);
This deposit cap is set when creating the margin collateral type. `Usd` is also a collateral type so there's a cap
function configure(
address collateralType,
uint128 depositCap,
uint120 loanToValue,
uint8 decimals,
address priceFeed,
uint32 priceFeedHearbeatSeconds
)
internal
{
Data storage self = load(collateralType);
self.depositCap = depositCap;
self.loanToValue = loanToValue;
self.decimals = decimals;
self.priceFeed = priceFeed;
self.priceFeedHearbeatSeconds = priceFeedHearbeatSeconds;
}
}
If a traders position had positive Pnl they are credited in Usd , which gets added to the collateral margin of the trader by calling deposit
in the traders tradingAccount
if (ctx.pnlUsdX18.gt(SD59x18\_ZERO)) {
ctx.marginToAddX18 = ctx.pnlUsdX18.intoUD60x18();
tradingAccount.deposit(ctx.usdToken, ctx.marginToAddX18);
However when depositing into the traders trading account the totalCollateralDeposited
isn't updated
function deposit(Data storage self, address collateralType, UD60x18 amountX18) internal {
EnumerableMap.AddressToUintMap storage marginCollateralBalanceX18 = self.marginCollateralBalanceX18;
UD60x18 newMarginCollateralBalance = getMarginCollateralBalance(self, collateralType).add(amountX18);
marginCollateralBalanceX18.set(collateralType, newMarginCollateralBalance.intoUint256());
MarginCollateralConfiguration.Data storage marginCollateralConfiguration =
MarginCollateralConfiguration.load(collateralType);
marginCollateralConfiguration.totalDeposited =
ud60x18(marginCollateralConfiguration.totalDeposited).add(amountX18).intoUint256();
}
Vulneriability Details
When the trader attempts to deposit more usd as collateral _requireEnoughDepositCap
will pass even if the accumulated amount is above the cap.
function depositMargin(uint128 tradingAccountId, address collateralType, uint256 amount) public virtual {
MarginCollateralConfiguration.Data storage marginCollateralConfiguration =
MarginCollateralConfiguration.load(collateralType);
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
UD60x18 amountX18 = marginCollateralConfiguration.convertTokenAmountToUd60x18(amount);
UD60x18 depositCapX18 = ud60x18(marginCollateralConfiguration.depositCap);
UD60x18 totalCollateralDepositedX18 = ud60x18(marginCollateralConfiguration.totalDeposited);
\_requireAmountNotZero(amountX18);
\_requireEnoughDepositCap(collateralType, amountX18, depositCapX18, totalCollateralDepositedX18);
\_requireCollateralLiquidationPriorityDefined(collateralType);
IERC20(collateralType).safeTransferFrom(msg.sender, address(this), amount);
tradingAccount.deposit(collateralType, amountX18);
emit LogDepositMargin(msg.sender, tradingAccountId, collateralType, amount);
}
totalCollateralDeposited
will be less than the deposit cap as it was not updated during settlement while actual traders collateralType deposit will be above the cap.
function _requireEnoughDepositCap(
address collateralType,
UD60x18 amount,
UD60x18 depositCap,
UD60x18 totalCollateralDeposited
)
internal
pure
{
if (amount.add(totalCollateralDeposited).gt(depositCap)) {
revert Errors.DepositCap(collateralType, amount.intoUint256(), depositCap.intoUint256());
}
}
For example; depositCap
for USD is set to 1000
Trader initial deposit totalCollateralDeposited
= 900 USD
The trader gets positive Pnl
and gets credited 100 USD
The actual total is now = 1000( the deposit cap)
However the user can still deposit more margin because when the credit got added totalCollateralDeposited
wasn't increased
The trader can deposit 1000 and since _requireEnoughDepositCap
is still true the trader gets 2000usd as collateral.. bypassing the the 1000 cap
Impact
The deposit cap is meant to limit the total amount of collateral that can be deposited for a given collateral type. By not updating totalCollateralDeposited
when crediting positive PnL, the contract allows traders to exceed this cap.
Tools Used
Manual Review
Recommendations
Increase the totalCollateralDeposited
when adding credit to prevent the cap from being breached, this will force users to withdraw first if cap is reached