Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

Risk of blacklisting by the maganer of the `USDC` contract

Summary

Any and all addresses related to the Mafia can be blacklisted in the USDC contract. Blacklisted Mafia addresses cannot transfer or receive funds in USDC.

Vulnerability Details

The USDC contract is centralized and includes a mechanism for blacklisting addresses, primarily controlled by Centre, the consortium founded by Circle and Coinbase. This blacklisting capability allows Centre to comply with regulatory and law enforcement requirements to prevent illegal activities.

Centre can be ordered to blacklist any and all addresses related to the Mafia by

  • regulatory agencies,

  • law enforcement agencies,

  • court orders.

Should an address be added to the blacklist, that address cannot receive or transfer USDC funds anymore (or until it is removed from the blacklist).

This is demonstrated by the following test:

Proof of Code
function testUsdcBlacklist() public {
///////////////////
///// SETUP ///////
///////////////////
// STEP 1: deploy mock usdc contract with blacklist functionality
address centre = makeAddr("centre");
vm.prank(centre);
RealMockUSDC rusdc = new RealMockUSDC();
uint256 initialRealUsdcBalance = 1_000_000e6; // from the mock contract
// STEP 2: redeploy MoneyShelf with the correct USDC == realUsdc
MoneyShelf realMoneyShelf = new MoneyShelf(kernel, rusdc, crimeMoney);
// STEP 3: replace MoneyShelf module with realMoneyShelf module
vm.prank(godFather);
kernel.executeAction(Actions.UpgradeModule, address(realMoneyShelf));
// STEP 4: deactivate then re-activate the policy
// needed so that the configureDependencies function is called again to set the new MoneyShelf address.
vm.startPrank(godFather);
kernel.executeAction(Actions.DeactivatePolicy, address(laundrette));
kernel.executeAction(Actions.ActivatePolicy, address(laundrette));
vm.stopPrank();
// STEP 5: grantRole to realMoneyShelf
vm.prank(kernel.admin()); // @note supposed to be the godFather (?) but in reality it is the laundrette
kernel.grantRole(Role.wrap("moneyshelf"), address(realMoneyShelf));
// STEP 5: Godfather acquires USDC - in practice, not from Circle :)
vm.prank(centre);
rusdc.transfer(godFather, initialRealUsdcBalance);
// STEP 6: initial balance checks
assertEq(rusdc.balanceOf(godFather), initialRealUsdcBalance);
assert(crimeMoney.balanceOf(godFather) == 0);
// STEP 7: Godfather deposits hald of his USDC
vm.startPrank(godFather);
uint256 depositAmount = initialRealUsdcBalance / 2;
rusdc.approve(address(realMoneyShelf), depositAmount);
laundrette.depositTheCrimeMoneyInATM(godFather, godFather, depositAmount);
assertEq(rusdc.balanceOf(godFather), depositAmount);
assertEq(realMoneyShelf.getAccountAmount(godFather), depositAmount);
///////////////////
//// EXECUTION ////
///////////////////
address alternativeAddress = makeAddr("alternativeAddress");
// STEP 1: Circle blacklists Godfather and RealMoneyShelf
vm.startPrank(centre);
rusdc.addToBlacklist(godFather);
rusdc.addToBlacklist(address(realMoneyShelf));
vm.stopPrank();
///////////////////
/// ASSERTIONS ////
///////////////////
bytes memory expectedRevertReason = abi.encodeWithSignature("AddressBlacklisted(address)", godFather);
// Godfather cannot transfer his USDC
vm.prank(godFather);
vm.expectRevert(expectedRevertReason);
rusdc.transfer(alternativeAddress, depositAmount); // assume Godfather controls alternativeAddress
// others cannot transfer from Godfather even if approved
vm.prank(godFather);
rusdc.approve(alternativeAddress, depositAmount);
vm.prank(alternativeAddress);
vm.expectRevert(expectedRevertReason);
rusdc.transferFrom(godFather, alternativeAddress, depositAmount);
// no one can deposit to (or witdraw from) realMoneyShelf
vm.startPrank(alternativeAddress);
expectedRevertReason = abi.encodeWithSignature("AddressBlacklisted(address)", realMoneyShelf);
vm.expectRevert(expectedRevertReason);
laundrette.depositTheCrimeMoneyInATM(alternativeAddress, alternativeAddress, depositAmount);
vm.stopPrank();
}
Mock USDC contract with blacklisting functionality
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import { console } from "lib/forge-std/src/console.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
error AddressBlacklisted(address account);
contract RealMockUSDC is ERC20 {
address public immutable i_owner;
mapping(address => bool) private _blacklist;
////////////////////////////////////////////
/////////////// Modifiers //////////////////
////////////////////////////////////////////
modifier onlyOwner() {
require(msg.sender == i_owner, "Not the owner!");
_;
}
modifier notBlacklisted(address account) {
if (_blacklist[account]) {
revert AddressBlacklisted(account);
}
_;
}
////////////////////////////////////////////
/////////////// Events /////////////////////
////////////////////////////////////////////
event Blacklisted(address indexed account);
event Whitelisted(address indexed account);
////////////////////////////////////////////
/////////////// Functions //////////////////
////////////////////////////////////////////
constructor() ERC20("RUSDC", "RUSDC") {
i_owner = msg.sender;
_mint(msg.sender, 1_000_000e6);
}
function decimals() public view override returns (uint8) {
return 6;
}
////////////////////////////////////////////
/////// Blacklisting functionality /////////
////////////////////////////////////////////
function addToBlacklist(address account) external onlyOwner {
_blacklist[account] = true;
emit Blacklisted(account);
}
function removeFromBlacklist(address account) external onlyOwner {
_blacklist[account] = false;
emit Whitelisted(account);
}
////////////////////////////////////////////
/////// Transfer functions /////////////////
////////////////////////////////////////////
function transfer(
address recipient,
uint256 amount
)
public
override
notBlacklisted(msg.sender)
notBlacklisted(recipient)
returns (bool)
{
return super.transfer(recipient, amount);
}
function transferFrom(
address sender,
address recipient,
uint256 amount
)
public
override
notBlacklisted(sender)
notBlacklisted(recipient)
returns (bool)
{
return super.transferFrom(sender, recipient, amount);
}
}

