Skip to content

maple-labs/maple-cross-chain-receiver

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

277 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Maple Cross Chain Receiver

Chainlink CCIP-powered bridge enabling direct cross-chain deposits/redemptions into Maple Finance pools with automatic share token returns and comprehensive recovery mechanisms. Built in collaboration with Chainlink.

Overview

Users deposit tokens from any supported chain (Ethereum, Solana, Polygon, Base) into Maple Finance liquidity pools on Ethereum and receive pool shares back on their origin chain. Users can also redeem pool shares by sending them back to the contract, receiving underlying tokens on their origin chain. The system handles universal addressing, fee collection, and failed transaction recovery.

Architecture

graph LR
    A[Multi-Chain Sources] --> B[CCIP Router]
    B --> C[Universal Bridge]
    C --> D[Maple Pools]
    D --> E[Pool Shares]
    E --> F[CCIP Return]
    
    C --> G[Fee Collection]
    C --> H[Recovery System]
Loading

Flow:

  • Deposits: Source Chain → CCIP → Bridge Contract → Pool Deposit → Share Return → CCIP → Origin Chain
  • Withdrawals: Source Chain → CCIP → Bridge Contract → Redemption Request → (Later) Execute Redemption → Token Return → CCIP → Origin Chain

Core Features

Universal Chain Support

  • EVM: Ethereum, Polygon, Arbitrum, Base
  • SVM: Solana
  • Addressing: Unified bytes32 format for cross-chain compatibility

Security & Validation

  • Sender Validation: Message sender address must match universalSenderAddress in message data
  • Pool Validation: Contract is bound to a single configured pool
  • Token Validation: Deposits accept pool asset tokens, withdrawals accept pool share tokens
  • Atomic Operations: Complete success or safe failure with recovery

Fee Management

// Admin configurable per-token fees
setTokenDepositFee(address token, uint256 amount)

// Automatic collection before pool deposit
if (totalAmount > feeAmount) {
    deposit(totalAmount - feeAmount);
    collectFee(feeAmount);
}

Key Functions

User Operations

// Fee estimation
estimateFee(uint64 destChain, bytes32 beneficiary, EVMTokenAmount[] tokens) → uint256

// Self-recovery for failed transactions (sends tokens back to original sender)
recoverFailedTokensBackToSender(bytes32 messageId) payable
estimateRecoveryFee(bytes32 messageId) → uint256

// Message retry (re-processes failed message with original logic)
// Note: Function is payable but doesn't use msg.value - fees paid from contract balance
retryFailedMessage(bytes32 messageId) payable

Admin Controls

// Deposit/redemption control
enableDeposits(bool enabled)
enableRedemptions(bool enabled)

// Emergency token rescue (last resort when recovery methods fail)
adminRescueTokens(bytes32 messageId, address beneficiary)

// Fee management
setTokenDepositFee(address token, uint256 amount)
withdrawCollectedFeesForToken(address token, uint256 amount, address to)

// Emergency functions
withdrawToken(address token, uint256 amount, address to)
withdrawNative(uint256 amount, address payable to)
setChainType(uint64 chainSelector, ChainType chainType)

Redemption Executor Controls

// Redemption execution (requires REDEMPTION_EXECUTOR role in pool's Globals)
executeRedeemList(uint128[] requestIds)
removeRedemptionRequests(uint128[] requestIds) payable

View Functions

// System state
getFailedMessages(uint256 offset, uint256 limit) → FailedMessage[]
getMessage(bytes32 messageId) → Any2EVMMessage
getCollectedFees(address token) → uint256
getPool() → address
getTokenDepositFee(address token) → uint256
depositsEnabled() → bool
redemptionsEnabled() → bool
getChainType(uint64 chainSelector) → ChainType
requestedRedemptions(uint128 requestId) → (bytes32, uint256, address, uint64, bool)
estimateRecoveryFee(bytes32 messageId) → uint256

Message Format

struct UniversalMessage {
    bytes32 universalSenderAddress;  // Universal address format (must match CCIP sender)
    address pool;                    // Target Maple pool (must match configured pool)
    bytes32 metaData;                // Metadata to identify partner for deposits and withdrawals
}

Recovery System

