From 5685f4b5e84bf919b812ecc25ba599a26a6cee6f Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:23:03 +0100 Subject: [PATCH 01/14] fix(validation): add InvalidCharacters variant and whitespace trimming --- contracts/teachlink/src/validation.rs | 79 ++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/contracts/teachlink/src/validation.rs b/contracts/teachlink/src/validation.rs index b4d41778..a4cda6f4 100644 --- a/contracts/teachlink/src/validation.rs +++ b/contracts/teachlink/src/validation.rs @@ -36,6 +36,8 @@ pub enum ValidationError { DuplicateSigners, InvalidBytesLength, InvalidCrossChainData, + /// String contains a character outside the allowed set. + InvalidCharacters, } /// Result type for validation operations @@ -152,16 +154,15 @@ impl StringValidator { Ok(()) } - /// Validates string contains only allowed characters + /// Validates string contains only allowed characters (alphanumeric + safe punctuation). pub fn validate_characters(string: &String) -> ValidationResult<()> { - // Allow alphanumeric, spaces, and basic punctuation let string_bytes = string.to_bytes(); for byte in string_bytes.iter() { - let char = byte as char; - if !char.is_alphanumeric() - && !char.is_whitespace() + let ch = byte as char; + if !ch.is_alphanumeric() + && !ch.is_whitespace() && !matches!( - char, + ch, '-' | '_' | '.' | ',' @@ -178,18 +179,61 @@ impl StringValidator { | ':' ) { - return Err(ValidationError::InvalidStringLength); + return Err(ValidationError::InvalidCharacters); } } Ok(()) } - /// Comprehensive string validation + /// Comprehensive string validation (length + character set). pub fn validate(string: &String, max_length: u32) -> ValidationResult<()> { Self::validate_length(string, max_length)?; Self::validate_characters(string)?; Ok(()) } + + /// Validate after stripping ASCII whitespace from both ends. + /// + /// Returns `InvalidStringLength` if the trimmed result is empty or exceeds + /// `max_length`; returns `InvalidCharacters` if forbidden bytes are present. + pub fn trim_and_validate(env: &Env, string: &String, max_length: u32) -> ValidationResult { + let bytes = string.to_bytes(); + let len = bytes.len(); + + if len == 0 { + return Err(ValidationError::InvalidStringLength); + } + + // Find first non-whitespace index. + let mut start = 0u32; + loop { + if start >= len { + return Err(ValidationError::InvalidStringLength); // entirely whitespace + } + if !(bytes.get(start).unwrap() as char).is_ascii_whitespace() { + break; + } + start += 1; + } + + // Find last non-whitespace index. + let mut end = len - 1; + while end > start && (bytes.get(end).unwrap() as char).is_ascii_whitespace() { + end -= 1; + } + + // Build trimmed Bytes by copying the [start, end] range. + let mut trimmed_bytes = Bytes::new(env); + let mut i = start; + while i <= end { + trimmed_bytes.push_back(bytes.get(i).unwrap()); + i += 1; + } + + let trimmed = String::from_bytes(env, &trimmed_bytes); + Self::validate(&trimmed, max_length)?; + Ok(trimmed) + } } /// Bytes validation utilities @@ -451,6 +495,16 @@ impl InputSanitizer { pub fn sanitize_destination_address(bytes: &Bytes) -> ValidationResult<()> { BytesValidator::validate_cross_chain_address(bytes) } + + /// Trim whitespace from `string`, then validate length and character set. + /// + /// Use this instead of calling `StringValidator::validate` directly when the + /// input originates from an untrusted user (description fields, reason strings, + /// reward-type labels, etc.) so that leading/trailing whitespace is always + /// stripped before the length cap is applied. + pub fn sanitize_string(env: &Env, string: &String, max_length: u32) -> ValidationResult { + StringValidator::trim_and_validate(env, string, max_length) + } } /// Bridge-specific validation utilities @@ -502,6 +556,13 @@ impl BridgeValidator { validator_signatures: &Vec
, min_validators: u32, ) -> Result<(), crate::errors::BridgeError> { + // Enforce maximum validator count to prevent DoS via unbounded loop + #[allow(clippy::cast_possible_truncation)] + crate::dos_protection::check_batch_size( + validator_signatures.len() as u32, + crate::dos_protection::MAX_VALIDATORS_PER_COMPLETION, + )?; + // Validate validator signatures count if validator_signatures.len() < min_validators { return Err(crate::errors::BridgeError::InsufficientValidatorSignatures); @@ -538,7 +599,7 @@ impl RewardsValidator { NumberValidator::validate_amount(amount) .map_err(|_| crate::errors::RewardsError::AmountMustBePositive)?; - StringValidator::validate(reward_type, config::MAX_STRING_LENGTH) + InputSanitizer::sanitize_string(env, reward_type, config::MAX_STRING_LENGTH) .map_err(|_| crate::errors::RewardsError::AmountMustBePositive)?; Ok(()) From 3c0241607e2af0e7b00be9d4e4914901e1b6fb50 Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:23:25 +0100 Subject: [PATCH 02/14] feat(dos): add dos_protection module with batch limits, rate limiting, and instruction budget guards --- contracts/teachlink/src/dos_protection.rs | 148 ++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 contracts/teachlink/src/dos_protection.rs diff --git a/contracts/teachlink/src/dos_protection.rs b/contracts/teachlink/src/dos_protection.rs new file mode 100644 index 00000000..d71ca692 --- /dev/null +++ b/contracts/teachlink/src/dos_protection.rs @@ -0,0 +1,148 @@ +//! DoS protection: batch-size limits, resource quotas, and per-address rate limiting. +//! +//! All bulk-operation limits live here so they can be reviewed and tuned in +//! one place. + +use soroban_sdk::{symbol_short, Address, Env, Map, Symbol}; + +use crate::errors::BridgeError; + +// ── Resource quotas / batch-size limits ────────────────────────────────────── + +/// Maximum validator signatures accepted in a single `complete_bridge` call. +pub const MAX_VALIDATORS_PER_COMPLETION: u32 = 50; + +/// Maximum chain IDs that may be paused or resumed in a single call. +pub const MAX_CHAIN_BATCH_SIZE: u32 = 50; + +/// Maximum notification preferences a user may submit in a single call. +pub const MAX_PREFERENCE_BATCH_SIZE: u32 = 20; + +/// Maximum packets scanned per `check_timeouts` invocation. +pub const MAX_TIMEOUT_SCAN_BATCH: u32 = 100; + +/// Maximum questions an assessment may contain. +pub const MAX_QUESTIONS_PER_ASSESSMENT: u32 = 200; + +/// Maximum proctor-log entries per assessment submission. +pub const MAX_PROCTOR_LOGS_PER_SUBMISSION: u32 = 50; + +// ── Instruction budget (gas) limits ────────────────────────────────────────── +// +// Soroban measures execution cost in CPU instructions and memory bytes. +// The constants below document the expected instruction budget for each +// bulk-operation category so callers can reason about worst-case costs. +// Exceeding the network's per-transaction limit causes an automatic abort. + +/// Approximate CPU-instruction budget consumed per validator checked in +/// `complete_bridge` (signature verification is the dominant cost). +pub const INSTRUCTIONS_PER_VALIDATOR_CHECK: u64 = 5_000; + +/// Approximate CPU-instruction budget consumed per chain ID processed in +/// `pause_chains` / `resume_chains`. +pub const INSTRUCTIONS_PER_CHAIN_OP: u64 = 2_000; + +/// Approximate CPU-instruction budget consumed per packet evaluated in +/// `check_timeouts`. +pub const INSTRUCTIONS_PER_TIMEOUT_CHECK: u64 = 1_500; + +/// Approximate CPU-instruction budget consumed per notification preference. +pub const INSTRUCTIONS_PER_PREFERENCE: u64 = 1_000; + +/// Absolute upper bound on CPU instructions a single contract invocation may +/// consume before hitting the Soroban network limit (~100 million). +/// Used as a soft guard: if a batch's estimated cost exceeds this, reject early. +pub const MAX_INSTRUCTIONS_PER_INVOCATION: u64 = 50_000_000; + +// ── Rate limiting ───────────────────────────────────────────────────────────── + +/// Minimum gap (seconds) between two `bridge_out` calls from the same address. +pub const BRIDGE_OUT_RATE_LIMIT_SECONDS: u64 = 60; + +/// Minimum gap (seconds) between two admin bulk operations (pause/resume/scan) +/// from the same address. Prevents a rogue admin from looping bulk calls. +pub const ADMIN_OP_RATE_LIMIT_SECONDS: u64 = 10; + +const BRIDGE_RATE_LIMIT_KEY: Symbol = symbol_short!("BR_RLMT"); + +/// Storage key for per-address admin operation rate-limit timestamps. +const ADMIN_RATE_LIMIT_KEY: Symbol = symbol_short!("ADM_RLMT"); + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +/// Return `BatchSizeLimitExceeded` if `actual > max`. +pub fn check_batch_size(actual: u32, max: u32) -> Result<(), BridgeError> { + if actual > max { + Err(BridgeError::BatchSizeLimitExceeded) + } else { + Ok(()) + } +} + +/// Estimate instruction cost for `batch_size` items at `cost_per_item` and +/// return `BatchSizeLimitExceeded` if the estimate would exceed +/// `MAX_INSTRUCTIONS_PER_INVOCATION`. +/// +/// Call this alongside `check_batch_size` when the per-item cost is non-trivial +/// (e.g. crypto operations, storage writes) to provide a second, cost-aware guard. +pub fn check_instruction_budget(batch_size: u32, cost_per_item: u64) -> Result<(), BridgeError> { + let estimated = (batch_size as u64).saturating_mul(cost_per_item); + if estimated > MAX_INSTRUCTIONS_PER_INVOCATION { + Err(BridgeError::BatchSizeLimitExceeded) + } else { + Ok(()) + } +} + +/// Enforce a per-sender cooldown for `bridge_out`. +/// +/// Reads and updates a per-address timestamp under `BRIDGE_RATE_LIMIT_KEY`. +/// Returns `BridgeError::RetryBackoffActive` if the caller is within the +/// cooldown window. +pub fn check_bridge_out_rate_limit(env: &Env, sender: &Address) -> Result<(), BridgeError> { + let mut limits: Map = env + .storage() + .instance() + .get(&BRIDGE_RATE_LIMIT_KEY) + .unwrap_or_else(|| Map::new(env)); + + let now = env.ledger().timestamp(); + if let Some(last) = limits.get(sender.clone()) { + if now < last.saturating_add(BRIDGE_OUT_RATE_LIMIT_SECONDS) { + return Err(BridgeError::RetryBackoffActive); + } + } + + limits.set(sender.clone(), now); + env.storage() + .instance() + .set(&BRIDGE_RATE_LIMIT_KEY, &limits); + Ok(()) +} + +/// Enforce a per-sender cooldown for admin bulk operations (pause/resume/timeout +/// scan). Prevents a rogue or compromised admin key from flooding the ledger +/// with back-to-back bulk writes. +/// +/// Returns `BridgeError::RetryBackoffActive` if the caller last performed an +/// admin op within `ADMIN_OP_RATE_LIMIT_SECONDS`. +pub fn check_admin_rate_limit(env: &Env, admin: &Address) -> Result<(), BridgeError> { + let mut limits: Map = env + .storage() + .instance() + .get(&ADMIN_RATE_LIMIT_KEY) + .unwrap_or_else(|| Map::new(env)); + + let now = env.ledger().timestamp(); + if let Some(last) = limits.get(admin.clone()) { + if now < last.saturating_add(ADMIN_OP_RATE_LIMIT_SECONDS) { + return Err(BridgeError::RetryBackoffActive); + } + } + + limits.set(admin.clone(), now); + env.storage() + .instance() + .set(&ADMIN_RATE_LIMIT_KEY, &limits); + Ok(()) +} From deae3435b6511468f20d4170e89fc3a332cbc76d Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:25:15 +0100 Subject: [PATCH 03/14] fix(dos): enforce batch limits and admin rate-limiting in pause/resume operations --- contracts/teachlink/src/emergency.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/contracts/teachlink/src/emergency.rs b/contracts/teachlink/src/emergency.rs index 1f1a8965..80911462 100644 --- a/contracts/teachlink/src/emergency.rs +++ b/contracts/teachlink/src/emergency.rs @@ -1,4 +1,4 @@ -//! Emergency Pause and Recovery Module +//! Emergency Pause and Recovery Module //! //! This module implements circuit breaker functionality and emergency controls //! to protect the bridge during critical situations. @@ -127,6 +127,16 @@ impl EmergencyManager { crate::types::AccessRole::EmergencyManager, ); + crate::dos_protection::check_admin_rate_limit(env, &pauser)?; + + #[allow(clippy::cast_possible_truncation)] + let batch_len = chain_ids.len() as u32; + crate::dos_protection::check_batch_size(batch_len, crate::dos_protection::MAX_CHAIN_BATCH_SIZE)?; + crate::dos_protection::check_instruction_budget( + batch_len, + crate::dos_protection::INSTRUCTIONS_PER_CHAIN_OP, + )?; + let mut paused_chains: Map = env .storage() .instance() @@ -164,6 +174,16 @@ impl EmergencyManager { crate::types::AccessRole::EmergencyManager, ); + crate::dos_protection::check_admin_rate_limit(env, &resumer)?; + + #[allow(clippy::cast_possible_truncation)] + let batch_len = chain_ids.len() as u32; + crate::dos_protection::check_batch_size(batch_len, crate::dos_protection::MAX_CHAIN_BATCH_SIZE)?; + crate::dos_protection::check_instruction_budget( + batch_len, + crate::dos_protection::INSTRUCTIONS_PER_CHAIN_OP, + )?; + let mut paused_chains: Map = env .storage() .instance() From 6486d79e9e832658b165bdbefae285f835a6c4b5 Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:26:15 +0100 Subject: [PATCH 04/14] feat(dos): register dos_protection module in lib.rs --- contracts/teachlink/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/teachlink/src/lib.rs b/contracts/teachlink/src/lib.rs index 52073185..d7969e4c 100644 --- a/contracts/teachlink/src/lib.rs +++ b/contracts/teachlink/src/lib.rs @@ -99,6 +99,7 @@ mod audit; mod backup; mod bft_consensus; mod bridge; +mod dos_protection; // TODO: Fix collaboration module compilation errors (pre-existing issue) // mod collaboration; // TODO: Fix content_nft module compilation errors (pre-existing issue) From 15d2d0510fc886d2e637e07d0f69fc649c04d1dd Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:27:34 +0100 Subject: [PATCH 05/14] feat(ci): add cargo-audit and cargo-deny security scanning --- .github/workflows/ci.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72f3aaef..846648c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,3 +34,26 @@ jobs: - name: Run integration tests run: cargo test --workspace --test cross_chain_integration + + security-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + + - name: Install cargo-audit + run: cargo install cargo-audit --locked + + - name: Run dependency vulnerability audit + run: cargo audit + + - name: Install cargo-deny + run: cargo install cargo-deny --locked + + - name: Check dependency licenses and advisories + run: cargo deny check From 85b7327dbc6fc812195fc5564aa8b7f5e10ee294 Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:27:35 +0100 Subject: [PATCH 06/14] feat(deps): add Dependabot configuration for automated dependency updates --- .github/dependabot.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..c1abbfee --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,27 @@ +version: 2 +updates: + - package-ecosystem: cargo + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 10 + labels: + - dependencies + - rust + commit-message: + prefix: "chore(deps)" + reviewers: + - Spagero763 + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + labels: + - dependencies + - github-actions + commit-message: + prefix: "chore(deps)" From 067815f2990c206668f4058256f1d34af7c61a9d Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:27:37 +0100 Subject: [PATCH 07/14] feat(deps): add cargo-deny configuration for license and advisory checks --- deny.toml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 deny.toml diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..a733e652 --- /dev/null +++ b/deny.toml @@ -0,0 +1,37 @@ +[advisories] +db-path = "~/.cargo/advisory-db" +db-urls = ["https://github.com/rustsec/advisory-db"] +vulnerability = "deny" +unmaintained = "warn" +yanked = "deny" +notice = "warn" +ignore = [] + +[licenses] +unlicensed = "deny" +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Unicode-DFS-2016", +] +deny = [] +copyleft = "warn" +allow-osi-fsf-free = "neither" +default = "deny" +confidence-threshold = 0.8 + +[bans] +multiple-versions = "warn" +wildcards = "allow" +highlight = "all" +deny = [] + +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] From 5ae3a683caa60b8645d07b947f79c01960d0c782 Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:41:37 +0100 Subject: [PATCH 08/14] style: apply rustfmt formatting to dos_protection.rs --- contracts/teachlink/src/dos_protection.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/teachlink/src/dos_protection.rs b/contracts/teachlink/src/dos_protection.rs index d71ca692..2958bfe6 100644 --- a/contracts/teachlink/src/dos_protection.rs +++ b/contracts/teachlink/src/dos_protection.rs @@ -141,8 +141,6 @@ pub fn check_admin_rate_limit(env: &Env, admin: &Address) -> Result<(), BridgeEr } limits.set(admin.clone(), now); - env.storage() - .instance() - .set(&ADMIN_RATE_LIMIT_KEY, &limits); + env.storage().instance().set(&ADMIN_RATE_LIMIT_KEY, &limits); Ok(()) } From 4d95c7ed35018b194f7d848ab45cc689e6f214d5 Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:49:02 +0100 Subject: [PATCH 09/14] style: apply rustfmt formatting to validation.rs --- contracts/teachlink/src/validation.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/contracts/teachlink/src/validation.rs b/contracts/teachlink/src/validation.rs index a4cda6f4..fae5f590 100644 --- a/contracts/teachlink/src/validation.rs +++ b/contracts/teachlink/src/validation.rs @@ -1,4 +1,4 @@ -use crate::errors::EscrowError; +use crate::errors::EscrowError; use crate::types::EscrowSigner; use soroban_sdk::{Address, Bytes, Env, String, Vec}; @@ -196,7 +196,11 @@ impl StringValidator { /// /// Returns `InvalidStringLength` if the trimmed result is empty or exceeds /// `max_length`; returns `InvalidCharacters` if forbidden bytes are present. - pub fn trim_and_validate(env: &Env, string: &String, max_length: u32) -> ValidationResult { + pub fn trim_and_validate( + env: &Env, + string: &String, + max_length: u32, + ) -> ValidationResult { let bytes = string.to_bytes(); let len = bytes.len(); @@ -502,7 +506,11 @@ impl InputSanitizer { /// input originates from an untrusted user (description fields, reason strings, /// reward-type labels, etc.) so that leading/trailing whitespace is always /// stripped before the length cap is applied. - pub fn sanitize_string(env: &Env, string: &String, max_length: u32) -> ValidationResult { + pub fn sanitize_string( + env: &Env, + string: &String, + max_length: u32, + ) -> ValidationResult { StringValidator::trim_and_validate(env, string, max_length) } } From 22af673ade0f25610dbc890a40b1e97bcf93524f Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:49:04 +0100 Subject: [PATCH 10/14] fix(ci): use valid cargo-deny unmaintained scope value --- deny.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/deny.toml b/deny.toml index a733e652..a7ba99d6 100644 --- a/deny.toml +++ b/deny.toml @@ -1,10 +1,8 @@ -[advisories] -db-path = "~/.cargo/advisory-db" +[advisories] db-urls = ["https://github.com/rustsec/advisory-db"] vulnerability = "deny" -unmaintained = "warn" +unmaintained = "workspace" yanked = "deny" -notice = "warn" ignore = [] [licenses] From 932b05bcd7cfaf1bc4e80e8b21603faf7279bf80 Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:49:33 +0100 Subject: [PATCH 11/14] style: apply rustfmt formatting to emergency.rs --- contracts/teachlink/src/emergency.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/teachlink/src/emergency.rs b/contracts/teachlink/src/emergency.rs index 80911462..8a27f371 100644 --- a/contracts/teachlink/src/emergency.rs +++ b/contracts/teachlink/src/emergency.rs @@ -1,4 +1,4 @@ -//! Emergency Pause and Recovery Module +//! Emergency Pause and Recovery Module //! //! This module implements circuit breaker functionality and emergency controls //! to protect the bridge during critical situations. @@ -131,7 +131,10 @@ impl EmergencyManager { #[allow(clippy::cast_possible_truncation)] let batch_len = chain_ids.len() as u32; - crate::dos_protection::check_batch_size(batch_len, crate::dos_protection::MAX_CHAIN_BATCH_SIZE)?; + crate::dos_protection::check_batch_size( + batch_len, + crate::dos_protection::MAX_CHAIN_BATCH_SIZE, + )?; crate::dos_protection::check_instruction_budget( batch_len, crate::dos_protection::INSTRUCTIONS_PER_CHAIN_OP, @@ -178,7 +181,10 @@ impl EmergencyManager { #[allow(clippy::cast_possible_truncation)] let batch_len = chain_ids.len() as u32; - crate::dos_protection::check_batch_size(batch_len, crate::dos_protection::MAX_CHAIN_BATCH_SIZE)?; + crate::dos_protection::check_batch_size( + batch_len, + crate::dos_protection::MAX_CHAIN_BATCH_SIZE, + )?; crate::dos_protection::check_instruction_budget( batch_len, crate::dos_protection::INSTRUCTIONS_PER_CHAIN_OP, From 2be54944d91a3098651f44c6aab51f9566fdedb6 Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 22:05:54 +0100 Subject: [PATCH 12/14] fix: remove UTF-8 BOM from emergency.rs --- contracts/teachlink/src/emergency.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/teachlink/src/emergency.rs b/contracts/teachlink/src/emergency.rs index 8a27f371..c97a7475 100644 --- a/contracts/teachlink/src/emergency.rs +++ b/contracts/teachlink/src/emergency.rs @@ -1,4 +1,4 @@ -//! Emergency Pause and Recovery Module +//! Emergency Pause and Recovery Module //! //! This module implements circuit breaker functionality and emergency controls //! to protect the bridge during critical situations. From a657aa8e8c0b571b3f6e25de68dfb024c0294625 Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 22:05:56 +0100 Subject: [PATCH 13/14] fix: remove UTF-8 BOM from validation.rs --- contracts/teachlink/src/validation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/teachlink/src/validation.rs b/contracts/teachlink/src/validation.rs index fae5f590..bdf1e570 100644 --- a/contracts/teachlink/src/validation.rs +++ b/contracts/teachlink/src/validation.rs @@ -1,4 +1,4 @@ -use crate::errors::EscrowError; +use crate::errors::EscrowError; use crate::types::EscrowSigner; use soroban_sdk::{Address, Bytes, Env, String, Vec}; From c9f405ba21d45674c31fc6588a3cc78fbc8dd82d Mon Sep 17 00:00:00 2001 From: Afolabi Ayomide Emmanuel <138625749+Spagero763@users.noreply.github.com> Date: Sat, 25 Apr 2026 22:05:57 +0100 Subject: [PATCH 14/14] fix(ci): rewrite deny.toml with valid cargo-deny 0.16+ keys only --- deny.toml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/deny.toml b/deny.toml index a7ba99d6..9bc3e10f 100644 --- a/deny.toml +++ b/deny.toml @@ -1,12 +1,9 @@ -[advisories] +[advisories] db-urls = ["https://github.com/rustsec/advisory-db"] -vulnerability = "deny" unmaintained = "workspace" -yanked = "deny" ignore = [] [licenses] -unlicensed = "deny" allow = [ "MIT", "Apache-2.0", @@ -16,11 +13,6 @@ allow = [ "ISC", "Unicode-DFS-2016", ] -deny = [] -copyleft = "warn" -allow-osi-fsf-free = "neither" -default = "deny" -confidence-threshold = 0.8 [bans] multiple-versions = "warn" @@ -32,4 +24,4 @@ deny = [] unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] -allow-git = [] +allow-git = [] \ No newline at end of file