In the PerpetualVault.sol
contract, the deposit function charges an ExecutionFee only when the vault has an active position (positionIsClosed == false
). When the vault has no active position (positionIsClosed == true
), users are not required to pay any ExecutionFee. This behavior leads to an unfair distribution of costs among users, depending on the state of the vault at the time of deposit, which can create a sense of unfairness or impact user trust.
Location: PerpetualVault.sol
FIle, deposit function
Relevant Code:
Explanation: When positionIsClosed == true
, the deposit is immediately processed by calling _mint
and _finalize
, and no call to _payExecutionFee
is made. However, when positionIsClosed == false
, the user must call _payExecutionFee
, which transfers the ExecutionFee
to the GmxProxy. This fee is used to compensate Keepers for increasing the GMX position.
Reason: The difference lies in the complexity of operations. The no-position state does not require asynchronous interaction with GMX, while the active-position state requires Keepers and order execution on GMX.
Financial Impact: Users depositing during an active position incur higher costs (transaction gas + ExecutionFee) compared to users depositing when the position is closed (only transaction gas). This difference can range from a few dollars to a significant amount when tx.gasprice
is high.
User Experience: The inequality in costs may reduce user trust or cause dissatisfaction, especially if users are unaware of this behavior.
Manual Analysis: Review of the source code of PerpetualVault.sol
and the logic of the deposit function.
Foundry: For writing the Proof of Concept and simulating contract behavior.
PoC Explanation:
Test 1: User A deposits when positionIsClosed == true
and does not send any ETH (as ExecutionFee). Shares are immediately allocated.
Test 2: User B deposits when positionIsClosed == false
and must pay an ExecutionFee (ETH). Shares are not yet allocated and deferred to INCREASE_ACTION
.
Result: User A incurs lower costs compared to User B.
Charge a Fixed Fee: Always charge an ExecutionFee from users, regardless of positionIsClosed
. In the closed state, this fee can be transferred to the treasury:
Transparency in Documentation: Clearly explain in the documentation or UI that ExecutionFee is only charged during an open position, and the reason is the cost of Keepers and GMX.
Distribute Costs from Profits: Use vault profits (e.g., positive funding fees) to cover Keeper costs, reducing the need for ExecutionFee from new users.
Optional Minimum Fee: Allow users to optionally pay a fee during positionIsClosed == true
, which can be used as a reserve for future operations.
These changes can enhance fairness and improve the user experience while maintaining the technical logic of the contract.
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.