Sparkn

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

[M-02] Rewards can be delayed if more than 1 whitelisted tokens is transferred by sponsors to proxy address

Impact

ProxyFactory.sol#L216

function distributeByOwner(
address proxy,
address organizer,
bytes32 contestId,
address implementation,
bytes calldata data
) public onlyOwner {
if (proxy == address(0)) revert ProxyFactory__ProxyAddressCannotBeZero();
bytes32 salt = _calculateSalt(organizer, contestId, implementation);
if (saltToCloseTime[salt] == 0) revert ProxyFactory__ContestIsNotRegistered();
// distribute only when it exists and expired
if (saltToCloseTime[salt] + EXPIRATION_TIME > block.timestamp) revert ProxyFactory__ContestIsNotExpired();
_distribute(proxy, data);
}

If more than one whitelisted token is supplied to proxy contract, there is no way to distribute the tokens not distributed by organizer unless conducted by owner via ProxyFactory.distributedByOwner() after contest expiration. This is further excarbated by the fact that there is no checks stopping a proxy contract from holding more than 1 type of ERC20 token.

Hence, this can result in a delay of reward disbursement after contest ends and additional charge of 5% comission fees since another distribution is required to be performed by the owner to disburse rewards.

Proof of Concept

Add the following test in ProxyFactortTest.t.sol and run forge test --match-test testDoubleSponsorTokens

Following is the description of PoC:

  1. Owner sets contest

  2. Sponsor sends two tokens (in this case, JPYCV1 and JPYCV2 ) to proxy contract

  3. Setup calldata for deployment and distribution

  4. Warp to contest completion (deadline set as 8 days, so we warp forward 9 days)

  5. Organizer successfully distribute rewards to winners, with token set as JPYCV1

  6. Organizer wants to distribute rewards to winners with token set as JPYCV2, but fails and reverts as proxy is already deployed

  7. Warp to contest expiration

  8. Owner can then rescue funds, but winner rewards are delayed by 1 week

function testDoubleSponsorTokens() public {
// 1. Owner set contest
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)));
// 2. Sponsor transfer 2 tokens
address proxyAddress = proxyFactory.getProxyAddress(salt, address(distributor));
vm.startPrank(sponsor);
MockERC20(jpycv1Address).transfer(proxyAddress, 10000 ether);
MockERC20(jpycv2Address).transfer(proxyAddress, 10000 ether);
vm.stopPrank();
assertEq(MockERC20(jpycv1Address).balanceOf(proxyAddress), 10000 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(proxyAddress), 10000 ether);
// 3. Setup calldata data
address[] memory tokens_ = new address[](2);
tokens_[0] = jpycv1Address;
address[] memory winners = new address[](1);
winners[0] = user1;
uint256[] memory percentages_ = new uint256[](1);
percentages_[0] = 9500;
bytes memory data1 = abi.encodeWithSelector(Distributor.distribute.selector, jpycv1Address, winners, percentages_, "");
bytes memory data2 = createData();
// 4. Warp to contest completion
vm.warp(9 days);
// 5. Organizer distribute funds of
vm.startPrank(organizer);
address proxyDeployed = proxyFactory.deployProxyAndDistribute(randomId, address(distributor), data1);
vm.stopPrank();
assertEq(MockERC20(jpycv1Address).balanceOf(user1), 9500 ether);
assertEq(MockERC20(jpycv1Address).balanceOf(stadiumAddress), 500 ether);
// 6. Organizer try to distribute funds of token JPYcv2, but reverts as proxy is already deployed
vm.startPrank(organizer);
vm.expectRevert();
proxyFactory.deployProxyAndDistribute(randomId, address(distributor), data2);
vm.stopPrank();
// 7. Warp to contest expiration
vm.warp(16 days);
// 8. Owner distribute remaining rewards to winners
// but is delayed (In this case by 7 days)
vm.startPrank(factoryAdmin);
proxyFactory.distributeByOwner(proxyDeployed, organizer, randomId, address(distributor), data2);
assertEq(MockERC20(jpycv1Address).balanceOf(user1), 9500 ether);
assertEq(MockERC20(jpycv1Address).balanceOf(stadiumAddress), 500 ether);
}

Tools Used

Manual Analysis, Foundry

Recommendation

Some recommendations:

  • Either add clear comments in code to prevent sponsor from sending multiple tokens

  • Or allow distribution of more than one tokens by organizer after contest completion.

Support

FAQs

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