Skip to content

Commit 058bb26

Browse files
committed
feat: add and happy case unit tests
1 parent 662e6ac commit 058bb26

File tree

3 files changed

+108
-0
lines changed

3 files changed

+108
-0
lines changed

foundry.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ ffi = true
77
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
88

99
remappings = [
10+
'contract-libs/=src/',
1011
'forge-std/=lib/forge-std/src/',
1112
'ds-test/=lib/forge-std/lib/ds-test/src/',
1213
'@openzeppelin/=lib/openzeppelin-contracts/',

src/transfers/LibNativeTransfer.sol

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
5+
import { LibErrorHandler } from "../LibErrorHandler.sol";
6+
7+
using { toAmount } for Gas global;
8+
9+
enum Gas {
10+
Strictly,
11+
NoGriefing,
12+
ForwardAll,
13+
NoStorageWrite
14+
}
15+
16+
/// @dev see: https://github.com/axieinfinity/ronin-dpos-contracts/pull/195
17+
uint256 constant GAS_STIPEND_STRICT = 0;
18+
19+
/// @dev Suggested gas stipend for contract receiving NATIVE
20+
/// that disallows any storage writes.
21+
uint256 constant GAS_STIPEND_NO_STORAGE_WRITES = 2_300;
22+
23+
/// @dev Suggested gas stipend for contract receiving NATIVE to perform a few
24+
/// storage reads and writes, but low enough to prevent griefing.
25+
/// Multiply by a small constant (e.g. 2), if needed.
26+
uint256 constant GAS_STIPEND_NO_GRIEF = 100_000;
27+
28+
function toAmount(Gas gas) view returns (uint256) {
29+
if (gas == Gas.ForwardAll) return gasleft();
30+
if (gas == Gas.NoGriefing) return GAS_STIPEND_NO_GRIEF;
31+
if (gas == Gas.NoStorageWrite) return GAS_STIPEND_NO_STORAGE_WRITES;
32+
return GAS_STIPEND_STRICT;
33+
}
34+
35+
/**
36+
* @title NativeTransferHelper
37+
*/
38+
library LibNativeTransfer {
39+
using Strings for *;
40+
using LibErrorHandler for bool;
41+
42+
/**
43+
* @dev Transfers Native Coin and wraps result for the method caller to a recipient.
44+
*/
45+
function safeTransfer(address to, uint256 value, Gas gas) internal {
46+
(bool success, bytes memory returnOrRevertData) = trySendValue(to, value, gas.toAmount());
47+
success.handleRevert(bytes4(0x0), returnOrRevertData);
48+
}
49+
50+
/**
51+
* @dev Unsafe send `amount` Native to the address `to`. If the sender's balance is insufficient,
52+
* the call does not revert.
53+
*
54+
* Note:
55+
* - Does not assert whether the balance of sender is sufficient.
56+
* - Does not assert whether the recipient accepts NATIVE.
57+
* - Consider using `ReentrancyGuard` before calling this function.
58+
*
59+
*/
60+
function trySendValue(address to, uint256 value, uint256 gasAmount)
61+
internal
62+
returns (bool success, bytes memory returnOrRevertData)
63+
{
64+
(success, returnOrRevertData) = to.call{ value: value, gas: gasAmount }("");
65+
}
66+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
import { Test } from "forge-std/Test.sol";
5+
import { Gas, LibNativeTransfer } from "contract-libs/transfers/LibNativeTransfer.sol";
6+
7+
contract LibNativeTransferTest is Test {
8+
function testFork_RevertWhen_TransferNativeToContractWithoutFallback_safeTransfer(
9+
address any,
10+
uint256 amount,
11+
uint8 v
12+
) external {
13+
vm.deal(any, amount);
14+
vm.expectRevert();
15+
vm.prank(any);
16+
LibNativeTransfer.safeTransfer(address(this), amount, _toGas(v));
17+
}
18+
19+
function testConcrete_TransferNative(uint8 v) external {
20+
LibNativeTransfer.safeTransfer(address(0xBEEF), 1e18, _toGas(v));
21+
assertEq(address(0xBEEF).balance, 1e18);
22+
}
23+
24+
function testFork_TransferNativeToRecipient(address recipient, uint256 amount, uint8 v) external {
25+
// Transferring to msg.sender can fail because it's possible to overflow their ETH balance as it begins non-zero.
26+
if (recipient.code.length > 0 || uint256(uint160(recipient)) <= 18 || recipient == msg.sender) return;
27+
28+
amount = bound(amount, 0, address(this).balance);
29+
LibNativeTransfer.safeTransfer(recipient, amount, _toGas(v));
30+
31+
assertEq(recipient.balance, amount);
32+
}
33+
34+
function _toGas(uint8 v) internal view returns (Gas gas) {
35+
v = uint8(bound(v, 0, 3));
36+
if (v == uint8(Gas.Strictly)) gas = Gas.Strictly;
37+
if (v == uint8(Gas.NoGriefing)) gas = Gas.NoGriefing;
38+
if (v == uint8(Gas.ForwardAll)) gas = Gas.ForwardAll;
39+
if (v == uint8(Gas.NoStorageWrite)) gas = Gas.NoStorageWrite;
40+
}
41+
}

0 commit comments

Comments
 (0)