Skip to content

Commit 09617bb

Browse files
committed
handle intents for native token
1 parent ec24717 commit 09617bb

File tree

3 files changed

+219
-16
lines changed

3 files changed

+219
-16
lines changed

solidity/src/Base7683.sol

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

44
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
55
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6+
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
67
import { TypeCasts } from "@hyperlane-xyz/libs/TypeCasts.sol";
78
import { IPermit2, ISignatureTransfer } from "@uniswap/permit2/src/interfaces/IPermit2.sol";
89

@@ -76,6 +77,7 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
7677
error OrderFillNotExpired();
7778
error InvalidDomain();
7879
error InvalidSender();
80+
error InvalidAmount();
7981

8082
// ============ Constructor ============
8183

@@ -120,7 +122,7 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
120122
/// @dev To be called by the user
121123
/// @dev This method must emit the Open event
122124
/// @param order The OnchainCrossChainOrder definition
123-
function open(OnchainCrossChainOrder calldata order) external {
125+
function open(OnchainCrossChainOrder calldata order) external payable {
124126
(ResolvedCrossChainOrder memory resolvedOrder, OrderData memory orderData) = _resolvedOrder(
125127
order.orderDataType,
126128
msg.sender,
@@ -135,9 +137,13 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
135137
orderStatus[orderId] = OrderStatus.OPENED;
136138
senderNonce[msg.sender] += 1;
137139

138-
IERC20(TypeCasts.bytes32ToAddress(orderData.inputToken)).safeTransferFrom(
139-
msg.sender, address(this), orderData.amountIn
140-
);
140+
if (orderData.inputToken != TypeCasts.addressToBytes32(address(0))) {
141+
IERC20(TypeCasts.bytes32ToAddress(orderData.inputToken)).safeTransferFrom(
142+
msg.sender, address(this), orderData.amountIn
143+
);
144+
} else {
145+
if (msg.value != orderData.amountIn) revert InvalidAmount();
146+
}
141147

142148
emit Open(orderId, resolvedOrder);
143149
}
@@ -182,7 +188,7 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
182188
/// @param _originData Data emitted on the origin to parameterize the fill
183189
/// @param _fillerData Data provided by the filler to inform the fill or express their preferences. It should
184190
/// contain the bytes32 encoded address of the receiver which is the used at settlement time
185-
function fill(bytes32 _orderId, bytes calldata _originData, bytes calldata _fillerData) external virtual {
191+
function fill(bytes32 _orderId, bytes calldata _originData, bytes calldata _fillerData) external payable virtual {
186192
OrderData memory orderData = OrderEncoder.decode(_originData);
187193

188194
if (_orderId != _getOrderId(orderData)) revert InvalidOrderId();
@@ -197,9 +203,14 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
197203

198204
emit Filled(_orderId, _originData, _fillerData);
199205

200-
IERC20(TypeCasts.bytes32ToAddress(orderData.outputToken)).safeTransferFrom(
201-
msg.sender, TypeCasts.bytes32ToAddress(orderData.recipient), orderData.amountOut
202-
);
206+
if (orderData.outputToken != TypeCasts.addressToBytes32(address(0))) {
207+
IERC20(TypeCasts.bytes32ToAddress(orderData.outputToken)).safeTransferFrom(
208+
msg.sender, TypeCasts.bytes32ToAddress(orderData.recipient), orderData.amountOut
209+
);
210+
} else {
211+
if (msg.value != orderData.amountOut) revert InvalidAmount();
212+
Address.sendValue(payable(TypeCasts.bytes32ToAddress(orderData.recipient)), orderData.amountOut);
213+
}
203214
}
204215

205216
function settle(bytes32[] calldata _orderIds) external payable {
@@ -365,9 +376,13 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
365376

366377
emit Settled(_orderId, receiver);
367378

368-
IERC20(TypeCasts.bytes32ToAddress(orderData.inputToken)).safeTransfer(
369-
receiver, orderData.amountIn
370-
);
379+
if (orderData.inputToken != TypeCasts.addressToBytes32(address(0))) {
380+
IERC20(TypeCasts.bytes32ToAddress(orderData.inputToken)).safeTransfer(
381+
receiver, orderData.amountIn
382+
);
383+
} else {
384+
Address.sendValue(payable(receiver), orderData.amountIn);
385+
}
371386
}
372387

373388
/**
@@ -386,9 +401,13 @@ abstract contract Base7683 is IOriginSettler, IDestinationSettler {
386401

387402
emit Refunded(_orderId, orderSender);
388403

389-
IERC20(TypeCasts.bytes32ToAddress(orderData.inputToken)).safeTransfer(
390-
orderSender, orderData.amountIn
391-
);
404+
if (orderData.inputToken != TypeCasts.addressToBytes32(address(0))) {
405+
IERC20(TypeCasts.bytes32ToAddress(orderData.inputToken)).safeTransfer(
406+
orderSender, orderData.amountIn
407+
);
408+
} else {
409+
Address.sendValue(payable(orderSender), orderData.amountIn);
410+
}
392411
}
393412

394413
/**

solidity/src/ERC7683/IERC7683.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ interface IOriginSettler {
121121
/// @dev To be called by the user
122122
/// @dev This method must emit the Open event
123123
/// @param order The OnchainCrossChainOrder definition
124-
function open(OnchainCrossChainOrder calldata order) external;
124+
function open(OnchainCrossChainOrder calldata order) external payable;
125125

126126
/// @notice Resolves a specific GaslessCrossChainOrder into a generic ResolvedCrossChainOrder
127127
/// @dev Intended to improve standardized integration of various order types and settlement contracts
@@ -150,5 +150,5 @@ interface IDestinationSettler {
150150
/// @param orderId Unique order identifier for this order
151151
/// @param originData Data emitted on the origin to parameterize the fill
152152
/// @param fillerData Data provided by the filler to inform the fill or express their preferences
153-
function fill(bytes32 orderId, bytes calldata originData, bytes calldata fillerData) external;
153+
function fill(bytes32 orderId, bytes calldata originData, bytes calldata fillerData) external payable;
154154
}

solidity/test/Base7683.t.sol

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,17 @@ contract Base7683Test is Test, DeployPermit2 {
255255
return _balances;
256256
}
257257

258+
function balances() internal view returns (uint256[] memory) {
259+
uint256[] memory _balances = new uint256[](5);
260+
_balances[0] = kakaroto.balance;
261+
_balances[1] = karpincho.balance;
262+
_balances[2] = vegeta.balance;
263+
_balances[3] = counterpart.balance;
264+
_balances[4] = address(base).balance;
265+
266+
return _balances;
267+
}
268+
258269
function orderDataById(bytes32 orderId) internal view returns (OrderData memory orderData) {
259270
(
260271
bytes32 _sender,
@@ -348,6 +359,70 @@ contract Base7683Test is Test, DeployPermit2 {
348359
vm.stopPrank();
349360
}
350361

362+
function test_open__native_token_works(uint32 _fillDeadline) public {
363+
OrderData memory orderData = prepareOrderData();
364+
orderData.inputToken = TypeCasts.addressToBytes32(address(0));
365+
orderData.outputToken = TypeCasts.addressToBytes32(address(0));
366+
367+
OnchainCrossChainOrder memory order =
368+
prepareOnchainOrder(orderData, _fillDeadline, OrderEncoder.orderDataType());
369+
370+
deal(kakaroto, 1_000_000);
371+
372+
vm.startPrank(kakaroto);
373+
374+
uint256 nonceBefore = base.senderNonce(kakaroto);
375+
uint256[] memory balancesBefore = balances();
376+
377+
vm.recordLogs();
378+
base.open{value: amount}(order);
379+
380+
(bytes32 orderId, ResolvedCrossChainOrder memory resolvedOrder) = getOrderIDFromLogs();
381+
382+
// assertResolvedOrder(resolvedOrder, orderData, kakaroto, _fillDeadline, type(uint32).max);
383+
assertEq(resolvedOrder.maxSpent.length, 1);
384+
assertEq(resolvedOrder.maxSpent[0].token, TypeCasts.addressToBytes32(address(0)));
385+
assertEq(resolvedOrder.maxSpent[0].amount, amount);
386+
assertEq(resolvedOrder.maxSpent[0].recipient, base.counterpart());
387+
assertEq(resolvedOrder.maxSpent[0].chainId, destination);
388+
389+
assertEq(resolvedOrder.minReceived.length, 1);
390+
assertEq(resolvedOrder.minReceived[0].token, TypeCasts.addressToBytes32(address(0)));
391+
assertEq(resolvedOrder.minReceived[0].amount, amount);
392+
assertEq(resolvedOrder.minReceived[0].recipient, bytes32(0));
393+
assertEq(resolvedOrder.minReceived[0].chainId, origin);
394+
395+
assertEq(resolvedOrder.fillInstructions.length, 1);
396+
assertEq(resolvedOrder.fillInstructions[0].destinationChainId, destination);
397+
assertEq(resolvedOrder.fillInstructions[0].destinationSettler, base.counterpart());
398+
399+
orderData.fillDeadline = _fillDeadline;
400+
assertEq(resolvedOrder.fillInstructions[0].originData, OrderEncoder.encode(orderData));
401+
402+
assertEq(resolvedOrder.user, kakaroto);
403+
assertEq(resolvedOrder.originChainId, base.localDomain());
404+
assertEq(resolvedOrder.openDeadline, type(uint32).max);
405+
assertEq(resolvedOrder.fillDeadline, _fillDeadline);
406+
407+
// assertOpenOrder(orderId, kakaroto, orderData, balancesBefore, kakaroto, nonceBefore);
408+
OrderData memory savedOrderData = orderDataById(orderId);
409+
410+
assertEq(base.senderNonce(kakaroto), nonceBefore + 1);
411+
assertEq(OrderEncoder.encode(savedOrderData), OrderEncoder.encode(orderData));
412+
// assertOrder(orderId, orderData, balancesBefore, inputToken, user, address(base), Base7683.OrderStatus.OPENED);
413+
414+
Base7683.OrderStatus status = base.orderStatus(orderId);
415+
416+
assertEq(OrderEncoder.encode(savedOrderData), OrderEncoder.encode(orderData));
417+
assertTrue(status == Base7683.OrderStatus.OPENED);
418+
419+
uint256[] memory balancesAfter = balances();
420+
assertEq(balancesBefore[balanceId[kakaroto]] - amount, balancesAfter[balanceId[kakaroto]]);
421+
assertEq(balancesBefore[balanceId[address(base)]] + amount, balancesAfter[balanceId[address(base)]]);
422+
423+
vm.stopPrank();
424+
}
425+
351426
function getPermitWitnessTransferSignature(
352427
ISignatureTransfer.PermitTransferFrom memory permit,
353428
uint256 privateKey,
@@ -475,6 +550,45 @@ contract Base7683Test is Test, DeployPermit2 {
475550
vm.stopPrank();
476551
}
477552

553+
function test_fill_native_works() public {
554+
OrderData memory orderData = prepareOrderData();
555+
orderData.inputToken = TypeCasts.addressToBytes32(address(0));
556+
orderData.outputToken = TypeCasts.addressToBytes32(address(0));
557+
558+
orderData.originDomain = destination;
559+
orderData.destinationDomain = origin;
560+
561+
bytes32 orderId = OrderEncoder.id(orderData);
562+
563+
deal(vegeta, 1_000_000);
564+
565+
uint256[] memory balancesBefore = balances();
566+
567+
vm.startPrank(vegeta);
568+
569+
bytes memory fillerData = abi.encode(TypeCasts.addressToBytes32(vegeta));
570+
571+
vm.expectEmit(false, false, false, true);
572+
emit Filled(orderId, OrderEncoder.encode(orderData), fillerData);
573+
574+
base.fill{value: amount}(orderId, OrderEncoder.encode(orderData), fillerData);
575+
576+
// assertOrder(orderId, orderData, balancesBefore, outputToken, vegeta, karpincho, Base7683.OrderStatus.FILLED);
577+
OrderData memory savedOrderData = orderDataById(orderId);
578+
Base7683.OrderStatus status = base.orderStatus(orderId);
579+
580+
assertEq(OrderEncoder.encode(savedOrderData), OrderEncoder.encode(orderData));
581+
assertTrue(status == Base7683.OrderStatus.FILLED);
582+
583+
uint256[] memory balancesAfter = balances();
584+
assertEq(balancesBefore[balanceId[vegeta]] - amount, balancesAfter[balanceId[vegeta]]);
585+
assertEq(balancesBefore[balanceId[karpincho]] + amount, balancesAfter[balanceId[karpincho]]);
586+
587+
assertEq(base.orderFillerData(orderId), fillerData);
588+
589+
vm.stopPrank();
590+
}
591+
478592
// settle
479593
function test_settle_works() public {
480594
OrderData memory orderData = prepareOrderData();
@@ -562,6 +676,40 @@ contract Base7683Test is Test, DeployPermit2 {
562676
assertEq(balancesBefore[balanceId[vegeta]] + amount, balancesAfter[balanceId[vegeta]]);
563677
}
564678

679+
function test_settleOrder_native_works() public {
680+
OrderData memory orderData = prepareOrderData();
681+
orderData.inputToken = TypeCasts.addressToBytes32(address(0));
682+
orderData.outputToken = TypeCasts.addressToBytes32(address(0));
683+
684+
OnchainCrossChainOrder memory order =
685+
prepareOnchainOrder(orderData, orderData.fillDeadline, OrderEncoder.orderDataType());
686+
687+
deal(kakaroto, 1_000_000);
688+
689+
vm.startPrank(kakaroto);
690+
691+
vm.recordLogs();
692+
base.open{value: amount}(order);
693+
694+
(bytes32 orderId,) = getOrderIDFromLogs();
695+
696+
vm.stopPrank();
697+
698+
uint256[] memory balancesBefore = balances();
699+
700+
vm.expectEmit(false, false, false, true);
701+
emit Settled(orderId, vegeta);
702+
703+
vm.prank(vegeta);
704+
base.settleOrder(orderId);
705+
706+
uint256[] memory balancesAfter = balances();
707+
708+
assertTrue(base.orderStatus(orderId) == Base7683.OrderStatus.SETTLED);
709+
assertEq(balancesBefore[balanceId[address(base)]] - amount, balancesAfter[balanceId[address(base)]]);
710+
assertEq(balancesBefore[balanceId[vegeta]] + amount, balancesAfter[balanceId[vegeta]]);
711+
}
712+
565713
// _refundOrder
566714
function test_refundOrder_works() public {
567715
OrderData memory orderData = prepareOrderData();
@@ -594,4 +742,40 @@ contract Base7683Test is Test, DeployPermit2 {
594742
assertEq(balancesBefore[balanceId[address(base)]] - amount, balancesAfter[balanceId[address(base)]]);
595743
assertEq(balancesBefore[balanceId[kakaroto]] + amount, balancesAfter[balanceId[kakaroto]]);
596744
}
745+
746+
function test_refundOrder_native_works() public {
747+
OrderData memory orderData = prepareOrderData();
748+
orderData.inputToken = TypeCasts.addressToBytes32(address(0));
749+
orderData.outputToken = TypeCasts.addressToBytes32(address(0));
750+
751+
OnchainCrossChainOrder memory order =
752+
prepareOnchainOrder(orderData, orderData.fillDeadline, OrderEncoder.orderDataType());
753+
754+
deal(kakaroto, 1_000_000);
755+
756+
vm.startPrank(kakaroto);
757+
758+
vm.recordLogs();
759+
base.open{value: amount}(order);
760+
761+
(bytes32 orderId,) = getOrderIDFromLogs();
762+
763+
vm.stopPrank();
764+
765+
vm.warp(orderData.fillDeadline + 1);
766+
767+
uint256[] memory balancesBefore = balances();
768+
769+
vm.expectEmit(false, false, false, true);
770+
emit Refunded(orderId, kakaroto);
771+
772+
vm.prank(vegeta);
773+
base.refundOrder(orderId);
774+
775+
uint256[] memory balancesAfter = balances();
776+
777+
assertTrue(base.orderStatus(orderId) == Base7683.OrderStatus.REFUNDED);
778+
assertEq(balancesBefore[balanceId[address(base)]] - amount, balancesAfter[balanceId[address(base)]]);
779+
assertEq(balancesBefore[balanceId[kakaroto]] + amount, balancesAfter[balanceId[kakaroto]]);
780+
}
597781
}

0 commit comments

Comments
 (0)