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/PolygonBridgeModule.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Modules
import { BridgeModuleBase } from "@modules/bridge/base/BridgeModuleBase.sol";
// Interfaces
import { IPolygonBridgeModule } from "@modules/bridge/interfaces/polygon/IPolygonBridgeModule.sol";
import { IRootChainManager } from "@modules/bridge/interfaces/polygon/IRootChainManager.sol";
import { IModule } from "@modules/interfaces/IModule.sol";
// Libraries
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";
import { ERC20UtilsLib } from "@modules/libraries/ERC20UtilsLib.sol";
import { OrderHashLib } from "@modules/libraries/OrderHashLib.sol";
import { BridgeLib } from "@modules/libraries/BridgeLib.sol";
// Types
import { Order } from "@types/Order.sol";
/// @title Polygon Bridge Module
/// @notice Bridge module implementation for Polygon's PoS Bridge
/// @dev Supports ERC20 and ETH bridging to Polygon using RootChainManager
contract PolygonBridgeModule is BridgeModuleBase, IPolygonBridgeModule {
using SafeTransferLib for address;
using ERC20UtilsLib for address;
using OrderHashLib for Order;
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
/// @dev The RootChainManager contract address on Ethereum
IRootChainManager public immutable ROOT_CHAIN_MANAGER;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @notice Constructor for PolygonBridgeModule
/// @param _portikusV2 The PortikusV2 contract address
/// @param _rootChainManager The RootChainManager contract address on Ethereum
constructor(
address _portikusV2,
address _rootChainManager
)
BridgeModuleBase("Polygon Bridge Module", "1.0.0", _portikusV2)
{
ROOT_CHAIN_MANAGER = IRootChainManager(_rootChainManager);
}
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Bridges tokens to Polygon using PoS Bridge
/// @dev This is the unique function signature for Polygon bridging
/// @param order The order containing bridge details
/// @param beneficiary The address of the beneficiary on Polygon
/// @param inputAmount The amount of tokens to bridge
/// @param // bridgeData Agent-provided bridge execution data
function bridgeWithPolygon(
Order memory order,
address beneficiary,
uint256 inputAmount,
bytes memory /* bridgeData */
)
external
payable
{
// Require bridge access authorization from settlement module
BridgeLib.requireBridgeInitiated();
// Perform the internal bridge operation
_bridgeInternal(order, beneficiary, inputAmount);
}
/*//////////////////////////////////////////////////////////////
SELECTORS
//////////////////////////////////////////////////////////////*/
/// @inheritdoc IModule
function selectors() external pure override returns (bytes4[] memory moduleSelectors) {
moduleSelectors = new bytes4[](1);
moduleSelectors[0] = this.bridgeWithPolygon.selector;
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Internal bridge implementation for Polygon
/// @param order The order containing bridge details
/// @param beneficiary The address of the beneficiary on Polygon
/// @param bridgeAmount The amount of tokens to bridge
function _bridgeInternal(Order memory order, address beneficiary, uint256 bridgeAmount) internal {
// Note: Settlement modules handle all msg.value validation and refunding
// Bridge modules should not validate msg.value due to delegateCall context
if (order.destToken.isEth()) {
ROOT_CHAIN_MANAGER.depositEtherFor{ value: bridgeAmount }(beneficiary);
} else {
// Validate and decode protocol data
PolygonBridgeData memory protocolData = _validateAndDecodeProtocolData(order);
_bridgeERC20(order.destToken, beneficiary, bridgeAmount, protocolData.predicate);
}
// Emit the bridge event with destination-decimal output amount
emit TokensBridged(
order.hash(),
beneficiary,
order.bridge.destinationChainId,
bridgeAmount,
_convertToDestDecimals(bridgeAmount, order.bridge.scalingFactor),
"polygon"
);
}
/// @notice Bridge ERC20 tokens to Polygon
/// @param rootToken The root token address on Ethereum
/// @param beneficiary The address of the beneficiary on Polygon
/// @param amount The amount of tokens to bridge
/// @param predicate The predicate address for the bridge
function _bridgeERC20(address rootToken, address beneficiary, uint256 amount, address predicate) internal {
// Use predicate from protocol data (more efficient than dynamic lookup)
// If predicate is zero address, fallback to dynamic lookup
if (predicate == address(0)) {
bytes32 tokenType = ROOT_CHAIN_MANAGER.tokenToType(rootToken);
predicate = ROOT_CHAIN_MANAGER.typeToPredicate(tokenType);
}
// Approve the predicate contract to spend tokens (not RootChainManager)
SafeTransferLib.safeApprove(rootToken, predicate, amount);
// Perform the deposit, depositData must be abi.encode(amount)
ROOT_CHAIN_MANAGER.depositFor(beneficiary, rootToken, abi.encode(amount));
}
/// @notice Validates and decodes the protocol data from an order
/// @param order The order containing the protocol data
/// @return protocolData The decoded PolygonBridgeData struct
function _validateAndDecodeProtocolData(Order memory order)
internal
pure
returns (PolygonBridgeData memory protocolData)
{
if (order.bridge.protocolData.length == 0) {
revert MissingProtocolData();
}
protocolData = abi.decode(order.bridge.protocolData, (PolygonBridgeData));
// Validate childToken (settlement modules already validate destToken)
if (protocolData.childToken == address(0)) {
revert InvalidChildToken();
}
}
}
"
},
"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/polygon/IPolygonBridgeModule.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Types
import { Order } from "@types/Order.sol";
/// @title Polygon Bridge Module Interface
/// @notice Interface for Polygon bridge module using PoS Bridge
interface IPolygonBridgeModule {
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when child token address is invalid (zero address)
error InvalidChildToken();
/*//////////////////////////////////////////////////////////////
DATA STRUCTURES
//////////////////////////////////////////////////////////////*/
/// @notice Data structure for Polygon bridge operations
/// @dev rootToken is derived from order.destToken, so not included here
struct PolygonBridgeData {
/// @dev The child token address on Polygon (different from root token address)
address childToken;
/// @dev The predicate address for this token type (can be zero for dynamic lookup)
address predicate;
}
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Bridges tokens to Polygon using PoS Bridge
/// @param order The order containing bridge details
/// @param beneficiary The address of the beneficiary on Polygon
/// @param inputAmount The amount of tokens to bridge
/// @param bridgeData Agent-provided bridge execution data
function bridgeWithPolygon(
Order memory order,
address beneficiary,
uint256 inputAmount,
bytes memory bridgeData
)
external
payable;
}
"
},
"src/modules/bridge/interfaces/polygon/IRootChainManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
/// @title Root Chain Manager Interface
/// @notice Simplified interface for Polygon's RootChainManager contract
interface IRootChainManager {
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Deposits tokens for a user on the root chain to be minted on child chain
/// @param rootToken The address of the token on the root chain
/// @param depositData ABI encoded deposit data containing recipient and amount
function depositFor(address user, address rootToken, bytes calldata depositData) external payable;
/// @notice Deposits ETH for a user on the root chain to be minted as WETH on child chain
/// @param user The address of the user on the child chain
function depositEtherFor(address user) external payable;
/// @notice Maps a root token to its child token
/// @param rootToken The address of the token on the root chain
/// @return childToken The address of the corresponding token on the child chain
function rootToChildToken(address rootToken) external view returns (address childToken);
/// @notice Gets the token type for a given token
/// @param rootToken The address of the token on the root chain
/// @return tokenType The token type identifier
function tokenToType(address rootToken) external view returns (bytes32 tokenType);
/// @notice Gets the predicate address for a given token type
/// @param tokenType The token type identifier
/// @return predicate The address of the predicate contract
function typeToPredicate(bytes32 tokenType) external view returns (address predicate);
}
"
},
"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);
}
"
},
"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, 0x2b67b57000000000000000000000000000000000000000000000000000000000) // store the selector
mstore(add(x, 0x04), owner) // store the owner address
mstore(add(x, 0x24), token) // store the token address
calldatacopy(add(x, 0x44), data.offset, 0x60) // copy amount, expiration, nonce
mstore(add(x, 0xa4), address()) // store this contract's address as the spender
calldatacopy(add(x, 0xc4), add(data.offset, 0x60), 0x20) // copy sigDeadline
mstore(add(x, 0xe4), 0x100) // store the offset for signature
mstore(add(x, 0x104), 0x40) // store the length of signature
calldatacopy(add(x, 0x124), add(data.offset, 0x80), 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 * 7 = 224 EIP2612 Permit
case 0xe0 {
let x := mload(0x40) // get the free memory pointer
mstore(x, 0xd505accf00000000000000000000000000000000000000000000000000000000) // store the selector
calldatacopy(add(x, 0x04), data.offset, 0xe0) // store the args
pop(call(gas(), token, 0x00, x, 0xe4, 0x00, 0x20)) // call ERC20 permit, skip checking return data
}
// 32 * 8 = 256 DAI-Style Permit
case 0x100 {
let x := mload(0x40) // get the free memory pointer
mstore(x, 0x8fcbaf0c00000000000000000000000000000000000000000000000000000000) // store the selector
calldatacopy(add(x, 0x04), data.offset, 0x100) // store the args
pop(call(gas(), token, 0x00, x, 0x104, 0x00, 0x20)) // call ERC20 permit, skip checking return data
}
// Otherwise revert
default {
mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the selector
revert(0x00, 0x04) // Revert with PermitFailed error
}
}
}
/// @dev A fillable version of the permit function from this lib, that doesn't support Permit2 SignatureTransfer
/// @param token The address of the token
/// @param data The permit data
/// @param owner The owner of the token (used for Permit2)
function permit(address token, bytes calldata data, address owner) 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(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, 0x2b67b57000000000000000000000000000000000000000000000000000000000) // store the selector
mstore(add(x, 0x04), owner) // store the owner address
mstore(add(x, 0x24), token) // store the token address
calldatacopy(add(x, 0x44), data.offset, 0x60) // copy amount, expiration, nonce
mstore(add(x, 0xa4), address()) // store this contract's address as the spender
calldatacopy(add(x, 0xc4), add(data.offset, 0x60), 0x20) // copy sigDeadline
mstore(add(x, 0xe4), 0x100) // store the offset for signature
mstore(add(x, 0x104), 0x40) // store the length of signature
calldatacopy(add(x, 0x124), add(data.offset, 0x80), 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 * 7 = 224 EIP2612 Permit
case 0xe0 {
let x := mload(0x40) // get the free memory pointer
mstore(x, 0xd505accf00000000000000000000000000000000000000000000000000000000) // store the selector
calldatacopy(add(x, 0x04), data.offset, 0xe0) // store the args
pop(call(gas(), token, 0x00, x, 0xe4, 0x00, 0x20)) // call ERC20 permit, skip checking return data
}
// 32 * 8 = 256 DAI-Style Permit
case 0x100 {
let x := mload(0x40) // get the free memory pointer
mstore(x, 0x8fcbaf0c00000000000000000000000000000000000000000000000000000000) // store the selector
calldatacopy(add(x, 0x04), data.offset, 0x100) // store the args
pop(call(gas(), token, 0x00, x, 0x104, 0x00, 0x20)) // call ERC20 permit, skip checking return data
}
// Otherwise revert
default {
mstore(0x00, 0xb78cb0dd00000000000000000000000000000000000000000000000000000000) // store the selector
revert(0x00, 0x04) // Revert with PermitFailed error
}
}
}
/*//////////////////////////////////////////////////////////////
UTILITIES
//////////////////////////////////////////////////////////////*/
/// @notice Checks if a token is ETH
/// @param token The token address to check
/// @return True if the token represents ETH
function isEth(address token) internal pure returns (bool) {
return token == ETH_ADDRESS;
}
/*//////////////////////////////////////////////////////////////
BALANCE
//////////////////////////////////////////////////////////////*/
/// @dev Returns the balance of address(this), works for both ETH and ERC20 tokens
function getBalance(address token) internal view returns (uint256 balanceOf) {
// solhint-disable-next-line no-inline-assembly
assembly {
switch eq(token, ETH_ADDRESS)
// ETH
case 0x01 { balanceOf := selfbalance() }
// ERC20
default {
let x := mload(0x40) // get the free memory pointer
mstore(x, 0x70a0823100000000000000000000000000000000000000000000000000000000) // store the selector
mstore(add(x, 0x04), address()) // store the account
let success := staticcall(gas(), token, x, 0x24, x, 0x20) // call balanceOf
if success { balanceOf := mload(x) } // load the balance
}
}
}
}
"
},
"src/modules/libraries/OrderHashLib.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Types
import { Order } from "@types/Order.sol";
import { Bridge } from "@types/Bridge.sol";
/// @title Order Hash Library
/// @dev Library with functions to handle hashing of Order structs
library OrderHashLib {
/*//////////////////////////////////////////////////////////////
TYPESTRINGS
//////////////////////////////////////////////////////////////*/
/// @dev The type of the order struct
bytes internal constant _ORDER_TYPESTRING = // solhint-disable-next-line max-line-length
"Order(address owner,address beneficiary,address srcToken,address destToken,uint256 srcAmount,uint256 destAmount,uint256 expectedAmount,uint256 deadline,uint8 kind,uint256 nonce,uint256 partnerAndFee,bytes permit,bytes metadata,Bridge bridge)Bridge(bytes4 protocolSelector,uint256 destinationChainId,address outputToken,int8 scalingFactor,bytes protocolData)";
bytes internal constant _BRIDGE_TYPESTRING = // solhint-disable-next-line max-line-length
"Bridge(bytes4 protocolSelector,uint256 destinationChainId,address outputToken,int8 scalingFactor,bytes protocolData)";
/*//////////////////////////////////////////////////////////////
TYPEHASH
//////////////////////////////////////////////////////////////*/
/// @dev The type hash of the order struct
bytes32 internal constant _ORDER_TYPEHASH = 0xc75d848e51cd0f81113e24c5a62c9b8566b0ff0d476245a7882709315eefbbf7;
/// @dev The type hash of the Bridge struct
bytes32 internal constant _BRIDGE_TYPEHASH = 0x8d8bcbf96f8246b1dcca3f4e1750aecf6cd6971d370b908f10af1d459a2df6eb;
/*//////////////////////////////////////////////////////////////
HASH
//////////////////////////////////////////////////////////////*/
/// @dev EIP-712 struct-hash of an `Order` (all done in assembly, no `abi.encode`)
/// @param order The order to hash
/// @return result The 32-byte hash
function hash(Order memory order) internal pure returns (bytes32 result) {
// Pre-compute hash of the dynamic bytes field
bytes32 permitHash = keccak256(order.permit);
bytes32 metadataHash = keccak256(order.metadata);
bytes32 protocolDataHash = keccak256(order.bridge.protocolData);
// bring struct into the stack
Bridge memory bridge = order.bridge;
assembly ("memory-safe") {
/* ───────────────────────────────── Bridge hash ─────────────────────── */
let ptr := mload(0x40) // free-mem pointer
mstore(ptr, _BRIDGE_TYPEHASH) // type-hash
mstore(add(ptr, 0x20), mload(bridge)) // protocolSelector (bytes4)
mstore(add(ptr, 0x40), mload(add(bridge, 0x20))) // destinationChainId
mstore(add(ptr, 0x60), mload(add(bridge, 0x40))) // outputToken
mstore(add(ptr, 0x80), signextend(0, mload(add(bridge, 0x60)))) // scalingFactor (int8)
mstore(add(ptr, 0xA0), protocolDataHash) // keccak256(protocolData)
let bridgeHash := keccak256(ptr, 0xC0) // 6 words → 192 bytes
/* ────────────────────────────── Order hash ────────────────────────── */
ptr := add(ptr, 0xC0) // reuse memory after bridge blob
mstore(ptr, _ORDER_TYPEHASH) // 0x00 type-hash
mstore(add(ptr, 0x20), mload(order)) // owner
mstore(add(ptr, 0x40), mload(add(order, 0x20))) // beneficiary
mstore(add(ptr, 0x60), mload(add(order, 0x40))) // srcToken
mstore(add(ptr, 0x80), mload(add(order, 0x60))) // destToken
mstore(add(ptr, 0xA0), mload(add(order, 0x80))) // srcAmount
mstore(add(ptr, 0xC0), mload(add(order, 0xA0))) // destAmount
mstore(add(ptr, 0xE0), mload(add(order, 0xC0))) // expectedAmount
mstore(add(ptr, 0x100), mload(add(order, 0xE0))) // deadline
mstore(add(ptr, 0x120), mload(add(order, 0x100))) // kind
mstore(add(ptr, 0x140), mload(add(order, 0x120))) // nonce
mstore(add(ptr, 0x160), mload(add(order, 0x140))) // partnerAndFee
mstore(add(ptr, 0x180), permitHash) // keccak256(order.permit)
mstore(add(ptr, 0x1A0), metadataHash) // keccak256(order.metadata)
mstore(add(ptr, 0x1C0), bridgeHash) // keccak256(Bridge blob)
result := keccak256(ptr, 0x1E0) // 15 words → 480 bytes
}
}
}
"
},
"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() int
Submitted on: 2025-10-08 10:52:10
Comments
Log in to comment.
No comments yet.