Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
359 changes: 182 additions & 177 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions contracts/auction_factory/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn test_factory() {
let env = Env::default();
env.mock_all_auths();

let factory_id = env.register_contract(None, AuctionFactory);
let factory_id = env.register(AuctionFactory, ());
let factory_client = AuctionFactoryClient::new(&env, &factory_id);

// For full test, need WASM hashes, which require building the contracts.
Expand All @@ -26,4 +26,4 @@ fn test_factory() {
// In practice, we need real WASM.

// assert!(factory_client.create_english_auction(&seller, &nft_contract, &1, &payment_token, &100, &200, &10, &dummy_hash).is_some());
}
}
16 changes: 10 additions & 6 deletions contracts/cross_chain_verifier/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![no_std]

use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env, Vec, Bytes};
use soroban_sdk::{contract, contractimpl, contracttype, Address, Bytes, BytesN, Env, Vec};

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
Expand All @@ -27,19 +27,23 @@ impl CrossChainVerifier {
pub fn update_root(env: Env, block_height: u32, new_root: BytesN<32>) {
let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
admin.require_auth();

env.storage().persistent().set(&DataKey::StateRoot(block_height), &new_root);

env.storage()
.persistent()
.set(&DataKey::StateRoot(block_height), &new_root);
}

/// Retrieve a stored state root by block height.
pub fn get_root(env: Env, block_height: u32) -> Option<BytesN<32>> {
env.storage().persistent().get(&DataKey::StateRoot(block_height))
env.storage()
.persistent()
.get(&DataKey::StateRoot(block_height))
}

/// Verifies a Binary Merkle Tree proof.
/// In a cross-chain context, this allows proving that a specific message or transaction
/// (the `leaf`) was included in the block matching `block_height` state root.
///
///
/// * `block_height`: The block height of the state root to verify against.
/// * `leaf`: The hash of the cross-chain message to be verified.
/// * `proof`: A list of sibling hashes forming the Merkle proof.
Expand Down Expand Up @@ -75,7 +79,7 @@ impl CrossChainVerifier {
combined[0..32].copy_from_slice(&current_hash);
combined[32..64].copy_from_slice(&sibling);
}

// Compute sha256 of the combined 64 bytes
let combined_bytes = Bytes::from_slice(&env, &combined);
current_hash = env.crypto().sha256(&combined_bytes).to_array();
Expand Down
24 changes: 15 additions & 9 deletions contracts/cross_chain_verifier/src/test.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#![cfg(test)]

use crate::{CrossChainVerifier, CrossChainVerifierClient};
use soroban_sdk::{testutils::Address as _, Address, BytesN, Env, Vec, Bytes};
use soroban_sdk::{testutils::Address as _, Address, Bytes, BytesN, Env, Vec};

#[test]
fn test_initialization() {
let env = Env::default();
let contract_id = env.register_contract(None, CrossChainVerifier);
let contract_id = env.register(CrossChainVerifier, ());
let client = CrossChainVerifierClient::new(&env, &contract_id);
let admin = Address::generate(&env);

Expand All @@ -17,7 +17,7 @@ fn test_initialization() {
#[should_panic(expected = "already initialized")]
fn test_double_initialization() {
let env = Env::default();
let contract_id = env.register_contract(None, CrossChainVerifier);
let contract_id = env.register(CrossChainVerifier, ());
let client = CrossChainVerifierClient::new(&env, &contract_id);
let admin = Address::generate(&env);

Expand All @@ -30,7 +30,7 @@ fn test_root_update() {
let env = Env::default();
env.mock_all_auths();

let contract_id = env.register_contract(None, CrossChainVerifier);
let contract_id = env.register(CrossChainVerifier, ());
let client = CrossChainVerifierClient::new(&env, &contract_id);
let admin = Address::generate(&env);

Expand All @@ -50,7 +50,7 @@ fn test_verify_message_success() {
let env = Env::default();
env.mock_all_auths();

let contract_id = env.register_contract(None, CrossChainVerifier);
let contract_id = env.register(CrossChainVerifier, ());
let client = CrossChainVerifierClient::new(&env, &contract_id);
let admin = Address::generate(&env);

Expand All @@ -65,13 +65,19 @@ fn test_verify_message_success() {
let mut combined_1 = [0u8; 64];
combined_1[0..32].copy_from_slice(&sibling1.to_array());
combined_1[32..64].copy_from_slice(&leaf.to_array());
let hash_1 = env.crypto().sha256(&Bytes::from_slice(&env, &combined_1)).to_array();
let hash_1 = env
.crypto()
.sha256(&Bytes::from_slice(&env, &combined_1))
.to_array();

// Level 2: Hash(hash_1 || sibling2) since proof_flags = false (right sibling)
let mut combined_2 = [0u8; 64];
combined_2[0..32].copy_from_slice(&hash_1);
combined_2[32..64].copy_from_slice(&sibling2.to_array());
let final_root = env.crypto().sha256(&Bytes::from_slice(&env, &combined_2)).to_array();
let final_root = env
.crypto()
.sha256(&Bytes::from_slice(&env, &combined_2))
.to_array();

let expected_root_bytes = BytesN::from_array(&env, &final_root);

Expand All @@ -83,7 +89,7 @@ fn test_verify_message_success() {
proof.push_back(sibling2);

let mut proof_flags = Vec::new(&env);
proof_flags.push_back(true); // left
proof_flags.push_back(true); // left
proof_flags.push_back(false); // right

let result = client.verify_message(&block_height, &leaf, &proof, &proof_flags);
Expand All @@ -94,7 +100,7 @@ fn test_verify_message_success() {
#[should_panic(expected = "State root not found")]
fn test_verify_message_no_root() {
let env = Env::default();
let contract_id = env.register_contract(None, CrossChainVerifier);
let contract_id = env.register(CrossChainVerifier, ());
let client = CrossChainVerifierClient::new(&env, &contract_id);
let admin = Address::generate(&env);

Expand Down
8 changes: 4 additions & 4 deletions contracts/dutch_auction/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ fn test_dutch_auction() {
let buyer = Address::generate(&env);

// Deploy NFT contract
let nft_contract_id = env.register_contract(None, token_contract::Token);
let nft_contract_id = env.register(token_contract::Token, ());
let nft_client = TokenClient::new(&env, &nft_contract_id);
nft_client.initialize(&seller, &0, &"NFT".into_val(&env), &"NFT".into_val(&env));
nft_client.mint(&seller, &1);

// Deploy payment token
let payment_contract_id = env.register_contract(None, token_contract::Token);
let payment_contract_id = env.register(token_contract::Token, ());
let payment_client = TokenClient::new(&env, &payment_contract_id);
payment_client.initialize(&seller, &7, &"USD".into_val(&env), &"USD".into_val(&env));
payment_client.mint(&buyer, &1000);

// Deploy auction contract
let auction_contract_id = env.register_contract(None, DutchAuction);
let auction_contract_id = env.register(DutchAuction, ());
let auction_client = DutchAuctionClient::new(&env, &auction_contract_id);

// Transfer NFT to auction
Expand Down Expand Up @@ -59,4 +59,4 @@ fn test_dutch_auction() {
// Check payment
assert_eq!(payment_client.balance(&seller), 150);
assert_eq!(auction_client.is_sold(), true);
}
}
7 changes: 2 additions & 5 deletions contracts/emergency_guard/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@ pub enum GuardError {
AlreadyInitialized = 6,
}

/// Result type for guard operations
// Result type for guard operations replaced inline

/// EmergencyGuard trait for standardized pause and admin management
pub trait EmergencyGuardTrait {
/// Check if an operation is paused. Returns Err if paused.
Expand Down Expand Up @@ -125,7 +122,7 @@ impl EmergencyGuard {
}

// Verify threshold is valid
if threshold == 0 || threshold > admins.len() as u32 {
if threshold == 0 || threshold > admins.len() {
return Err(GuardError::InvalidThreshold);
}

Expand Down Expand Up @@ -264,7 +261,7 @@ impl EmergencyGuard {
let admins = Self::get_admins(env.clone());
let threshold = Self::get_threshold(env.clone());

if admins.len() as u32 <= threshold {
if admins.len() <= threshold {
return Err(GuardError::InvalidThreshold);
}

Expand Down
8 changes: 4 additions & 4 deletions contracts/english_auction/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn test_english_auction() {
let bidder2 = Address::generate(&env);

// Deploy NFT contract
let nft_contract_id = env.register_contract(None, token_contract::Token);
let nft_contract_id = env.register(token_contract::Token, ());
let nft_client = TokenClient::new(&env, &nft_contract_id);

// Initialize NFT
Expand All @@ -24,14 +24,14 @@ fn test_english_auction() {
nft_client.mint(&seller, &1);

// Deploy payment token
let payment_contract_id = env.register_contract(None, token_contract::Token);
let payment_contract_id = env.register(token_contract::Token, ());
let payment_client = TokenClient::new(&env, &payment_contract_id);
payment_client.initialize(&seller, &7, &"USD".into_val(&env), &"USD".into_val(&env));
payment_client.mint(&bidder1, &1000);
payment_client.mint(&bidder2, &1000);

// Deploy auction contract
let auction_contract_id = env.register_contract(None, EnglishAuction);
let auction_contract_id = env.register(EnglishAuction, ());
let auction_client = EnglishAuctionClient::new(&env, &auction_contract_id);

// Approve NFT transfer
Expand Down Expand Up @@ -68,4 +68,4 @@ fn test_english_auction() {
// Check balances
assert_eq!(payment_client.balance(&seller), 180);
assert_eq!(nft_client.balance(&bidder2), 1);
}
}
17 changes: 11 additions & 6 deletions contracts/flash_loan_vault/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! ## Safety Model
//!
//! 1. **Atomic rollback**: Soroban reverts all state changes if any step panics.
//! If the borrower fails to repay, the balance check at step 9 panics and
//! If the borrower fails to repay, the balance check at step 9 fails and
//! the transfer at step 6 is rolled back — funds never leave the vault.
//!
//! 2. **Reentrancy guard**: A `FlashLoanActive` flag prevents nested flash
Expand Down Expand Up @@ -341,7 +341,9 @@ impl FlashLoanVault {
/// `receiver.execute_operation(token, amount, fee, initiator)`.
///
/// After the callback returns, the vault verifies that its token balance
/// is at least `pre_balance + fee`. If not, the entire transaction reverts.
/// is at least `pre_balance + fee`, which proves the callback returned the
/// original `amount` plus the required fee. If not, the entire transaction
/// reverts.
///
/// # Arguments
/// * `initiator` – the address initiating the flash loan (must authorize)
Expand Down Expand Up @@ -395,12 +397,15 @@ impl FlashLoanVault {
let receiver_client = FlashLoanReceiverClient::new(&e, &receiver);
receiver_client.execute_operation(&token_addr, &amount, &fee, &initiator);

// 10. Verify repayment: vault balance must be >= pre_balance + fee.
// 10. Verify repayment: after lending `amount`, the receiver must
// return `amount + fee`, leaving the vault with its original balance
// plus the fee.
let required_balance = pre_balance + fee;
let post_balance = token.balance(&e.current_contract_address());
if post_balance < pre_balance + fee {
// This panic causes the entire transaction to revert.
if post_balance < required_balance {
// The transfer at step 8 is rolled back — funds are safe.
panic!("flash loan not repaid");
set_flash_loan_active(&e, false);
return Err(Error::LoanNotRepaid);
}

// 11. Clear reentrancy guard.
Expand Down
47 changes: 42 additions & 5 deletions contracts/flash_loan_vault/src/test.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use super::*;
use soroban_sdk::{
contract, contractimpl, contracttype, testutils::Address as _, Address, Env,
String as SorobanString,
};
use soroban_sdk::{contract, contractimpl, contracttype, testutils::Address as _, Address, Env};

// ── Mock receivers ───────────────────────────────────────────────────────────

Expand Down Expand Up @@ -441,6 +438,46 @@ fn test_flash_loan_borrow_entire_vault() {
// .flash_loan(&initiator, &receiver_id, &5_000);
// }

#[test]
fn test_flash_loan_no_repay_returns_error() {
let s = setup();
fund_vault(&s, 10_000);

let receiver_id = s.e.register(BadReceiver, ());
let initiator = Address::generate(&s.e);

let result = s
.vault_client
.try_flash_loan(&initiator, &receiver_id, &5_000);

assert_eq!(result, Err(Ok(Error::LoanNotRepaid)));
assert_eq!(s.vault_client.get_available(), 10_000);
assert_eq!(s.token_client.balance(&receiver_id), 0);
}

#[test]
fn test_flash_loan_partial_repay_returns_error() {
let s = setup();
fund_vault(&s, 10_000);

// Set fee so partial repay (principal only) is insufficient.
s.vault_client.set_fee(&100);

let receiver_id = s.e.register(partial::PartialReceiver, ());
let receiver_client = partial::PartialReceiverClient::new(&s.e, &receiver_id);
receiver_client.set_vault(&s.vault_id);

let initiator = Address::generate(&s.e);

let result = s
.vault_client
.try_flash_loan(&initiator, &receiver_id, &5_000);

assert_eq!(result, Err(Ok(Error::LoanNotRepaid)));
assert_eq!(s.vault_client.get_available(), 10_000);
assert_eq!(s.vault_client.get_total_deposited(), 10_000);
}

#[test]
fn test_flash_loan_overpay() {
let s = setup();
Expand All @@ -465,7 +502,7 @@ fn test_flash_loan_overpay() {
// ── Flash loan: reentrancy ───────────────────────────────────────────────────

#[test]
#[should_panic(expected = "Error(Contract, #4)")]
#[should_panic(expected = "Contract re-entry is not allowed")]
fn test_reentrancy_guard() {
let s = setup();
fund_vault(&s, 10_000);
Expand Down
16 changes: 6 additions & 10 deletions contracts/liquidity_pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ fn guard_init(e: &Env, admin: Address) {
let admins = vec![e, admin];
e.storage().instance().set(&DataKey::GuardAdmins, &admins);
e.storage().instance().set(&DataKey::GuardThreshold, &1u32);
e.storage().instance().set(&DataKey::GuardPauseState, &0u32);
}

fn guard_pause_state(e: &Env) -> u32 {
Expand Down Expand Up @@ -245,6 +244,7 @@ fn guard_require_multisig(e: &Env, approvers: &Vec<Address>) -> Result<(), Error
.instance()
.get(&DataKey::GuardThreshold)
.ok_or(Error::NotInitialized)?;
let admins = guard_admins(e);

let mut valid = 0u32;
let mut seen = Vec::new(e);
Expand All @@ -254,7 +254,7 @@ fn guard_require_multisig(e: &Env, approvers: &Vec<Address>) -> Result<(), Error
continue;
}
seen.push_back(addr.clone());
if guard_is_admin(e, &addr) {
if admins.iter().any(|a| a == addr) {
addr.require_auth();
valid += 1;
}
Expand All @@ -267,15 +267,11 @@ fn guard_require_multisig(e: &Env, approvers: &Vec<Address>) -> Result<(), Error
}

fn guard_set_ops(e: &Env, ops: u32, paused: bool) {
let mut state = guard_pause_state(e);
if paused {
state |= ops;
} else {
state &= !ops;
let state = guard_pause_state(e);
let next = if paused { state | ops } else { state & !ops };
if next != state {
e.storage().instance().set(&DataKey::GuardPauseState, &next);
}
e.storage()
.instance()
.set(&DataKey::GuardPauseState, &state);
}

fn set_primary_admin(e: &Env, admin: Address) -> Result<(), Error> {
Expand Down
Loading
Loading