Skip to content

Commit bc2822a

Browse files
authored
Merge pull request #112 from BootNodeDev/refund-wrong-chain
Check msg origin and settler when refunding
2 parents 5d4b3e1 + 648cf3a commit bc2822a

File tree

8 files changed

+393
-48
lines changed

8 files changed

+393
-48
lines changed

solidity/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
"openOrder": "forge script script/OpenOrder.s.sol:OpenOrder -f $NETWORK --broadcast --verify --slow -vvv",
7070
"run:openOrder": "dotenv-run-script openOrder",
7171
"enrollRouter": "forge script script/EnrollRouter.s.sol:EnrollRouter -f $NETWORK --broadcast --slow -vvv",
72-
"run:enrollRouter": "dotenv-run-script enrollRouter"
72+
"run:enrollRouter": "dotenv-run-script enrollRouter",
73+
"upgradeSimple": "forge script script/UpgradeSimple.s.sol:UpgradeSimple -f $NETWORK --broadcast --slow -vvv",
74+
"run:upgradeSimple": "dotenv-run-script upgradeSimple"
7375
}
7476
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.8.25 <0.9.0;
3+
4+
import { Script } from "forge-std/Script.sol";
5+
import { console2 } from "forge-std/console2.sol";
6+
7+
import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
8+
import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
9+
10+
import { Hyperlane7683 } from "../src/Hyperlane7683.sol";
11+
12+
/// @dev See the Solidity Scripting tutorial: https://book.getfoundry.sh/tutorials/solidity-scripting
13+
contract UpgradeSimple is Script {
14+
function run() public {
15+
uint256 proxyAdminOwner = vm.envUint("PROXY_ADMIN_OWNER_PK");
16+
17+
address routerProxy = vm.envAddress("ROUTER");
18+
address proxyAdmin = vm.envAddress("PROXY_ADMIN");
19+
20+
vm.startBroadcast(proxyAdminOwner);
21+
22+
address newRouterImpl = deployImplementation();
23+
24+
ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(routerProxy), newRouterImpl);
25+
26+
vm.stopBroadcast();
27+
28+
// solhint-disable-next-line no-console
29+
console2.log("New Implementation:", newRouterImpl);
30+
}
31+
32+
function deployImplementation() internal returns (address routerImpl) {
33+
address mailbox = vm.envAddress("MAILBOX");
34+
address permit2 = vm.envAddress("PERMIT2");
35+
36+
return address(new Hyperlane7683(mailbox, permit2));
37+
}
38+
}

