Sparkn

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

The rescue feature does not work if rescued token and prize token are the same

Summary

ProxyFactory#distributeByOwner is supposed to rescue tokens that were accidentally sent to a proxy. However, the current implementation would only work if the prize token and the mistakenly sent token are different (e.g. USDT and JPYC respectively), but wouldn't work if they are the same.

Vulnerability Details

Consider the following:

  1. A 14-day contest has started. Sponsors start sending their JPYC rewards to the contest's proxy.

  2. One week later, Alice sends 100 JPYC to the proxy by mistake. Alice realizes her mistake and contacts the owner for rescue.

  3. After one more week, the contest ends.

  4. Organizer distributes the JPYC rewards to the winners via
    ProxyFactory#deployProxyAndDistributeByOwner -> Proxy#fallback -> Distributor#distribute, which sends 95% of proxy's JPYC tokens to the winners and 5% to stadiumAddress.

  5. One week after the end of the contest, EXPIRATION_TIME ends and the owner is finally able to rescue Alice's tokens - but there's no tokens to rescue anymore.

Proof of Concept

Change ProxyFactoryTest.t.sol#testSucceedsIfAllConditionsMet:

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();
+ // set up
+ address alice = address(0x1337);
+ deal(jpycv2Address, alice, 100 ether);
+ bytes32 salt = keccak256(abi.encode(organizer, randomId_, address(distributor)));
+ address proxy = proxyFactory.getProxyAddress(salt, address(distributor));
+ // Alice sends tokens by accident
+ vm.prank(alice);
+ MockERC20(jpycv2Address).transfer(proxy, 100 ether);
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);
+ // Alice's funds went to the winner and stadiumAddress
+ assertEq(MockERC20(jpycv2Address).balanceOf(user1), 9500 ether + 95 ether);
+ assertEq(MockERC20(jpycv2Address).balanceOf(stadiumAddress), 500 ether + 5 ether);
}

Impact

The protocol fails to deliver its rescue feature: a token accidentally sent to the proxy can not be saved unless it is different from the prize token.

Tools Used

Manual review

Recommendations

  • Consider reimplementing the rescue mechanism, so that the funds can be recovered from the proxy before they are distributed by organizer.

  • Or make sure that the organizer's front-end will include rescue requesters in the distribution.

Support

FAQs

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