Trade closing can revert in sendAssets
Description
During the process of closing out a trade, if a trader made a profit, OstiumVault.sendAssets is called to send them assets.
In OstiumVault, the accPnlPerToken variable slowly (through averaging each epoch) follows the return value of OstiumOpenPnl.getOpenPnl, which is the total PNL of open trades owed to traders. So, assuming a steady state, before the position close, accPnlPerToken has a component corresponding to the amount that would be sent to the trader if their position closed.
However, during sendAssets, this variable is increased, and if it exceeds the maximum, the send reverts:
function sendAssets(uint256 assets, address receiver) external onlyCallbacks {
// [...]
int256 accPnlDelta = int256(assets.mulDiv(PRECISION_6, totalSupply(),
MathUpgradeable.Rounding.Up));
accPnlPerToken += accPnlDelta;
if (accPnlPerToken > int256(maxAccPnlPerToken())) {
revert NotEnoughAssets();
}The closure of the position immediately decreases the return value of OstiumOpenPnl.getOpenPnl by the PNL, since OstiumOpenPnl.accClosedPnl immediately increases. However, due to the time averaging, this is not reflected in accPnlPerToken yet, so the full PNL of the trade is already in the variable. Adding the PNL of the trade means the PNL is temporarily double counted until the decrease in accPnlPerToken brings it back down to counting it once.
Impact
When maxAccPnlPerToken is constrained, large trades can fail to close with NotEnoughAssets even though there are enough assets to send out, due to an undue double counting of the position size.
Recommendations
We recommend reworking how accClosedPnl works so that when a position is closed, the effect of that close does not have to go through averaging to get reintroduced into the accPnlPerToken summation, because it is already in the variable.