Skip to content

Commit 2d7cf25

Browse files
committed
use call, not transfer, to include Gnosis Safes
1 parent b307211 commit 2d7cf25

File tree

6 files changed

+57
-29
lines changed

6 files changed

+57
-29
lines changed

.gas-snapshot

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
OrderOriginPermit2Test:test_fillPermit2() (gas: 225275)
2-
OrderOriginPermit2Test:test_fillPermit2_multi() (gas: 1019098)
3-
OrderOriginPermit2Test:test_initiatePermit2() (gas: 235756)
4-
OrderOriginPermit2Test:test_initiatePermit2_multi() (gas: 992082)
5-
OrdersTest:test_fill_ERC20() (gas: 71615)
6-
OrdersTest:test_fill_ETH() (gas: 68524)
7-
OrdersTest:test_fill_both() (gas: 167851)
8-
OrdersTest:test_fill_multiETH() (gas: 132145)
9-
OrdersTest:test_fill_underflowETH() (gas: 115425)
10-
OrdersTest:test_initiate_ERC20() (gas: 82688)
11-
OrdersTest:test_initiate_ETH() (gas: 45150)
12-
OrdersTest:test_initiate_both() (gas: 119963)
13-
OrdersTest:test_initiate_multiERC20() (gas: 724742)
14-
OrdersTest:test_initiate_multiETH() (gas: 75538)
15-
OrdersTest:test_orderExpired() (gas: 28106)
16-
OrdersTest:test_sweepERC20() (gas: 60713)
17-
OrdersTest:test_sweepETH() (gas: 82348)
18-
OrdersTest:test_underflowETH() (gas: 63690)
19-
PassagePermit2Test:test_disallowedEnterPermit2() (gas: 696817)
1+
GnosisSafeTest:test_gnosis_receive() (gas: 15927)
2+
OrderOriginPermit2Test:test_fillPermit2() (gas: 225741)
3+
OrderOriginPermit2Test:test_fillPermit2_multi() (gas: 1016764)
4+
OrderOriginPermit2Test:test_initiatePermit2() (gas: 236222)
5+
OrderOriginPermit2Test:test_initiatePermit2_multi() (gas: 992548)
6+
OrdersTest:test_fill_ERC20() (gas: 72081)
7+
OrdersTest:test_fill_ETH() (gas: 69017)
8+
OrdersTest:test_fill_both() (gas: 168344)
9+
OrdersTest:test_fill_multiETH() (gas: 132665)
10+
OrdersTest:test_fill_underflowETH() (gas: 115740)
11+
OrdersTest:test_initiate_ERC20() (gas: 83154)
12+
OrdersTest:test_initiate_ETH() (gas: 45616)
13+
OrdersTest:test_initiate_both() (gas: 120429)
14+
OrdersTest:test_initiate_multiERC20() (gas: 725208)
15+
OrdersTest:test_initiate_multiETH() (gas: 76004)
16+
OrdersTest:test_orderExpired() (gas: 28394)
17+
OrdersTest:test_sweepERC20() (gas: 61179)
18+
OrdersTest:test_sweepETH() (gas: 83304)
19+
OrdersTest:test_underflowETH() (gas: 63978)
20+
PassagePermit2Test:test_disallowedEnterPermit2() (gas: 699617)
2021
PassagePermit2Test:test_enterTokenPermit2() (gas: 145435)
2122
PassageTest:test_configureEnter() (gas: 128989)
2223
PassageTest:test_disallowedEnter() (gas: 57692)

src/orders/IOrders.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
pragma solidity ^0.8.24;
33