Failed Transaction Types

  1. Validation Failures: Sender address mismatch, pool mismatch, invalid token
  2. Pool Failures: Wrong token type, insufficient allowance, deposits/redemptions disabled
  3. Return Failures: Insufficient ETH for fees, unsupported destination chain

Recovery Mechanism

  • Public Recovery: Anyone can recover failed tokens by paying CCIP fees using recoverFailedTokensBackToSender()
  • Message Retry: Anyone can retry failed message processing using retryFailedMessage()
  • Automatic Return: Tokens sent back to original sender address
  • Admin Emergency Rescue: adminRescueTokens() as last resort when other methods fail

Events

// Cross-chain deposit events
event CrossChainDeposit(
    address indexed pool,
    address indexed depositToken,
    uint256 indexed depositTokenAmount,
    bytes32 universalSenderAddress,
    uint256 shares,
    bytes32 metaData
)

event CrossChainRedeem(
    address indexed pool,
    address indexed redeemToken,
    uint256 indexed redeemTokenAmount,
    bytes32 universalSenderAddress,
    bytes32 metaData
)

// Message events
event MessageSent(
    bytes32 indexed messageId,
    uint64 indexed destinationChainSelector,
    bytes32 indexed universalBeneficiary,
    bytes receiver,
    Client.EVMTokenAmount[] tokenAmounts,
    address feeToken,
    uint256 fees
)

event MessageReceived(
    bytes32 indexed messageId,
    uint64 indexed sourceChainSelector,
    bytes indexed sender,
    bytes data,
    address token,
    uint256 tokenAmount
)

event MessageFailed(bytes32 indexed messageId, Client.Any2EVMMessage message)
event MessageSucceeded(bytes32 indexed messageId)
event MessageRecovered(bytes32 indexed messageId)

// Recovery events
event TokensRecovered(
    bytes32 indexed messageId,
    address indexed recoverer,
    bytes32 indexed universalAddress,
    address token,
    uint256 amount,
    uint256 feesPaid
)

// Administrative events
event EthWithdrawn(address indexed beneficiary, uint256 amount)
event TokenWithdrawn(address indexed beneficiary, address indexed token, uint256 amount)
event DepositsEnabledSet(bool indexed enabled)
event RedemptionsEnabledSet(bool indexed enabled)

// Fee collection events
event TokenDepositFeeSet(address indexed token, uint256 feeAmount)
event CollectedFee(address indexed token, uint256 amount)

// Redemption request events
event RedeemRequest(uint128 indexed requestId, uint256 indexed shares, address indexed pool)
event RedemptionRequestRemoved(
    uint128 indexed requestId,
    uint256 indexed sharesReturned,
    address indexed pool,
    bytes32 universalBeneficiary,
    uint64 chainSelector
)

// Chain management events
event ChainTypeSet(uint64 indexed chainSelector, ChainType chainType)

// Debugging events
event DecodedData(
    bytes32 indexed universalSenderAddress,
    address pool
)

Security Model

  • Upgradeable Proxy: UUPS pattern with admin-controlled upgrades
  • Access Control: OpenZeppelin role-based permissions
  • Reentrancy Protection: Critical functions (message processing, recovery, redemption execution) protected with nonReentrant modifier
  • Try-Catch Handling: Graceful failure with full message preservation
  • Pool Binding: Contract is bound to a single configured pool at initialization

Technical Specifications

  • Solidity: ^0.8.20
  • Dependencies: OpenZeppelin (upgradeable, access control), Chainlink CCIP
  • Pattern: UUPS Upgradeable Proxy
  • Storage Layout: ERC-7201 namespaced storage for upgrade safety
  • Gas Optimization: Batch operations, efficient storage layout
  • Chain Support: EVM (native), SVM (via CCIP universal addressing)

Quick Start

Prerequisites

Before starting, ensure you have:

  • Node.js (v18 or higher)
  • pnpm
  • Git
  • Sepolia testnet access and ETH
  • Private key for EVM deployment and for Solana CCIP test invocation (testnet tokens for supported pools for testing e.g. USDC)

1. Clone and Setup

# Clone the repository with submodules
git clone https://github.com/smartcontractkit/Maple-Withdrawals.git
cd Maple-Withdrawals

# Install dependencies
pnpm install

2. Environment Configuration

Create a .env file in the project root. See .env.example for a complete reference.

Core Required Variables

