40,000 USDC
View results
Submission Details
Severity: medium
Valid

Funds can gets locked if an actor is blacklisted by reward token

Summary

resolveDispute purpose is to distribute the respective amounts to buyer, seller and arbiter. When payment token used is USDC (or any other token that implement a blacklist like functionality) if any involved actor is added to blacklist, resolveDispute will revert, obstructing the other parties from receiving their rewards.

Vulnerability Details

For simplicity let's follow this example. Important note is that only arbiter is specified as trusted. The other 2 actors can misbehave on purpose or not and get blacklisted.

  • the buyer creates an Escrow contract using USDC as payment token;

  • for any reason, the buyer or the seller initiate a dispute through Escrow::initiateDispute.

  • seller gets blacklisted by USDC contract;

  • the arbiter confers with both parties off-chain. Arbiter then calls Escrow::resolveDispute but this reverts with Blacklistable: account is blacklisted error.

Codded PoC:

  • Add USDCMock.sol in test/mocks folder and import it in EscrowTest.t.sol with import {USDCMock} from "../mocks/USDCMock.sol";:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import {ERC20Mock} from "@openzeppelin/contracts/mocks/ERC20Mock.sol";
contract USDCMock is ERC20Mock {
bool public passes;
mapping(address => bool) public blacklisted;
function blacklist(address _account) external {
blacklisted[_account] = true;
}
modifier notBlacklisted(address _account) {
require(
!blacklisted[_account],
"Blacklistable: account is blacklisted"
);
_;
}
function transferFrom(address sender, address recipient, uint256 amount) public virtual override notBlacklisted(sender) notBlacklisted(recipient) returns (bool) {
return super.transferFrom(sender, recipient, amount);
}
function transfer(address recipient, uint256 amount) public virtual override notBlacklisted(recipient) returns (bool) {
return super.transfer(recipient, amount);
}
}
  • Add following test to Escrow.t.sol file:

function testResolveDisputeReverts_blackListedSeller() public escrowDeployed {
USDCMock USDC = new USDCMock();
buyerAward = 1e16;
vm.startPrank(BUYER);
USDCMock(USDC).mint(BUYER, PRICE);
USDCMock(USDC).approve(address(escrowFactory), PRICE);
escrow = escrowFactory.newEscrow(PRICE, USDC, SELLER, ARBITER, ARBITER_FEE, SALT1);
escrow.initiateDispute();
vm.stopPrank();
USDCMock(USDC).blacklist(SELLER);
vm.prank(ARBITER);
vm.expectRevert("Blacklistable: account is blacklisted");
escrow.resolveDispute(buyerAward);
}
  • Run test with : forge test --mt testResolveDisputeReverts_blackListedSeller.

Impact

Funds gets locked in the escrow contract.

Tools Used

Manual review

Recommendations

Use a pull pattern and allow each actor to withdraw their pay.

Support

FAQs

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