diff --git a/contracts/usdfree/Interfaces.sol b/contracts/usdfree/Interfaces.sol new file mode 100644 index 000000000..070bba25b --- /dev/null +++ b/contracts/usdfree/Interfaces.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.30; + +struct TypedData { + uint8 typ; + bytes data; +} + +// Defines sequence of substeps performed as a part of that step +enum StepType { + // 1. Alter user reqs + // Note. Alter user requirements can take many different forms. For example, an offchain auction authority can sign + // over some payload to bump up the user's balanceReq. Or a user can trust a submitter (e.g. RL submitter) to bump + // up balanceReq (effectively, this is the same as having auction authority == RL submitter). Alternatively, imagine + // an Alter substep that: takes token amount in (from mint on dst chain), takes onchain oracle price, takes user + // BPS discount / premium required, alters balanceReq: balanceReq = tokenIn * price * (1 + bps_disc_or_premium) + // 2. Submitter actions to meet user reqs (MulticallHandler instructions or weiroll) + // 3. User req checks and action/transfer (see UserReqsAndAction / UserReqsAndTransfer) + AlterSubmitterUser, + // only 2. and 3. + SubmitterUser, + // only 3. + User +} + +struct RefundSettings { + bytes32 refundRecipient; + uint256 reverseDeadline; +} + +enum AlterSubstepType { + OffchainAuction, // user specifies auction authority + DutchOnchainAuction, // user specifies dutch auction params + OffchainAuctionWithDutchOnchainFallback, // user specifies offchain auction authority + fallback deadline + onchain dutch auction params + PriceOraclePlusDelta, // user specifies oracle address plus a delta (discount or permium) that they expect + SimpleSubmitterAlter // user doesn't specify anything besides the type. Submitter (checked by submitterReq) has the option to bump the user balanceReq up (e.g. a trusted submitter like RL submitter) +} + +enum UserSubstepType { + ReqPlusAction, + ReqPlusTransfer +} + +enum UserReqType { + Deadline, + Submitter, + Staticcall +} + +struct UserReqs { + TypedData balanceReq; + TypedData[] otherReqs; +} + +struct UserReqsAndAction { + UserReqs reqs; + bytes32 target; + bytes message; +} + +struct UserReqsAndTransfer { + UserReqs reqs; + bytes32 recipient; +} + +struct Step { + StepType typ; + RefundSettings refundSettings; + TypedData userSubstep; + bytes[] parts; // user-defined settings (often guardrails) for other steps +} + +struct MerkleOrder { + bytes32 salt; + bytes32 routesRoot; +} + +struct TokenAmount { + address token; + uint256 amount; +} + +enum NextStepsType { + PlainArray, // Step[] + PlainRecursive, // StepAndNext + Obfuscated // bytes32 +} + +struct StepAndNext { + Step curStep; + TypedData nextSteps; +} + +struct MerkleRoute { + StepAndNext stepAndNext; + bytes32[] proof; +} + +struct SubmitterData { + TokenAmount[] extraFunding; + bytes[] parts; +} + +enum OrderFundingType { + Approval, + Permit2, + TransferWithAuthorization +} + +interface IOrderGateway { + function submit( + MerkleOrder calldata order, + MerkleRoute calldata route, + // Funding by the party authorizing the order. Funding has the amount of a single ERC20 token + TypedData calldata orderFunding, + SubmitterData calldata submitterData + ) external payable; +} + +interface IExecutor { + function execute( + bytes32 orderId, + TokenAmount calldata userTokenAmount, + StepAndNext calldata stepAndNext, + address submitter, + bytes[] calldata submitterParts + ) external payable; // onlyOrderGateway / onlyOrderStore +} + +interface IUserActionExecutor { + function execute( + bytes32 orderId, + TokenAmount calldata tokenAmount, + bytes calldata actionParams, // params for the execution on the current chain + TypedData calldata nextSteps // steps to pass along + ) external payable; +} + +interface IOrderStore { + function handle(bytes32 orderId, TokenAmount calldata tokenAmount, TypedData calldata steps) external payable; + + // handle + fill immediately + function handleAtomic( + bytes32 orderId, + TokenAmount calldata tokenAmount, + TypedData calldata steps, + // Note: submitter here is technically an untrusted argument. However, it's up to the user on src chain to ensure that they're + // setting a receiving contract on the DST chain that will only allow a valid submitter value here (think, DstCctpPeriphery + // that will allow to finalize cctp + handleAtomic and just pass in msg.sender). It's on the underlying bridge to + // ensure the validity of the message passed, and on the receiving periphery contract to ensure the correctness of + // submitter address passed + address submitter, + SubmitterData calldata submitterData + ) external payable; + + function fill(uint256 localOrderIndex, SubmitterData calldata submitterData) external payable; +} diff --git a/contracts/usdfree/README.md b/contracts/usdfree/README.md new file mode 100644 index 000000000..355f285aa --- /dev/null +++ b/contracts/usdfree/README.md @@ -0,0 +1,101 @@ +# USDFree + +A mechanism-agnostic cross-chain order system that unifies CCTP, OFT, and Across bridge flows into a single architecture. + +## Goals + +- Bridge tokens cross-chain regardless of underlying mechanism +- Single upgradeable entry point (no re-approvals needed) +- Support gasless flows and sponsorship +- Enable chained cross-chain operations (src → A → B → dst) +- Execute arbitrary user actions after token delivery + +## Architecture + +``` +Source Chain: +User → OrderGateway → Executor → IUserActionExecutor + ↓ + [Bridge Message with nextSteps] + ↓ + Destination Chain: +BridgeHandler → OrderStore → Executor → IUserActionExecutor +``` + +### Components + +**OrderGateway** - Entry point for all order submissions + +- `submit()` calculates `orderId` and pulls tokens from user and submitter (gasless- or approval-based) +- Pushes all of token amounts to `Executor`. Pushes tokens one by one starting from the orderFunding token. If submitter + provided extraFunding and the first token in that array is the same as the orderFunding token, the amounts get pushed + via a single Transfer to reduce gas costs. + +**Executor** - Executes a single step (a series of atomic substeps) + +- Runs a series of substeps + +_AlterSubmitterUser_ variant + +- Run alter substep (produces `Changes` to modify user requirements in the later step): + alter user requirements may take many different forms. For example, an offchain auction authority can sign + over some payload to bump up the user's balanceReq. Or a user can trust a submitter (e.g. RL submitter) to bump + up balanceReq (effectively, this is the same as having auction authority == RL submitter). Alternatively, imagine + an Alter substep that: takes token amount in (from mint on dst chain), takes onchain oracle price, takes user + BPS discount / premium required, alters balanceReq: balanceReq = tokenIn _ price _ (1 + bps_disc_or_premium) +- Run submitter substep (e.g. DEX swaps, or taking a fee as long as it meets user balance requirement in the next step) +- Run user substep: check requirements and execute transfer or action (based on `UserSubstepType`). Action variant calls + `IUserActionExecutor`. Transfer is push-based, action is approve-tranferFrom-based. + +_SubmitterUser_ + +- only submitter and user substeps from above + +_User_ + +- only user substep + +**IUserActionExecutor** - Final action interface + +- Pull tokens from Executor using the provided tokenAmount +- Execute user-specified action. For example, talk to IOFT to bridge tokens over to the next chain. It's the responsibility + of the user action executor to propagate next steps correctly to the next execution leg (e.g. in `composeMsg` for OFT). + Other bridge options can include CCTP, SpokePool and others. + +**OrderStore** - Destination-side order management + +- `handle()` - Receive tokens from bridge, store for filling +- `handleAtomic()` - Receive and fill in one transaction +- `fill()` - Fill a stored order + +## Design Decisions + +**Order ID Scope**: Computed once at OrderGateway with domain separation (srcChainId). Propagated cross-chain, but can be +spoofed on destination chains. Crosschain tracking is done as described below. + +**Cross-Chain Tracking**: Uses bridge-native identifiers (OFT guid, CCTP nonce, etc.). API/indexer correlates the full chain. + +**Multi-Chain Orders**: `nextSteps` are passed with bridge messages, enabling multi-hop orders. + +**Token Custody**: Tokens sit in OrderStore until fill. During step execution, temporarily in Executor. Executor should never have approvals. + +**Refunds**: Permissionless after reverse deadline - anyone can trigger, tokens go to `refundSettings.refundRecipient`. + +**Obfuscation**: Trust-based. If user needs to hide their action, they specify a trusted submitter via `submitterReq`. + +## Dst chain paths + +**CCTP/OFT flows**: Oft/CctpHandler → OrderStore.handle() → fill() +\*Cctp finalizer can call CctpHandler.handleAtomic → OrderStore.handleAtomic() + +**SpokePool**: Uses `handleV3AcrossMessage` wrapper that routes to IUserActionExecutor directly (skips OrderStore since SpokePool already checks requirements). + +## Sponsorship + +Two models supported: + +1. **Phase0-compatible**: Uses an Order with a single user action, which conatins all of the Phase0 parameters (including + the API signature). Essentially hands over execution to the Phase0 system after src chain's submitterActions-reqChecks- + -userAction sequence is done. +2. **Off-chain**: API reimburses relayers post-execution based on orderId and execution chain tracking by the Indexer. API + only sponsors orders it can track completion of. diff --git a/contracts/usdfree/diagram.png b/contracts/usdfree/diagram.png new file mode 100644 index 000000000..b30973383 Binary files /dev/null and b/contracts/usdfree/diagram.png differ