Skip to content

Commit

Permalink
add permit2 lib
Browse files Browse the repository at this point in the history
Signed-off-by: nicholaspai <[email protected]>
  • Loading branch information
nicholaspai committed Dec 3, 2024
1 parent 4e73544 commit 37c7dc9
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 75 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 0 additions & 19 deletions script/Counter.s.sol

This file was deleted.

14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

8 changes: 4 additions & 4 deletions src/DestinationSettler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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.
Expand Down
72 changes: 72 additions & 0 deletions src/ERC7683Permit2Lib.sol
Original file line number Diff line number Diff line change
@@ -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
)
);
}
}
30 changes: 30 additions & 0 deletions src/IPermit2.sol
Original file line number Diff line number Diff line change
@@ -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;
}
51 changes: 39 additions & 12 deletions src/OriginSettler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,11 +24,6 @@ contract OriginSettler {
Authorization[] authlist;
}

struct InputAsset {
IERC20 token;
uint256 amount;
}

error WrongSettlementContract();
error WrongChainId();
error WrongOrderDataType();
Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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)) {
Expand All @@ -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
Expand All @@ -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
Expand Down
24 changes: 0 additions & 24 deletions test/Counter.t.sol

This file was deleted.

0 comments on commit 37c7dc9

Please sign in to comment.