Summary
If sponsors provide rewards in different tokens (e.g. USDC, USDT and JPYC), the organizer will be able to distribute only one of the tokens.
Vulnerability Details
Both deployProxyAndDistribute
and deployProxyAndDistributeBySignature
, intended for reward distribution, attempt to deploy the proxy before sending the rewards. This makes it impossible for the organizer to distribute different tokens for the same contest, because the subsequent calls would try to deploy a contract to an address with already existing bytecode, which will throw an error.
The organizer will have to contact the owner, so they will distribute the remaining tokens via distributeByOwner
- the only function that doesn't deploy the proxy.
Proof of Concept
Add to ProxyTest.t.sol
function testSecondTokenFails() public {
vm.startPrank(factoryAdmin);
bytes32 randomId = keccak256(abi.encode("James", "001"));
proxyFactory.setContest(organizer, randomId, block.timestamp, address(distributor));
bytes32 salt = keccak256(abi.encode(organizer, randomId, address(distributor)));
address proxyAddress = proxyFactory.getProxyAddress(salt, address(distributor));
vm.startPrank(sponsor);
MockERC20(jpycv1Address).transfer(proxyAddress, 100_000 ether);
MockERC20(jpycv2Address).transfer(proxyAddress, 100_000 ether);
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 =
abi.encodeWithSelector(Distributor.distribute.selector, jpycv2Address, winners, percentages_, "");
vm.startPrank(organizer);
proxyFactory.deployProxyAndDistribute(randomId, address(distributor), data1);
assertEq(MockERC20(jpycv1Address).balanceOf(user1), 95_000 ether);
vm.expectRevert();
proxyFactory.deployProxyAndDistribute(randomId, address(distributor), data2);
}
Impact
Organizers are unable to distribute multi-token rewards.
Tools Used
Manual review
Recommendations
Deploy the proxy only if it doesn't already exist, so the subsequent distributions will succeed:
function _deployProxy(address organizer, bytes32 contestId, address implementation) internal returns (address) {
bytes32 salt = _calculateSalt(organizer, contestId, implementation);
- address proxy = address(new Proxy{salt: salt}(implementation));
+ address proxy = getProxyAddress(salt, implementation);
+ if (proxy.code.length == 0) {
+ new Proxy{salt: salt}(implementation);
+ }
return proxy;
}