The _transferToken()
function in PerpetualVault.sol transfers tokens to the user and treasury without proper reentrancy protection. This could allow an attacker to re-enter the contract during the transfer and manipulate the state.
The function uses collateralToken.transfer
and collateralToken.safeTransfer
, which could trigger a callback to the contract if the recipient is a malicious contract.
If the attacker re-enters the contract during the transfer, they could manipulate the state (e.g., withdraw additional funds or bypass checks).
Alice: A legitimate user who deposits funds into the PerpetualVault contract.
Bob: A malicious attacker who exploits the reentrancy vulnerability.
PerpetualVault: The vulnerable smart contract that allows users to deposit and withdraw funds.
Step 1: Alice Deposits Funds
Alice deposits 100 tokens into the PerpetualVault contract. The contract records her deposit and assigns her a depositId.
Step 2: Bob Prepares for the Attack
Bob notices that the _transferToken
function in the PerpetualVault contract lacks reentrancy protection. He decides to exploit this vulnerability.
Bob deploys a malicious contract (let's call it MaliciousWithdrawer) that is designed to exploit the reentrancy bug. This contract has a receive or fallback function that will be triggered when it receives tokens.
Step 3: Bob Deposits Funds
Bob deposits a small amount of tokens (e.g., 1 token) into the PerpetualVault contract. This gives him a valid depositId and makes him eligible to withdraw funds.
Step 4: Bob Initiates the Attack
Bob calls the withdraw function on the PerpetualVault contract, specifying his depositId and setting the recipient address to his MaliciousWithdrawer contract.
The withdraw function calls the vulnerable _transferToken function, which attempts to transfer tokens to Bob's MaliciousWithdrawer contract.
Step 5: Reentrancy Exploit
When the _transferToken
function sends tokens to Bob's MaliciousWithdrawer contract, the contract's receive or fallback function is triggered.
Inside this function, Bob's contract re-enters the PerpetualVault contract by calling the withdraw function again before the initial transfer is completed.
Because the _transferToken
function has not yet updated the state (e.g., reducing Bob's balance or marking the withdrawal as complete), the contract allows Bob to withdraw funds multiple times.
Step 6: Draining the Contract
Bob's MaliciousWithdrawer contract repeatedly re-enters the PerpetualVault contract, each time withdrawing more funds.
Since the contract's state is not updated until the _transferToken
function completes, Bob can drain a significant portion of the contract's funds (e.g., Alice's 100 tokens and any other deposits) before the reentrancy loop is stopped.
Step 7: Bob Escapes with the Funds
Once the attack is complete, Bob transfers the stolen funds from his MaliciousWithdrawer contract to his personal wallet.
Alice and other users are left with significantly reduced or no funds in the PerpetualVault contract
An attacker could drain funds from the contract or manipulate the state to their advantage.
Manual code review.
Use a reentrancy guard (ReentrancyGuard) for the _transferToken
function.
Alternatively, use the checks-effects-interactions pattern to ensure state changes are finalized before external calls.
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.
There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.
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.
There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.
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.