Variable Name Purpose Required For
SEPOLIA_RPC_URL Ethereum Sepolia RPC endpoint for deployment and testing All operations
PRIVATE_KEY Private key for transactions (without 0x prefix) All operations
ETHERSCAN_API_KEY API key for contract verification on Etherscan Contract verification

Chain Configuration Variables

Variable Name Purpose Required For
CONFIG_SEPOLIA_ETHEREUM_CHAIN_ID Ethereum Sepolia chain ID (11155111) E2E testing
CONFIG_SEPOLIA_ETHEREUM_CHAIN_SELECTOR Ethereum Sepolia CCIP selector E2E testing
CONFIG_SEPOLIA_BASE_CHAIN_CHAIN_ID Base Sepolia chain ID (84532) EVM E2E testing
CONFIG_SEPOLIA_BASE_CHAIN_SELECTOR Base Sepolia CCIP selector EVM E2E testing
CONFIG_SOLANA_DEVNET_CHAIN_SELECTOR Solana Devnet CCIP selector Solana E2E testing

Contract & Pool Configuration

Variable Name Purpose Required For
UNIVERSAL_RECEIVER Deployed MapleCCIPReceiver contract address E2E testing
CONFIG_MAPLE_POOL Maple Finance pool address on Ethereum Sepolia E2E testing
CONFIG_SEPOLIA_CCIP_ROUTER CCIP Router address on Ethereum Sepolia Deployment
CONFIG_USDC_TOKEN USDC token address on Ethereum Sepolia Deployment
CONFIG_USDC_FEE Deposit fee in token units Deployment

E2E Testing Variables

Variable Name Purpose Required For
E2E_SOLANA_DEVNET_POOL_TOKEN_MINT Pool token mint address on Solana Devnet Solana E2E testing (withdrawals)
E2E_BASE_CCIP_ROUTER Base Sepolia CCIP Router address EVM E2E testing
E2E_BASE_USDC_TOKEN USDC token address on Base Sepolia EVM E2E testing
E2E_SOLANA_DEVNET_CCIP_PROGRAM_ID Solana Devnet CCIP Program ID Solana E2E testing
E2E_SOLANA_DEVNET_USDC_TOKEN_MINT USDC token mint on Solana Devnet Solana E2E testing
E2E_SOLANA_DEVNET_NATIVE_MINT Native SOL mint address Solana E2E testing

Network RPC URLs

Variable Name Purpose Required For
BASE_SEPOLIA_RPC_URL Base Sepolia RPC for cross-chain testing EVM E2E testing
ANCHOR_PROVIDER_URL Solana RPC endpoint for SVM cross-chain tests Solana E2E testing
ANCHOR_WALLET Path to Solana wallet keypair file Solana E2E testing

Optional Optimization Variables

Variable Name Purpose Required For
CCIP_LOOKUP_TABLE Pre-created Solana lookup table for deposits Solana E2E testing (optional)
CCIP_LOOKUP_TABLE_WITHDRAWAL Pre-created Solana lookup table for withdrawals Solana E2E testing (optional)

Example Configuration

# Core Required
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_PROJECT_ID
PRIVATE_KEY=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
ETHERSCAN_API_KEY=YOUR_ETHERSCAN_API_KEY

# Solana (for SVM testing)
ANCHOR_PROVIDER_URL=https://api.devnet.solana.com
ANCHOR_WALLET=/path/to/your/solana/id.json

# Cross-chain testing
BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
UNIVERSAL_RECEIVER=0x551D606BF8CE1588e9a15639801f7c4B9C001D24

Security Note: Never commit real private keys to version control. Use test keys only and consider using hardware wallets for mainnet deployments.

3. Compile and Test

# Compile all contracts
forge build

# Run the full test suite
forge test

# Run tests with detailed output
forge test -vv

# Run specific test files
forge test --match-path "test/MapleCCIPReceiver.t.sol"

4. Deploy to Sepolia

# Deploy fresh proxy + implementation
pnpm run deploy:sepolia

# Configure the deployed contract
PROXY_ADDRESS=0x... pnpm run configure:sepolia

# Validate deployment
PROXY_ADDRESS=0x... pnpm run validate:sepolia

5. Post-Deployment Configuration

