Skip to content

Commit c27f35d

Browse files
committed
use call, not transfer, to include Gnosis Safes
1 parent 37f386a commit c27f35d

File tree

6 files changed

+56
-28
lines changed

6 files changed

+56
-28
lines changed

.gas-snapshot

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
OrderOriginPermit2Test:test_fillPermit2() (gas: 225289)
2-
OrderOriginPermit2Test:test_fillPermit2_multi() (gas: 1019134)
3-
OrderOriginPermit2Test:test_initiatePermit2() (gas: 235752)
4-
OrderOriginPermit2Test:test_initiatePermit2_multi() (gas: 989274)
5-
OrdersTest:test_fill_ERC20() (gas: 70537)
6-
OrdersTest:test_fill_ETH() (gas: 68498)
7-
OrdersTest:test_fill_both() (gas: 166773)
8-
OrdersTest:test_fill_multiETH() (gas: 132119)
9-
OrdersTest:test_fill_underflowETH() (gas: 115403)
10-
OrdersTest:test_initiate_ERC20() (gas: 81636)
11-
OrdersTest:test_initiate_ETH() (gas: 45150)
12-
OrdersTest:test_initiate_both() (gas: 118911)
13-
OrdersTest:test_initiate_multiERC20() (gas: 722642)
14-
OrdersTest:test_initiate_multiETH() (gas: 75538)
15-
OrdersTest:test_orderExpired() (gas: 28106)
16-
OrdersTest:test_sweepERC20() (gas: 60491)
17-
OrdersTest:test_sweepETH() (gas: 82186)
18-
OrdersTest:test_underflowETH() (gas: 63690)
1+
GnosisSafeTest:test_gnosis_receive() (gas: 15927)
2+
OrderOriginPermit2Test:test_fillPermit2() (gas: 225755)
3+
OrderOriginPermit2Test:test_fillPermit2_multi() (gas: 1016800)
4+
OrderOriginPermit2Test:test_initiatePermit2() (gas: 236218)
5+
OrderOriginPermit2Test:test_initiatePermit2_multi() (gas: 989740)
6+
OrdersTest:test_fill_ERC20() (gas: 71003)
7+
OrdersTest:test_fill_ETH() (gas: 68991)
8+
OrdersTest:test_fill_both() (gas: 167266)
9+
OrdersTest:test_fill_multiETH() (gas: 132639)
10+
OrdersTest:test_fill_underflowETH() (gas: 115718)
11+
OrdersTest:test_initiate_ERC20() (gas: 82102)
12+
OrdersTest:test_initiate_ETH() (gas: 45616)
13+
OrdersTest:test_initiate_both() (gas: 119377)
14+
OrdersTest:test_initiate_multiERC20() (gas: 723108)
15+
OrdersTest:test_initiate_multiETH() (gas: 76004)
16+
OrdersTest:test_orderExpired() (gas: 28394)
17+
OrdersTest:test_sweepERC20() (gas: 60957)
18+
OrdersTest:test_sweepETH() (gas: 83142)
19+
OrdersTest:test_underflowETH() (gas: 63978)
1920
PassagePermit2Test:test_disallowedEnterPermit2() (gas: 699630)
2021
PassagePermit2Test:test_enterTokenPermit2() (gas: 145449)
2122
PassageTest:test_configureEnter() (gas: 125771)

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
@@ -4,9 +4,10 @@ pragma solidity ^0.8.24;
44
import {OrdersPermit2} from "./OrdersPermit2.sol";
55
import {IOrders} from "./IOrders.sol";
66
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
7+
import {ReentrancyGuardTransient} from "openzeppelin-contracts/contracts/utils/ReentrancyGuardTransient.sol";
78

