Skip to content
This repository was archived by the owner on Dec 2, 2025. It is now read-only.
This repository was archived by the owner on Dec 2, 2025. It is now read-only.

Cross-Chain Bridge Contracts Implementation #16

@JosueBrenes

Description

@JosueBrenes

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

  1. Bridge Validator Contract

    • Path: contracts/bridge-validator/
    • Purpose: Multi-signature validation for cross-chain transfers
  2. Bridge Relay Contract

    • Path: contracts/bridge-relay/
    • Purpose: Cross-chain message passing and state synchronization
  3. Wrapped Asset Factory

    • Path: contracts/wrapped-asset-factory/
    • Purpose: Deploy and manage wrapped asset contracts
  4. Wrapped Asset Contract

    • Path: contracts/wrapped-asset/
    • Purpose: ERC-20 compatible wrapped assets on Stellar
  5. Bridge Treasury Contract

    • Path: contracts/bridge-treasury/
    • Purpose: Manage bridge reserves, fees, and validator rewards
  6. Bridge Oracle Contract

    • Path: contracts/bridge-oracle/
    • Purpose: Cross-chain price feeds and bridge status verification

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

  1. 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++);
    }
  2. 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;
        }
    }
  3. Validation on Stellar

    // Validators submit signatures to Stellar bridge
    let result = bridge_validator_client.validate_bridge_transaction(
        &bridge_transaction,
        &signatures
    );
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    good first issueGood for newcomersonlydust-waveContribute to awesome OSS repos during OnlyDust's open source week

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions