The PerpetualVault contract directly transfers native tokens using .transfer(), which enforces a fixed gas limit of 2300 gas. Due to and future gas repricing updates, .transfer() can fail unexpectedly, breaking contract execution and potentially causing funds to be locked.
The _payExecutionFee function in PerpetualVault.sol uses the deprecated .transfer() method:
The .transfer() method has a hard-coded gas limit of 2300 gas. This limitation was originally intended as a security measure to prevent reentrancy attacks. However, this approach has become problematic with network upgrades like EIP 1884
which increased the gas costs for certain operations. If the receiving contract (in this case, gmxProxy) has a fallback function that requires more than 2300 gas to execute, the transfer will fail.
Transaction failure if the receiver contract's fallback function requires more than 2300 gas.
Potential loss of funds if the transaction cannot be retried.
Deposits or withdrawals could be permanently blocked if the .transfer() consistently fails.
The contract may become completely unusable if the GMX proxy is upgraded to include more complex fallback logic.
Severity: π‘ Medium
Manual code review
Use low-level calls instead of .transfer() to avoid the 2300 gas limit:
This approach:
Uses .call{value: amount}("") which forwards all available gas
Properly checks the return value to ensure the transfer succeeded
Reverts with a clear error message if the transfer fails
This proof of concept demonstrates how the use of .transfer() with its fixed 2300 gas limit can cause transactions to fail when the receiving contract needs more gas to execute its fallback function.
User: A user attempting to deposit funds into the vault.
PerpetualVault: The contract using an unsafe transfer method.
GMX Proxy: A proxy contract with complex operations in its fallback function.
Setup: We create a ComplexGmxProxy contract with a gas-intensive fallback function that requires more than 2300 gas to execute.
Failure Scenario:
When a user attempts to deposit funds using the original PerpetualVault contract, the execution fails because .transfer() only forwards 2300 gas.
The ComplexGmxProxy's fallback function requires more gas to complete its operations.
Fixed Scenario:
We implement a fixed version of the vault that uses .call() instead of .transfer().
The same deposit operation succeeds because .call() forwards all available gas.
Verification:
We verify that the ether was properly transferred to the GMX proxy contract.
This demonstrates that using .transfer() with its fixed gas limit can cause legitimate transactions to fail under certain conditions, particularly when the receiving contract has a complex fallback function.
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.