From 37c7dc96390e947ae0b01b4f7162423e6db00957 Mon Sep 17 00:00:00 2001
From: nicholaspai <npai.nyc@gmail.com>
Date: Tue, 3 Dec 2024 11:38:25 -0500
Subject: [PATCH] add permit2 lib

Signed-off-by: nicholaspai <npai.nyc@gmail.com>
---
 README.md                  |  4 +--
 script/Counter.s.sol       | 19 ----------
 src/Counter.sol            | 14 --------
 src/DestinationSettler.sol |  8 ++---
 src/ERC7683Permit2Lib.sol  | 72 ++++++++++++++++++++++++++++++++++++++
 src/IPermit2.sol           | 30 ++++++++++++++++
 src/OriginSettler.sol      | 51 ++++++++++++++++++++-------
 test/Counter.t.sol         | 24 -------------
 8 files changed, 147 insertions(+), 75 deletions(-)
 delete mode 100644 script/Counter.s.sol
 delete mode 100644 src/Counter.sol
 create mode 100644 src/ERC7683Permit2Lib.sol
 create mode 100644 src/IPermit2.sol
 delete mode 100644 test/Counter.t.sol

diff --git a/README.md b/README.md
index d192e50..91a5c62 100644
--- a/README.md
+++ b/README.md
@@ -26,8 +26,8 @@ This repository contains contracts and scripts demonstrating this flow.
 
 ## On-chain Components
 