solidity/src/Base7683.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
226226
* @param _orderId Unique order identifier for this order
227227
* @param _originData Data emitted on the origin to parameterize the fill
228228
* @param _fillerData Data provided by the filler to inform the fill or express their preferences. It should
229-
* contain the bytes32 encoded address of the receiver which is the used at settlement time
229+
* contain the bytes32 encoded address of the receiver which is used at settlement time
230230
*/
231231
function fill(bytes32 _orderId, bytes calldata _originData, bytes calldata _fillerData) external payable virtual {
232232
if (orderStatus[_orderId] != UNKNOWN) revert InvalidOrderStatus();
@@ -244,7 +244,7 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
244244
* @dev Pays the filler the amount locked when the orders were opened.
245245
* The settled status should not be changed here but rather on the origin chain. To allow the filler to retry in
246246
* case some error occurs.
247-
* Ensuring the order is not settled in the origin chain is the responsibility of the caller.
247+
* Ensuring the order is eligible for settling in the origin chain is the responsibility of the caller.
248248
* @param _orderIds An array of IDs for the orders to settle.
249249
*/
250250
function settle(bytes32[] calldata _orderIds) external payable {
@@ -267,7 +267,7 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
267267
* @notice Refunds a batch of expired GaslessCrossChainOrders on the chain where the orders were opened.
268268
* The refunded status should not be changed here but rather on the origin chain. To allow the user to retry in
269269
* case some error occurs.
270-
* Ensuring the order is not refunded in the origin chain is the responsibility of the caller.
270+
* Ensuring the order is eligible for refunding in the origin chain is the responsibility of the caller.
271271
* @param _orders An array of GaslessCrossChainOrders to refund.
272272
*/
273273
function refund(GaslessCrossChainOrder[] memory _orders) external payable {
@@ -289,7 +289,7 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
289289
* @notice Refunds a batch of expired OnchainCrossChainOrder on the chain where the orders were opened.
290290
* The refunded status should not be changed here but rather on the origin chain. To allow the user to retry in
291291
* case some error occurs.
292-
* Ensuring the order is not refunded in the origin chain is the responsibility of the caller.
292+
* Ensuring the order is eligible for refunding the origin chain is the responsibility of the caller.
293293
* @param _orders An array of GaslessCrossChainOrders to refund.
294294
*/
295295
function refund(OnchainCrossChainOrder[] memory _orders) external payable {

solidity/src/BasicSwap7683.sol

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ abstract contract BasicSwap7683 is Base7683 {
8585

8686
/**
8787
* @dev Settles multiple orders by dispatching the settlement instructions.
88+
* The proper status of all the orders (filled) is validated on the Base7683 before calling this function.
89+
* It assumes that all orders were originated in the same originDomain so it uses the the one from the first one for
90+
* dispatching the message, but if some order differs on the originDomain it can be re-settle later.
8891
* @param _orderIds The IDs of the orders to settle.
8992
* @param _ordersOriginData The original data of the orders.
9093
* @param _ordersFillerData The filler data for the orders.
@@ -98,85 +101,135 @@ abstract contract BasicSwap7683 is Base7683 {
98101
override
99102
{
100103
// at this point we are sure all orders are filled, use the first order to get the originDomain
101-
// if some order differs on the originDomain ir can be re-settle later
104+
// if some order differs on the originDomain it can be re-settle later
102105
_dispatchSettle(OrderEncoder.decode(_ordersOriginData[0]).originDomain, _orderIds, _ordersFillerData);
103106
}
104107

105108
/**
106109
* @dev Refunds multiple OnchainCrossChain orders by dispatching refund instructions.
110+
* The proper status of all the orders (NOT filled and expired) is validated on the Base7683 before calling this
111+
* function.
112+
* It assumes that all orders were originated in the same originDomain so it uses the the one from the first one for
113+
* dispatching the message, but if some order differs on the originDomain it can be re-refunded later.
107114
* @param _orders The orders to refund.
108115
* @param _orderIds The IDs of the orders to refund.
109116
*/
110117
function _refundOrders(OnchainCrossChainOrder[] memory _orders, bytes32[] memory _orderIds) internal override {
111-
// at this point we are sure all orders are filled, use the first order to get the originDomain
112-
// if some order differs on the originDomain ir can be re-refunded later
113118
_dispatchRefund(OrderEncoder.decode(_orders[0].orderData).originDomain, _orderIds);
114119
}
115120

116121
/**
117122
* @dev Refunds multiple GaslessCrossChain orders by dispatching refund instructions.
123+
* The proper status of all the orders (NOT filled and expired) is validated on the Base7683 before calling this
124+
* function.
125+
* It assumes that all orders were originated in the same originDomain so it uses the the one from the first one for
126+
* dispatching the message, but if some order differs on the originDomain it can be re-refunded later.
118127
* @param _orders The orders to refund.
119128
* @param _orderIds The IDs of the orders to refund.
120129
*/
121130
function _refundOrders(GaslessCrossChainOrder[] memory _orders, bytes32[] memory _orderIds) internal override {
122-
// at this point we are sure all orders are filled, use the first order to get the originDomain
123-
// if some order differs on the originDomain ir can be re-refunded later
124131
_dispatchRefund(OrderEncoder.decode(_orders[0].orderData).originDomain, _orderIds);
125132
}
126133

127134
/**
128135
* @dev Handles settling an individual order, should be called by the inheriting contract when receiving a setting
129136
* instruction from a remote chain.
137+
* @param _messageOrigin The domain from which the message originates.
138+
* @param _messageSender The address of the sender on the origin domain.
130139
* @param _orderId The ID of the order to settle.
131140
* @param _receiver The receiver address (encoded as bytes32).
132141
*/
133-
function _handleSettleOrder(bytes32 _orderId, bytes32 _receiver) internal virtual {
134-
// check if the order is opened to ensure it belongs to this domain, skip otherwise
135-
if (orderStatus[_orderId] != OPENED) return;
136-
137-
(,bytes memory _orderData) = abi.decode(openOrders[_orderId], (bytes32, bytes));
138-
OrderData memory orderData = OrderEncoder.decode(_orderData);
142+
function _handleSettleOrder(
143+
uint32 _messageOrigin,
144+
bytes32 _messageSender,
145+
bytes32 _orderId,
146+
bytes32 _receiver
147+
) internal virtual {
148+
(
149+
bool isEligible,
150+
OrderData memory orderData
151+
) = _checkOrderEligibility(_messageOrigin, _messageSender, _orderId);
152+
153+
if (!isEligible) return;
139154

140155
orderStatus[_orderId] = SETTLED;
141156

142157
address receiver = TypeCasts.bytes32ToAddress(_receiver);
143158
address inputToken = TypeCasts.bytes32ToAddress(orderData.inputToken);
144159

145-
if (inputToken == address(0)) {
146-
Address.sendValue(payable(receiver), orderData.amountIn);
147-
} else {
148-
IERC20(inputToken).safeTransfer(receiver, orderData.amountIn);
149-
}
160+
_transferTokenOut(inputToken, receiver, orderData.amountIn);
150161

151162
emit Settled(_orderId, receiver);
152163
}
153164

154165
/**
155166
* @dev Handles refunding an individual order, should be called by the inheriting contract when receiving a
156167
* refunding instruction from a remote chain.
168+
* @param _messageOrigin The domain from which the message originates.
169+
* @param _messageSender The address of the sender on the origin domain.
157170
* @param _orderId The ID of the order to refund.
158171
*/
159-
function _handleRefundOrder(bytes32 _orderId) internal virtual {
160-
// check if the order is opened to ensure it belongs to this domain, skip otherwise
161-
if (orderStatus[_orderId] != OPENED) return;
172+
function _handleRefundOrder(uint32 _messageOrigin, bytes32 _messageSender, bytes32 _orderId) internal virtual {
173+
(
174+
bool isEligible,
175+
OrderData memory orderData
176+
) = _checkOrderEligibility(_messageOrigin, _messageSender, _orderId);
162177

163-
(,bytes memory _orderData) = abi.decode(openOrders[_orderId], (bytes32, bytes));
164-
OrderData memory orderData = OrderEncoder.decode(_orderData);
178+
if (!isEligible) return;
165179

166180
orderStatus[_orderId] = REFUNDED;
167181

168182
address orderSender = TypeCasts.bytes32ToAddress(orderData.sender);
169183
address inputToken = TypeCasts.bytes32ToAddress(orderData.inputToken);
170184

171-
if (inputToken == address(0)) {
172-
Address.sendValue(payable(orderSender), orderData.amountIn);
173-
} else {
174-
IERC20(inputToken).safeTransfer(orderSender, orderData.amountIn);
175-
}
185+
_transferTokenOut(inputToken, orderSender, orderData.amountIn);
176186

177187
emit Refunded(_orderId, orderSender);
178188
}
179189

190+
/**
191+
* @notice Checks if order is eligible for settlement or refund .
192+
* @dev Order must be OPENED and the message was sent from the appropriated chain and contract.
193+
* @param _messageOrigin The origin domain of the message.
194+
* @param _messageSender The sender identifier of the message.
195+
* @param _orderId The unique identifier of the order.
196+
* @return A boolean indicating if the order is valid, and the decoded OrderData structure.
197+
*/
198+
function _checkOrderEligibility(
199+
uint32 _messageOrigin,
200+
bytes32 _messageSender,
201+
bytes32 _orderId
202+
) internal virtual returns (bool, OrderData memory) {
203+
OrderData memory orderData;
204+
205+
// check if the order is opened to ensure it belongs to this domain, skip otherwise
206+
if (orderStatus[_orderId] != OPENED) return (false, orderData);
207+
208+
(,bytes memory _orderData) = abi.decode(openOrders[_orderId], (bytes32, bytes));
209+
orderData = OrderEncoder.decode(_orderData);
210+
211+
if (orderData.destinationDomain != _messageOrigin || orderData.destinationSettler != _messageSender)
212+
return (false, orderData);
213+
214+
return (true, orderData);
215+
}
216+
217+
/**
218+
* @notice Transfers tokens or ETH out of the contract.
219+
* @dev If _token is the zero address, transfers ETH using a safe method; otherwise, performs an ERC20 token
220+
* transfer.
221+
* @param _token The address of the token to transfer (use address(0) for ETH).
222+
* @param _to The recipient address.
223+
* @param _amount The amount of tokens or ETH to transfer.
224+
*/
225+
function _transferTokenOut(address _token, address _to, uint256 _amount) internal {
226+
if (_token == address(0)) {
227+
Address.sendValue(payable(_to), _amount);
228+
} else {
229+
IERC20(_token).safeTransfer(_to, _amount);
230+
}
231+
}
232+
180233
/**
181234
* @dev Gets the ID of a GaslessCrossChainOrder.
182235
* @param _order The GaslessCrossChainOrder to compute the ID for.

solidity/src/Hyperlane7683.sol

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,19 @@ contract Hyperlane7683 is GasRouter, BasicSwap7683 {
8484
/**
8585
* @notice Handles incoming messages.
8686
* @dev Decodes the message and processes settlement or refund operations accordingly.
87-
* _originDomain The domain from which the message originates (unused in this implementation).
88-
* _sender The address of the sender on the origin domain (unused in this implementation).
87+
* @param _messageOrigin The domain from which the message originates (unused in this implementation).
88+
* @param _messageSender The address of the sender on the origin domain (unused in this implementation).
8989
* @param _message The encoded message received via Hyperlane.
9090
*/
91-
function _handle(uint32, bytes32, bytes calldata _message) internal virtual override {
91+
function _handle(uint32 _messageOrigin, bytes32 _messageSender, bytes calldata _message) internal virtual override {
9292
(bool _settle, bytes32[] memory _orderIds, bytes[] memory _ordersFillerData) =
9393
Hyperlane7683Message.decode(_message);
9494

9595
for (uint256 i = 0; i < _orderIds.length; i++) {
9696
if (_settle) {
97-
_handleSettleOrder(_orderIds[i], abi.decode(_ordersFillerData[i], (bytes32)));
97+
_handleSettleOrder(_messageOrigin, _messageSender, _orderIds[i], abi.decode(_ordersFillerData[i], (bytes32)));
9898
} else {
99-
_handleRefundOrder(_orderIds[i]);
99+
_handleRefundOrder(_messageOrigin, _messageSender, _orderIds[i]);
100100
}
101101
}
102102
}

0 commit comments

Comments
 (0)