Sparkn

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

The `distributeByOwner` function of **ProxyFactory** contract charge commission fees

Summary

If anyone sent token to the implementation(Distributor) by mistake can contact the owner of the contracts ProxyFactory to rescue the funds.
The owner with the distributeByOwner rescue the stuck funds but this charge fees(0.05%) that are sent to the STADIUM_ADDRESS

Vulnerability Details

The distribute function if Distributor contract charge fees(0.05%) to rescue founds by the owner and this fees go to the STADIUM_ADDRESS of Distributor contract:

pragma solidity 0.8.18;
import {Test} from "forge-std/Test.sol";
import {ProxyFactory} from "../../src/ProxyFactory.sol";
import {MockERC20} from "../mock/MockERC20.sol";
import {Distributor} from "../../src/Distributor.sol";
contract PoC is Test {
bytes32 idAlice = keccak256("Alice");
address alice = address(uint160(uint256(idAlice)));
address feeReceive = address(123);
MockERC20 token = new MockERC20("", "");
ProxyFactory proxyFactory;
Distributor distributor;
address calculatedProxyAddress;
function setUp() public {
// Mint tokens to alice
MockERC20(token).mint(alice, 110 ether);
// Deploy proxyFactory and distributor
address[] memory tokensToWhitelist = new address[](1);
tokensToWhitelist[0] = address(token);
proxyFactory = new ProxyFactory(tokensToWhitelist);
distributor = new Distributor(address(proxyFactory), feeReceive);
// Set alice's contest
proxyFactory.setContest(alice, idAlice, block.timestamp, address(distributor));
// Calculate proxy address
bytes32 salt = keccak256(abi.encode(alice, idAlice, address(distributor)));
calculatedProxyAddress = proxyFactory.getProxyAddress(salt, address(distributor));
// Alice send tokens to distribute
vm.prank(alice);
MockERC20(token).transfer(calculatedProxyAddress, 10 ether);
// Deploy and distribute
address[] memory winners = new address[](1);
winners[0] = address(666);
uint256[] memory percentages = new uint256[](1);
percentages[0] = 9500;
bytes memory data = abi.encodeWithSelector(Distributor.distribute.selector, address(token), winners, percentages, "");
vm.prank(alice);
proxyFactory.deployProxyAndDistribute(idAlice, address(distributor), data);
}
function test() public {
// Alice send 100 tokens by mistake
vm.prank(alice);
MockERC20(token).transfer(calculatedProxyAddress, 100 ether);
// Forward time to EXPIRATION_TIME(7 days)
vm.warp(proxyFactory.EXPIRATION_TIME() + 1);
// The owner of proxyFactory rescue the tokens
address[] memory winners = new address[](1);
winners[0] = address(alice);
uint256[] memory percentages = new uint256[](1);
percentages[0] = 9500;
bytes memory data = abi.encodeWithSelector(Distributor.distribute.selector, address(token), winners, percentages, "");
proxyFactory.distributeByOwner(calculatedProxyAddress, alice, idAlice, address(distributor), data);
assertEq(token.balanceOf(alice), 100 ether, "The balance of alice has to be the one she sent by mistake");
}
}

Impact

The organizer has to pay fees to the STADIUM_ADDRESS for rescue his tokens

Recommendations

Remove the distributeByOwner and add specific function to rescue funds:

function rescueFunds(
address organizer,
address token,
bytes32 contestId,
address implementation
) external onlyOwner {
bytes32 salt = _calculateSalt(organizer, contestId, implementation);
if (saltToCloseTime[salt] == 0) revert ProxyFactory__ContestIsNotRegistered();
if (saltToCloseTime[salt] + EXPIRATION_TIME > block.timestamp) revert ProxyFactory__ContestIsNotExpired();
ProxyRescue(getProxyAddress(salt, implementation)).rescue(msg.sender, organizer, token);
}

With the interface:

interface ProxyRescue {
function rescue(address sender, address organizer, address token) external;
}

And implement this interface in the Distributor implementation:

function rescue(address sender, address organizer, IERC20 token) external {
require(ProxyFactory(FACTORY_ADDRESS).owner() == sender, "Not ProxyFactory owner");
token.safeTransfer(organizer, token.balanceOf(address(this)));
}

Support

FAQs

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