Impact

Any and all addresses related to the Mafia can be blacklisted on the USDC contract.
Blacklisted addresses cannot receive or transfer funds in USDC.
All funds the Mafia keeps in USDC can be frozen.

Tools Used

Manual review, Foundry.

Recommendations

If illegal activities are a part of your "business model", do not keep any funds in USDC. Consider using other, less centralied stablecoins instead, and rewrite your protocol accordingly. Some suggestions:

  • DAI (DAI):

-- Decentralized Issuance: DAI is a decentralized stablecoin issued by the MakerDAO protocol. It is governed by a decentralized autonomous organization (DAO) rather than a central entity.

-- Collateral-backed: DAI maintains its peg to the USD through a system of collateralized debt positions (CDPs) backed by various cryptocurrencies.

-- Governance: While DAI has a governance body, decisions are made through community voting involving MKR token holders, reducing the risk of unilateral blacklisting.

  • LUSD (LUSD):

-- Fully Decentralized: LUSD is issued by the Liquity protocol, which operates without a governance token, minimizing centralized control.

-- ETH-backed: LUSD is collateralized exclusively by ETH, enhancing its decentralization.

-- No Blacklisting: The protocol is designed to be fully decentralized, with no mechanisms for blacklisting or censorship.

Updates

Lead Judging Commences

n0kto Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
paprikrumplikas Submitter
over 1 year ago
n0kto Lead Judge
over 1 year ago
paprikrumplikas Submitter
over 1 year ago
n0kto Lead Judge
over 1 year ago
n0kto Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

UDSC blacklist

Support

FAQs

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