Sparkn

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

Tokens will be stuck if sponsor accidentally send other whitelisted token to the deployed proxy contract for a contest.

Summary

The Distributor: :_distribute() function in Distributor.sol is designed in a way that the tokens distributed to it are send to winners by percentage and remaining tokens is transferred to the STADIUM_ADDRESS but if Sponsor (anyone) sends other whitelisted tokens to the contract, it will not be withdrawn and gets stuck inside the contract.

There is a known issue that non-whitelisted tokens can get stuck but whitelisted tokens can get stuck too!!!

Vulnerability Details

File:Distributor.sol
function _distribute(address token, address[] memory winners, uint256[] memory percentages, bytes memory data)
internal
{
// token address input check
//Notice that a single token is given as input and only that will be used throughout this process
if (token == address(0)) revert Distributor__NoZeroAddress();
if (!_isWhiteListed(token)) {
revert Distributor__InvalidTokenAddress();
}
............
............
............
IERC20 erc20 = IERC20(token);
uint256 totalAmount = erc20.balanceOf(address(this));
// if there is no token to distribute, then revert
if (totalAmount == 0) revert Distributor__NoTokenToDistribute();
uint256 winnersLength = winners.length; // cache length
for (uint256 i; i < winnersLength;) {
uint256 amount = totalAmount * percentages[i] / BASIS_POINTS; //@audit check the maths later
erc20.safeTransfer(winners[i], amount);
unchecked {
++i;
}
}
// send commission fee as well as all the remaining tokens to STADIUM_ADDRESS to avoid dust remaining
//@audit I dont think all erc20 tokens are transferred
_commissionTransfer(erc20);
emit Distributed(token, winners, percentages, data);
}

Only the single token whose address that is passed to the function is transferred. Other tokens will be stuck.

Proof of Concept

Using a modified version of testIfAllConditionsMetThenJpycv2SendingCallShouldSuceed() so this PoC can be tested by just pasting inside test/integration/ProxyTest.t.sol.

//Hey my first coded PoC!!!!
//Using testIfAllConditionsMetThenJpycv2SendingCallShouldSuceed() for ease and I'm lazy lol
function testIfDifferentTokensAreSendToProxy()
public
setUpContestForNameAndSentAmountToken("James", jpycv2Address, 10000 ether)
{
// before
assertEq(MockERC20(jpycv1Address).balanceOf(address(user1)), 0);
assertEq(MockERC20(jpycv1Address).balanceOf(address(user2)), 0);
// deploy proxy
bytes32 randomId_ = keccak256(abi.encode("James", "001"));
bytes memory data = createDataToDistributeJpycv2();
vm.startPrank(organizer);
//Notice that that at this time there is no token in the balance of the proxy
deployedProxy = proxyFactory.deployProxyAndDistribute(randomId_, address(distributor), data);
vm.stopPrank();
proxyWithDistributorLogic = Distributor(address(deployedProxy));
// prepare data
address[] memory winners = new address[](2);
winners[0] = user1;
winners[1] = user2;
uint256[] memory percentages_ = new uint256[](2);
percentages_[0] = 9000;
percentages_[1] = 500;
deal(address(jpycv1Address), sponsor, 200000 ether);
deal(address(jpycv2Address),sponsor, 100000 ether);
vm.startPrank(sponsor);
//tokens are send to the proxy at this moment
MockERC20(jpycv1Address).transfer(deployedProxy, 200000 ether);
//Sponsor sends another token, which is whitelisted but not used in this contest, to the contract by mistake
MockERC20(jpycv2Address).transfer(deployedProxy, 100000 ether);
vm.stopPrank();
vm.startPrank(address(proxyFactory));
vm.expectEmit(true, false, false, false);
emit Distributed(jpycv1Address, winners, percentages_, "");
proxyWithDistributorLogic.distribute(jpycv1Address, winners, percentages_, "");
vm.stopPrank();
// after this, token should be distributed correctly as expected
//@audit I dont think so
//Commenting out asserts I dont want, can umcomment if you want to
// assertEq(MockERC20(jpycv1Address).balanceOf(address(user1)), 180000 ether);
// assertEq(MockERC20(jpycv1Address).balanceOf(address(user2)), 10000 ether);
// assertEq(MockERC20(jpycv1Address).balanceOf(stadiumAddress), 10000 ether);
// This will pass because there are other tokens in the proxy that are not transferred to the stadium address
assertEq(MockERC20(jpycv2Address).balanceOf(deployedProxy), 100000 ether);
//yep passed, LFGGG!!!
}

Impact

If other whitelisted tokens are accidentally send to the deployedProxy, they will be stuck and can lead to loss of funds. Impact is high and likelihood depends on who sends the tokens, if the protocol is handling it then low else if people are directly interacting, then likelihood is Medium/High , they did mention in the stream that target audience might necessarily not be crypto native people. Severity might be Medium to High depending on that.

Tools Used

Manual review, Foundry

Recommendations

Modify the functionality of the existing Distributor: :_commissionTransfer() function or create another contract to facilitate unused whitelisted token transfer.

Support

FAQs

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