A vault with no open positions & no ongoing flows can be exploited by a depositor since their shares are minted immediately and are not subjected to price impact calculations or are asked to pay any execution fees. This benefits both:
The first depositor of the vault.
If Keeper ever decides to momentarily close all open positions due to volatile market conditions, any user who deposits at this point of time.
Note that positionIsClosed = true
simply means Gamma does not currently have a open position in the target GMX market. The GMX market liquidity exists independently and hence any position opened up by Gamma, even if it is the first from the PerpetualVault's perspective is capable of impacting GMX's existing market's prices and hence is subject to price impact calculations by GMX.
deposit() mints tokens to the user via _mint()
whenever positionIsClosed = true
:
Note that:
positionIsClosed
is true
for the first depositor to the vault.
Also, if Keeper ever decides to momentarily close all open positions due to volatile market conditions, OR there is some time gap between closing of an existing position & opening of a new one, then positionIsClosed
is true
for this duration, with no ongoing flows.
Any deposits made in such conditions can -
escape paying the execution fee.
escape being subjected to price impact calculations which happens only in the case of FLOW.DEPOSIT
inside the afterOrderExecution()
callback:
Inside _mint() it's never checked if execution fee has been paid or not. So let's suppose Keeper decides to create a long position in the next run, they have to fund this execution fee out of their own pockets.
As the contest page specifies for Depositors:
Must provide sufficient execution fees for operations
Depositor is able to escape any price impact penalties and receives more than their rightful proportion of shares.
Other depositors have to bear a higher burden.
User receives shares without paying for execution fee.
This could lead to a situation where the Keeper doesn't have enough gas to execute the tx and the queue remains stuck. However this impact could be ignored since the contest page states that:
Assume that Keeper will always have enough gas to execute transactions. There is a pay execution fee function, but the assumption should be that there's more than enough gas to cover transaction failures, retries, etc
This PoC shows that the depositor can pay no execution fees and still get shares minted. Add this inside test/PerpetualVault.t.sol
and run via forge test --mt test_Deposit_With_No_Exec_Fee -vv --via-ir --rpc-url arbitrum
to see it pass:
The fix could involve multiple changes but at its core:
The protocol needs to do the price impact calculations even when a user deposits during positionIsClosed = true
and mint shares to them only after these calculations are taken into account.
Charge the execution fee even when positionIsClosed = true
.
Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.
Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.