Skip to content

Commit 8745736

Browse files
authored
Use SafeERC20 operations (#4)
1 parent 436774d commit 8745736

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

src/ForwardingAddress.sol

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.28;
33

4+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
46
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
57
import {Initializable} from "solady/utils/Initializable.sol";
6-
import {IERC20} from "forge-std/interfaces/IERC20.sol";
78

89
contract ForwardingAddress is ReentrancyGuardTransient, Initializable {
10+
using SafeERC20 for IERC20;
11+
912
error FailedETHWithdraw(address receiver, address token);
1013

1114
address payable public receiver;
@@ -25,7 +28,7 @@ contract ForwardingAddress is ReentrancyGuardTransient, Initializable {
2528
(bool success,) = receiver.call{value: address(this).balance}("");
2629
require(success, FailedETHWithdraw(receiver, token));
2730
} else {
28-
IERC20(token).transfer(receiver, IERC20(token).balanceOf(address(this)));
31+
IERC20(token).safeTransfer(receiver, IERC20(token).balanceOf(address(this)));
2932
}
3033
}
3134
}

test/ForwardingAddressFactory.t.sol

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,45 @@
22
pragma solidity ^0.8.28;
33

44
import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
5-
import {IERC20} from "forge-std/interfaces/IERC20.sol";
5+
import {ERC20NoReturnMock} from "@openzeppelin/contracts/mocks/token/ERC20NoReturnMock.sol";
6+
import {ERC20ReturnFalseMock} from "@openzeppelin/contracts/mocks/token/ERC20ReturnFalseMock.sol";
7+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
8+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
9+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
610
import {Test} from "forge-std/Test.sol";
711

812
import {ForwardingAddress} from "../src/ForwardingAddress.sol";
913
import {ForwardingAddressFactory} from "../src/ForwardingAddressFactory.sol";
1014

1115
contract NonPayableReceiver {}
1216

17+
contract ERC20NoReturn is ERC20NoReturnMock {
18+
constructor() ERC20("ERC20Mock", "E20M") {}
19+
20+
function mint(address account, uint256 amount) external {
21+
_mint(account, amount);
22+
}
23+
}
24+
25+
contract ERC20ReturnFalse is ERC20ReturnFalseMock {
26+
constructor() ERC20("ERC20Mock", "E20M") {}
27+
28+
function mint(address account, uint256 amount) external {
29+
_mint(account, amount);
30+
}
31+
}
32+
1333
contract ForwardingAddressFactoryTest is Test {
1434
ForwardingAddressFactory public factory;
1535
ERC20Mock public erc20Mock;
36+
ERC20NoReturn public erc20NoReturn;
37+
ERC20ReturnFalse public erc20ReturnFalse;
1638

1739
function setUp() public {
1840
factory = new ForwardingAddressFactory();
1941
erc20Mock = new ERC20Mock();
42+
erc20NoReturn = new ERC20NoReturn();
43+
erc20ReturnFalse = new ERC20ReturnFalse();
2044
}
2145

2246
function testFuzz_createForwardingAddress(bytes32 salt) public {
@@ -108,6 +132,37 @@ contract ForwardingAddressFactoryTest is Test {
108132
assertEq(IERC20(address(erc20Mock)).balanceOf(forwarder), 0);
109133
}
110134

135+
function testFuzz_sweepForERC20NoReturn(bytes32 salt, uint256 amount) public {
136+
(address receiver,) = makeAddrAndKey("receiver");
137+
138+
address forwarder = factory.getAddress(receiver, salt);
139+
erc20NoReturn.mint(forwarder, amount);
140+
assertEq(IERC20(address(erc20NoReturn)).balanceOf(receiver), 0);
141+
assertEq(IERC20(address(erc20NoReturn)).balanceOf(forwarder), amount);
142+
143+
address[] memory tokens = new address[](1);
144+
tokens[0] = address(erc20NoReturn);
145+
factory.sweepFor(payable(receiver), salt, tokens);
146+
assertEq(IERC20(address(erc20NoReturn)).balanceOf(receiver), amount);
147+
assertEq(IERC20(address(erc20NoReturn)).balanceOf(forwarder), 0);
148+
}
149+
150+
function testFuzz_sweepForERC20ReturnFalse(bytes32 salt, uint256 amount) public {
151+
(address receiver,) = makeAddrAndKey("receiver");
152+
153+
address forwarder = factory.getAddress(receiver, salt);
154+
erc20ReturnFalse.mint(forwarder, amount);
155+
assertEq(IERC20(address(erc20ReturnFalse)).balanceOf(receiver), 0);
156+
assertEq(IERC20(address(erc20ReturnFalse)).balanceOf(forwarder), amount);
157+
158+
address[] memory tokens = new address[](1);
159+
tokens[0] = address(erc20ReturnFalse);
160+
vm.expectRevert(abi.encodeWithSelector(SafeERC20.SafeERC20FailedOperation.selector, tokens[0]));
161+
factory.sweepFor(payable(receiver), salt, tokens);
162+
assertEq(IERC20(address(erc20ReturnFalse)).balanceOf(receiver), 0);
163+
assertEq(IERC20(address(erc20ReturnFalse)).balanceOf(forwarder), amount);
164+
}
165+
111166
function testFuzz_sweepForMulti(bytes32 salt, uint256 amount) public {
112167
(address receiver,) = makeAddrAndKey("receiver");
113168

0 commit comments

Comments
 (0)