Sparkn

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

A malicious `Sponsors` can trick the `Supporters` by not transfering the funds to the proxy contract and get their help for free.

Summary

The protocol admins do not check whether the Sponsors transfer the contest funds to the proxy contract. They just set the contest for them.

A malicious Sponsors can trick the Supporters to get their help and leave the distribution part.

If the owners of the protocol after the expiration of the contest, try to distribute the funds then they will not be able to do so because the proxy will not have funds to distribute.

Vulnerability Details

Open

This is the test written by protocol team inside test/integration/ProxyFactoryTest.t.sol file.

I am going to modify it to show the effect when the malicious sponser will not transfer the funds and owner tries to distribute the funds.

function testSucceedsIfAllConditionsMet()
public
setUpContestForJasonAndSentJpycv2Token(organizer)
{
// before
assertEq(MockERC20(jpycv2Address).balanceOf(user1), 0 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(stadiumAddress), 0 ether);
bytes32 randomId_ = keccak256(abi.encode("Jason", "001"));
bytes memory data = createData();
vm.warp(16 days);
vm.startPrank(factoryAdmin);
proxyFactory.deployProxyAndDistributeByOwner(
organizer,
randomId_,
address(distributor),
data
);
vm.stopPrank();
// after
assertEq(MockERC20(jpycv2Address).balanceOf(user1), 9500 ether);
assertEq(MockERC20(jpycv2Address).balanceOf(stadiumAddress), 500 ether);
}

Here in this modifier, I have comment out the code of sponsor transfering the funds to proxy contract.

modifier setUpContestForJasonAndSentJpycv2Token(address _organizer) {
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)
);
// vm.startPrank(sponsor);
// MockERC20(jpycv2Address).transfer(proxyAddress, 10000 ether);
// vm.stopPrank();
// console.log(MockERC20(jpycv2Address).balanceOf(proxyAddress));
// assertEq(MockERC20(jpycv2Address).balanceOf(proxyAddress), 10000 ether);
_;
}

Now if you run the test using this command forge test --mt testSucceedsIfAllConditionsMet -vvv

You will see this error message;

│ ├─ [28306] → new <Unknown>@0x717a630f36795235Aa9Bd5bf6DF42c431d2875FD
│ │ └─ ← 140 bytes of code
│ ├─ [11077] 0x717a630f36795235Aa9Bd5bf6DF42c431d2875FD::distribute(MockERC20: [0x90193C961A926261B756D1E5bb255e67ff9498A1], [0x000000000000000000000000000000000000000E], [9500], 0x)
│ │ ├─ [8287] Distributor::distribute(MockERC20: [0x90193C961A926261B756D1E5bb255e67ff9498A1], [0x000000000000000000000000000000000000000E], [9500], 0x) [delegatecall]
│ │ │ ├─ [2575] ProxyFactory::whitelistedTokens(MockERC20: [0x90193C961A926261B756D1E5bb255e67ff9498A1]) [staticcall]
│ │ │ │ └─ ← true
│ │ │ ├─ [2563] MockERC20::balanceOf(0x717a630f36795235Aa9Bd5bf6DF42c431d2875FD) [staticcall]
│ │ │ │ └─ ← 0
│ │ │ └─ ← "Distributor__NoTokenToDistribute()"
│ │ └─ ← "Distributor__NoTokenToDistribute()"
│ └─ ← "ProxyFactory__DelegateCallFailed()"
└─ ← "ProxyFactory__DelegateCallFailed()"

Impact

The Supporters will not be able to get their reward for the time they spent on solving malicious sponsors issues.

Tools Used

Manual Analysis

Recommendations

We already know the address of the proxy contract before even deploying through getProxyAddress. The admins should check in the setContest function whether the sponsors transfer the funds or not. If not they should not create the contest.

View Mitigation

+ import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
function setContest(
address organizer,
bytes32 contestId,
uint256 closeTime,
address implementation,
+ address token,
+ uint256 reward
) public onlyOwner {
if (organizer == address(0) || implementation == address(0))
revert ProxyFactory__NoZeroAddress();
if (
closeTime > block.timestamp + MAX_CONTEST_PERIOD ||
closeTime < block.timestamp
) {
revert ProxyFactory__CloseTimeNotInRange();
}
bytes32 salt = _calculateSalt(organizer, contestId, implementation);
if (saltToCloseTime[salt] != 0)
revert ProxyFactory__ContestIsAlreadyRegistered();
+ address proxyAddress = getProxyAddress(salt, implementation);
+ if (reward == 0 || IERC20(token).balanceOf(proxyAddress) < reward) {
+ revert ProxyFactory__NotEnoughBalance();
+ }
saltToCloseTime[salt] = closeTime;
emit SetContest(organizer, contestId, closeTime, implementation);
}

Support

FAQs

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