This repository was archived by the owner on Dec 2, 2025. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 8
This repository was archived by the owner on Dec 2, 2025. It is now read-only.
Cross-Chain Bridge Contracts Implementation #16
Copy link
Copy link
Open
Labels
good first issueGood for newcomersGood for newcomersonlydust-waveContribute to awesome OSS repos during OnlyDust's open source weekContribute to awesome OSS repos during OnlyDust's open source week
Description
Description
Implement the core cross-chain bridge functionality that gives TrustBridge its name. Currently, despite being called "TrustBridge", the protocol has no cross-chain bridge contracts. This issue will create a comprehensive bridge system to enable asset transfers between Stellar and other blockchain networks (starting with Ethereum).
What to Implement
- Bridge Validator Contract for multi-signature validation
- Bridge Relay Contract for cross-chain message passing
- Wrapped Asset Contract for managing cross-chain assets
- Bridge Treasury Contract for reserves and fee management
- Bridge Oracle for cross-chain price and status verification
Acceptance Criteria
- Assets can be bridged from Ethereum to Stellar
- Multi-signature validation system working
- Wrapped asset management implemented
- Bridge fees and treasury management functional
- Cross-chain message verification working
- Emergency pause and recovery mechanisms implemented
Technical Requirements
New Contracts to Create
-
Bridge Validator Contract
- Path:
contracts/bridge-validator/ - Purpose: Multi-signature validation for cross-chain transfers
- Path:
-
Bridge Relay Contract
- Path:
contracts/bridge-relay/ - Purpose: Cross-chain message passing and state synchronization
- Path:
-
Wrapped Asset Factory
- Path:
contracts/wrapped-asset-factory/ - Purpose: Deploy and manage wrapped asset contracts
- Path:
-
Wrapped Asset Contract
- Path:
contracts/wrapped-asset/ - Purpose: ERC-20 compatible wrapped assets on Stellar
- Path:
-
Bridge Treasury Contract
- Path:
contracts/bridge-treasury/ - Purpose: Manage bridge reserves, fees, and validator rewards
- Path:
-
Bridge Oracle Contract
- Path:
contracts/bridge-oracle/ - Purpose: Cross-chain price feeds and bridge status verification
- Path:
Implementation Details
Bridge Architecture Overview
Ethereum Network | Stellar Network
|
┌─────────────────┐ | ┌──────────────────┐
│ Ethereum Bridge │────────────│ Bridge Validator │
│ Contract │ | │ Contract │
└─────────────────┘ | └──────────────────┘
| │
┌─────────────────┐ | ┌──────────────────┐
│ User Deposits │ | │ Bridge Relay │
│ ETH/USDC │ | │ Contract │
└─────────────────┘ | └──────────────────┘
| │
| ┌──────────────────┐
| │ Wrapped Asset │
| │ Factory │
| └──────────────────┘
| │
| ┌──────────────────┐
| │ Wrapped USDC │
| │ Contract │
| └──────────────────┘
Bridge Validator Contract
// contracts/bridge-validator/src/contract.rs
use soroban_sdk::{contract, contractimpl, Address, Env, Vec, Map, Bytes};
use shared::types::{BridgeTransaction, ValidationStatus, ValidatorSet};
#[contract]
pub struct BridgeValidator;
#[contractimpl]
impl BridgeValidator {
/// Initialize the bridge validator with initial validator set
pub fn initialize(
env: Env,
admin: Address,
validators: Vec<Address>,
threshold: u32,
bridge_treasury: Address
) -> Result<(), BridgeError> {
if env.storage().instance().has(&DataKey::Initialized) {
return Err(BridgeError::AlreadyInitialized);
}
let validator_set = ValidatorSet {
validators,
threshold,
nonce: 0,
};
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::ValidatorSet, &validator_set);
env.storage().instance().set(&DataKey::BridgeTreasury, &bridge_treasury);
env.storage().instance().set(&DataKey::Initialized, &true);
emit_validator_set_updated(&env, validator_set);
Ok(())
}
/// Validate a cross-chain bridge transaction
pub fn validate_bridge_transaction(
env: Env,
transaction: BridgeTransaction,
signatures: Vec<Bytes>
) -> Result<ValidationStatus, BridgeError> {
let validator_set = Self::get_validator_set(&env)?;
// Verify transaction hasn't been processed
if Self::is_transaction_processed(&env, &transaction.hash) {
return Err(BridgeError::TransactionAlreadyProcessed);
}
// Verify signatures
let valid_signatures = Self::verify_signatures(&env, &transaction, &signatures)?;
if valid_signatures >= validator_set.threshold {
// Mark transaction as validated
env.storage().instance().set(
&DataKey::ProcessedTransaction(transaction.hash.clone()),
&true
);
// Execute bridge action
Self::execute_bridge_action(&env, &transaction)?;
emit_transaction_validated(&env, transaction.hash.clone());
Ok(ValidationStatus::Validated)
} else {
Ok(ValidationStatus::InsufficientSignatures)
}
}
/// Add a new validator (admin only)
pub fn add_validator(
env: Env,
admin: Address,
new_validator: Address
) -> Result<(), BridgeError> {
admin.require_auth();
Self::require_admin(&env, &admin)?;
let mut validator_set = Self::get_validator_set(&env)?;
if validator_set.validators.contains(&new_validator) {
return Err(BridgeError::ValidatorAlreadyExists);
}
validator_set.validators.push_back(new_validator.clone());
validator_set.nonce += 1;
env.storage().instance().set(&DataKey::ValidatorSet, &validator_set);
emit_validator_added(&env, new_validator);
Ok(())
}
/// Remove a validator (admin only)
pub fn remove_validator(
env: Env,
admin: Address,
validator: Address
) -> Result<(), BridgeError> {
admin.require_auth();
Self::require_admin(&env, &admin)?;
let mut validator_set = Self::get_validator_set(&env)?;
let initial_len = validator_set.validators.len();
validator_set.validators.retain(|v| v != &validator);
if validator_set.validators.len() == initial_len {
return Err(BridgeError::ValidatorNotFound);
}
// Ensure we still meet minimum threshold requirements
if validator_set.validators.len() < validator_set.threshold {
return Err(BridgeError::InsufficientValidators);
}
validator_set.nonce += 1;
env.storage().instance().set(&DataKey::ValidatorSet, &validator_set);
emit_validator_removed(&env, validator);
Ok(())
}
/// Update signature threshold (admin only)
pub fn update_threshold(
env: Env,
admin: Address,
new_threshold: u32
) -> Result<(), BridgeError> {
admin.require_auth();
Self::require_admin(&env, &admin)?;
let mut validator_set = Self::get_validator_set(&env)?;
if new_threshold == 0 || new_threshold > validator_set.validators.len() {
return Err(BridgeError::InvalidThreshold);
}
validator_set.threshold = new_threshold;
validator_set.nonce += 1;
env.storage().instance().set(&DataKey::ValidatorSet, &validator_set);
emit_threshold_updated(&env, new_threshold);
Ok(())
}
// Internal helper functions
fn verify_signatures(
env: &Env,
transaction: &BridgeTransaction,
signatures: &Vec<Bytes>
) -> Result<u32, BridgeError> {
let validator_set = Self::get_validator_set(env)?;
let message_hash = Self::get_transaction_hash(transaction);
let mut valid_count = 0;
let mut used_validators = Vec::<Address>::new(env);
for signature in signatures {
if let Ok(validator) = Self::recover_signer(env, &message_hash, signature) {
if validator_set.validators.contains(&validator) &&
!used_validators.contains(&validator) {
valid_count += 1;
used_validators.push_back(validator);
}
}
}
Ok(valid_count)
}
fn execute_bridge_action(
env: &Env,
transaction: &BridgeTransaction
) -> Result<(), BridgeError> {
match &transaction.action {
BridgeAction::Lock { asset, amount, recipient } => {
Self::mint_wrapped_asset(env, asset, *amount, recipient)?;
},
BridgeAction::Unlock { asset, amount, recipient } => {
Self::burn_wrapped_asset(env, asset, *amount, recipient)?;
},
BridgeAction::ValidatorUpdate { new_validator_set } => {
Self::update_validator_set(env, new_validator_set)?;
}
}
Ok(())
}
}
// Shared types
#[derive(Clone, Debug)]
pub struct BridgeTransaction {
pub hash: Bytes,
pub source_chain: u32,
pub target_chain: u32,
pub nonce: u64,
pub action: BridgeAction,
pub fee: i128,
pub timestamp: u64,
}
#[derive(Clone, Debug)]
pub enum BridgeAction {
Lock { asset: Address, amount: i128, recipient: Address },
Unlock { asset: Address, amount: i128, recipient: Address },
ValidatorUpdate { new_validator_set: ValidatorSet },
}
#[derive(Clone, Debug)]
pub struct ValidatorSet {
pub validators: Vec<Address>,
pub threshold: u32,
pub nonce: u64,
}
#[derive(Clone, Debug)]
pub enum ValidationStatus {
Pending,
Validated,
InsufficientSignatures,
Invalid,
}Wrapped Asset Factory Contract
// contracts/wrapped-asset-factory/src/contract.rs
use soroban_sdk::{contract, contractimpl, Address, Env, String, Bytes};
use wrapped_asset::WrappedAssetClient;
#[contract]
pub struct WrappedAssetFactory;
#[contractimpl]
impl WrappedAssetFactory {
/// Deploy a new wrapped asset contract
pub fn deploy_wrapped_asset(
env: Env,
deployer: Address,
source_chain: u32,
source_asset: Address,
name: String,
symbol: String,
decimals: u32
) -> Result<Address, BridgeError> {
deployer.require_auth();
// Check if wrapped asset already exists
let asset_key = (source_chain, source_asset.clone());
if env.storage().persistent().has(&DataKey::WrappedAsset(asset_key.clone())) {
return Err(BridgeError::WrappedAssetAlreadyExists);
}
// Deploy new wrapped asset contract
let wrapped_asset_wasm = env.storage().instance()
.get(&DataKey::WrappedAssetWasm)
.ok_or(BridgeError::WrappedAssetWasmNotSet)?;
let salt = Bytes::from_array(&env, &asset_key.clone().into());
let wrapped_asset_address = env.deployer().with_current_contract(salt)
.deploy(wrapped_asset_wasm);
// Initialize the wrapped asset
let wrapped_asset_client = WrappedAssetClient::new(&env, &wrapped_asset_address);
wrapped_asset_client.initialize(
&env.current_contract_address(), // This factory is the admin
&source_chain,
&source_asset,
&name,
&symbol,
&decimals
);
// Store mapping
env.storage().persistent().set(
&DataKey::WrappedAsset(asset_key),
&wrapped_asset_address
);
emit_wrapped_asset_deployed(&env, wrapped_asset_address.clone(), source_chain, source_asset);
Ok(wrapped_asset_address)
}
/// Get wrapped asset address for a source asset
pub fn get_wrapped_asset(
env: Env,
source_chain: u32,
source_asset: Address
) -> Option<Address> {
let asset_key = (source_chain, source_asset);
env.storage().persistent().get(&DataKey::WrappedAsset(asset_key))
}
/// Mint wrapped assets (bridge validator only)
pub fn mint_wrapped_asset(
env: Env,
bridge_validator: Address,
source_chain: u32,
source_asset: Address,
amount: i128,
recipient: Address
) -> Result<(), BridgeError> {
bridge_validator.require_auth();
Self::require_bridge_validator(&env, &bridge_validator)?;
let wrapped_asset_address = Self::get_wrapped_asset(env.clone(), source_chain, source_asset)
.ok_or(BridgeError::WrappedAssetNotFound)?;
let wrapped_asset_client = WrappedAssetClient::new(&env, &wrapped_asset_address);
wrapped_asset_client.mint(&recipient, &amount);
emit_wrapped_asset_minted(&env, wrapped_asset_address, recipient, amount);
Ok(())
}
/// Burn wrapped assets (bridge validator only)
pub fn burn_wrapped_asset(
env: Env,
bridge_validator: Address,
source_chain: u32,
source_asset: Address,
amount: i128,
from: Address
) -> Result<(), BridgeError> {
bridge_validator.require_auth();
Self::require_bridge_validator(&env, &bridge_validator)?;
let wrapped_asset_address = Self::get_wrapped_asset(env.clone(), source_chain, source_asset)
.ok_or(BridgeError::WrappedAssetNotFound)?;
let wrapped_asset_client = WrappedAssetClient::new(&env, &wrapped_asset_address);
wrapped_asset_client.burn(&from, &amount);
emit_wrapped_asset_burned(&env, wrapped_asset_address, from, amount);
Ok(())
}
}Bridge Treasury Contract
// contracts/bridge-treasury/src/contract.rs
use soroban_sdk::{contract, contractimpl, Address, Env, Map};
#[contract]
pub struct BridgeTreasury;
#[contractimpl]
impl BridgeTreasury {
/// Initialize bridge treasury
pub fn initialize(
env: Env,
admin: Address,
bridge_validator: Address,
fee_recipient: Address
) -> Result<(), BridgeError> {
if env.storage().instance().has(&DataKey::Initialized) {
return Err(BridgeError::AlreadyInitialized);
}
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::BridgeValidator, &bridge_validator);
env.storage().instance().set(&DataKey::FeeRecipient, &fee_recipient);
env.storage().instance().set(&DataKey::Initialized, &true);
Ok(())
}
/// Set bridge fee for an asset
pub fn set_bridge_fee(
env: Env,
admin: Address,
asset: Address,
fee_rate: u32, // Basis points (10000 = 100%)
min_fee: i128,
max_fee: i128
) -> Result<(), BridgeError> {
admin.require_auth();
Self::require_admin(&env, &admin)?;
if fee_rate > 10000 {
return Err(BridgeError::InvalidFeeRate);
}
let fee_config = FeeConfig {
rate: fee_rate,
min_fee,
max_fee,
};
env.storage().persistent().set(&DataKey::BridgeFee(asset), &fee_config);
emit_bridge_fee_updated(&env, asset, fee_config);
Ok(())
}
/// Calculate bridge fee for an amount
pub fn calculate_bridge_fee(
env: Env,
asset: Address,
amount: i128
) -> Result<i128, BridgeError> {
let fee_config: FeeConfig = env.storage().persistent()
.get(&DataKey::BridgeFee(asset))
.unwrap_or_default();
let calculated_fee = (amount * fee_config.rate as i128) / 10000;
let fee = calculated_fee
.max(fee_config.min_fee)
.min(fee_config.max_fee);
Ok(fee)
}
/// Collect bridge fees
pub fn collect_bridge_fee(
env: Env,
bridge_validator: Address,
asset: Address,
amount: i128,
from: Address
) -> Result<i128, BridgeError> {
bridge_validator.require_auth();
Self::require_bridge_validator(&env, &bridge_validator)?;
let fee = Self::calculate_bridge_fee(env.clone(), asset.clone(), amount)?;
if fee > 0 {
// Transfer fee to treasury
token::Client::new(&env, &asset).transfer(&from, &env.current_contract_address(), &fee);
// Update collected fees
let current_fees = Self::get_collected_fees(&env, &asset);
env.storage().persistent().set(
&DataKey::CollectedFees(asset.clone()),
&(current_fees + fee)
);
emit_bridge_fee_collected(&env, asset, fee, from);
}
Ok(fee)
}
/// Withdraw collected fees (admin only)
pub fn withdraw_fees(
env: Env,
admin: Address,
asset: Address,
amount: i128,
recipient: Address
) -> Result<(), BridgeError> {
admin.require_auth();
Self::require_admin(&env, &admin)?;
let collected_fees = Self::get_collected_fees(&env, &asset);
if amount > collected_fees {
return Err(BridgeError::InsufficientFees);
}
// Transfer fees
token::Client::new(&env, &asset).transfer(
&env.current_contract_address(),
&recipient,
&amount
);
// Update collected fees
env.storage().persistent().set(
&DataKey::CollectedFees(asset.clone()),
&(collected_fees - amount)
);
emit_fees_withdrawn(&env, asset, amount, recipient);
Ok(())
}
fn get_collected_fees(env: &Env, asset: &Address) -> i128 {
env.storage().persistent()
.get(&DataKey::CollectedFees(asset.clone()))
.unwrap_or(0)
}
}
#[derive(Clone, Debug, Default)]
pub struct FeeConfig {
pub rate: u32, // Basis points
pub min_fee: i128,
pub max_fee: i128,
}Bridge Integration with Existing Contracts
Integration with Pool Contract
// Add to contracts/pool/src/contract.rs
impl Pool {
/// Supply wrapped assets to the pool
pub fn supply_wrapped_asset(
env: Env,
from: Address,
wrapped_asset: Address,
amount: i128
) -> Result<(), PoolError> {
from.require_auth();
// Verify this is a legitimate wrapped asset
let wrapped_asset_factory = Self::get_wrapped_asset_factory(&env);
let factory_client = WrappedAssetFactoryClient::new(&env, &wrapped_asset_factory);
if !factory_client.is_wrapped_asset(&wrapped_asset) {
return Err(PoolError::InvalidWrappedAsset);
}
// Continue with normal supply logic
Self::supply(env, from, wrapped_asset, amount)
}
}Cross-Chain Transaction Flow
Bridge Transaction Lifecycle
-
User Initiation (Ethereum)
// Ethereum bridge contract function bridgeToStellar( address token, uint256 amount, string calldata stellarRecipient ) external { // Lock tokens on Ethereum IERC20(token).transferFrom(msg.sender, address(this), amount); // Emit bridge event emit BridgeInitiated(token, amount, stellarRecipient, nonce++); }
-
Validator Monitoring (Off-chain)
// Validator listens to Ethereum events async fn monitor_ethereum_bridge() { let bridge_events = ethereum_client.get_bridge_events().await; for event in bridge_events { let bridge_tx = BridgeTransaction { hash: event.tx_hash, source_chain: ETHEREUM_CHAIN_ID, target_chain: STELLAR_CHAIN_ID, action: BridgeAction::Lock { asset: event.token, amount: event.amount, recipient: event.stellar_recipient, }, // ... other fields }; // Sign and submit to Stellar let signature = sign_transaction(&bridge_tx).await; submit_to_stellar_validator(&bridge_tx, &signature).await; } }
-
Validation on Stellar
// Validators submit signatures to Stellar bridge let result = bridge_validator_client.validate_bridge_transaction( &bridge_transaction, &signatures );
-
Asset Minting (Automatic)
// Bridge validator automatically mints wrapped assets wrapped_asset_factory_client.mint_wrapped_asset( source_chain, source_asset, amount, recipient );
Security Considerations
Multi-Signature Security
- Configurable validator threshold (e.g., 7 of 10 validators)
- Validator rotation mechanisms
- Emergency pause functionality
Economic Security
- Bridge fees to incentivize validators
- Slashing mechanisms for malicious validators
- Insurance pool for bridge failures
Technical Security
- Replay attack protection
- Message verification and signatures
- Time-locked emergency procedures
Testing Strategy
Unit Tests
#[cfg(test)]
mod tests {
#[test]
fn test_bridge_validation() {
let env = Env::default();
let contract_id = env.register_contract(None, BridgeValidator);
let client = BridgeValidatorClient::new(&env, &contract_id);
// Test bridge transaction validation
let transaction = create_test_bridge_transaction();
let signatures = create_test_signatures();
let result = client.validate_bridge_transaction(&transaction, &signatures);
assert_eq!(result, ValidationStatus::Validated);
}
}Integration Tests
#[test]
fn test_full_bridge_flow() {
let env = TestEnvironment::new();
// Deploy all bridge contracts
let bridge_contracts = deploy_bridge_contracts(&env);
// Test complete bridge flow from Ethereum to Stellar
let result = execute_bridge_transaction(&env, &bridge_contracts);
assert!(result.is_ok());
}Dependencies
- Soroban SDK for Stellar contracts
- Cross-chain communication libraries
- Cryptographic signature verification
- Oracle integration for cross-chain data
Definition of Done
- All bridge contracts compile and deploy successfully
- Cross-chain asset transfers working Ethereum ↔ Stellar
- Multi-signature validation system operational
- Wrapped asset management fully functional
- Bridge fees and treasury management working
- Security measures properly implemented
- Comprehensive testing suite passing
- Documentation and integration guides complete
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
good first issueGood for newcomersGood for newcomersonlydust-waveContribute to awesome OSS repos during OnlyDust's open source weekContribute to awesome OSS repos during OnlyDust's open source week