Sparkn

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

The `organizer` can only distribute one token which is incorrect because the `Distributor` contract can have multiple tokens, so some tokens may be stuck in the contract

Summary

The organizer can distribute only one token using the deployProxyAndDistribute() function, that is incorrect because the sponsor can deposit to the Distributor contract multiple whitelisted tokens causing that some tokens may be stuck in the contract.

Vulnerability Details

The organizer can use the deployProxyAndDistribute() to deploy the Distributor contract and then execute the distribution to winners. Additionally, the sponsor can deposit to the Distributor contract the tokens that are whitelisted, so those tokens can be distributed to the winners.

The problem is that the organizer only have an option to distribute one token because the Distributor::distribute() only accept one token at a time and if was not enough, the organizer can't call again the deployProxyAndDistribute() function with another toker because it will be reverted.

I created a test where the organizer calls deployProxyAndDistribute() for the token jpycv2Address and the distribution to winners is correct but the organizer wants to distribute the token jpycv1Address and the deployProxyAndDistribute() will be reverted. Test steps:

  1. Sponsor deposits jpycv1Address and jpycv2Address.

  2. Organizer calls deployProxyAndDistribute() in order to distributes the token jpycv2Address.

  3. Organizer calls deployProxyAndDistribute() in order to distributes the token jpycv1Address but the transaction will be reverted.

  4. The amount of jpycv1Address will be stuck in the Distributor contract.

// test/integration/ProxyFactoryTest.t.sol:ProxyFactoryTest
// $ forge test --match-test "testDistributeIsPossibleJustForOneToken" -vvv
//
function testDistributeIsPossibleJustForOneToken() public {
// The organizer can distribute just for one token which is incorrect because
// the sponsor can deposit to the Distributor contract all the whitelisted tokens.
//
// before
assertEq(MockERC20(jpycv2Address).balanceOf(user1), 0 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(stadiumAddress), 0 ether);
vm.startPrank(factoryAdmin);
bytes32 randomId = keccak256(abi.encode("Jason", "001"));
proxyFactory.setContest(organizer, randomId, block.timestamp + 8 days, address(distributor));
vm.stopPrank();
bytes32 salt = keccak256(abi.encode(organizer, randomId, address(distributor)));
address proxyAddress = proxyFactory.getProxyAddress(salt, address(distributor));
//
// 1. Sponsor deposits jpycv1Address and jpycv2Address
//
vm.startPrank(sponsor);
MockERC20(jpycv1Address).transfer(proxyAddress, 100 ether);
MockERC20(jpycv2Address).transfer(proxyAddress, 10000 ether);
vm.stopPrank();
assertEq(MockERC20(jpycv1Address).balanceOf(proxyAddress), 100 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(proxyAddress), 10000 ether);
bytes32 randomId_ = keccak256(abi.encode("Jason", "001"));
address[] memory winners = new address[](1);
winners[0] = user1;
uint256[] memory percentages_ = new uint256[](1);
percentages_[0] = 9500;
vm.warp(9 days); // 9 days later
vm.startPrank(organizer);
//
// 2. Organizer calls deployProxyAndDistribute() in order to distributes the token jpycv2Address
//
bytes memory data = abi.encodeWithSelector(Distributor.distribute.selector, jpycv2Address, winners, percentages_, "");
proxyFactory.deployProxyAndDistribute(randomId_, address(distributor), data);
//
// 3. Organizer calls deployProxyAndDistribute() in order to distributes the token jpycv1Address but
// the transaction will be reverted.
//
data = abi.encodeWithSelector(Distributor.distribute.selector, jpycv1Address, winners, percentages_, "");
vm.expectRevert();
proxyFactory.deployProxyAndDistribute(randomId_, address(distributor), data);
vm.stopPrank();
// after
assertEq(MockERC20(jpycv2Address).balanceOf(user1), 9500 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(stadiumAddress), 500 ether);
//
// 4. The amount of jpycv1Address will be stuck in the Distributor contract.
//
assertEq(MockERC20(jpycv1Address).balanceOf(proxyAddress), 100 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(proxyAddress), 0);
}

The owner can call the distributeByOwner() function but the purpose of the function is not to assign winners, the purpose is to rescue funds if token is stuck after the deployment and contest is over for a while.

* @notice Owner can rescue funds if token is stuck after the deployment and contest is over for a while
* @dev only owner can call this function and it is supposed not to be called often

Impact

The organizer can deploy and distribute only one token, that is a problem because sponsors can deposit to the Distributor contract multiple tokens (the whitelisted tokens) causing that those tokens that the organizer did not distribute to get trapped in the contract.

Tools used

Manual review

Recommendations

Add the feature that helps the organizer to distribute all the whitelisted tokens.

Support

FAQs

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