This directory contains the core contract implementations for the cross-chain bridge and staking system.
src/core/
├── PoolManager.sol # Main contract for fund pool and staking management
├── PoolManagerStorage.sol # Storage layer for fund pool
├── MessageManager.sol # Cross-chain message management contract
└── MessageManagerStorage.sol # Message management storage layer
┌─────────────────────────────────────────────────────────┐
│ User Interaction Layer │
├─────────────────────────────────────────────────────────┤
│ Staking Features │ Bridge Features │ Withdrawal │
│ - DepositStaking │ - BridgeInitiate │ - Withdraw │
│ - ClaimReward │ - BridgeFinalize │ - ClaimAll │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ PoolManager.sol │
│ - Fund pool management │
│ - Staking logic │
│ - Bridge interface │
│ - Reward distribution │
└─────────────────────────────────────────────────────────┘
↓
┌────────────────────┬───────────────────────────────────┐
│ MessageManager │ PoolManagerStorage │
│ - Cross-chain msg │ - State storage │
│ - Message tracking│ - Pool data │
│ - Replay attack │ - User data │
│ prevention │ │
└────────────────────┴───────────────────────────────────┘
Main Contract - Fund Pool and Staking Management
Provides base liquidity for the cross-chain bridge.
// Deposit ETH to provide liquidity
function depositEthToBridge() public payable returns (bool)
// Deposit ERC20 to provide liquidity
function depositErc20ToBridge(address tokenAddress, uint256 amount) public returns (bool)
// Admin withdraws liquidity (only withdrawManager)
function withdrawEthFromBridge(address payable withdrawAddress, uint256 amount) public returns (bool)
function withdrawErc20FromBridge(address tokenAddress, address withdrawAddress, uint256 amount) public returns (bool)Transfer assets between different chains.
// Source chain: Initiate cross-chain transfer
function BridageInitiateETH(uint256 sourceChainId, uint256 destChainId, address destTokenAddress, address to) external payable
function BridgeInitiateERC20(
uint256 sourceChainId,
uint256 destChainId,
address sourceTokenAddress,
address destTokenAddress,
address to,
uint256 value
) external
// Destination chain: Complete cross-chain transfer (only Relayer)
function BridgeFinalizeETH(...) external payable onlyRelayer
function BridgeFinalizeERC20(...) external onlyRelayerCross-Chain Flow Example:
User on Ethereum Relayer monitors User receives on BSC
│ │ │
├─ BridageInitiateETH │ │
│ (1 ETH) │ │
│ │ │
│ ─────────────────────>│ │
│ Send 1 ETH to contract│ │
│ Deduct fee 0.001 ETH │ │
│ │ │
│ ├─ Listen MessageSent event │
│ │ │
│ ├─ Verify transaction │
│ │ │
│ ├─ BridgeFinalizeETH ────────>│
│ │ Execute on BSC │
│ │ │
│ │ ├─ Receive 0.999 ETH
Users stake assets to earn cross-chain transaction fee rewards.
// Stake
function DepositAndStakingETH() external payable
function DepositAndStakingERC20(address _token, uint256 _amount) external
// Withdraw (principal + rewards)
function WithdrawAll() external // Withdraw all tokens
function WithdrawByID(uint256 i) external // Withdraw specific stake record
// Claim rewards only (principal continues staking)
function ClaimAllReward() external // Claim all token rewards
function ClaimbyID(uint256 i) external // Claim specific record rewardsStaking Reward Mechanism:
Each staking period (21 days):
├─ Cross-chain fees accumulate to FeePoolValue
├─ Relayer calls CompletePoolAndNew to rotate pools
│ └─ Distributes FeePoolValue to completed pool
└─ Users claim rewards
User reward calculation formula:
Reward = (User stake amount / Pool total stake) × Pool total fees
Core Advantages of Pool Rotation Mechanism:
1. Continuous Staking + Flexible Withdrawal
// Users can stake to "not started" pools anytime
if (Pools[address(ETH_ADDRESS)][PoolIndex].startTimeStamp > block.timestamp) {
Users[msg.sender].push(...); // Staking successful
}
- ✅ Users don't need locking, can withdraw principal+rewards anytime
- ✅ Continuing stakers automatically enter next period (TotalAmount inherited)
- ✅ New users can join before pool starts, fair participation
2. Fair Distribution of Transaction Fees
// Calculate user rewards in each pool
uint256 _Reward = (Amount * Pools[_token][j].TotalFee) / Pools[_token][j].TotalAmount;
Distribution logic:
- Each pool's TotalFee is determined when period ends
- User reward = (Individual stake / Pool total stake) × Pool total fees
- Users staking across multiple periods accumulate rewards from multiple pools
Example scenario:
User A stakes 100 ETH in Pool 1
- Pool 1 (21 days) ends: 10 ETH fees, A gets their share
- Pool 2 (21 days): A's 100 ETH continues staking, gets new fee share
- Pool 3: And so on...
User A withdraws on day 60: receives accumulated rewards from 3 pools
3. Incentivizes Long-term Stakers
// Traverse all participating pools during withdrawal
for (uint256 j = startPoolId; j < EndPoolId; j++) {
uint256 _Reward = (Amount * Pools[_token][j].TotalFee) / Pools[_token][j].TotalAmount;
Reward += _Reward;
}
- Longer staking time = more pool periods participated
- More accumulated rewards (compound effect)
- Encourages users to hold long-term, increases liquidity stability
4. Gas Efficiency Optimization
Compare to traditional approach:
- ❌ Real-time reward calculation: Every bridge transaction updates all stakers' rewards (O(n) operation)
- ✅ Pool rotation: Rewards calculated only on withdrawal, bridging only accumulates FeePoolValue (O(1) operation)
// Bridging only needs to accumulate fees (very low gas)
FeePoolValue[ETH_ADDRESS] += fee;
// Only traverse pools to calculate during withdrawal (user bears gas)
for (uint256 j = startPoolId; j < EndPoolId; j++) {
// Calculate reward...
}
// Query user staked principal
function getPrincipal() external view returns (KeyValuePair[] memory)
// Query user unclaimed rewards
function getReward() external view returns (KeyValuePair[] memory)
// Query funding pool balance
function fetchFundingPoolBalance(address token) external view returns (uint256)
// Query pool information
function getPoolLength(address _token) external view returns (uint256)
function getPool(address _token, uint256 _index) external view returns (Pool memory)
// Query user information
function getUserLength(address _user) external view returns (uint256)
function getUser(address _user) external view returns (User[] memory)// Relayer permissions
function CompletePoolAndNew(Pool[] memory CompletePools) external onlyRelayer
function setMinTransferAmount(uint256 _MinTransferAmount) external onlyReLayer
function setValidChainId(uint256 chainId, bool isValid) external onlyReLayer
function setSupportToken(address _token, bool _isSupport, uint32 startTimes) external onlyReLayer
function setPerFee(uint256 _PerFee) external onlyReLayer
function setMinStakeAmount(address _token, uint256 _amount) external onlyReLayer
// Owner permissions
function pause() external onlyOwner
function unpause() external onlyOwner
// WithdrawManager permissions
function QuickSendAssertToUser(address _token, address to, uint256 _amount) external onlyWithdrawManager// Staking pool
struct Pool {
uint32 startTimestamp; // Pool start time
uint32 endTimestamp; // Pool end time
address token; // Token address
uint256 TotalAmount; // Total staked amount
uint256 TotalFee; // Total fees
uint256 TotalFeeClaimed; // Total fees claimed
bool IsCompleted; // Whether completed
}
// User stake record
struct User {
bool isWithdrawed; // Whether withdrawn
uint256 StartPoolId; // Start staking pool ID
uint256 EndPoolId; // End pool ID (currently unused)
address token; // Staked token
uint256 Amount; // Stake amount
}
// Key-value pair (for query returns)
struct KeyValuePair {
address key; // Token address
uint256 value; // Value (principal or reward)
}| Role | Permissions | Description |
|---|---|---|
| Owner | Pause/unpause contract | Emergency contract state control |
| Relayer | Execute bridge, rotate pools, set | Core operational role |
| parameters | ||
| WithdrawManager | Withdraw liquidity, emergency | Fund management role |
| transfers | ||
| Regular User | Stake, withdraw, bridge | Daily operations |
Storage Layer - State Variable Definitions
// Constants
address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
// Configuration parameters
uint32 public periodTime; // Staking period (default 21 days)
uint256 public MinTransferAmount; // Minimum bridge amount
uint256 public PerFee; // Fee rate (default 10000 = 0.1%)
uint256 public stakingMessageNumber; // Staking message number
// Role addresses
address public relayerAddress; // Relayer address
address public withdrawManager; // Withdrawal manager address
IMessageManager public messageManager; // Message manager contract
// Supported tokens
address[] public SupportTokens; // Supported token list
// Mappings
mapping(uint256 => bool) public IsSupportedChainId; // Supported chain IDs
mapping(address => bool) public IsSupportToken; // Supported tokens
mapping(address => uint256) public FundingPoolBalance; // Funding pool balance
mapping(address => uint256) public FeePoolValue; // Fee pool balance
mapping(address => uint256) public MinStakeAmount; // Minimum stake amount
mapping(address => Pool[]) public Pools; // Staking pool list
mapping(address => User[]) public Users; // User staking recordsMessage Manager - Cross-Chain Message Verification
function sendMessage(
uint256 sourceChainId,
uint256 destChainId,
address sourceTokenAddress,
address destTokenAddress,
address _from,
address _to,
uint256 _value,
uint256 _fee
) external onlyTokenBridgeFunctionality:
- Generate unique message hash
- Increment message number (nonce)
- Mark message as sent
- Emit
MessageSentevent for Relayer to listen
function claimMessage(
uint256 sourceChainId,
uint256 destChainId,
address sourceTokenAddress,
address destTokenAddress,
address _from,
address _to,
uint256 _value,
uint256 _fee,
uint256 _nonce
) external onlyTokenBridge nonReentrantFunctionality:
- Verify message hash
- Check if message already claimed (prevent replay)
- Mark message as claimed
- Emit
MessageClaimedevent
Replay Attack Prevention:
├─ Source chain: sentMessageStatus[messageHash] = true
└─ Destination chain: claimMessageStatus[messageHash] = true
Message Uniqueness:
messageHash = keccak256(
sourceChainId,
destChainId,
sourceToken,
destToken,
from,
to,
value,
fee,
nonce ← Unique per message
)
Message Storage Layer
abstract contract MessageManagerStorage is IMessageManager {
uint256 public nextMessageNumber; // Next message number
address public poolManagerAddress; // PoolManager contract address
mapping(bytes32 => bool) public sentMessageStatus; // Sent messages
mapping(bytes32 => bool) public claimMessageStatus; // Claimed messages
}// User calls on Ethereum
poolManager.BridageInitiateETH{value: 1 ether}(
1, // sourceChainId: Ethereum
56, // destChainId: BSC
WBNB, // destTokenAddress
user // to
);Contract Execution:
- Verify chain ID and amount
- Receive user's 1 ETH
- Calculate fee: 0.001 ETH
- Actual bridge amount: 0.999 ETH
- Call
messageManager.sendMessage() - Emit
InitiateETHandMessageSentevents
// Relayer listens to source chain events
poolManager.on("MessageSent", async (event) => {
const { sourceChainId, destChainId, messageHash, ...params } = event;
// Verify transaction confirmations
await waitForConfirmations(event.transactionHash, 12);
// Execute Finalize on destination chain
await destPoolManager.BridgeFinalizeETH(params);
});// Relayer calls on BSC
poolManager.BridgeFinalizeETH{value: 0.999 ether}(
1, // sourceChainId: Ethereum
56, // destChainId: BSC
ETH_ADDRESS,
user, // from
user, // to
0.999 ether,
0.001 ether,
nonce
);Contract Execution:
- Verify chain ID and Relayer permissions
- Send 0.999 ETH to user
- Call
messageManager.claimMessage()to prevent replay - Emit
FinalizeETHandMessageClaimedevents
// Add ETH staking support
poolManager.setSupportToken(
ETH_ADDRESS,
true,
block.timestamp + 1 days // Start accepting stakes in 1 day
);
// Result: Creates two pools
// Pool 0: Genesis pool (placeholder)
// Pool 1: First real staking pool (21-day period)// User stakes 10 ETH
poolManager.DepositAndStakingETH{value: 10 ether}();
// Record created:
// Users[user].push({
// isWithdrawed: false,
// StartPoolId: 1,
// token: ETH_ADDRESS,
// Amount: 10 ether
// });Bridge transactions over 21 days:
├─ Transaction 1: 1 ETH × 0.1% = 0.001 ETH
├─ Transaction 2: 5 ETH × 0.1% = 0.005 ETH
├─ Transaction 3: 2 ETH × 0.1% = 0.002 ETH
└─ ...
Accumulated in FeePoolValue[ETH_ADDRESS] = 0.5 ETH
// After 21 days, Relayer rotates pool
Pool[] memory pools = new Pool[](1);
pools[0].token = ETH_ADDRESS;
poolManager.CompletePoolAndNew(pools);
// Execution:
// 1. Mark Pool 1 as completed
// 2. Distribute 0.5 ETH fees to Pool 1
// 3. Create new Pool 2, inherit all stakes// Query rewards
KeyValuePair[] memory rewards = poolManager.getReward();
// Returns: { key: ETH_ADDRESS, value: 0.05 ether }
// Calculation: (10 ETH / 100 ETH total stake) × 0.5 ETH fees = 0.05 ETH
// Claim rewards (principal continues staking)
poolManager.ClaimAllReward();
// Or withdraw principal + rewards
poolManager.WithdrawAll();-
✅ Reentrancy Attack Prevention
- Uses
nonReentrantmodifier - CEI pattern (some functions)
- Uses
-
✅ Replay Attack Prevention
- MessageManager message hash verification
- Nonce mechanism
-
✅ Access Control
onlyOwner,onlyRelayer,onlyWithdrawManager- Multi-role permission separation
-
✅ Pause Mechanism
- Can pause contract in emergency situations
-
✅ Safe Math Operations
- Uses SafeERC20 for token transfers
- Prevents integer overflow (Solidity 0.8+)
┌─────────────┐
│ User │
└──────┬──────┘
│
│ 1. Stake/Bridge/Withdraw
↓
┌─────────────────────────────┐
│ PoolManager.sol │
│ ┌─────────────────────────┐│
│ │ Liquidity Management ││
│ │ - depositEthToBridge ││
│ │ - withdrawEthFromBridge││
│ └─────────────────────────┘│
│ ┌─────────────────────────┐│
│ │ Bridge Features ││
│ │ - BridageInitiateETH ││
│ │ - BridgeFinalizeETH ││
│ └─────────────────────────┘│
│ ┌─────────────────────────┐│
│ │ Staking System ││
│ │ - DepositAndStaking ││
│ │ - WithdrawAll ││
│ │ - ClaimReward ││
│ └─────────────────────────┘│
└───────┬──────────────────┬──┘
│ │
│ 2. Send/Claim │
↓ messages │
┌────────────────────┐ │
│ MessageManager.sol │ │
│ - sendMessage │ │
│ - claimMessage │ │
│ - Replay attack │ │
│ prevention │ │
└────────────────────┘ │
│
│ 3. Read/Write state
↓
┌──────────────────────────────┐
│ Storage Layer │
│ ┌───────────────────────────┤
│ │ PoolManagerStorage │
│ │ - Pools │
│ │ - Users │
│ │ - FundingPoolBalance │
│ └───────────────────────────┤
│ │ MessageManagerStorage │
│ │ - sentMessageStatus │
│ │ - claimMessageStatus │
│ └───────────────────────────┘
└──────────────────────────────┘
| Parameter | Default | Description | Modification Function |
|---|---|---|---|
periodTime |
21 days | Staking period | Set during init |
MinTransferAmount |
0.1 ether | Minimum bridge amount | setMinTransferAmount |
PerFee |
10000 (0.1%) | Fee rate | setPerFee |
MinStakeAmount |
Per token | Minimum staking amount | setMinStakeAmount |
event DepositToken(address indexed token, address indexed from, uint256 amount);
event WithdrawToken(address indexed token, address indexed operator, address indexed to, uint256 amount);
event InitiateETH(uint256 indexed sourceChainId, uint256 indexed destChainId, address destTokenAddress, address indexed from, address to, uint256 amount);
event InitiateERC20(uint256 indexed sourceChainId, uint256 indexed destChainId, address sourceTokenAddress, address destTokenAddress, address indexed from, address to, uint256 amount);
event FinalizeETH(uint256 indexed sourceChainId, uint256 indexed destChainId, address sourceTokenAddress, address indexed from, address to, uint256 amount);
event FinalizeERC20(uint256 indexed sourceChainId, uint256 indexed destChainId, address sourceTokenAddress, address destTokenAddress, address indexed from, address to, uint256 amount);
event StakingETHEvent(address indexed user, uint256 indexed chainId, uint256 amount);
event StakingERC20Event(address indexed user, address indexed token, uint256 indexed chainId, uint256 amount);
event Withdraw(address indexed user, uint256 startPoolId, uint256 endPoolId, uint256 indexed chainId, address indexed token, uint256 amount, uint256 reward);
event ClaimReward(address indexed user, uint256 startPoolId, uint256 endPoolId, uint256 indexed chainId, address indexed token, uint256 reward);
event CompletePoolEvent(address indexed token, uint256 indexed poolId, uint256 indexed chainId);
event SetSupportTokenEvent(address indexed token, bool isSupport, uint256 indexed chainId);
event SetMinStakeAmountEvent(address indexed token, uint256 amount, uint256 indexed chainId);
event SetMinTransferAmount(uint256 amount);
event SetValidChainId(uint256 indexed chainId, bool isValid);
event SetPerFee(uint256 fee);event MessageSent(
uint256 indexed sourceChainId,
uint256 indexed destChainId,
address sourceTokenAddress,
address destTokenAddress,
address indexed from,
address to,
uint256 fee,
uint256 value,
uint256 messageNumber,
bytes32 messageHash
);
event MessageClaimed(
uint256 indexed sourceChainId,
uint256 indexed destChainId,
address sourceTokenAddress,
address destTokenAddress,
bytes32 indexed messageHash,
uint256 nonce
);// 1. Deploy MessageManager
MessageManager messageManager = new MessageManager();
messageManager.initialize(owner, address(0)); // poolManager address set later
// 2. Deploy PoolManager
PoolManager poolManager = new PoolManager();
poolManager.initialize(
owner,
address(messageManager),
relayerAddress,
withdrawManagerAddress
);
// 3. Update MessageManager's poolManager address
messageManager.updatePoolManager(address(poolManager)); // Need to add this function// Set supported chains
poolManager.setValidChainId(1, true); // Ethereum
poolManager.setValidChainId(56, true); // BSC
poolManager.setValidChainId(137, true); // Polygon
// Add token support and create staking pool
poolManager.setSupportToken(
ETH_ADDRESS,
true,
block.timestamp + 1 days
);
// Set staking parameters
poolManager.setMinStakeAmount(ETH_ADDRESS, 0.1 ether);
poolManager.setPerFee(10000); // 0.1%// Project provides initial liquidity
poolManager.depositEthToBridge{value: 100 ether}();const tx = await poolManager.DepositAndStakingETH({
value: ethers.utils.parseEther("10"),
});
await tx.wait();
console.log("Staking successful!");// Bridge 1 ETH from Ethereum to BSC
const tx = await poolManager.BridageInitiateETH(
1, // Ethereum
56, // BSC
WBNB_ADDRESS,
userAddress,
{ value: ethers.utils.parseEther("1") }
);
await tx.wait();
console.log("Bridge transaction initiated!");// Query rewards
const rewards = await poolManager.getReward();
console.log("ETH rewards:", ethers.utils.formatEther(rewards[0].value));
// Claim rewards
const tx = await poolManager.ClaimAllReward();
await tx.wait();
console.log("Rewards claimed!");# Set valid chain ID on Sepolia
cast send --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY 0x015DD02C6D1CF3f1711dcf453ed4b4f34B778E65 "setValidChainId(uint256,bool)" 11155111 true
# Set valid chain ID on custom network
cast send --rpc-url $S_URP_URL --private-key $PRIVATE_KEY 0x9B3F87aa9ABbC18b78De9fF245cc945F794F7559 "setValidChainId(uint256,bool)" 90101 true
# Set supported ERC20 token on Sepolia
cast send --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY 0x9B3F87aa9ABbC18b78De9fF245cc945F794F7559 "setSupportERC20Token(address,bool)" 0x12E60438898FB3b4aac8439DEeD57194Dc9C87aa true