Christmas Dinner

First Flight #31
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Severity: medium
Valid

Gas-limited ETH transfer in `ChristmasDinner::_refundETH` function

Summary

The ChristmasDinner::_refundETH function uses the transfer() which has a hard gas limit of 2300 gas to send ETH to the participant.

Vulnerability Details

The ChristmasDinner::_refundETH function uses the transfer() method to send ETH to the participant.

However this is problematic because:

  1. transfer() has a hard gas limit of 2300 gas.

  2. Smart contract wallets, for example, Gnosis Safe, typically require more than 2300 gas (simple transfer requires around 21,000 gas) to process incoming ETH.

  3. Modern smart contract wallets commonly used as multi-sigs would fail to receive funds.

  4. The operation would revert and the participant will not be able to withdraw their deposits.

POC

Add the following GnosisSafeMock contract and test to the ChristmasDinnerTest .t.sol:

  • The test will revert with "[OutOfGas] EvmError: OutOfGas" error.

contract GnosisSafeMock {
// Mock Gnosis Safe receive function that uses more than 2300 gas
receive() external payable {
// Perform some operations that consume gas
uint256 operation = 1;
for (uint i = operation; i <= 100; i++) {
operation *= i;
}
}
}
contract ChristmasDinnerTest is Test {
ChristmasDinner cd;
ERC20Mock wbtc;
ERC20Mock weth;
ERC20Mock usdc;
GnosisSafeMock gnosisSafeMock;
uint256 constant DEADLINE = 7;
address deployer = makeAddr("deployer");
address user1;
address user2;
address user3;
function setUp() public {
wbtc = new ERC20Mock();
weth = new ERC20Mock();
usdc = new ERC20Mock();
gnosisSafeMock = new GnosisSafeMock();
vm.startPrank(deployer);
cd = new ChristmasDinner(address(wbtc), address(weth), address(usdc));
vm.warp(1);
cd.setDeadline(DEADLINE);
vm.stopPrank();
_makeParticipants();
}
function test_revert_refundETH() public {
address gnosisSafeMockAddress = address(gnosisSafeMock);
vm.deal(gnosisSafeMockAddress, 1 ether);
vm.startPrank(gnosisSafeMockAddress);
(bool success, ) = address(cd).call{value: 1 ether}("");
require(success, "transfer failed");
cd.refund();
vm.stopPrank();
}
// More code
}

Impact

Participant will not be able to withdraw their deposit if the participant is a smart contract (Gnosis Safe, other smart wallets).

Tools Used

  • Manual review

  • Foundry

Recommendations

Replace the transfer() function with the call() function.

+ UnsuccessfulEthRefund();
function _refundETH(address payable _to) internal {
uint256 refundValue = etherBalance[_to];
- _to.transfer(refundValue);
etherBalance[_to] = 0;
+ (bool success, ) = _to.call{value: refundValue}("");
+ if (!success) revert UnsuccessfulEthRefund();
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

transfer instead of call

0xtimefliez Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

transfer instead of call

Support

FAQs

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