Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/modules/bridge/UnichainBridgeModule.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Contracts
import { OptimismStackBridgeModule } from "@modules/bridge/base/OptimismStackBridgeModule.sol";
// Interfaces
import { IUnichainBridgeModule } from "./interfaces/unichain/IUnichainBridgeModule.sol";
import { IModule } from "@modules/interfaces/IModule.sol";
// Libraries
import { BridgeLib } from "@modules/libraries/BridgeLib.sol";
// Types
import { Order } from "@types/Order.sol";
/// @title Unichain Bridge Module
/// @notice Bridge module implementation for Unichain's L1StandardBridge (Optimism Stack)
/// @dev Supports both ETH and ERC20 bridging to Unichain (L2)
contract UnichainBridgeModule is OptimismStackBridgeModule, IUnichainBridgeModule {
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @notice Constructor for UnichainBridgeModule
/// @param _portikusV2 The PortikusV2 contract address
/// @param _l1StandardBridge The L1StandardBridge contract address for Unichain
constructor(
address _portikusV2,
address _l1StandardBridge
)
OptimismStackBridgeModule("Unichain Bridge Module", "1.0.0", _portikusV2, _l1StandardBridge, "unichain")
{ }
/*//////////////////////////////////////////////////////////////
EXTERNAL
//////////////////////////////////////////////////////////////*/
/// @notice Bridges tokens using Unichain's L1StandardBridge
/// @dev This is the unique function signature for Unichain bridging
/// @param order The order containing bridge details
/// @param beneficiary The address of the beneficiary on Unichain (L2)
/// @param inputAmount The amount of tokens to bridge
function bridgeWithUnichain(
Order memory order,
address beneficiary,
uint256 inputAmount,
bytes memory /* bridgeData */
)
external
payable
{
// Require bridge access authorization from settlement module
BridgeLib.requireBridgeInitiated();
_bridgeInternal(order, beneficiary, inputAmount);
}
/// @inheritdoc IModule
function selectors() external pure override returns (bytes4[] memory moduleSelectors) {
moduleSelectors = new bytes4[](1);
moduleSelectors[0] = this.bridgeWithUnichain.selector;
}
}
"
},
"src/modules/bridge/base/OptimismStackBridgeModule.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Contracts
import { BridgeModuleBase } from "@modules/bridge/base/BridgeModuleBase.sol";
// Interfaces
import { IL1StandardBridge } from "@modules/bridge/interfaces/optimism/IL1StandardBridge.sol";
import { IOptimismStackBridgeModule } from "@modules/bridge/interfaces/optimism/IOptimismStackBridgeModule.sol";
// Libraries
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";
import { ERC20UtilsLib } from "@modules/libraries/ERC20UtilsLib.sol";
import { OrderHashLib } from "@modules/libraries/OrderHashLib.sol";
// Types
import { Order } from "@types/Order.sol";
/// @title Optimism Stack Bridge Module Base
/// @notice Base contract for Optimism Stack bridge modules (Optimism, Base, etc.)
/// @dev Supports both ETH and ERC20 bridging using L1StandardBridge
abstract contract OptimismStackBridgeModule is BridgeModuleBase {
using SafeTransferLib for address;
using ERC20UtilsLib for address;
using OrderHashLib for Order;
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/// @notice Minimum gas limit for L2 transactions (default safety buffer)
uint32 private constant MIN_GAS_LIMIT = 200_000;
/// @notice The L1StandardBridge contract address
address public immutable L1_STANDARD_BRIDGE;
/// @notice The bridge protocol name for event emission
string private BRIDGE_PROTOCOL;
/// @notice Thrown when the declared destination token does not match the L1 token being bridged
error InvalidDestinationToken();
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @notice Constructor for OptimismStackBridgeModule
/// @param _name The module name
/// @param _version The module version
/// @param _portikusV2 The PortikusV2 contract address
/// @param _l1StandardBridge The L1StandardBridge contract address
/// @param _bridgeProtocol The bridge protocol name (e.g., "optimism", "base")
constructor(
string memory _name,
string memory _version,
address _portikusV2,
address _l1StandardBridge,
string memory _bridgeProtocol
)
BridgeModuleBase(_name, _version, _portikusV2)
{
L1_STANDARD_BRIDGE = _l1StandardBridge;
BRIDGE_PROTOCOL = _bridgeProtocol;
}
/*//////////////////////////////////////////////////////////////
INTERNAL
//////////////////////////////////////////////////////////////*/
/// @notice Internal bridge implementation for Optimism Stack chains
/// @param order The order containing bridge details
/// @param beneficiary The address of the beneficiary on L2
/// @param bridgeAmount The amount of tokens to bridge
function _bridgeInternal(Order memory order, address beneficiary, uint256 bridgeAmount) internal {
IOptimismStackBridgeModule.OptimismStackBridgeData memory protocolData = _validateAndDecodeProtocolData(order);
// Handle ETH vs ERC20 bridging based on destination token
if (order.destToken.isEth()) {
_bridgeETH(protocolData, order, beneficiary, bridgeAmount);
} else {
_bridgeERC20(protocolData, order, beneficiary, bridgeAmount);
}
}
/// @notice Bridges ETH to L2
/// @param protocolData The validated bridge configuration
/// @param order The order containing bridge details
/// @param beneficiary The address of the beneficiary on L2
/// @param amount The amount of ETH to bridge
function _bridgeETH(
IOptimismStackBridgeModule.OptimismStackBridgeData memory protocolData,
Order memory order,
address beneficiary,
uint256 amount
)
internal
{
// Note: No msg.value validation needed for OptimismStack bridges
// - OptimismStack has no separate native fees (unlike Stargate/Arbitrum)
// Call L1StandardBridge to deposit ETH
IL1StandardBridge(L1_STANDARD_BRIDGE).depositETHTo{ value: amount }(
beneficiary, protocolData.minGasLimit, protocolData.extraData
);
// Emit the bridge event with destination-decimal output amount
emit TokensBridged(
order.hash(),
beneficiary,
order.bridge.destinationChainId,
amount,
_convertToDestDecimals(amount, order.bridge.scalingFactor),
BRIDGE_PROTOCOL
);
}
/// @notice Bridges ERC20 tokens to L2
/// @param protocolData The validated bridge configuration
/// @param order The order containing bridge details
/// @param beneficiary The address of the beneficiary on L2
/// @param amount The amount of tokens to bridge
function _bridgeERC20(
IOptimismStackBridgeModule.OptimismStackBridgeData memory protocolData,
Order memory order,
address beneficiary,
uint256 amount
)
internal
{
// Validate protocol data
if (protocolData.l1Token == address(0)) revert IOptimismStackBridgeModule.InvalidL1Token();
if (protocolData.l2Token == address(0)) revert IOptimismStackBridgeModule.InvalidL2Token();
// Ensure the declared destination token matches the L1 token being bridged
// Prevents accidental bridging of a different token while accounting as if destToken
if (order.destToken != protocolData.l1Token) revert InvalidDestinationToken();
// Approve the L1StandardBridge to spend tokens
protocolData.l1Token.safeApprove(L1_STANDARD_BRIDGE, amount);
// Call L1StandardBridge to deposit ERC20 tokens
IL1StandardBridge(L1_STANDARD_BRIDGE).depositERC20To(
protocolData.l1Token,
protocolData.l2Token,
beneficiary,
amount,
protocolData.minGasLimit,
protocolData.extraData
);
// Emit the bridge event with destination-decimal output amount
emit TokensBridged(
order.hash(),
beneficiary,
order.bridge.destinationChainId,
amount,
_convertToDestDecimals(amount, order.bridge.scalingFactor),
BRIDGE_PROTOCOL
);
}
/// @notice Validates and decodes the protocol data from the order
/// @param order The order containing the protocol data to validate
/// @return protocolData The validated and decoded bridge data
function _validateAndDecodeProtocolData(Order memory order)
internal
pure
returns (IOptimismStackBridgeModule.OptimismStackBridgeData memory protocolData)
{
// Validate protocol data exists
if (order.bridge.protocolData.length == 0) revert MissingProtocolData();
// Decode protocol data
protocolData = abi.decode(order.bridge.protocolData, (IOptimismStackBridgeModule.OptimismStackBridgeData));
// Validate decoded data
if (protocolData.minGasLimit < MIN_GAS_LIMIT) revert IOptimismStackBridgeModule.InsufficientGasLimit();
}
}
"
},
"src/modules/bridge/interfaces/unichain/IUnichainBridgeModule.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Interfaces
import { IOptimismStackBridgeModule } from "@modules/bridge/interfaces/optimism/IOptimismStackBridgeModule.sol";
// Types
import { Order } from "@types/Order.sol";
/// @title Unichain Bridge Module Interface
/// @notice Interface for Unichain's L1StandardBridge protocol (Optimism Stack)
interface IUnichainBridgeModule is IOptimismStackBridgeModule {
/*//////////////////////////////////////////////////////////////
EXTERNAL
//////////////////////////////////////////////////////////////*/
/// @notice Bridges tokens using Unichain's L1StandardBridge
/// @param order The order containing bridge details
/// @param beneficiary The address of the beneficiary on Unichain (L2)
/// @param inputAmount The amount of tokens to bridge
/// @param bridgeData Agent-provided bridge execution data (unused for Unichain)
function bridgeWithUnichain(
Order memory order,
address beneficiary,
uint256 inputAmount,
bytes memory bridgeData
)
external
payable;
}
"
},
"src/modules/interfaces/IModule.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
/// @title IModule
/// @notice Core interfaces that all modules must implement to be compatible with the Portikus protocol
interface IModule {
/*//////////////////////////////////////////////////////////////
METADATA
//////////////////////////////////////////////////////////////*/
/// @notice Returns the name of the module
function name() external view returns (string memory);
/// @notice Returns the version of the module
function version() external view returns (string memory);
/*//////////////////////////////////////////////////////////////
SELECTORS
//////////////////////////////////////////////////////////////*/
/// @notice Used by the executor to determine which functions should be installed
/// @dev The implementation should not include any of the function selectors defined in the IModule interface itself
/// @return moduleSelectors An array of function selectors that the module implements
function selectors() external pure returns (bytes4[] memory moduleSelectors);
}
"
},
"src/modules/libraries/BridgeLib.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { Address } from "@openzeppelin/utils/Address.sol";
// Types
import { Order } from "@types/Order.sol";
/// @title Bridge Library
/// @dev A library that provides functions for bridging tokens using various protocols
library BridgeLib {
using Address for address;
/*//////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
/// @notice keccak256(abi.encode(uint256(keccak256("BridgeLib.initiated")) - 1)) & ~bytes32(uint256(0xff));
bytes32 internal constant BRIDGE_INITIATED_SLOT = 0x55784d688e8aecdf5908e67d2532e5fa1b0a3f379bbfebaef09e5fc7c0e69100;
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when bridge is not initiated
error BridgeNotInitiated();
/*//////////////////////////////////////////////////////////////
BRIDGE INITIATION
//////////////////////////////////////////////////////////////*/
/// @notice Initiates a bridge operation to allow bridge calls
/// @dev Should only be called by settlement modules after proper validation
function initiateBridge() internal {
bytes32 slot = BRIDGE_INITIATED_SLOT;
/// @solidity memory-safe-assembly
assembly {
sstore(slot, 1)
}
}
/// @notice Completes a bridge operation to prevent further bridge calls
/// @dev Should be called after bridge execution is complete
function completeBridge() internal {
bytes32 slot = BRIDGE_INITIATED_SLOT;
/// @solidity memory-safe-assembly
assembly {
sstore(slot, 0)
}
}
/// @notice Checks if bridge operation is currently initiated
/// @return initiated True if bridge is initiated, false otherwise
function isBridgeInitiated() internal view returns (bool initiated) {
bytes32 slot = BRIDGE_INITIATED_SLOT;
/// @solidity memory-safe-assembly
assembly {
initiated := sload(slot)
}
}
/// @notice Modifier function to require bridge initiation
/// @dev Should be called at the beginning of bridge functions
function requireBridgeInitiated() internal view {
if (!isBridgeInitiated()) {
revert BridgeNotInitiated();
}
}
/*//////////////////////////////////////////////////////////////
UNIVERSAL BRIDGE
//////////////////////////////////////////////////////////////*/
/// @notice Universal bridge function that handles dynamic routing to different bridge protocols
/// @dev Bridge protocol is determined by order.bridge.protocolSelector (set by protocol backend)
/// @param order The order containing bridge details with protocolSelector
/// @param beneficiary The address of the beneficiary
/// @param amount The amount of tokens to bridge
/// @param bridgeData The encoded protocol-specific bridge data containing agent-provided execution parameters
/// @return nativeFee The native fee paid to the bridge protocol, 0 if not applicable
function doBridge(
Order memory order,
address beneficiary,
uint256 amount,
bytes memory bridgeData
)
internal
returns (uint256 nativeFee)
{
// Initiate bridge operation before making the bridge call
initiateBridge();
// Call the bridge function using the protocol selector from the order (set by backend)
// bridgeData is passed directly as protocol-specific data
// Use OpenZeppelin's Address library for better error handling and revert reason bubbling
bytes memory result = address(this).functionDelegateCall(
abi.encodeWithSelector(order.bridge.protocolSelector, order, beneficiary, amount, bridgeData)
);
if (result.length != 0) nativeFee = abi.decode(result, (uint256));
// Complete bridge operation after the call
completeBridge();
}
/*//////////////////////////////////////////////////////////////
BRIDGE UTILITIES
//////////////////////////////////////////////////////////////*/
/// @notice Checks if the given order requires bridging
/// @param order The order to check
/// @return bool True if the order is a bridge order, false otherwise
function isBridge(Order memory order) internal view returns (bool) {
return order.bridge.destinationChainId != 0 && order.bridge.destinationChainId != block.chainid;
}
}
"
},
"src/types/Order.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { Bridge } from "./Bridge.sol";
/// @dev OrderKind enum to differentiate between sell and buy orders
enum OrderKind {
SELL,
BUY
}
/// @dev Order data structure containing the order details to be settled,
/// the order is signed by the owner to be executed by an agent
/// on behalf of the owner, executing the swap and transferring the
/// dest token to the beneficiary
struct Order {
/// @dev The address of the order owner
address owner;
/// @dev The address of the order beneficiary
address beneficiary;
/// @dev The address of the src token
address srcToken;
/// @dev The address of the dest token
address destToken;
/// @dev The amount of src token to swap
uint256 srcAmount;
/// @dev The minimum amount of dest token to receive
uint256 destAmount;
/// @dev The expected amount of tokens (for sell orders this is expected amount of dest token to receive, for buy
/// orders this is the expected amount of src token to spend)
uint256 expectedAmount;
/// @dev The deadline for the order (timestamp)
uint256 deadline;
/// @dev The type of order (SELL or BUY)
OrderKind kind;
/// @dev The nonce of the order
uint256 nonce;
/// @dev Encoded partner address, fee bps, and flags for the order
/// partnerAndFee = (partner << 96) | (partnerTakesSurplus << 8) | (capSurplus << 9) |
/// fee in bps (max fee is 2%)
uint256 partnerAndFee;
/// @dev Optional permit signature for the src token
bytes permit;
/// @dev Optional metadata for the order
bytes metadata;
/// @dev The bridge configuration, should contain all empty fields if cross-chain is not required
Bridge bridge;
}
/// @dev Order with signature
struct OrderWithSig {
/// @dev The order data
Order order;
/// @dev The signature of the order
bytes signature;
}
"
},
"src/modules/bridge/base/BridgeModuleBase.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Contracts
import { BaseModule } from "@modules/base/BaseModule.sol";
// Libraries
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";
import { ERC20UtilsLib } from "@modules/libraries/ERC20UtilsLib.sol";
/// @title Bridge Module Base
/// @notice Abstract base contract for bridge modules containing shared functionality
/// @dev Provides common patterns, validations, and utilities for bridge implementations
abstract contract BridgeModuleBase is BaseModule {
using SafeTransferLib for address;
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when protocol data is missing or empty
error MissingProtocolData();
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when tokens are bridged across chains
/// @param orderHash The hash of the order being bridged
/// @param beneficiary The recipient on the destination chain
/// @param destChainId The destination chain ID
/// @param inputAmount The amount of tokens being bridged (expressed in source token decimals)
/// @param outputAmount The expected amount on the destination chain, expressed in destination token decimals
/// after applying `_convertToDestDecimals`
/// @param bridgeProtocol The bridge protocol used (e.g., "optimism", "polygon", "cctp")
event TokensBridged(
bytes32 indexed orderHash,
address indexed beneficiary,
uint256 indexed destChainId,
uint256 inputAmount,
uint256 outputAmount,
string bridgeProtocol
);
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @notice Constructor for BridgeModuleBase
/// @param _name The module name
/// @param _version The module version
/// @param _portikusV2 The PortikusV2 contract address
constructor(
string memory _name,
string memory _version,
address _portikusV2
)
BaseModule(_name, _version, _portikusV2)
{ }
/*//////////////////////////////////////////////////////////////
SHARED UTILITIES
//////////////////////////////////////////////////////////////*/
/// @notice Calculates agent fee with cap protection
/// @param requestedFee The requested agent fee
/// @param maxFee The maximum allowed fee
/// @return The capped agent fee
function _calculateCappedFee(uint256 requestedFee, uint256 maxFee) internal pure returns (uint256) {
return requestedFee > maxFee ? maxFee : requestedFee;
}
/*//////////////////////////////////////////////////////////////
DECIMAL CONVERSION
//////////////////////////////////////////////////////////////*/
/// @notice Converts an amount from source token decimals to destination token decimals
/// @dev scalingFactor = destDecimals - srcDecimals
///
/// Behavior:
/// - scalingFactor > 0 (dest has more decimals than src): multiply by 10^scalingFactor
/// - scalingFactor < 0 (dest has fewer decimals than src): divide by 10^(-scalingFactor)
///
/// Constraints:
/// - |scalingFactor| <= 18 to avoid 10^N overflow and keep gas predictable
///
/// Examples:
/// - USDC (6) -> WETH (18): scalingFactor = +12; 1e6 -> 1e6 * 10^12 = 1e18
/// - WETH (18) -> USDC (6): scalingFactor = -12; 1e18 -> 1e18 / 10^12 = 1e6
///
/// @param amount Amount expressed in source token decimals
/// @param scalingFactor Signed decimal difference (destDecimals - srcDecimals)
/// @return Amount expressed in destination token decimals
function _convertToDestDecimals(uint256 amount, int8 scalingFactor) internal pure returns (uint256) {
if (scalingFactor == 0) return amount;
// Validate scaling factor to prevent overflow
int8 absFactor = scalingFactor >= 0 ? scalingFactor : -scalingFactor;
require(uint8(absFactor) <= 18, "Bridge: scaling factor too large");
uint256 scale = 10 ** uint8(absFactor);
if (scalingFactor > 0) {
// destDecimals > srcDecimals: multiply to convert src -> dest
return amount * scale;
} else {
// srcDecimals > destDecimals: divide to convert src -> dest
return amount / scale;
}
}
}
"
},
"src/modules/bridge/interfaces/optimism/IL1StandardBridge.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
/// @title L1 Standard Bridge Interface
/// @notice Interface for Optimism's L1StandardBridge contract
/// @dev Based on Optimism's official L1StandardBridge implementation
interface IL1StandardBridge {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when ETH is deposited from L1 to L2
/// @param from Address that deposited ETH on L1
/// @param to Address that will receive ETH on L2
/// @param amount Amount of ETH deposited
/// @param extraData Extra data attached to the deposit
event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData);
/// @notice Emitted when ERC20 tokens are deposited from L1 to L2
/// @param l1Token L1 token address
/// @param l2Token L2 token address
/// @param from Address that deposited tokens on L1
/// @param to Address that will receive tokens on L2
/// @param amount Amount of tokens deposited
/// @param extraData Extra data attached to the deposit
event ERC20DepositInitiated(
address indexed l1Token,
address indexed l2Token,
address indexed from,
address to,
uint256 amount,
bytes extraData
);
/*//////////////////////////////////////////////////////////////
ETH BRIDGING
//////////////////////////////////////////////////////////////*/
/// @notice Deposits ETH to the caller's address on L2
/// @param _minGasLimit Minimum gas limit for the L2 transaction
/// @param _extraData Optional data to forward to L2 (usually empty)
function depositETH(uint32 _minGasLimit, bytes calldata _extraData) external payable;
/// @notice Deposits ETH to a specified address on L2
/// @param _to Address to receive ETH on L2
/// @param _minGasLimit Minimum gas limit for the L2 transaction
/// @param _extraData Optional data to forward to L2 (usually empty)
function depositETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) external payable;
/*//////////////////////////////////////////////////////////////
ERC20 BRIDGING
//////////////////////////////////////////////////////////////*/
/// @notice Deposits ERC20 tokens to the caller's address on L2
/// @param _l1Token L1 token address
/// @param _l2Token Corresponding L2 token address
/// @param _amount Amount of tokens to deposit
/// @param _minGasLimit Minimum gas limit for the L2 transaction
/// @param _extraData Optional data to forward to L2 (usually empty)
function depositERC20(
address _l1Token,
address _l2Token,
uint256 _amount,
uint32 _minGasLimit,
bytes calldata _extraData
)
external;
/// @notice Deposits ERC20 tokens to a specified address on L2
/// @param _l1Token L1 token address
/// @param _l2Token L2 token address
/// @param _to Address to receive tokens on L2
/// @param _amount Amount of tokens to deposit
/// @param _minGasLimit Minimum gas limit for the L2 transaction
/// @param _extraData Optional data to forward to L2 (usually empty)
function depositERC20To(
address _l1Token,
address _l2Token,
address _to,
uint256 _amount,
uint32 _minGasLimit,
bytes calldata _extraData
)
external;
/*//////////////////////////////////////////////////////////////
UTILITIES
//////////////////////////////////////////////////////////////*/
/// @notice Returns the L2 token bridge address
/// @return L2 token bridge address
function l2TokenBridge() external view returns (address);
/// @notice Returns the messenger contract address
/// @return Messenger contract address
function messenger() external view returns (address);
}
"
},
"src/modules/bridge/interfaces/optimism/IOptimismStackBridgeModule.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Types
import { Order } from "@types/Order.sol";
/// @title Optimism Stack Bridge Module Interface
/// @notice Common interface for all Optimism Stack-based bridge modules (Optimism, Base, etc.)
interface IOptimismStackBridgeModule {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when L1 token address is invalid (zero address)
error InvalidL1Token();
/// @notice Thrown when L2 token address is invalid (zero address)
error InvalidL2Token();
/// @notice Thrown when gas limit is too low for L2 execution
error InsufficientGasLimit();
/// @notice Thrown when srcToken doesn't match l1Token in protocol data
error TokenMismatch();
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
/// @notice Protocol-controlled Optimism Stack bridge configuration
/// @dev This struct defines the data encoded in order.bridge.protocolData by the backend
/// These parameters cannot be manipulated by agents during execution
/// @dev Used by all Optimism Stack chains (Optimism, Base, Arbitrum One, Polygon, etc.)
struct OptimismStackBridgeData {
/// @dev The L1 token address (source chain)
address l1Token;
/// @dev The corresponding L2 token address (destination chain)
address l2Token;
/// @dev Minimum gas limit for L2 transaction execution
uint32 minGasLimit;
/// @dev Optional calldata for L2 execution (usually empty)
bytes extraData;
}
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when tokens are successfully bridged to an Optimism Stack L2
/// @param order The order that initiated the bridge
/// @param beneficiary The recipient address on L2
/// @param inputAmount The amount of tokens bridged
/// @param l1Token The L1 token address
/// @param l2Token The L2 token address
/// @param destinationChainId The L2 chain ID
event TokensBridged(
Order indexed order,
address indexed beneficiary,
uint256 inputAmount,
address indexed l1Token,
address l2Token,
uint256 destinationChainId
);
}
"
},
"lib/solady/src/utils/SafeTransferLib.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
// Check the `extcodesize` again just in case the token selfdestructs lol.
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// If the initial attempt fails, try to use Permit2 to transfer the token.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
/// Reverts upon failure.
function permit2TransferFrom(address token, address from, address to, uint256 amount)
internal
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.
mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists := eq(chainid(), 1)
if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
if iszero(
and(
call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00),
lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists.
)
) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of
/// another user's tokens via native EIP-2612 permit if possible, falling
/// back to Permit2 if native permit fails or is not implemented on the token.
function permit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
// Gas stipend to limit gas burn for tokens that don't refund gas when
// an non-existing function is called. 5K should be enough for a SLOAD.
staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.
let m := mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
// `nonces` is already at `add(m, 0x54)`.
// `1` is already stored at `add(m, 0x94)`.
mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.
function simplePermit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask := shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p := mul(PERMIT2, iszero(shr(160, amount)))
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
// `owner` is already `add(m, 0x20)`.
// `token` is already at `add(m, 0x40)`.
mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
// `nonce` is already at `add(m, 0xa0)`.
// `spender` is already at `add(m, 0xc0)`.
mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.
mstore(add(m, 0x120), 0x41) // `signature` length.
mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
if iszero( // Revert if token does not have code, or if the call fails.
mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
}
"
},
"src/modules/libraries/ERC20UtilsLib.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Libraries
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";
/// @title ERC20 Utility Library
/// @dev Library with common functions used by different modules within the Portikus V2 protocol
library ERC20UtilsLib {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Emitted when the permit execution fails
error PermitFailed();
/*//////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////*/
/// @dev An address used to represent the native token
address internal constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev The address of the Permit2 contract
address internal constant PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*//////////////////////////////////////////////////////////////
LIBRARIES
//////////////////////////////////////////////////////////////*/
using SafeTransferLib for address;
/*//////////////////////////////////////////////////////////////
TRANSFER
//////////////////////////////////////////////////////////////*/
/// @dev Transfer the dest token to the order beneficiary, reducing the amount by 1 wei for gas optimization
/// purposes. If the destToken is ETH, it will transfer native ETH to recipient
/// @param destToken The address of the dest token
/// @param recipient The address to transfer to
/// @param amount The amount to transfer
function transferTo(address destToken, address recipient, uint256 amount) internal {
// If the destToken is ETH, transfer native ETH
if (isEth(destToken)) {
recipient.safeTransferETH(amount);
} else {
// Otherwise, transfer the dest token to the recipient, reducing the amount by 1 wei
// for gas optimization purposes
destToken.safeTransfer(recipient, amount);
}
}
/// @dev Transfer the src token from the user to a recipient, using permit2 allowance or erc20 allowance based on
/// permit length, if the permit length is 192 or 1 it will call transferFrom using permit2
/// allowance, otherwise it will call transferFrom using erc20 allowance unless permit length is 96
/// @param srcToken The address of the src token
/// @param owner The owner of the token
/// @param recipient The recipient of the token
/// @param amount The amount to transfer
/// @param permitLength The length of the permit
function transferFrom(
address srcToken,
address owner,
address recipient,
uint256 amount,
uint256 permitLength
)
internal
{
// Skip transferring if the permit length is 96 (permit2TransferFrom)
if (permitLength == 96) {
return;
}
// If the permit length is 192 or 1 execute permit2TransferFrom to transfer the
// input assets from the owner to the agent, otherwise execute ERC20 transferFrom
if (permitLength == 192 || permitLength == 1) {
// Transfer the input assets from the owner to the recipient using permit2 allowance
srcToken.permit2TransferFrom(owner, recipient, amount);
} else {
// Transfer the input assets from the owner to the recipient using the ERC20 transferFrom function
srcToken.safeTransferFrom(owner, recipient, amount);
}
}
/*//////////////////////////////////////////////////////////////
PERMIT
//////////////////////////////////////////////////////////////*/
/// @dev Executes the permit function on the provided token, depending on the permit length,
/// it will call the EIP2612 permit (224), DAI-Style permit (256), Permit2 AllowanceTransfer (192)
/// or Permit2 Signature (96)
/// @param token The address of the token
/// @param data The permit data
/// @param owner The owner of the token (used for Permit2)
/// @param deadline The deadline for the permit (used for Permit2)
/// @param amount The amount to permit (used for Permit2)
function permit(
address token,
bytes calldata data,
address owner,
uint256 deadline,
uint256 amount,
address recipient
)
internal
{
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
// check the permit length
switch data.length
// 0x00 = no permit
case 0x00 {
// do nothing
}
case 0x01 {
// 0x01 is used to signify already existing permit2 allowance
}
// 32(permit2nonce) + 64(signature) = 96 Permit2 Transfer format
case 0x60 {
let x := mload(0x40) // get the free memory pointer
mstore(x, 0x30f28b7a00000000000000000000000000000000000000000000000000000000) // store the selector
mstore(add(x, 0x04), token) // store the srcToken
mstore(add(x, 0x24), amount) // store the amount
calldatacopy(add(x, 0x44), data.offset, 0x20) // copy the nonce
mstore(add(x, 0x64), deadline) // store the deadline
mstore(add(x, 0x84), recipient) // store the recipient address (executor)
mstore(add(x, 0xa4), amount) // store the amount
mstore(add(x, 0xc4), owner) // store the owner
mstore(add(x, 0xe4), 0x100) // store the offset
mstore(add(x, 0x104), 0x40) // store the length
calldatacopy(add(x, 0x124), add(data.offset, 0x20), 0x40) // copy the signature
// Call Permit2 contract and revert on failure
if iszero(call(gas(), PERMIT2_ADDRESS, 0x00, x, 0x164, 0x00, 0x00)) {
mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the
// selector
revert(0x00, 0x04) // Revert with PermitFailed error
}
}
// 32(amount) + 32(nonce) + 32(expiration) + 32(sigDeadline) + 64(signature) = 192 Permit2 allowance format
case 0xc0 {
let x := mload(0x40) // get the free memory pointer
mstore(x, 0x2b67b57
Submitted on: 2025-10-17 12:03:13
Comments
Log in to comment.
No comments yet.