Skip to content

Commit

Permalink
Griefing fix - v2.6.0 (#13)
Browse files Browse the repository at this point in the history
Update to the `TransferHelper.sol` to avoid gas griefing attacks when
sending ETH to an address.
  • Loading branch information
mpeyfuss authored Oct 25, 2023
2 parents 02b14ae + dfb9c11 commit c72ea81
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 4 deletions.
17 changes: 14 additions & 3 deletions src/payments/TransferHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ error InsufficentERC20Transfer();
/// @title Transfer Helper
/// @notice Abstract contract that has helper function for sending ETH and ERC20's safely
/// @author transientlabs.xyz
/// @custom:last-updated 2.3.0
/// @custom:last-updated 2.6.0
abstract contract TransferHelper {
/*//////////////////////////////////////////////////////////////////////////
State Variables
Expand All @@ -34,14 +34,25 @@ abstract contract TransferHelper {
ETH Functions
//////////////////////////////////////////////////////////////////////////*/

/// @notice Function to force transfer ETH
/// @notice Function to force transfer ETH, defaulting to forwarding 100k gas
/// @dev On failure to send the ETH, the ETH is converted to WETH and sent
/// @dev Care should be taken to always pass the proper WETH address that adheres to IWETH
/// @param recipient The recipient of the ETH
/// @param amount The amount of ETH to send
/// @param weth The WETH token address
function _safeTransferETH(address recipient, uint256 amount, address weth) internal {
(bool success,) = recipient.call{value: amount}("");
_safeTransferETH(recipient, amount, weth, 1e5);
}

/// @notice Function to force transfer ETH, with a gas limit
/// @dev On failure to send the ETH, the ETH is converted to WETH and sent
/// @dev Care should be taken to always pass the proper WETH address that adheres to IWETH
/// @param recipient The recipient of the ETH
/// @param amount The amount of ETH to send
/// @param weth The WETH token address
/// @param gasLimit The gas to forward
function _safeTransferETH(address recipient, uint256 amount, address weth, uint256 gasLimit) internal {
(bool success,) = recipient.call{value: amount, gas: gasLimit}("");
if (!success) {
IWETH token = IWETH(weth);
token.deposit{value: amount}();
Expand Down
48 changes: 47 additions & 1 deletion test/payments/TransferHelper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.17;

import "forge-std/Test.sol";
import {Receiver, RevertingReceiver} from "../utils/Receivers.sol";
import {Receiver, RevertingReceiver, GriefingReceiver} from "../utils/Receivers.sol";
import {WETH9} from "../utils/WETH9.sol";
import {MockERC20, MockERC20WithFee} from "../utils/MockERC20.sol";
import {TransferHelper, ETHTransferFailed, InsufficentERC20Transfer} from "tl-sol-tools/payments/TransferHelper.sol";
Expand All @@ -14,6 +14,10 @@ contract ExternalTransferHelper is TransferHelper {
_safeTransferETH(recipient, amount, weth);
}

function safeTransferETHWithGasLimit(address recipient, uint256 amount, address weth, uint256 gasLimit) external {
_safeTransferETH(recipient, amount, weth, gasLimit);
}

function safeTransferERC20(address recipient, address currency, uint256 amount) external {
_safeTransferERC20(recipient, currency, amount);
}
Expand All @@ -28,6 +32,7 @@ contract TestTransferHelper is Test {
address weth;
address receiver;
address revertingReceiver;
address griefingReceiver;
MockERC20 erc20;
MockERC20WithFee erc20fee;

Expand All @@ -40,6 +45,7 @@ contract TestTransferHelper is Test {
weth = address(new WETH9());
receiver = address(new Receiver());
revertingReceiver = address(new RevertingReceiver());
griefingReceiver = address(new GriefingReceiver());
erc20 = new MockERC20(ben);
erc20fee = new MockERC20WithFee(ben);
}
Expand All @@ -50,6 +56,8 @@ contract TestTransferHelper is Test {
recipient.code.length == 0 && recipient > address(100)
);

vm.assume(amount < 1_000_000_000_000_000_000 ether);

// test contract receiver
vm.deal(address(th), amount);
uint256 b1 = receiver.balance;
Expand All @@ -67,6 +75,44 @@ contract TestTransferHelper is Test {
uint256 b3 = IERC20(weth).balanceOf(revertingReceiver);
th.safeTransferETH(revertingReceiver, amount, weth);
assert(IERC20(weth).balanceOf(revertingReceiver) - b3 == amount);

// test griefing receiver
vm.deal(address(th), amount);
uint256 b4 = IERC20(weth).balanceOf(griefingReceiver);
th.safeTransferETH(griefingReceiver, amount, weth);
assert(IERC20(weth).balanceOf(griefingReceiver) - b4 == amount);
}

function testSafeTransferETHWithGasLimit(address recipient, uint256 amount) public {
vm.assume(
recipient.code.length == 0 && recipient > address(100)
);

vm.assume(amount < 1_000_000_000_000_000_000 ether);

// test contract receiver
vm.deal(address(th), amount);
uint256 b1 = receiver.balance;
th.safeTransferETHWithGasLimit(receiver, amount, weth, 1e4);
assert(receiver.balance - b1 == amount);

// test recipient
vm.deal(address(th), amount);
uint256 b2 = recipient.balance;
th.safeTransferETHWithGasLimit(recipient, amount, weth, 1e4);
assert(recipient.balance - b2 == amount);

// test reverting receiver
vm.deal(address(th), amount);
uint256 b3 = IERC20(weth).balanceOf(revertingReceiver);
th.safeTransferETHWithGasLimit(revertingReceiver, amount, weth, 1e4);
assert(IERC20(weth).balanceOf(revertingReceiver) - b3 == amount);

// test griefing receiver
vm.deal(address(th), amount);
uint256 b4 = IERC20(weth).balanceOf(griefingReceiver);
th.safeTransferETHWithGasLimit(griefingReceiver, amount, weth, 1e4);
assert(IERC20(weth).balanceOf(griefingReceiver) - b4 == amount);
}

function testSafeTransferERC20(address recipient, uint256 amount) public {
Expand Down
9 changes: 9 additions & 0 deletions test/utils/Receivers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,13 @@ contract RevertingReceiver {
receive() external payable {
revert("you shall not pass");
}
}

contract GriefingReceiver {
event Grief();
receive() external payable {
for (uint256 i = 0; i < type(uint256).max; i++) {
emit Grief();
}
}
}

0 comments on commit c72ea81

Please sign in to comment.