44
interface IOrders {
5+
/// @notice Thrown when a transfer of Ether fails.
6+
error EthTransferFailed();
7+
58
/// @notice Tokens sent by the swapper as inputs to the order
69
/// @dev From ERC-7683
710
struct Input {

src/orders/OrderDestination.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import {OrdersPermit2} from "./OrdersPermit2.sol";
55
import {IOrders} from "./IOrders.sol";
66
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
77
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
8+
import {ReentrancyGuardTransient} from "openzeppelin-contracts/contracts/utils/ReentrancyGuardTransient.sol";
89

910
/// @notice Contract capable of processing fulfillment of intent-based Orders.
10-
abstract contract OrderDestination is IOrders, OrdersPermit2 {
11+
abstract contract OrderDestination is IOrders, OrdersPermit2, ReentrancyGuardTransient {
1112
using SafeERC20 for IERC20;
1213

1314
/// @notice Emitted when Order Outputs are sent to their recipients.
@@ -19,7 +20,7 @@ abstract contract OrderDestination is IOrders, OrdersPermit2 {
1920
/// @dev NOTE that here, Output.chainId denotes the *origin* chainId.
2021
/// @param outputs - The Outputs to be transferred.
2122
/// @custom:emits Filled
22-
function fill(Output[] memory outputs) external payable {
23+
function fill(Output[] memory outputs) external payable nonReentrant {
2324
// transfer outputs
2425
_transferOutputs(outputs);
2526

@@ -37,7 +38,7 @@ abstract contract OrderDestination is IOrders, OrdersPermit2 {
3738
/// @param outputs - The Outputs to be transferred. signed over via permit2 witness.
3839
/// @param permit2 - the permit2 details, signer, and signature.
3940
/// @custom:emits Filled
40-
function fillPermit2(Output[] memory outputs, OrdersPermit2.Permit2Batch calldata permit2) external {
41+
function fillPermit2(Output[] memory outputs, OrdersPermit2.Permit2Batch calldata permit2) external nonReentrant {
4142
// transfer all tokens to the Output recipients via permit2 (includes check on nonce & deadline)
4243
_permitWitnessTransferFrom(
4344
outputWitness(outputs), _fillTransferDetails(outputs, permit2.permit.permitted), permit2
@@ -54,7 +55,8 @@ abstract contract OrderDestination is IOrders, OrdersPermit2 {
5455
if (outputs[i].token == address(0)) {
5556
// this line should underflow if there's an attempt to spend more ETH than is attached to the transaction
5657
value -= outputs[i].amount;
57-
payable(outputs[i].recipient).transfer(outputs[i].amount);
58+
(bool success,) = payable(outputs[i].recipient).call{value: outputs[i].amount, gas: 5000}("");
59+
if (!success) revert EthTransferFailed();
5860
} else {
5961
IERC20(outputs[i].token).safeTransferFrom(msg.sender, outputs[i].recipient, outputs[i].amount);
6062
}

src/orders/OrderOrigin.sol

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import {OrdersPermit2} from "./OrdersPermit2.sol";
55
import {IOrders} from "./IOrders.sol";
66
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
77
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
8+
import {ReentrancyGuardTransient} from "openzeppelin-contracts/contracts/utils/ReentrancyGuardTransient.sol";
89

910
/// @notice Contract capable of registering initiation of intent-based Orders.
10-
abstract contract OrderOrigin is IOrders, OrdersPermit2 {
11+
abstract contract OrderOrigin is IOrders, OrdersPermit2, ReentrancyGuardTransient {
1112
using SafeERC20 for IERC20;
1213

1314
/// @notice Thrown when an Order is submitted with a deadline that has passed.
@@ -36,7 +37,7 @@ abstract contract OrderOrigin is IOrders, OrdersPermit2 {
3637
/// @param outputs - The token amounts that must be received on their target chain(s) in order for the Order to be executed.
3738
/// @custom:reverts OrderExpired if the deadline has passed.
3839
/// @custom:emits Order if the transaction mines.
39-
function initiate(uint256 deadline, Input[] memory inputs, Output[] memory outputs) external payable {
40+
function initiate(uint256 deadline, Input[] memory inputs, Output[] memory outputs) external payable nonReentrant {
4041
// check that the deadline hasn't passed
4142
if (block.timestamp > deadline) revert OrderExpired();
4243

@@ -59,7 +60,7 @@ abstract contract OrderOrigin is IOrders, OrdersPermit2 {
5960
address tokenRecipient,
6061
Output[] memory outputs,
6162
OrdersPermit2.Permit2Batch calldata permit2
62-
) external {
63+
) external nonReentrant {
6364
// transfer all tokens to the tokenRecipient via permit2 (includes check on nonce & deadline)
6465
_permitWitnessTransferFrom(
6566
outputWitness(outputs), _initiateTransferDetails(tokenRecipient, permit2.permit.permitted), permit2
@@ -77,10 +78,11 @@ abstract contract OrderOrigin is IOrders, OrdersPermit2 {
7778
/// @param token - The token to transfer.
7879
/// @custom:emits Sweep
7980
/// @custom:reverts OnlyBuilder if called by non-block builder
80-
function sweep(address recipient, address token, uint256 amount) external {
81+
function sweep(address recipient, address token, uint256 amount) external nonReentrant {
8182
// send ETH or tokens
8283
if (token == address(0)) {
83-
payable(recipient).transfer(amount);
84+
(bool success,) = payable(recipient).call{value: amount}("");
85+
if (!success) revert EthTransferFailed();
8486
} else {
8587
IERC20(token).safeTransfer(recipient, amount);
8688
}

src/passage/Passage.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ contract Passage is PassagePermit2 {
2525
/// @notice Thrown when attempting to enter the rollup with an ERC20 token that is not currently allowed.
2626
error DisallowedEnter(address token);
2727

28+
/// @notice Thrown when a transfer of Ether fails.
29+
error EthTransferFailed();
30+
2831
/// @notice Emitted when Ether enters the rollup.
2932
/// @param rollupChainId - The chainId of the destination rollup.
3033
/// @param rollupRecipient - The recipient of Ether on the rollup.
@@ -128,7 +131,8 @@ contract Passage is PassagePermit2 {
128131
function withdraw(address token, address recipient, uint256 amount) external {
129132
if (msg.sender != tokenAdmin) revert OnlyTokenAdmin();
130133
if (token == address(0)) {
131-
payable(recipient).transfer(amount);
134+
(bool success,) = payable(recipient).call{value: amount, gas: 5000}("");
135+
if (!success) revert EthTransferFailed();
132136
} else {
133137
IERC20(token).safeTransfer(recipient, amount);
134138
}

test/Safe.t.sol

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.24;
3+
4+
// utils
5+
import {Test, console2} from "forge-std/Test.sol";
6+
7+
contract GnosisSafeTest is Test {
8+
function setUp() public {
9+
vm.createSelectFork("https://ethereum-rpc.publicnode.com");
10+
}
11+
12+
// NOTE: this test fails if 4000 gas is provided. seems 4100 is approx the minimum.
13+
function test_gnosis_receive() public {
14+
payable(address(0x7c68c42De679ffB0f16216154C996C354cF1161B)).call{value: 1 ether, gas: 4100}("");
15+
}
16+
}

0 commit comments

Comments
 (0)