Skip to content

Commit b280394

Browse files
committed
refactor: prevent ETH from being sent to the contract directly
1 parent 808909d commit b280394

2 files changed

Lines changed: 65 additions & 54 deletions

File tree

contracts/contracts/core/GasTankDepositor.sol

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ contract GasTankDepositor {
1010
address public immutable RPC_SERVICE;
1111
uint256 public immutable MAXIMUM_DEPOSIT;
1212

13-
event FundsRecovered(address indexed owner, uint256 indexed amount);
1413
event GasTankFunded(address indexed smartAccount, address indexed caller, uint256 indexed amount);
1514

16-
error FailedToRecoverFunds(address owner, uint256 amount);
1715
error FailedToFundGasTank(address rpcProvider, uint256 transferAmount);
1816
error RPCServiceNotSet(address provider);
1917
error NotRPCService(address caller);
@@ -40,22 +38,14 @@ contract GasTankDepositor {
4038
MAXIMUM_DEPOSIT = _maxDeposit;
4139
}
4240

43-
receive() external payable { /* ETH transfers allowed. */ }
41+
receive() external payable {
42+
revert Errors.InvalidReceive();
43+
}
4444

4545
fallback() external payable {
4646
revert Errors.InvalidFallback();
4747
}
4848

49-
/// @notice Recovers funds inadvertently sent to this contract directly.
50-
function recoverFunds() external onlyRPCService {
51-
uint256 balance = address(this).balance;
52-
53-
(bool success,) = RPC_SERVICE.call{value: balance}("");
54-
require(success, FailedToRecoverFunds(RPC_SERVICE, balance));
55-
56-
emit FundsRecovered(RPC_SERVICE, balance);
57-
}
58-
5949
/// @notice Transfers ETH from the EOA's balance to the Gas RPC Service.
6050
/// @param _amount The amount to fund the gas tank with.
6151
/// @dev Only the EOA can call this function.

contracts/test/core/GasTankDepositorTest.sol

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity 0.8.26;
33

44
import "forge-std/Test.sol";
55
import {GasTankDepositor} from "../../contracts/core/GasTankDepositor.sol";
6+
import {Errors} from "../../contracts/utils/Errors.sol";
67

78
contract GasTankDepositorTest is Test {
89
uint256 public constant ALICE_PK = uint256(0xA11CE);
@@ -27,6 +28,21 @@ contract GasTankDepositorTest is Test {
2728

2829
//=======================TESTS=======================
2930

31+
function testConstructorSetsValues() public view {
32+
assertEq(_gasTankDepositorImpl.RPC_SERVICE(), rpcService);
33+
assertEq(_gasTankDepositorImpl.MAXIMUM_DEPOSIT(), MAXIMUM_DEPOSIT);
34+
}
35+
36+
function testConstructorRevertsWhenRpcServiceIsZero() public {
37+
vm.expectRevert(abi.encodeWithSelector(GasTankDepositor.RPCServiceNotSet.selector, ZERO_ADDRESS));
38+
new GasTankDepositor(ZERO_ADDRESS, MAXIMUM_DEPOSIT);
39+
}
40+
41+
function testConstructorRevertsWhenMaxDepositIsZero() public {
42+
vm.expectRevert(abi.encodeWithSelector(GasTankDepositor.MaximumDepositNotMet.selector, 0, 0));
43+
new GasTankDepositor(rpcService, 0);
44+
}
45+
3046
function testSetsDelegationCodeAtAddress() public {
3147
// Initial code is empty
3248
assertEq(alice.code.length, 0);
@@ -50,62 +66,35 @@ contract GasTankDepositorTest is Test {
5066
assertEq(alice.code.length, 0);
5167
}
5268

53-
//=======================TESTS FOR IMPROPER CALLS TO THE GAS TANK MANAGER=======================
69+
//=======================TESTS FOR IMPROPER CALLS TO THE GAS TANK DEPOSITOR=======================
5470

5571
function testFallbackRevert() public {
56-
bytes memory badData =
57-
abi.encodeWithSelector(GasTankDepositor.recoverFunds.selector, address(0x55555), 1 ether, 1 ether, 1 ether);
72+
bytes memory badData = abi.encodeWithSelector(bytes4(0x12345678));
5873
vm.prank(alice);
59-
(bool success,) = address(_gasTankDepositorImpl).call{value: 1 ether}(badData);
74+
(bool success, bytes memory returnData) = address(_gasTankDepositorImpl).call{value: 1 ether}(badData);
6075
assertFalse(success);
76+
bytes4 errorSelector = bytes4(returnData);
77+
assertEq(errorSelector, Errors.InvalidFallback.selector);
6178
}
6279

63-
function testReceiveNoRevert() public {
64-
uint256 beforeBalance = alice.balance;
80+
function testReceiveRevert() public {
6581
vm.prank(bob);
66-
(bool success,) = address(alice).call{value: 1 ether}("");
67-
assertTrue(success);
68-
uint256 afterBalance = alice.balance;
69-
assertEq(afterBalance, beforeBalance + 1 ether, "balance not increased");
82+
(bool success, bytes memory returnData) = address(_gasTankDepositorImpl).call{value: 1 ether}("");
83+
assertFalse(success);
84+
bytes4 errorSelector = bytes4(returnData);
85+
assertEq(errorSelector, Errors.InvalidReceive.selector);
7086
}
7187

72-
function testFundsSentDirectlyToDelegateAddress() public {
73-
uint256 beforeBalance = address(_gasTankDepositorImpl).balance;
7488

89+
function testEOAReceiveNoRevert() public {
90+
uint256 beforeBalance = alice.balance;
7591
vm.prank(bob);
76-
(bool success,) = address(_gasTankDepositorImpl).call{value: 1 ether}("");
92+
(bool success,) = address(alice).call{value: 1 ether}("");
7793
assertTrue(success);
78-
79-
uint256 afterBalance = address(_gasTankDepositorImpl).balance;
94+
uint256 afterBalance = alice.balance;
8095
assertEq(afterBalance, beforeBalance + 1 ether, "balance not increased");
8196
}
8297

83-
function testWithdrawsFundsDirectlyFromDelegateAddress() public {
84-
uint256 gasTankBeforeBalance = address(_gasTankDepositorImpl).balance;
85-
uint256 depositAmount = 1 ether;
86-
87-
vm.prank(bob);
88-
(bool success,) = address(_gasTankDepositorImpl).call{value: depositAmount}("");
89-
assertTrue(success);
90-
91-
uint256 gasTankAfterBalance = address(_gasTankDepositorImpl).balance;
92-
assertEq(gasTankAfterBalance, gasTankBeforeBalance + depositAmount, "balance not increased");
93-
94-
vm.prank(rpcService);
95-
uint256 rpcServiceBeforeBalance = rpcService.balance;
96-
_gasTankDepositorImpl.recoverFunds();
97-
uint256 rpcServiceAfterBalance = rpcService.balance;
98-
99-
assertEq(address(_gasTankDepositorImpl).balance, 0, "funds not drained");
100-
assertEq(rpcServiceAfterBalance, rpcServiceBeforeBalance + depositAmount, "balance not recovered");
101-
}
102-
103-
function testRevertsWhenRecoverFundsIsCalledByUnknownCaller() public {
104-
vm.prank(bob);
105-
vm.expectRevert(abi.encodeWithSelector(GasTankDepositor.NotRPCService.selector, bob));
106-
_gasTankDepositorImpl.recoverFunds();
107-
}
108-
10998
//=======================TESTS FOR FUNDING THE GAS TANK=======================
11099

111100
function testRpcServiceFundsMaximumDeposit() public {
@@ -171,6 +160,38 @@ contract GasTankDepositorTest is Test {
171160
GasTankDepositor(payable(alice)).fundGasTank(MAXIMUM_DEPOSIT);
172161
}
173162

163+
function testEOAFundsWithZeroAmount() public {
164+
_delegate();
165+
166+
uint256 rpcBalanceBefore = rpcService.balance;
167+
_expectGasTankFunded(alice, 0);
168+
169+
vm.prank(alice);
170+
GasTankDepositor(payable(alice)).fundGasTank(0);
171+
172+
assertEq(rpcService.balance, rpcBalanceBefore);
173+
}
174+
175+
function testMultipleEOAFundings() public {
176+
_delegate();
177+
178+
uint256 amount1 = 0.5 ether;
179+
uint256 amount2 = 0.3 ether;
180+
uint256 rpcBalanceBefore = rpcService.balance;
181+
182+
// First funding
183+
_expectGasTankFunded(alice, amount1);
184+
vm.prank(alice);
185+
GasTankDepositor(payable(alice)).fundGasTank(amount1);
186+
187+
// Second funding
188+
_expectGasTankFunded(alice, amount2);
189+
vm.prank(alice);
190+
GasTankDepositor(payable(alice)).fundGasTank(amount2);
191+
192+
assertEq(rpcService.balance, rpcBalanceBefore + amount1 + amount2);
193+
}
194+
174195
//=======================HELPERS=======================
175196

176197
function _delegate() internal {

0 commit comments

Comments
 (0)