After deployment, the contract owner must perform the following steps to enable the contract. We recommend using a block explorer (e.g., Etherscan for Ethereum mainnet or Sepolia Etherscan for testnet) to interact with the contract.

Enable Deposits and Redemptions

The contract starts with both deposits and redemptions disabled. The owner must enable them:

  1. Navigate to your contract on the block explorer (e.g., https://sepolia.etherscan.io/address/YOUR_PROXY_ADDRESS)
  2. Go to the Contract tab and click Write Contract
  3. Connect your wallet (must be the contract owner/admin)
  4. Find the enableDeposits function:
    • Set enabled to true
    • Click Write and confirm the transaction
  5. Find the enableRedemptions function:
    • Set enabled to true
    • Click Write and confirm the transaction
  6. Verify they are enabled by checking the depositsEnabled() and redemptionsEnabled() view functions in the Read Contract tab

Fund Contract with ETH

The contract needs ETH to pay for CCIP fees when sending tokens back to users (for both deposits returning shares and redemptions returning underlying tokens). Send ETH to the contract address:

  1. Navigate to your contract on the block explorer
  2. Click the Send ETH or Transfer button (or use your wallet)
  3. Send ETH to the contract address (recommended: start with 0.1-0.5 ETH, adjust based on expected volume)
  4. Verify the balance on the contract's page

Important:

  • Monitor the contract's ETH balance regularly and top it up as needed
  • Insufficient ETH will cause CCIP return transactions to fail, requiring recovery mechanisms
  • You can check the balance on the block explorer (contract address balance)

End-to-End Testing Scripts

The project includes comprehensive E2E scripts to test real cross-chain transactions on testnets.

e2e/ccip-send-svm.ts - Solana → Ethereum

Purpose: Tests cross-chain deposits and withdrawals from Solana Devnet to Ethereum Sepolia using SVM CCIP infrastructure.

What it does:

  • Mode Detection: Supports both deposits (USDC) and withdrawals (pool tokens) via command-line argument
  • Message Construction: Builds UniversalMessage with Solana sender address, pool address, and metadata
  • CCIP Transaction: Sends tokens from Solana to Ethereum via Chainlink CCIP
  • Lookup Table Optimization: Uses separate Solana address lookup tables for deposits and withdrawals
  • Message ID Extraction: Captures CCIP message ID for tracking
  • Amount Configuration: Accepts custom transfer amount via command-line argument

Key Features:

// Mode detection via command-line argument
const isWithdrawal = args.includes('withdrawal')
const amountArg = args.find((arg) => !isNaN(Number(arg)) && arg !== 'withdrawal')
const TRANSFER_AMOUNT = amountArg ? parseInt(amountArg, 10) : DEFAULT_AMOUNT

// Token selection based on mode
const transferTokenMint = isWithdrawal 
  ? POOL_TOKEN_MINT  // Pool token for withdrawals
  : USDC_TOKEN_MINT  // USDC for deposits

// Constructs UniversalMessage
const encodedMessage = ethers.utils.defaultAbiCoder.encode(
  ['bytes32', 'address', 'bytes32'],
  [
    senderBytes32,      // universalSenderAddress
    POOL_ADDRESS,       // pool
    META_DATA           // metaData
  ]
)

// Constructs SVM2AnyMessage for CCIP
const message = {
  receiver: Buffer.from(ethereumContractAddress),
  data: encodedMessage,
  tokenAmounts: [{ token: transferTokenMint, amount: TRANSFER_AMOUNT }],
  feeToken: PublicKey.default, // Pay fees in SOL
  extraArgs: ccipExtraArgs
}

Usage:

# Deposit USDC (default)
pnpm run ccip-send:svm

# Deposit with custom amount
pnpm run ccip-send:svm 500000

# Withdrawal (pool tokens)
pnpm run ccip-send:svm withdrawal

# Withdrawal with custom amount
pnpm run ccip-send:svm withdrawal 1000000

Required Environment Variables:

# Core configuration
export SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_PROJECT_ID
export PRIVATE_KEY=your_private_key_without_0x_prefix

# Contract addresses
export UNIVERSAL_RECEIVER=0x375dcdF142704fcB157922faB6A599A71B4b0a56
export CONFIG_MAPLE_POOL=0xb1206B74F612F478c12A647D12E7e822AF5D8244

# Chain configuration
export CONFIG_SEPOLIA_ETHEREUM_CHAIN_ID=11155111
export CONFIG_SEPOLIA_ETHEREUM_CHAIN_SELECTOR=16015286601757825753

# Solana configuration
export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com
export ANCHOR_WALLET=/path/to/your/solana/id.json
export E2E_SOLANA_DEVNET_CCIP_PROGRAM_ID=Ccip8ZTcM2qHjVt8FYHtuCAqjc637yLKnsJ5q5r2e6eL
export E2E_SOLANA_DEVNET_USDC_TOKEN_MINT=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
export E2E_SOLANA_DEVNET_POOL_TOKEN_MINT=<pool_token_mint_address>  # For withdrawals
export E2E_SOLANA_DEVNET_NATIVE_MINT=So11111111111111111111111111111111111111112

# Optional lookup tables
export CCIP_LOOKUP_TABLE=<lookup_table_for_deposits>
export CCIP_LOOKUP_TABLE_WITHDRAWAL=<lookup_table_for_withdrawals>

# Run Solana to Ethereum test
pnpm run ccip-send:svm                    # Deposit
pnpm run ccip-send:svm withdrawal         # Withdrawal

e2e/ccip-send-evm.ts - EVM → Ethereum

Purpose: Tests cross-chain deposits and withdrawals from EVM chains (Polygon, Base, etc.) to Ethereum Sepolia.

What it does:

  • Mode Detection: Supports both deposits (USDC) and withdrawals (pool tokens) via command-line argument
  • Chain Configuration: Supports multiple EVM source chains
  • EVM Message Format: Constructs messages for EVM-to-EVM CCIP transfers
  • Fee Estimation: Calculates exact CCIP fees before sending
  • Cross-Chain Transfer: Executes actual token transfer via CCIP
  • Transaction Tracking: Provides transaction hashes and explorer links
  • Amount Configuration: Accepts custom transfer amount via command-line argument

Key Features:

// Mode detection via command-line argument
const isWithdrawal = args.includes('withdrawal')
const numericArg = args.find(arg => arg !== 'withdrawal' && !isNaN(parseInt(arg, 10)))
const TRANSFER_AMOUNT = numericArg ? BigNumber.from(numericArg) : DEFAULT_AMOUNT

// Token selection based on mode
const tokenAddress = isWithdrawal 
  ? POOL_TOKEN_ADDRESS  // Pool token for withdrawals
  : USDC_TOKEN_ADDRESS // USDC for deposits

// Constructs UniversalMessage
const encodedMessage = ethers.utils.defaultAbiCoder.encode(
  ['bytes32', 'address', 'bytes32'],
  [
    userSenderBytes32,  // universalSenderAddress
    POOL_ADDRESS,       // pool
    metaDataBytes32     // metaData
  ]
)

// EVM2AnyMessage construction
const ccipMessage = {
  receiver: abi.encode(receiverAddress),
  data: encodedMessage,
  tokenAmounts: [{ token: tokenAddress, amount: TRANSFER_AMOUNT }],
  extraArgs: evmExtraArgs, // EVM-specific gas limits
  feeToken: address(0) // Pay fees in native ETH
}

Usage:

# Deposit USDC (default)
pnpm run ccip-send:evm

# Deposit with custom amount
pnpm run ccip-send:evm 500000

# Withdrawal (pool tokens)
pnpm run ccip-send:evm withdrawal

# Withdrawal with custom amount
pnpm run ccip-send:evm withdrawal 1000000

Required Environment Variables:

# Core configuration
export SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_PROJECT_ID
export PRIVATE_KEY=your_private_key_without_0x_prefix

# Contract addresses
export UNIVERSAL_RECEIVER=0x375dcdF142704fcB157922faB6A599A71B4b0a56
export CONFIG_MAPLE_POOL=0xb1206B74F612F478c12A647D12E7e822AF5D8244

# Chain configuration
export CONFIG_SEPOLIA_ETHEREUM_CHAIN_ID=11155111
export CONFIG_SEPOLIA_ETHEREUM_CHAIN_SELECTOR=16015286601757825753
export CONFIG_SEPOLIA_BASE_CHAIN_CHAIN_ID=84532
export CONFIG_SEPOLIA_BASE_CHAIN_SELECTOR=10344971235874465080

# Base Sepolia configuration
export BASE_SEPOLIA_RPC_URL=https://base-sepolia.infura.io/v3/YOUR_PROJECT_ID
export E2E_BASE_CCIP_ROUTER=0xD3b06cEbF099CE7DA4AcCf578aaebFDBd6e88a93
export E2E_BASE_USDC_TOKEN=0x036CbD53842c5426634e7929541eC2318f3dCF7e
export E2E_BASE_POOL_TOKEN=<pool_token_address>  # For withdrawals

# Run EVM to Ethereum test
pnpm run ccip-send:evm                    # Deposit
pnpm run ccip-send:evm withdrawal         # Withdrawal

Key Differences

Feature ccip-send-svm.ts (SVM) ccip-send-evm.ts (EVM)
Source Chain Solana Devnet Any EVM chain
Address Format PublicKey → bytes32 address → bytes32
Fee Payment SOL (native) ETH (native)
Message Type SVM2AnyMessage EVM2AnyMessage
Optimization Address Lookup Tables Standard EVM transactions
Extra Args SVMExtraArgsV1 GenericExtraArgsV2

Deposit vs Withdrawal Mode

Both E2E scripts support two modes of operation:

Deposit Mode (Default)

  • Token: Sends pool asset token (e.g., USDC) from source chain
  • Action: Contract deposits tokens into Maple pool and returns pool shares
  • Usage: pnpm run ccip-send:svm or pnpm run ccip-send:evm

Withdrawal Mode (Redemption)

  • Token: Sends pool share tokens from source chain
  • Action: Contract creates a redemption request that must later be executed by a REDEMPTION_EXECUTOR (via executeRedeemList()) to convert shares to underlying tokens and return them via CCIP
  • Usage: pnpm run ccip-send:svm withdrawal or pnpm run ccip-send:evm withdrawal

Amount Configuration

  • Default: Uses default amount (111111 for SVM, configured default for EVM)
  • Custom: Specify amount as command-line argument: pnpm run ccip-send:svm 500000
  • Withdrawal with amount: pnpm run ccip-send:svm withdrawal 1000000

Testing Cross-Chain Recovery

Both scripts test the complete flow including potential recovery scenarios:

# Test successful flow
pnpm run ccip-send:svm       # Should complete successfully

# Test with insufficient pool allowance (triggers recovery)
# Modify pool address to non-whitelisted pool
# Message will fail and be stored for recovery

# Then test recovery mechanism
# Use recoverFailedTokensBackToSender() with the failed message ID

Monitoring & Debugging

Both scripts provide comprehensive logging:

  • Transaction Status: Success/failure with detailed error messages
  • Mode Indication: Clearly shows whether running in deposit or withdrawal mode
  • Token Information: Displays which token is being transferred
  • Amount: Shows the transfer amount being used
  • CCIP Message ID: For tracking on CCIP explorer
  • Gas Usage: Compute units (Solana) or gas costs (EVM)
  • Explorer Links: Direct links to view transactions

These E2E scripts serve as both testing tools and integration examples, demonstrating how to properly construct cross-chain messages that work with the Universal Bridge contract.

Development Commands

Core Development

# Compile contracts
forge build

# Run all tests
forge test

# Run tests with gas reporting
forge test --gas-report

# Run tests with coverage
forge coverage

Code Quality

# Check formatting
pnpm run prettier:check

# Auto-format code
pnpm run prettier:write

# Type checking
pnpm run typecheck

Deployment & Upgrades

# Deploy new proxy + implementation
pnpm run deploy:sepolia

# Upgrade existing proxy (if compatible)
PROXY_ADDRESS=0x... pnpm run upgrade:sepolia

# Configure deployed contracts
PROXY_ADDRESS=0x... pnpm run configure:sepolia

# Validate deployment
PROXY_ADDRESS=0x... pnpm run validate:sepolia

Testing

The project includes comprehensive tests covering:

  • Universal Message Processing: Cross-chain message handling and validation
  • Fee Collection: Token-specific fee deduction and withdrawal
  • Recovery System: Failed transaction recovery and admin overrides
  • Access Control: Admin-only function protection and REDEMPTION_EXECUTOR role validation
  • Universal Addressing: EVM and SVM address format handling
  • Emergency Functions: Token and ETH withdrawal scenarios
  • Redemption Flow: Redemption request creation, execution, and removal

Test Categories

# Run specific test suites
forge test --match-path "test/MapleCCIPReceiver.t.sol"    # Core functionality
forge test --match-path "test/AdminFunctions.t.sol"             # Admin functions
forge test --match-path "test/TokenRecovery.t.sol"              # Recovery system
forge test --match-path "test/UniversalAddressing.t.sol"        # Address handling
forge test --match-path "test/CrossChainRouting.t.sol"          # Chain routing
forge test --match-path "test/MessageReceiving.t.sol"           # Message receiving
forge test --match-path "test/MessageSending.t.sol"             # Message sending
forge test --match-path "test/RedeemRequest.t.sol"              # Redemption requests
forge test --match-path "test/Reentrancy.t.sol"                 # Reentrancy protection
forge test --match-path "test/FeePaymentModes.t.sol"            # Fee payment modes
forge test --match-path "test/Upgradeability.t.sol"              # Upgrade functionality
forge test --match-path "test/InterfaceSupport.t.sol"            # Interface support
forge test --match-path "test/Withdrawal.t.sol"                 # Withdrawal scenarios
forge test --match-path "test/EmergencyScenarios.t.sol"         # Emergency scenarios
forge test --match-path "test/Deployment.t.sol"                 # Deployment tests

# Run specific functionality
forge test --match-test "Fee"                                   # Fee-related tests
forge test --match-test "Recovery"                              # Recovery tests
forge test --match-test "Admin"                                 # Admin tests
forge test --match-test "Redeem"                                # Redemption tests

Configuration

Sepolia Testnet Addresses

Name Address
CCIP SEPOLIA ROUTER 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59
SEPOLIA LINK TOKEN 0x779877A7B0D9E8603169DdbD7836e478b4624789
SEPOLIA USDC TOKEN 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
SEPOLIA MAPLE POOL 0xb1206b74f612f478c12a647d12e7e822af5d8244

Usage Examples

1. Configure Fee Collection

import { ethers } from 'hardhat'

const contract = await ethers.getContractAt('MapleCCIPReceiver', PROXY_ADDRESS)

// Set 0.01 USDC fee for deposits
await contract.setTokenDepositFee(USDC_ADDRESS, ethers.utils.parseUnits('0.01', 6))

// Check current fee
const fee = await contract.getTokenDepositFee(USDC_ADDRESS)
console.log('Current fee:', ethers.utils.formatUnits(fee, 6))

2. Monitor Failed Messages

// Get failed messages
const failedMessages = await contract.getFailedMessages(0, 10)
console.log('Failed messages:', failedMessages.length)

// Check specific message details
const messageDetails = await contract.getMessage(messageId)
console.log('Message details:', messageDetails)

3. Emergency Recovery

// Withdraw collected fees
await contract.withdrawCollectedFeesForToken(USDC_ADDRESS, AMOUNT, RECIPIENT)

// Manual message retry (re-processes failed message)
// Note: Function is payable but doesn't use msg.value - fees paid from contract balance
await contract.retryFailedMessage(messageId)

// Emergency token rescue (last resort when other methods fail)
await contract.adminRescueTokens(messageId, beneficiaryAddress)

// Emergency token withdrawal
await contract.withdrawToken(TOKEN_ADDRESS, AMOUNT, RECIPIENT)

Deployment Requirements

  • Chainlink CCIP Router on all supported chains
  • Admin account (multisig recommended)
  • ETH reserves for outbound CCIP fees
  • Single Maple Finance pool configured at initialization

Security Considerations

Access Control

  • DEFAULT_ADMIN_ROLE: Controls upgrades, configuration, fee management, and emergency functions
  • REDEMPTION_EXECUTOR: Role in pool's Globals contract required for executing and removing redemption requests
  • Pool Binding: Contract is bound to a single configured pool at initialization
  • Sender Validation: Message sender address must match universalSenderAddress in message data
  • Deposit/Redemption Control: Admin can enable/disable deposits and redemptions globally

Upgrade Safety

  • UUPS Pattern: Secure upgrade mechanism with admin controls
  • Storage Layout: Careful consideration of storage slot compatibility

This bridge provides secure, efficient cross-chain access to Maple Finance's institutional lending infrastructure while maintaining full recoverability and administrative oversight.

Additional Resources

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors