Description: The ChristmasDinner::withdraw function transfers all the ERC20 tokens to the Host without changing the ChristmasDinner::balances array meaning that users will still have their ERC20 token balances written in the array but when they'll try to get a refund using ChristmasDinner::refund the function will revert and they won't be able to get their funds back.
This can have dire consequences where participants can't get their funds back may they change their mind about attending the event(dinner). This vulnerability can be maliciously used by the Host and seems like he has the ability to scam the participants making the contract centralized.
function withdraw() external onlyHost {
address _host = getHost();
@> i_WETH.safeTransfer(_host, i_WETH.balanceOf(address(this)));
@> i_WBTC.safeTransfer(_host, i_WBTC.balanceOf(address(this)));
@> i_USDC.safeTransfer(_host, i_USDC.balanceOf(address(this)));
}
Impact: Participants won't be able to get a refund anymore
Proof of Concept:
Users deposit ERC20 tokens to the contract
Host calls the ChristmasDinner::withdraw function taking all the ERC20 tokens
If a participants tries to get a refund by calling ChristmasDinner::refund function, the transaction will revert because of insufficient balance
PoC Code
function test_withdrawAsHostBeforeDeadline() public {
uint256 wethAmount;
uint256 wbtcAmount;
uint256 usdcAmount;
assertEq(wbtc.balanceOf(user1), 2e18);
vm.startPrank(user1);
cd.deposit(address(wbtc), 0.5e18);
wbtcAmount += 0.5e18;
cd.deposit(address(weth), 2e18);
wethAmount += 2e18;
vm.stopPrank();
vm.startPrank(user2);
cd.deposit(address(usdc), 2e18);
usdcAmount += 2e18;
cd.deposit(address(wbtc), 1e18);
wbtcAmount += 1e18;
vm.stopPrank();
vm.startPrank(deployer);
vm.warp(1 + 3 days);
cd.withdraw();
vm.stopPrank();
assertEq(wbtc.balanceOf(deployer), wbtcAmount);
assertEq(weth.balanceOf(deployer), wethAmount);
assertEq(usdc.balanceOf(deployer), usdcAmount);
assertEq(wbtc.balanceOf(user1), 1.5e18);
vm.startPrank(user1);
vm.expectRevert();
cd.refund();
}
Recommendation: To prevent this, we should only allow ChristmasDinner::withdraw function to be called by the host ONLY after the deadline.
////////////////////////////////////////////////////////////////
////////////////// Custom Errors /////////////////
////////////////////////////////////////////////////////////////
error NotHost();
error BeyondDeadline();
+ error BeforeDeadline();
error DeadlineAlreadySet();
error OnlyParticipantsCanBeHost();
error NotSupportedToken();
function withdraw() external onlyHost {
+ if(block.timestamp < deadline) {
+ error BeforeDeadline();
+ }
address _host = getHost();
i_WETH.safeTransfer(_host, i_WETH.balanceOf(address(this)));
i_WBTC.safeTransfer(_host, i_WBTC.balanceOf(address(this)));
i_USDC.safeTransfer(_host, i_USDC.balanceOf(address(this)));
}