89
/// @notice Contract capable of processing fulfillment of intent-based Orders.
9-
abstract contract OrderDestination is IOrders, OrdersPermit2 {
10+
abstract contract OrderDestination is IOrders, OrdersPermit2, ReentrancyGuardTransient {
1011
/// @notice Emitted when Order Outputs are sent to their recipients.
1112
/// @dev NOTE that here, Output.chainId denotes the *origin* chainId.
1213
event Filled(Output[] outputs);
@@ -16,7 +17,7 @@ abstract contract OrderDestination is IOrders, OrdersPermit2 {
1617
/// @dev NOTE that here, Output.chainId denotes the *origin* chainId.
1718
/// @param outputs - The Outputs to be transferred.
1819
/// @custom:emits Filled
19-
function fill(Output[] memory outputs) external payable {
20+
function fill(Output[] memory outputs) external payable nonReentrant {
2021
// transfer outputs
2122
_transferOutputs(outputs);
2223

@@ -34,7 +35,7 @@ abstract contract OrderDestination is IOrders, OrdersPermit2 {
3435
/// @param outputs - The Outputs to be transferred. signed over via permit2 witness.
3536
/// @param permit2 - the permit2 details, signer, and signature.
3637
/// @custom:emits Filled
37-
function fillPermit2(Output[] memory outputs, OrdersPermit2.Permit2Batch calldata permit2) external {
38+
function fillPermit2(Output[] memory outputs, OrdersPermit2.Permit2Batch calldata permit2) external nonReentrant {
3839
// transfer all tokens to the Output recipients via permit2 (includes check on nonce & deadline)
3940
_permitWitnessTransferFrom(
4041
outputWitness(outputs), _fillTransferDetails(outputs, permit2.permit.permitted), permit2
@@ -51,7 +52,8 @@ abstract contract OrderDestination is IOrders, OrdersPermit2 {
5152
if (outputs[i].token == address(0)) {
5253
// this line should underflow if there's an attempt to spend more ETH than is attached to the transaction
5354
value -= outputs[i].amount;
54-
payable(outputs[i].recipient).transfer(outputs[i].amount);
55+
(bool success,) = payable(outputs[i].recipient).call{value: outputs[i].amount, gas: 5000}("");
56+
if (!success) revert EthTransferFailed();
5557
} else {
5658
IERC20(outputs[i].token).transferFrom(msg.sender, outputs[i].recipient, outputs[i].amount);
5759
}

src/orders/OrderOrigin.sol

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ pragma solidity ^0.8.24;
44
import {OrdersPermit2} from "./OrdersPermit2.sol";
55
import {IOrders} from "./IOrders.sol";
66
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
7+
import {ReentrancyGuardTransient} from "openzeppelin-contracts/contracts/utils/ReentrancyGuardTransient.sol";
78

89
/// @notice Contract capable of registering initiation of intent-based Orders.
9-
abstract contract OrderOrigin is IOrders, OrdersPermit2 {
10+
abstract contract OrderOrigin is IOrders, OrdersPermit2, ReentrancyGuardTransient {
1011
/// @notice Thrown when an Order is submitted with a deadline that has passed.
1112
error OrderExpired();
1213

@@ -33,7 +34,7 @@ abstract contract OrderOrigin is IOrders, OrdersPermit2 {
3334
/// @param outputs - The token amounts that must be received on their target chain(s) in order for the Order to be executed.
3435
/// @custom:reverts OrderExpired if the deadline has passed.
3536
/// @custom:emits Order if the transaction mines.
36-
function initiate(uint256 deadline, Input[] memory inputs, Output[] memory outputs) external payable {
37+
function initiate(uint256 deadline, Input[] memory inputs, Output[] memory outputs) external payable nonReentrant {
3738
// check that the deadline hasn't passed
3839
if (block.timestamp > deadline) revert OrderExpired();
3940

@@ -56,7 +57,7 @@ abstract contract OrderOrigin is IOrders, OrdersPermit2 {
5657
address tokenRecipient,
5758
Output[] memory outputs,
5859
OrdersPermit2.Permit2Batch calldata permit2
59-
) external {
60+
) external nonReentrant {
6061
// transfer all tokens to the tokenRecipient via permit2 (includes check on nonce & deadline)
6162
_permitWitnessTransferFrom(
6263
outputWitness(outputs), _initiateTransferDetails(tokenRecipient, permit2.permit.permitted), permit2
@@ -74,12 +75,13 @@ abstract contract OrderOrigin is IOrders, OrdersPermit2 {
7475
/// @param token - The token to transfer.
7576
/// @custom:emits Sweep
7677
/// @custom:reverts OnlyBuilder if called by non-block builder
77-
function sweep(address recipient, address token) external {
78+
function sweep(address recipient, address token) external nonReentrant {
7879
// send ETH or tokens
7980
uint256 balance;
8081
if (token == address(0)) {
8182
balance = address(this).balance;
82-
payable(recipient).transfer(balance);
83+
(bool success,) = payable(recipient).call{value: balance}("");
84+
if (!success) revert EthTransferFailed();
8385
} else {
8486
balance = IERC20(token).balanceOf(address(this));
8587
IERC20(token).transfer(recipient, balance);

src/passage/Passage.sol

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

25+
/// @notice Thrown when a transfer of Ether fails.
26+
error EthTransferFailed();
27+
2528
/// @notice Emitted when Ether enters the rollup.
2629
/// @param rollupChainId - The chainId of the destination rollup.
2730
/// @param rollupRecipient - The recipient of Ether on the rollup.
@@ -125,7 +128,8 @@ contract Passage is PassagePermit2 {
125128
function withdraw(address token, address recipient, uint256 amount) external {
126129
if (msg.sender != tokenAdmin) revert OnlyTokenAdmin();
127130
if (token == address(0)) {
128-
payable(recipient).transfer(amount);
131+
(bool success,) = payable(recipient).call{value: amount, gas: 5000}("");
132+
if (!success) revert EthTransferFailed();
129133
} else {
130134
IERC20(token).transfer(recipient, amount);
131135
}

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)