Summary
The ChristmasDinner contract uses .transfer() for ETH refunds, limiting gas to 2300 units. This can permanently lock funds when recipients are smart contract wallets requiring >2300 gas.
Vulnerability Details
src/ChristmasDinner.sol#233-238
function _refundETH(address payable _to) internal {
   uint256 refundValue = etherBalance[_to];
   _to.transfer(refundValue); 
   etherBalance[_to] = 0;
}
Proof of concept
pragma solidity 0.8.27;
import {Test, console} from "forge-std/Test.sol";
import {ChristmasDinner} from "../src/ChristmasDinner.sol";
import {ERC20Mock} from "../lib/openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol";
contract GasHungryWallet {
    uint256 public counter;
    
    receive() external payable {
        
        
        counter += 1;
    }
    function getGasLeft() external view returns (uint256) {
        return gasleft();
    }
}
contract ChristmasDinnerETHTest is Test {
    ChristmasDinner cd;
    ERC20Mock wbtc;
    ERC20Mock weth;
    ERC20Mock usdc;
    GasHungryWallet wallet;
    function setUp() public {
        wbtc = new ERC20Mock();
        weth = new ERC20Mock();
        usdc = new ERC20Mock();
        cd = new ChristmasDinner(address(wbtc), address(weth), address(usdc));
        wallet = new GasHungryWallet();
        cd.setDeadline(7 days);
    }
    function test_ETHTransferGasLimit() public {
        
        vm.deal(address(wallet), 2 ether);
        vm.prank(address(wallet));
        (bool success,) = address(cd).call{value: 1 ether}("");
        assertTrue(success, "Deposit should succeed");
        
        assertEq(address(cd).balance, 1 ether);
        
        
        vm.prank(address(wallet));
        vm.expectRevert(bytes(""));
        cd.refund();
        
        
        assertEq(address(cd).balance, 1 ether);
    }
}
Result
forge test --match-test test_ETHTransferGasLimit -vvvv
[⠆] Compiling...
No files changed, compilation skipped
Ran 1 test for test/ChristmasDinnerETHTest.t.sol:ChristmasDinnerETHTest
[PASS] test_ETHTransferGasLimit() (gas: 97339)
Traces:
  [97339] ChristmasDinnerETHTest::test_ETHTransferGasLimit()
    ├─ [0] VM::deal(GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 2000000000000000000 [2e18])
    │   └─ ← [Return]
    ├─ [0] VM::prank(GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1])
    │   └─ ← [Return]
    ├─ [24234] ChristmasDinner::receive{value: 1000000000000000000}()
    │   ├─ emit NewSignup(: GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], : 1000000000000000000 [1e18], : true)
    │   └─ ← [Stop]
    ├─ [0] VM::assertTrue(true, "Deposit should succeed") [staticcall]
    │   └─ ← [Return]
    ├─ [0] VM::assertEq(1000000000000000000 [1e18], 1000000000000000000 [1e18]) [staticcall]
    │   └─ ← [Return]
    ├─ [0] VM::prank(GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1])
    │   └─ ← [Return]
    ├─ [0] VM::expectRevert(custom error 0xf28dceb3:  )
    │   └─ ← [Return]
    ├─ [52327] ChristmasDinner::refund()
    │   ├─ [7310] ERC20Mock::transfer(GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0)
    │   │   ├─ emit Transfer(from: ChristmasDinner: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], to: GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], value: 0)
    │   │   └─ ← [Return] true
    │   ├─ [7310] ERC20Mock::transfer(GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0)
    │   │   ├─ emit Transfer(from: ChristmasDinner: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], to: GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], value: 0)
    │   │   └─ ← [Return] true
    │   ├─ [7310] ERC20Mock::transfer(GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], 0)
    │   │   ├─ emit Transfer(from: ChristmasDinner: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], to: GasHungryWallet: [0xc7183455a4C133Ae270771860664b6B7ec320bB1], value: 0)
    │   │   └─ ← [Return] true
    │   ├─ [2258] GasHungryWallet::receive{value: 1000000000000000000}()
    │   │   └─ ← [OutOfGas] EvmError: OutOfGas
    │   └─ ← [Revert] EvmError: Revert
    ├─ [0] VM::assertEq(1000000000000000000 [1e18], 1000000000000000000 [1e18]) [staticcall]
    │   └─ ← [Return]
    └─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 312.73µs (74.81µs CPU time)
Ran 1 test suite in 611.28ms (312.73µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Contract receives 1 ETH deposit from wallet
 Refund attempt triggers OutOfGas error in wallet's receive()
ETH remains stuck in contract (balance stays 1 ETH)
Impact
ETH permanently locked in contract
Smart contract wallets unable to receive refunds
No fallback mechanism to recover stuck funds
Tools Used
Manual review
 Foundry test framework
Recommendations
function _refundETH(address payable _to) internal {
    uint256 refundValue = etherBalance[_to];
    (bool success,) = _to.call{value: refundValue}("");
    require(success, "ETH transfer failed");
    etherBalance[_to] = 0;
}