Sparkn

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

[M-01] No function to change whitelisted token

Impact

In Sparkn, sponsors presumably send whitelisted tokens to proxy contract before proxy contract deployment for rewards disburment after contest ends. This whitelisted tokens are decided in the constructor when owner deploys the ProxyFactory.sol contract:

ProxyFactory.sol#L81-L90

constructor(address[] memory _whitelistedTokens) EIP712("ProxyFactory", "1") Ownable() {
if (_whitelistedTokens.length == 0) revert ProxyFactory__NoEmptyArray();
for (uint256 i; i < _whitelistedTokens.length;) {
if (_whitelistedTokens[i] == address(0)) revert ProxyFactory__NoZeroAddress();
whitelistedTokens[_whitelistedTokens[i]] = true;
unchecked {
i++;
}
}
}

However, there is nothing preventing sponsors from sending non-whitelisted tokens.

If unaware sponsors or anybody accidentally send tokens that are not whitelisted to proxy contract, those tokens can never be retrieved since whitelistedTokens mapping can never be adjusted. This is due to this check in Distributor._distribute():

Distributor.sol#L121-L123

if (!_isWhiteListed(token)) {
revert Distributor__InvalidTokenAddress();
}

Proof of Concept

This issue is evident in the test case testIfTokenAdressIsNotWhitelistedThenRevert() present in the unit test ProxyTest.t.sol. For example, where if sponsor sends USDT that is not whitelisted by accident, even though the tokens cannot be distributed by organizer, owner calling ProxyFactory.distributeByOwner will also revert due to the _isWhiteListed() check.

ProxyTest.t.sol#L160-L188

function testIfTokenAdressIsNotWhitelistedThenRevert()
public
setUpContestForNameAndSentAmountToken("James", jpycv2Address, 10000 ether)
{
bytes32 randomId_ = keccak256(abi.encode("James", "001"));
bytes memory data = createDataToDistributeJpycv2();
vm.startPrank(organizer);
deployedProxy = proxyFactory.deployProxyAndDistribute(randomId_, address(distributor), data);
vm.stopPrank();
proxyWithDistributorLogic = Distributor(address(deployedProxy));
// prepare data
address[] memory winners = new address[](1);
winners[0] = user1;
uint256[] memory percentages_ = new uint256[](1);
percentages_[0] = 9500;
// sponsor send token to proxy by mistake
vm.startPrank(sponsor);
MockERC20(jpycv2Address).transfer(deployedProxy, 10000 ether);
vm.stopPrank();
// if token address is not whitelisted then revert
vm.startPrank(address(proxyFactory));
vm.expectRevert(Distributor.Distributor__InvalidTokenAddress.selector);
// @audit Similar revert will occur and accidental transfer of funds of USDT can never be retrieved by owner
// since `distributeByOwner` calls the same `_distribute()` function within `Proxy.sol`
-> proxyWithDistributorLogic.distribute(usdtAddress, winners, percentages_, "");
vm.stopPrank();
}

Tools Used

Manual Analysis

Recommendation

While this is a known issue, it can easily be prevented by:

  • Adding a onlyOwner function to send accidental funds of non-whitelisted tokens sent to proxy to STADIUM_ADDRESS. This also allows blacklist of previously whitelisted tokens in the event of emergencies

  • Exposing an external function for sponsors to directly call in ProxyFactory.sol to send funds to proxy contract, where in function logic, tokens sent by sponsors can be checked to ensure that the correct whitelisted tokens are being sent to proxy contract

Support

FAQs

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