Christmas Dinner

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

L-2: Inefficient ETH Refund Mechanism Using transfer Instead of .call

Summary

The _refundETH function uses the transfer method to send Ether to the recipient. While transfer is simple and often sufficient, it imposes a 2300 gas limit on the recipient, which can fail if the recipient's fallback function exceeds this gas limit. Replacing transfer with .call is a best practice, as it avoids gas limit issues and aligns with modern Solidity recommendations.

Vulnerability Details

Root Cause: The _refundETH function uses transfer, which imposes a fixed gas stipend of 2300 gas for the recipient's fallback function. This may lead to failures if the recipient requires more gas for execution.

  • Expected Behavior: The refund should succeed as long as the recipient address is valid and can receive Ether.

  • Current Behavior: The use of transfer introduces the risk of the refund failing if the recipient's fallback function has non-trivial logic or interacts with other contracts.

Impact

While it does not directly compromise security, it can cause operational issues by preventing funds from being refunded to valid addresses due to gas limit restrictions.

Tools Used

  • Manual code review

  • Foundry for testing

Recommendations

Replace transfer with .call in the _refundETH function:

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

PoC

contract GasHeavyRecipient {
// This fallback function consumes more gas than the 2300 gas stipend provided by transfer
fallback() external payable {
uint256 i = 0;
while (i < 1000) {
// Simulating heavy computation
i++;
}
}
}
function test_refundETH_withTransferFails() public {
uint256 refundAmount = 1 ether;
// Deploy a gas-heavy recipient
address payable recipient = payable(address(new GasHeavyRecipient()));
// Fund the contract
vm.deal(address(this), refundAmount);
// Attempt refund using transfer (expected to fail)
vm.expectRevert();
recipient.transfer(refundAmount);
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge 10 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.