-- `OriginSettler`: Origin chain contract that user interacts with to open an ERC7683 cross-chain intent. The `open` function helps the user to form an ERC7683 intent correctly containing the `calldata` that the user wants to delegate to a filler to execute on the destination chain.
-  - The `open` function also optionally lets the user include a 7702 authorization that the user wants the filler to submit on-chain on their behalf. This can be used to allow the user to set the `code` of their destination chain EOA to the `XAccount` contract.
+- `OriginSettler`: Origin chain contract that user interacts with to open an ERC7683 cross-chain intent. The `open` function helps the user to form an ERC7683 intent correctly containing the `calldata` that the user wants to delegate to a filler to execute on the destination chain. `openFor` can be used to help a user pass their signed order to a filler off-chain and subsequently allows the filler to create the 7683 order on the user's behalf. Therefore, `openFor` allows the user to experience a totally gas-free experience from origin to destination chain.
+  - The `open` functionality also optionally lets the user include a 7702 authorization that the user wants the filler to submit on-chain on their behalf. This can be used to allow the user to set the `code` of their destination chain EOA to the `XAccount` contract.
   - In the 7683 order, includes the 7702 authorization data and the destination chain calldata in a [`FillInstruction`](https://eips.ethereum.org/EIPS/eip-7683#fillerdata)
 - `DestinationSettler`: Destination chain contract that filler interacts with to fulfill a ERC7683 cross-chain intent. The `fill` function is used by the `filler` to credit the user's EOA with any assets that they had deposited on the `OriginSettler` when initiating the 7683 intent and subsequently execute any `calldata` on behalf of the user that was included in the 7683 intent.
   - The `fill` function will delegate execution of `calldata` to the `XAccount` 7702-compatible proxy contract so it is a prerequisite that the user has already set their destination chain EOA's `code` to `XAccount` via a 7702 transaction. The authorization should submitted by the user or delegated
diff --git a/script/Counter.s.sol b/script/Counter.s.sol
deleted file mode 100644
index cdc1fe9..0000000
--- a/script/Counter.s.sol
+++ /dev/null
@@ -1,19 +0,0 @@
-// SPDX-License-Identifier: UNLICENSED
-pragma solidity ^0.8.13;
-
-import {Script, console} from "forge-std/Script.sol";
-import {Counter} from "../src/Counter.sol";
-
-contract CounterScript is Script {
-    Counter public counter;
-
-    function setUp() public {}
-
-    function run() public {
-        vm.startBroadcast();
-
-        counter = new Counter();
-
-        vm.stopBroadcast();
-    }
-}
diff --git a/src/Counter.sol b/src/Counter.sol
deleted file mode 100644
index aded799..0000000
--- a/src/Counter.sol
+++ /dev/null
@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: UNLICENSED
-pragma solidity ^0.8.13;
-
-contract Counter {
-    uint256 public number;
-
-    function setNumber(uint256 newNumber) public {
-        number = newNumber;
-    }
-
-    function increment() public {
-        number++;
-    }
-}
diff --git a/src/DestinationSettler.sol b/src/DestinationSettler.sol
index 0b40680..ddc220a 100644
--- a/src/DestinationSettler.sol
+++ b/src/DestinationSettler.sol
@@ -6,7 +6,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
 import {OriginSettler} from "./OriginSettler.sol";
 
 struct Asset {
-    IERC20 token;
+    address token;
     uint256 amount;
 }
 
@@ -77,8 +77,8 @@ contract DestinationSettler {
     function _fundUserAndApproveXAccount(CallByUser memory call) internal {
         // TODO: Link the escrowed funds back to the user in case the delegation step fails, we don't want
         // user to lose access to funds.
-        call.asset.token.safeTransferFrom(msg.sender, address(this), call.asset.amount);
-        call.asset.token.forceApprove(call.user, call.asset.amount);
+        IERC20(call.asset.token).safeTransferFrom(msg.sender, address(this), call.asset.amount);
+        IERC20(call.asset.token).forceApprove(call.user, call.asset.amount);
     }
 }
 
@@ -164,7 +164,7 @@ contract XAccount {
     }
 
     function _fundUser(CallByUser memory call) internal {
-        call.asset.token.safeTransferFrom(msg.sender, call.user, call.asset.amount);
+        IERC20(call.asset.token).safeTransferFrom(msg.sender, call.user, call.asset.amount);
     }
 
     // Used if the caller is trying to unwrap the native token to this contract.
diff --git a/src/ERC7683Permit2Lib.sol b/src/ERC7683Permit2Lib.sol
new file mode 100644
index 0000000..2f25c44
--- /dev/null
+++ b/src/ERC7683Permit2Lib.sol
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.0;
+
+import "./OriginSettler.sol";
+import "./DestinationSettler.sol";
+import "./IPermit2.sol";
+import {GaslessCrossChainOrder} from "./ERC7683.sol";
+
+bytes constant CALL_BY_USER_TYPE = abi.encodePacked(
+    "CallByUser(", "address user,", "Asset asset,", "uint64 chainId,", "bytes32 delegateCodeHash,", "Call[] calls)"
+);
+
+bytes constant CALL_TYPE = abi.encodePacked("Call(", "address target,", "bytes callData,", "uint256 value)");
+
+bytes constant ASSET_TYPE = abi.encodePacked("Asset(", "address token,", "uint256 amount)");
+
+bytes32 constant CALL_BY_USER_TYPE_HASH = keccak256(CALL_BY_USER_TYPE);
+
+library ERC7683Permit2Lib {
+    bytes internal constant GASLESS_CROSS_CHAIN_ORDER_TYPE = abi.encodePacked(
+        "GaslessCrossChainOrder(",
+        "address originSettler,",
+        "address user,",
+        "uint256 nonce,",
+        "uint256 originChainId,",
+        "uint32 openDeadline,",
+        "uint32 fillDeadline,",
+        "bytes32 orderDataType,",
+        "CallByUser orderData)"
+    );
+
+    bytes internal constant GASLESS_CROSS_CHAIN_ORDER_EIP712_TYPE =
+        abi.encodePacked(GASLESS_CROSS_CHAIN_ORDER_TYPE, CALL_BY_USER_TYPE, CALL_TYPE, ASSET_TYPE);
+    bytes32 internal constant GASLESS_CROSS_CHAIN_ORDER_TYPE_HASH = keccak256(GASLESS_CROSS_CHAIN_ORDER_EIP712_TYPE);
+
+    string private constant TOKEN_PERMISSIONS_TYPE = "TokenPermissions(address token,uint256 amount)";
+    string internal constant PERMIT2_ORDER_TYPE = string(
+        abi.encodePacked(
+            "GaslessCrossChainOrder witness)", CALL_BY_USER_TYPE, GASLESS_CROSS_CHAIN_ORDER_TYPE, TOKEN_PERMISSIONS_TYPE
+        )
+    );
+
+    // Hashes an order to get an order hash. Needed for permit2.
+    function hashOrder(GaslessCrossChainOrder memory order, bytes32 orderDataHash) internal pure returns (bytes32) {
+        return keccak256(
+            abi.encode(
+                GASLESS_CROSS_CHAIN_ORDER_TYPE_HASH,
+                order.originSettler,
+                order.user,
+                order.nonce,
+                order.originChainId,
+                order.openDeadline,
+                order.fillDeadline,
+                order.orderDataType,
+                orderDataHash
+            )
+        );
+    }
+
+    function hashUserCallData(CallByUser memory userCallData) internal pure returns (bytes32) {
+        return keccak256(
+            abi.encode(
+                CALL_BY_USER_TYPE_HASH,
+                userCallData.user,
+                userCallData.asset,
+                userCallData.chainId,
+                userCallData.delegateCodeHash,
+                userCallData.calls
+            )
+        );
+    }
+}
diff --git a/src/IPermit2.sol b/src/IPermit2.sol
new file mode 100644
index 0000000..61df041
--- /dev/null
+++ b/src/IPermit2.sol
@@ -0,0 +1,30 @@
+pragma solidity ^0.8.0;
+
+interface IPermit2 {
+    struct TokenPermissions {
+        address token;
+        uint256 amount;
+    }
+
+    struct PermitTransferFrom {
+        TokenPermissions permitted;
+        uint256 nonce;
+        uint256 deadline;
+    }
+
+    struct SignatureTransferDetails {
+        address to;
+        uint256 requestedAmount;
+    }
+
+    function permitWitnessTransferFrom(
+        PermitTransferFrom memory permit,
+        SignatureTransferDetails calldata transferDetails,
+        address owner,
+        bytes32 witness,
+        string calldata witnessTypeString,
+        bytes calldata signature
+    ) external;
+
+    function transferFrom(address from, address to, uint160 amount, address token) external;
+}
diff --git a/src/OriginSettler.sol b/src/OriginSettler.sol
index e0bda1a..6a45eba 100644
--- a/src/OriginSettler.sol
+++ b/src/OriginSettler.sol
@@ -4,10 +4,14 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
 import {GaslessCrossChainOrder, ResolvedCrossChainOrder, IOriginSettler, Output, FillInstruction} from "./ERC7683.sol";
 import {CallByUser, Call, Asset} from "./DestinationSettler.sol";
+import "./IPermit2.sol";
+import "./ERC7683Permit2Lib.sol";
 
 contract OriginSettler {
     using SafeERC20 for IERC20;
 
+    IPermit2 public immutable PERMIT2 = IPermit2(address(0xf00d));
+
     // codeAddress will be set as the user's `code` on the `chainId` chain.
     struct Authorization {
         uint256 chainId;
@@ -20,11 +24,6 @@ contract OriginSettler {
         Authorization[] authlist;
     }
 
-    struct InputAsset {
-        IERC20 token;
-        uint256 amount;
-    }
-
     error WrongSettlementContract();
     error WrongChainId();
     error WrongOrderDataType();
@@ -35,16 +34,18 @@ contract OriginSettler {
     function openFor(GaslessCrossChainOrder calldata order, bytes calldata signature, bytes calldata originFillerData)
         external
     {
+        // TODO: Do we need to verify that signature is the signed order so that the filler can't just pass in any
+        // order data here? Or will this be implicitly handled by passing the signature into _processPermit2Order?
         (
             ResolvedCrossChainOrder memory resolvedOrder,
             CallByUser memory calls,
             EIP7702AuthData memory authData,
-            InputAsset memory inputAsset
+            Asset memory inputAsset
         ) = _resolveFor(order, originFillerData);
 
         // TODO: Support permit2 or approve+transferFrom flow or something else?
         // // Verify Permit2 signature and pull user funds into this contract
-        // _processPermit2Order(order, acrossOrderData, signature);
+        _processPermit2Order(order, calls, inputAsset, signature);
 
         // TODO: Escrow funds in this contract and release post 7755 proof of settlement? Or use some other
         // method.
@@ -53,12 +54,38 @@ contract OriginSettler {
         emit IOriginSettler.Open(keccak256(resolvedOrder.fillInstructions[0].originData), resolvedOrder);
     }
 
+    function _processPermit2Order(
+        GaslessCrossChainOrder memory order,
+        CallByUser memory calls,
+        Asset memory inputAsset,
+        bytes memory signature
+    ) internal {
+        IPermit2.PermitTransferFrom memory permit = IPermit2.PermitTransferFrom({
+            permitted: IPermit2.TokenPermissions({token: inputAsset.token, amount: inputAsset.amount}),
+            nonce: order.nonce,
+            deadline: order.openDeadline
+        });
+
+        IPermit2.SignatureTransferDetails memory signatureTransferDetails =
+            IPermit2.SignatureTransferDetails({to: address(this), requestedAmount: inputAsset.amount});
+
+        // Pull user funds.
+        PERMIT2.permitWitnessTransferFrom(
+            permit,
+            signatureTransferDetails,
+            order.user,
+            ERC7683Permit2Lib.hashOrder(order, ERC7683Permit2Lib.hashUserCallData(calls)), // witness data hash
+            ERC7683Permit2Lib.PERMIT2_ORDER_TYPE, // witness data type string
+            signature
+        );
+    }
+
     function decode(bytes memory orderData)
         public
         pure
-        returns (CallByUser memory calls, EIP7702AuthData memory authData, InputAsset memory asset)
+        returns (CallByUser memory calls, EIP7702AuthData memory authData, Asset memory asset)
     {
-        return (abi.decode(orderData, (CallByUser, EIP7702AuthData, InputAsset)));
+        return (abi.decode(orderData, (CallByUser, EIP7702AuthData, Asset)));
     }
 
     function _resolveFor(GaslessCrossChainOrder calldata order, bytes calldata fillerData)
@@ -68,7 +95,7 @@ contract OriginSettler {
             ResolvedCrossChainOrder memory resolvedOrder,
             CallByUser memory calls,
             EIP7702AuthData memory authData,
-            InputAsset memory inputAsset
+            Asset memory inputAsset
         )
     {
         if (order.originSettler != address(this)) {
@@ -89,7 +116,7 @@ contract OriginSettler {
         // Max outputs that filler should spend on destination chain.
         Output[] memory maxSpent = new Output[](1);
         maxSpent[0] = Output({
-            token: _toBytes32(address(calls.asset.token)),
+            token: _toBytes32(calls.asset.token),
             amount: calls.asset.amount,
             recipient: _toBytes32(calls.user),
             chainId: calls.chainId
@@ -98,7 +125,7 @@ contract OriginSettler {
         // Minimum outputs that must be pulled from caller on this chain.
         Output[] memory minReceived = new Output[](1);
         minReceived[0] = Output({
-            token: _toBytes32(address(inputAsset.token)),
+            token: _toBytes32(inputAsset.token),
             amount: inputAsset.amount,
             recipient: _toBytes32(msg.sender), // We assume that msg.sender is filler and wants to be repaid on this chain.
             chainId: block.chainid
diff --git a/test/Counter.t.sol b/test/Counter.t.sol
deleted file mode 100644
index 54b724f..0000000
--- a/test/Counter.t.sol
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: UNLICENSED
-pragma solidity ^0.8.13;
-
-import {Test, console} from "forge-std/Test.sol";
-import {Counter} from "../src/Counter.sol";
-
-contract CounterTest is Test {
-    Counter public counter;
-
-    function setUp() public {
-        counter = new Counter();
-        counter.setNumber(0);
-    }
-
-    function test_Increment() public {
-        counter.increment();
-        assertEq(counter.number(), 1);
-    }
-
-    function testFuzz_SetNumber(uint256 x) public {
-        counter.setNumber(x);
-        assertEq(counter.number(), x);
-    }
-}