OpenRouter
0x50cfe7c1938db66a1a6d2e86d36f39fbef3d5c4a
Verification
Verified
v0.8.34+commit.80d5c536
Type
Contract
6,140 bytes
ABI entries
17
1 read · 4 write
License
none
Contract information
- Address
- 0x50cfe7c1938db66a1a6d2e86d36f39fbef3d5c4a
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.34+commit.80d5c536
- Optimization
- Enabled
- Creator
- 0x41570b8A9D…534dF0492B
- Creation tx
- 0x449e63e612…1d66d94507
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (1)
balanceOf(address)
Events (1)
RequestExecuted
ABI
[
{
"inputs": [
{
"internalType": "uint256",
"name": "actionIndex",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "returndata",
"type": "bytes"
}
],
"name": "CallFailed",
"type": "error"
},
{
"inputs": [],
"name": "CallerNotSignedUser",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "actionIndex",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "sourceActionIndex",
"type": "uint256"
}
],
"name": "FutureSplice",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientMsgValue",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "actionIndex",
"type": "uint256"
}
],
"name": "MissingNativeValue",
"type": "error"
},
{
"inputs": [],
"name": "ReturnDataOutOfBounds",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "actionIndex",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "spliceIndex",
"type": "uint256"
}
],
"name": "SpliceOutOfBounds",
"type": "error"
},
{
"inputs": [],
"name": "SplicePositionOutOfBounds",
"type": "error"
},
{
"inputs": [],
"name": "SwapOutputInsufficient",
"type": "error"
},
{
"inputs": [],
"name": "TransferFailed",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "quoteId",
"type": "bytes32"
}
],
"name": "RequestExecuted",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "quoteId",
"type": "bytes32"
},
{
"components": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "inputToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "inputAmount",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.InputData",
"name": "input",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.FeeData",
"name": "fee",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "address",
"name": "approvalSpender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.BridgeData",
"name": "bridgeData",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "bridgeCallData",
"type": "bytes"
}
],
"name": "bridge",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "quoteId",
"type": "bytes32"
},
{
"components": [
{
"internalType": "uint256",
"name": "actionInfo",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
},
{
"internalType": "uint256[]",
"name": "splices",
"type": "uint256[]"
}
],
"internalType": "struct OpenRouter.Action[]",
"name": "actions",
"type": "tuple[]"
}
],
"name": "performActions",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "quoteId",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "flags",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "inputToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "inputAmount",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.InputData",
"name": "input",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.FeeData",
"name": "fee",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "address",
"name": "approvalSpender",
"type": "address"
},
{
"internalType": "address",
"name": "outputToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minOutput",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "returnDataWordOffset",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.SwapData",
"name": "swapData",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "swapCallData",
"type": "bytes"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "swap",
"outputs": [
{
"internalType": "uint256",
"name": "finalAmount",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "quoteId",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "flags",
"type": "uint256"
},
{
"components": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "address",
"name": "inputToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "inputAmount",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.InputData",
"name": "input",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.FeeData",
"name": "fee",
"type": "tuple"
},
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "address",
"name": "approvalSpender",
"type": "address"
},
{
"internalType": "address",
"name": "outputToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minOutput",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "returnDataWordOffset",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.SwapData",
"name": "swapData",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "swapCallData",
"type": "bytes"
},
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "address",
"name": "approvalSpender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"internalType": "struct OpenRouter.BridgeData",
"name": "bridgeData",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "bridgeCallData",
"type": "bytes"
}
],
"name": "swapAndBridge",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.34;
import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol";
import {IERC20} from "./common/interfaces/IERC20.sol";
import {AllowanceHolderContext} from "./common/allowance/AllowanceHolderContext.sol";
import {ALLOWANCE_HOLDER} from "./common/interfaces/IAllowanceHolder.sol";
import {BytesSpliceLib} from "./common/lib/BytesSpliceLib.sol";
import {CurrencyLib} from "./common/lib/CurrencyLib.sol";
/// @title OpenRouter
/// @notice Pull → optional fee → swap/bridge execution without backend signature verification.
/// Fund safety rests on AllowanceHolder's transient allowance scoping (operator + owner + token):
/// only the user whose address was passed to `AllowanceHolder.exec` can authorise a pull of
/// their own funds. The `_msgSender() == user` check in `_pullFromUser` enforces this.
contract OpenRouter is AllowanceHolderContext {
using SafeTransferLib for address;
// =========================================================================
// Structs
// =========================================================================
struct InputData {
address user;
address inputToken;
uint256 inputAmount;
}
struct FeeData {
address receiver;
uint256 amount;
}
struct SwapData {
address target;
address approvalSpender;
address outputToken;
uint256 value;
uint256 minOutput;
uint256 returnDataWordOffset;
}
struct BridgeData {
address target;
address approvalSpender;
uint256 value;
}
enum CallType {
CALL,
STATICCALL,
CALL_WITH_NATIVE
}
struct Action {
/// @dev Packed call metadata. Decode with masks/shifts below; encode with
/// `callType | (storeResult ? 1 << 8 : 0) | (uint160(target) << 16)`.
///
/// Bit layout (least significant bits first):
/// bits 255..176 : reserved (0)
/// bits 175..16 : target address (uint160, shifted left by 16 — occupies 160 bits)
/// bit 8 : storeResult — when set, returndata is saved to `results[i]`
/// even on success so later actions can splice from it
/// bits 7..3 : reserved (0)
/// bits 2..0 : CallType — CALL (0), STATICCALL (1), CALL_WITH_NATIVE (2)
///
/// CALL_WITH_NATIVE: first 32 bytes of `data` are forwarded as `msg.value`;
/// the remaining bytes are the call payload.
uint256 actionInfo;
/// @dev Calldata passed to the target. Splices from `splices[]` overwrite byte
/// ranges in a mutable memory copy before the external call runs.
bytes data;
/// @dev Packed splice descriptors applied to `data` before the call.
/// Each entry is one `uint256` with four uint64 fields (see layout below).
/// Encode with `packSpliceInfo` in `scripts/e2e/utils/modularActionsBuilder/index.js`.
///
/// Per-entry bit layout (least significant bits first):
/// bits 255..192 : length — number of bytes to copy (must be > 0)
/// bits 191..128 : dstOffset — byte offset into this action's `data` payload
/// (skips the bytes-array length word; for CALL_WITH_NATIVE,
/// offset 0 is the value word, offset 32 is payload start)
/// bits 127..64 : srcOffset — byte offset into `results[sourceActionIndex]`
/// payload (same length-prefix convention)
/// bits 63..0 : sourceActionIndex — index of a prior action (< current index)
///
/// Packing formula:
/// sourceActionIndex | (srcOffset << 64) | (dstOffset << 128) | (length << 192)
///
/// The source action must have bit 8 set in `actionInfo` (storeResult); the JS
/// builder sets this automatically when a splice references that action.
uint256[] splices;
}
// =========================================================================
// Flags (swap / swapAndBridge)
// =========================================================================
//
// Instead of bool parameters, one uint256 packs independent switches without adding
// ABI range checks or extra words for standalone bools.
//
// Bit layout (least significant bits); test with `(flags & MASK) != 0`:
// bits 255..32 : reserved (0)
// bits 31..16 : bridge amount word byte offset, uint16, used only when bit 3 is set
// bits 15..4 : reserved (0)
// bit 3 : BRIDGE_AMOUNT_POSITION_FLAG_BIT_MASK (0x08) — splice finalAmount into bridge calldata
// bit 2 : BRIDGE_VALUE_FLAG_BIT_MASK (0x04) — bridge msg.value: bridge.value alone vs finalAmount + bridge.value
// bit 1 : BALANCE_FLAG_BIT_MASK (0x02) — swap output: returndata vs balance delta
// bit 0 : POST_FEE_FLAG_BIT_MASK (0x01) — swap fee: pre- vs post-swap
//
// Combined values for flags:
//
// flags binary (low byte) postFee? balance-of output? bridge value?
// ───── ────────────────── ──────── ────────────────── ─────────────
// 0x00 00000000 no returndata word bridge.value
// 0x01 00000001 yes returndata word bridge.value
// 0x02 00000010 no balance delta on outputToken bridge.value
// 0x03 00000011 yes balance delta on outputToken bridge.value
// 0x04 00000100 no returndata word finalAmount + bridge.value
//
// POST_FEE_FLAG_BIT_MASK selects bit 0 — fee timing
// 0000 — pre-swap fee: pull → deduct fee from input token → swap remainder
// 0001 — post-swap fee: pull → swap full input → deduct fee from output token (after minOutput check on swap result)
//
// BALANCE_FLAG_BIT_MASK selects bit 1 — swap output sizing
// 0000 — returnData as swap output: decode returned amount from call returndata at `swapData.returnDataWordOffset`
// 0010 — balanceOf() delta as swap output: snapshot outputToken balance before call, measure (after − before) as output
//
// BRIDGE_VALUE_FLAG_BIT_MASK selects bit 2 — bridge native value source
// 0000 — bridge.value as msg.value: forward `bridge.value` as msg.value
// 0100 — finalAmount + bridge.value as msg.value: forward `finalAmount + bridge.value` as msg.value (bridge.value carries static addend, e.g. LZ nativeFee)
//
// BRIDGE_AMOUNT_POSITION_FLAG_BIT_MASK selects bit 3 — bridge calldata amount splicing.
// 0000 — no bridge calldata modification
// 1000 — bridge calldata modification: splice finalAmount at uint16(flags >> BRIDGE_AMOUNT_POSITION_SHIFT)
//
/// @dev Bit mask 0x01: post-swap fee path when `(flags & mask) != 0`; clear = pre-swap fee from input token.
uint256 internal constant POST_FEE_FLAG_BIT_MASK = 0x01;
/// @dev Bit mask 0x02: measure swap output by balance delta when `(flags & mask) != 0`; clear = returndata word.
uint256 internal constant BALANCE_FLAG_BIT_MASK = 0x02;
/// @dev Bit mask 0x04: `finalAmount + bridge.value` is forwarded as msg.value (bridge.value acts as a static addend, e.g. LZ nativeFee).
uint256 internal constant BRIDGE_VALUE_FLAG_BIT_MASK = 0x04;
/// @dev Bit mask 0x08: splice finalAmount into bridge calldata at the uint16 position packed in flags.
uint256 internal constant BRIDGE_AMOUNT_POSITION_FLAG_BIT_MASK = 0x08;
/// @dev Shift for the packed uint16 bridge amount position.
uint256 internal constant BRIDGE_AMOUNT_POSITION_SHIFT = 16;
/// @dev Mask for the packed uint16 bridge amount position after shifting.
uint256 internal constant BRIDGE_AMOUNT_POSITION_MASK = 0xffff;
// =========================================================================
// Errors
// =========================================================================
error SwapOutputInsufficient();
error CallerNotSignedUser();
error InsufficientMsgValue();
error FutureSplice(uint256 actionIndex, uint256 sourceActionIndex);
error SpliceOutOfBounds(uint256 actionIndex, uint256 spliceIndex);
error CallFailed(uint256 actionIndex, bytes returndata);
error MissingNativeValue(uint256 actionIndex);
error ReturnDataOutOfBounds();
// =========================================================================
// Events
// =========================================================================
event RequestExecuted(bytes32 indexed quoteId);
/// @notice Accepts native ETH forwarded with bridge/swap calls.
receive() external payable {}
// =========================================================================
// External functions
// =========================================================================
/**
* @notice Perform swap with optional pre/post fee.
* @param quoteId Caller-defined correlation id logged in `RequestExecuted`.
* @param flags Packed flags
* @param input User, input token, and pull amount.
* @dev For pre-fee / no-fee: the swap router must
* be instructed (via `swapCallData`) to send tokens directly to `receiver`; the contract never holds the output.
* For post-fee: tokens land at this contract, fee is deducted, net is forwarded to `receiver`.
* @param fee Fee collection info: receiver and amount. Set `amount` to 0 to skip fee collection.
* @param swapData Swap target, spender, output token, value, `minOutput`, and returndata offset.
* @param swapCallData Calldata forwarded to `swapData.target`.
* @param receiver Address that ultimately receives the swap output (net of any post-swap fee).
* @return finalAmount Gross swap output sent to receiver after any post-swap fee
* @dev `minOutput` is the minimum gross amount coming out of the swap (before any output-token fee). It is enforced immediately after `_execSwap`, then post-swap fee (if any) is collected.
* Pre-fee paths take the input-side fee before the swap; `minOutput` still guards the swap outcome.
*/
function swap(
bytes32 quoteId,
uint256 flags,
InputData calldata input,
FeeData calldata fee,
SwapData calldata swapData,
bytes calldata swapCallData,
address receiver
) external payable returns (uint256 finalAmount) {
// Parse flags
bool postFee = fee.amount != 0 && ((flags & POST_FEE_FLAG_BIT_MASK) != 0);
bool useBalanceOf = ((flags & BALANCE_FLAG_BIT_MASK) != 0);
{
// Pull funds from user via AllowanceHolder
_pullFromUser(input.inputToken, input.user, input.inputAmount);
// Collect pre-swap fee
uint256 swapInput = input.inputAmount;
if (fee.amount != 0 && !postFee) {
uint256 feeAmount = fee.amount;
CurrencyLib.transfer(input.inputToken, fee.receiver, feeAmount);
unchecked {
swapInput -= feeAmount;
}
}
// Approve spender
if (
// check spender & token
swapData.approvalSpender != address(0) && input.inputToken != CurrencyLib.NATIVE_TOKEN_ADDRESS &&
// check current allowance
swapInput > IERC20(input.inputToken).allowance(address(this), swapData.approvalSpender)
) {
// approve max allowance
SafeTransferLib.safeApproveWithRetry(input.inputToken, swapData.approvalSpender, type(uint256).max);
}
}
/// @dev Pre-fee / no-fee: swap calldata encodes `receiver` as the output recipient; tokens never touch this contract.
/// @dev Post-fee: swap output lands at this contract so the fee can be deducted before forwarding.
address outputReceiver = postFee ? address(this) : receiver;
// Execute swap
finalAmount = _execSwap(swapData, swapCallData, useBalanceOf, outputReceiver);
if (finalAmount < swapData.minOutput) revert SwapOutputInsufficient();
if (postFee) {
// Collect post-swap fee
uint256 feeAmount = fee.amount;
CurrencyLib.transfer(swapData.outputToken, fee.receiver, feeAmount);
unchecked {
finalAmount -= feeAmount;
}
// Transfer net output to receiver
CurrencyLib.transfer(swapData.outputToken, receiver, finalAmount);
}
// Pre-fee / no-fee: tokens were sent directly to `receiver` by the swap router; nothing to transfer
emit RequestExecuted(quoteId);
}
/**
* @notice Perform swap and bridge with optional pre/post swap fee.
* @param quoteId Caller-defined correlation id logged in `RequestExecuted`.
* @param flags Packed flags
* @param input User, input token, and pull amount.
* @param fee Fee collection info: receiver and amount. Set `amount` to 0 to skip fee collection.
* @param swapData Swap target, spender, output token, value, `minOutput`, and returndata offset.
* @param swapCallData Calldata forwarded to `swapData.target`.
* @param bridgeData Bridge target, approval spender, and static `msg.value` addend.
* @param bridgeCallData Bridge calldata; optionally spliced with swap output per `flags`.
* @dev Same `minOutput` rule as `swap`: validated on gross `_execSwap` output, then optional output fee applies.
*/
function swapAndBridge(
bytes32 quoteId,
uint256 flags,
InputData calldata input,
FeeData calldata fee,
SwapData calldata swapData,
bytes calldata swapCallData,
BridgeData calldata bridgeData,
bytes calldata bridgeCallData
) external payable {
// Execute swap before bridge
uint256 finalAmount = _swapBeforeBridge(flags, input, fee, swapData, swapCallData);
// Execute bridge
_execBridge(swapData.outputToken, finalAmount, flags, bridgeData, bridgeCallData);
emit RequestExecuted(quoteId);
}
/**
* @notice Perform bridge with optional pre-bridge fee.
* @param quoteId Caller-defined correlation id logged in `RequestExecuted`.
* @param input User, input token, and pull amount.
* @param fee Fee collection info: receiver and amount. Set `amount` to 0 to skip fee collection.
* @param bridgeData Bridge target, approval spender, and `msg.value` for the bridge call.
* @param bridgeCallData Calldata forwarded to `bridgeData.target` (amount must be baked in by the caller).
* @dev Because no swap is involved, `finalAmount = inputAmount - feeAmount` is fully knowable by the caller before signing.
* The caller must therefore bake the correct amount directly into `bridgeCallData` and set `bridgeData.value` to the desired `msg.value` for the bridge call.
* No runtime calldata splicing is performed. The caller MUST route through `AllowanceHolder.exec` for ERC-20 inputs so that `_msgSender()` resolves to `input.user`.
*/
function bridge(
bytes32 quoteId,
InputData calldata input,
FeeData calldata fee,
BridgeData calldata bridgeData,
bytes calldata bridgeCallData
) external payable {
// Pull funds from user via AllowanceHolder
_pullFromUser(input.inputToken, input.user, input.inputAmount);
// Collect pre-bridge fee
uint256 feeAmount = fee.amount;
if (feeAmount != 0) {
CurrencyLib.transfer(input.inputToken, fee.receiver, feeAmount);
}
uint256 netAmount;
unchecked {
netAmount = input.inputAmount - feeAmount;
}
// Approve bridge spender
if (
// check spender && token
bridgeData.approvalSpender != address(0) && input.inputToken != CurrencyLib.NATIVE_TOKEN_ADDRESS &&
// check current allowance
netAmount > IERC20(input.inputToken).allowance(address(this), bridgeData.approvalSpender)
) {
// approve max allowance
SafeTransferLib.safeApproveWithRetry(input.inputToken, bridgeData.approvalSpender, type(uint256).max);
}
// Execute bridge
_execCallCalldata(bridgeData.target, bridgeData.value, bridgeCallData, false);
emit RequestExecuted(quoteId);
}
/**
* @notice Runs a sequence of generic actions with optional returndata splicing between steps.
* @param quoteId Caller-defined correlation id logged in `RequestExecuted`.
* @param actions Ordered actions; each may splice bytes from a prior action's returndata into its calldata.
*/
function performActions(bytes32 quoteId, Action[] calldata actions) external payable {
_performActions(actions);
emit RequestExecuted(quoteId);
}
// =========================================================================
// Internal functions
// =========================================================================
// -------------------------------------
// swapAndBridge internal functions
// -------------------------------------
/**
* @dev Pull, optional pre/post swap fee, and swap for `swapAndBridge`. Swap output always remains at `address(this)` for bridging.
* @param flags Fee timing and swap output measurement flags (same as `swap`).
* @param input User, input token, and pull amount.
* @param fee Fee receiver and amount; `amount == 0` skips fee collection.
* @param swapData Swap target, spender, output token, value, `minOutput`, and returndata offset.
* @param swapCallData Calldata forwarded to `swapData.target`.
* @return finalAmount Swap output net of any post-swap fee, ready for `_execBridge`.
*/
function _swapBeforeBridge(
uint256 flags,
InputData calldata input,
FeeData calldata fee,
SwapData calldata swapData,
bytes calldata swapCallData
) internal returns (uint256 finalAmount) {
// Pull funds from user via AllowanceHolder
_pullFromUser(input.inputToken, input.user, input.inputAmount);
bool postFee;
{
// Collect pre-swap fee
uint256 feeAmount = fee.amount;
postFee = feeAmount != 0 && ((flags & POST_FEE_FLAG_BIT_MASK) != 0);
uint256 swapInput = input.inputAmount;
if (feeAmount != 0 && !postFee) {
CurrencyLib.transfer(input.inputToken, fee.receiver, feeAmount);
unchecked {
swapInput -= feeAmount;
}
}
// Approve swap spender
if (
// check spender & token
swapData.approvalSpender != address(0) && input.inputToken != CurrencyLib.NATIVE_TOKEN_ADDRESS &&
// check current allowance
swapInput > IERC20(input.inputToken).allowance(address(this), swapData.approvalSpender)
) {
// approve max allowance
SafeTransferLib.safeApproveWithRetry(input.inputToken, swapData.approvalSpender, type(uint256).max);
}
}
// Execute swap
/// @dev Swap output always lands at this contract regardless of fee timing — tokens must be here for bridging.
bool useBalanceOf = ((flags & BALANCE_FLAG_BIT_MASK) != 0);
finalAmount = _execSwap(swapData, swapCallData, useBalanceOf, address(this));
if (finalAmount < swapData.minOutput) revert SwapOutputInsufficient();
// Collect post-swap fee
if (postFee) {
uint256 feeAmount = fee.amount;
CurrencyLib.transfer(swapData.outputToken, fee.receiver, feeAmount);
unchecked {
finalAmount -= feeAmount;
}
}
}
/**
* @dev Splice `amount` into bridge calldata when flagged, approve the bridge spender, and call the bridge target.
* @param token ERC-20 bridged (or native sentinel); used for approval only.
* @param amount Post-swap token amount spliced into calldata and/or forwarded as `msg.value`.
* @param flags Bridge splice position, `msg.value` composition, and related bit flags.
* @param bridgeData Bridge target, approval spender, and static `msg.value` addend.
* @param bridgeCallData Base bridge calldata; copied to memory when splicing is required.
*/
function _execBridge(
address token,
uint256 amount,
uint256 flags,
BridgeData calldata bridgeData,
bytes calldata bridgeCallData
) internal {
bytes memory _bridgeCallData = bridgeCallData;
// Modify bridge calldata if splicing is required
if (flags & BRIDGE_AMOUNT_POSITION_FLAG_BIT_MASK != 0) {
uint256 position = flags >> BRIDGE_AMOUNT_POSITION_SHIFT & BRIDGE_AMOUNT_POSITION_MASK;
BytesSpliceLib.spliceWord({data: _bridgeCallData, position: position, word: amount});
}
// Approve bridge spender
if (
// check spender & token
bridgeData.approvalSpender != address(0) && token != CurrencyLib.NATIVE_TOKEN_ADDRESS &&
// check current allowance
amount > IERC20(token).allowance(address(this), bridgeData.approvalSpender)
) {
// approve max allowance
SafeTransferLib.safeApproveWithRetry(token, bridgeData.approvalSpender, type(uint256).max);
}
// Parse and set bridge value flag
uint256 bridgeValue = ((flags & BRIDGE_VALUE_FLAG_BIT_MASK) != 0) ? amount + bridgeData.value : bridgeData.value;
// Execute bridge call
_execCall(bridgeData.target, bridgeValue, _bridgeCallData);
}
// --------------------------------------
// performActions internal functions
// --------------------------------------
/**
* @dev Executes `actions` in order, applying returndata splices before each call.
* @dev See `Action` for `actionInfo` and `splices[]` bit layouts.
* @param actions Ordered list of actions to run.
*/
function _performActions(Action[] calldata actions) internal {
uint256 actionsLength = actions.length;
bytes[] memory results = new bytes[](actionsLength);
for (uint256 i; i < actionsLength;) {
Action calldata action = actions[i];
bytes memory callData = action.data;
// Patch callData with slices of prior action returndata.
uint256 splicesLength = action.splices.length;
for (uint256 j; j < splicesLength;) {
uint256 spliceInfo = action.splices[j];
uint256 sourceActionIndex = uint64(spliceInfo); // first 64 bits: index of the prior action to read returndata from.
if (sourceActionIndex >= i) revert FutureSplice(i, sourceActionIndex);
uint256 srcOffset = uint64(spliceInfo >> 64); // Next 64 bits: byte offset into source returndata
uint256 dstOffset = uint64(spliceInfo >> 128); // Next 64 bits: byte offset into next action's data
uint256 length = spliceInfo >> 192; // Top 64 bits: number of bytes to copy
// Fetch source action returndata
bytes memory source = results[sourceActionIndex];
if (srcOffset + length > source.length || dstOffset + length > callData.length) {
revert SpliceOutOfBounds(i, j);
}
assembly ("memory-safe") {
// copy `length` bytes from `source returndata starting from `srcOffset` to `callData` starting from `dstOffset`
mcopy(add(add(callData, 0x20), dstOffset), add(add(source, 0x20), srcOffset), length)
}
unchecked {
++j;
}
}
// Parse actionInfo
bool success;
uint256 actionInfo = action.actionInfo;
bool storeResult = (actionInfo & 0xff00) != 0; // Bit 8: persist returndata if set
uint256 callType = actionInfo & 0xff; // Bits 0–7: specify CallType
address target = address(uint160(actionInfo >> 16)); // Bits 16+: target address
if (callType == uint256(CallType.STATICCALL)) {
assembly ("memory-safe") {
// staticcall without copying return data by default
success := staticcall(gas(), target, add(callData, 0x20), mload(callData), 0, 0)
}
} else if (callType == uint256(CallType.CALL_WITH_NATIVE)) {
if (callData.length < 32) revert MissingNativeValue(i);
uint256 callValue;
uint256 payloadLength = callData.length - 32;
assembly ("memory-safe") {
// regular call with value forwarded without copying return data by default
callValue := mload(add(callData, 0x20)) // CALL_WITH_NATIVE prepends a 32-byte wei amount before the actual calldata payload.
success := call(gas(), target, callValue, add(callData, 0x40), payloadLength, 0, 0) // skips first two bytes to reach actuall calldata
}
} else {
assembly ("memory-safe") {
// regular call with zero value forwarded without copying return data by default
success := call(gas(), target, 0, add(callData, 0x20), mload(callData), 0, 0)
}
}
// Capture returndata on failure (for revert reason) or when explicitly requested.
if (!success || storeResult) {
bytes memory ret;
assembly ("memory-safe") {
// prep return / revert data
let returnDataSize := returndatasize()
ret := mload(0x40)
mstore(ret, returnDataSize) // write length prefix on free-mem pointer
returndatacopy(add(ret, 0x20), 0, returnDataSize) // copy returndata after length
mstore(0x40, and(add(add(add(ret, 0x20), returnDataSize), 0x1f), not(0x1f))) // Advance free pointer to next 32-byte boundary: (ret + 0x20 + size + 31) and clear last 5 bits with not(0x1f)
}
// if any call was failed, revert with the returndata
if (!success) revert CallFailed(i, ret);
// else, save returndata to results array
results[i] = ret;
}
unchecked {
++i;
}
}
}
// -------------------------------
// Common internal functions
// -------------------------------
/**
* @dev Pulls `amount` of `token` from `user` into this contract.
* For ERC20: enforces `_msgSender() == user` (caller must have routed through `AllowanceHolder.exec`) and calls AH.transferFrom via assembly.
* AH selector: transferFrom(address,address,address,uint256) = 0x15dacbea.
* For native ETH: ETH must already be present as msg.value; verify sufficient value was forwarded.
* @param token Input token or `CurrencyLib.NATIVE_TOKEN_ADDRESS`.
* @param user Owner whose AllowanceHolder-scoped allowance is consumed.
* @param amount Tokens or wei to pull.
*/
function _pullFromUser(address token, address user, uint256 amount) internal {
// Check input value if native token
if (token == CurrencyLib.NATIVE_TOKEN_ADDRESS) {
if (msg.value < amount) {
revert InsufficientMsgValue();
}
return;
}
// Check caller is user
if (_msgSender() != user) revert CallerNotSignedUser();
// Call AllowanceHolder.transferFrom()
address allowanceHolder = address(ALLOWANCE_HOLDER);
assembly ("memory-safe") {
// Manually ABI-encode AllowanceHolder.transferFrom(address token, address owner, address recipient, uint256 amount)
// selector 0x15dacbea. Calldata is 0x84 (132) bytes and starts at ptr+0x1c (see last mstore below).
//
// The `shl(0x60, addr)` trick left-aligns a 20-byte address in a 32-byte word: the high 20 bytes
// hold the address and the trailing 12 bytes are zero, which simultaneously encodes the address AND
// provides the ABI zero-padding for the *next* field — so each shifted mstore clears the following
// field's padding without a separate write.
//
// Calldata layout relative to ptr+0x1c:
// [0..3] selector (0x15dacbea)
// [4..35] token (12-byte pad + 20-byte address)
// [36..67] owner/user (12-byte pad + 20-byte address)
// [68..99] recipient (12-byte pad + 20-byte address = address(this))
// [100..131] amount (uint256)
let ptr := mload(0x40)
mstore(add(0x80, ptr), amount) // calldata[100..131]: amount (uint256, right-aligned)
mstore(add(0x60, ptr), address()) // calldata[68..99]: recipient = this contract (right-aligned, high 12 bytes are zero padding)
mstore(add(0x4c, ptr), shl(0x60, user)) // calldata[48..67]: user address; trailing 12 zero bytes fill calldata[68..79] (recipient padding)
// `shl(0x60)` (96-bit), NOT `shl(0xa0)` (160-bit): 0xa0 here is literal 160, which
// shifts the 20-byte address out of place and corrupts the calldata token. Same as 0x-settler `Permit2Payment._allowanceHolderTransferFrom`.
mstore(add(0x2c, ptr), shl(0x60, token)) // calldata[16..35]: token address; trailing 12 zero bytes fill calldata[36..47] (user padding)
mstore(add(0x0c, ptr), 0x15dacbea000000000000000000000000) // selector at calldata[0..3]; 12 zero bytes fill calldata[4..15] (token padding); calldata begins at ptr+0x1c
if iszero(call(gas(), allowanceHolder, 0x00, add(0x1c, ptr), 0x84, 0x00, 0x00)) {
// if call did not succeed, revert with the revert returndata
let p := mload(0x40)
returndatacopy(p, 0x00, returndatasize())
revert(p, returndatasize())
}
}
}
/**
* @dev Executes the swap call and returns the output amount.
* `useBalanceOf=true`: measure output as (balance after − balance before) at `outputReceiver`.
* `useBalanceOf=false`: decode output from returndata at `swapData.returnDataWordOffset`.
* `outputReceiver` must be `address(this)` when tokens are expected at the contract (post-swap fee path, bridge path)
* or the end user when the router sends directly to them.
* @param swapData Swap target, value, output token, and returndata layout.
* @param swapCallData Calldata forwarded to `swapData.target`.
* @param useBalanceOf When true, use balance delta instead of returndata decoding.
* @param outputReceiver Account whose output-token balance is measured or credited.
* @return finalAmount Gross swap output amount.
*/
function _execSwap(
SwapData calldata swapData,
bytes calldata swapCallData,
bool useBalanceOf,
address outputReceiver
) internal returns (uint256 finalAmount) {
if (useBalanceOf) {
// Measure output as (balance after − balance before) at `outputReceiver`
uint256 before = CurrencyLib.balanceOf(swapData.outputToken, outputReceiver);
_execCallCalldata(swapData.target, swapData.value, swapCallData, false);
finalAmount = CurrencyLib.balanceOf(swapData.outputToken, outputReceiver) - before;
} else {
// Decode output from returndata
bytes memory ret = _execCallCalldata(swapData.target, swapData.value, swapCallData, true);
finalAmount = _decodeReturnWord(ret, swapData.returnDataWordOffset);
}
}
/**
* @dev Low-level `call` with bubbled revert data on failure.
* @param target Call recipient.
* @param value Wei forwarded with the call.
* @param data ABI-encoded calldata in memory.
*/
function _execCall(address target, uint256 value, bytes memory data) internal {
bool success;
assembly ("memory-safe") {
success := call(gas(), target, value, add(data, 0x20), mload(data), 0, 0)
}
if (!success) {
bytes memory ret;
assembly ("memory-safe") {
// prep and return revert data
let returnDataSize := returndatasize()
ret := mload(0x40)
mstore(ret, returnDataSize) // write length prefix on free-mem pointer
returndatacopy(add(ret, 0x20), 0, returnDataSize) // copy returndata after length
mstore(0x40, and(add(add(add(ret, 0x20), returnDataSize), 0x1f), not(0x1f))) // bump free pointer
revert(add(ret, 0x20), mload(ret)) // bubbles up the original revert payload
}
}
}
/**
* @dev Low-level `call` using calldata copied to memory; optionally captures returndata.
* @dev Helps cheaper external calls avoiding early copy of calldata to memory.
* @param target Call recipient.
* @param value Wei forwarded with the call.
* @param data Calldata slice forwarded to `target`.
* @param storeResult When true, copy returndata into memory even on success.
* @return ret Returndata when `storeResult` is true or the call reverts (revert bubbles).
*/
function _execCallCalldata(address target, uint256 value, bytes calldata data, bool storeResult)
internal
returns (bytes memory ret)
{
bool success;
assembly ("memory-safe") {
let ptr := mload(0x40)
calldatacopy(ptr, data.offset, data.length) // copy calldata slice to fresh memory (avoids redundant memory alloc)
mstore(0x40, and(add(add(ptr, data.length), 0x1f), not(0x1f))) // advance free pointer to next 32-byte boundary
success := call(gas(), target, value, ptr, data.length, 0, 0)
}
if (!success || storeResult) {
assembly ("memory-safe") {
// prep and return revert data
let returnDataSize := returndatasize()
ret := mload(0x40)
mstore(ret, returnDataSize) // write length prefix on free-mem pointer
returndatacopy(add(ret, 0x20), 0, returnDataSize) // copy returndata after length
mstore(0x40, and(add(add(add(ret, 0x20), returnDataSize), 0x1f), not(0x1f))) // bump free pointer
}
if (!success) {
assembly ("memory-safe") {
revert(add(ret, 0x20), mload(ret)) // bubble up the raw revert payload
}
}
}
}
/**
* @dev Reads the 32-byte word at `wordOffset` from ABI-encoded `ret` (word index, not byte offset).
* @param ret Return blob from a prior call.
* @param wordOffset Zero-based index of the 32-byte word to load.
* @return word Decoded amount or value at that offset.
*/
function _decodeReturnWord(bytes memory ret, uint256 wordOffset) internal pure returns (uint256 word) {
uint256 offset = wordOffset * 32;
if (offset + 32 > ret.length) revert ReturnDataOutOfBounds();
assembly ("memory-safe") {
// read the word at the offset from return data
word := mload(add(add(ret, 0x20), offset))
}
}
}
Chain explorer5667msChain node107ms