diff --git a/contracts/teachlink/src/validation.rs b/contracts/teachlink/src/validation.rs index 67002d41..b4398419 100644 --- a/contracts/teachlink/src/validation.rs +++ b/contracts/teachlink/src/validation.rs @@ -128,12 +128,12 @@ impl NumberValidator { Ok(()) } - /// Validates threshold against signer count - pub fn validate_threshold(threshold: u32, signer_count: u32) -> ValidationResult<()> { + /// Validates threshold against total signer weight + pub fn validate_threshold(threshold: u32, total_weight: u32) -> ValidationResult<()> { if threshold < config::MIN_THRESHOLD { return Err(ValidationError::InvalidThreshold); } - if threshold > signer_count { + if threshold > total_weight { return Err(ValidationError::InvalidThreshold); } Ok(()) @@ -335,9 +335,6 @@ impl EscrowValidator { } } - // Check for duplicate signers - Self::check_duplicate_signers(signers)?; - Ok(()) } @@ -396,9 +393,6 @@ impl EscrowValidator { } } - // Check for duplicate signers - Self::check_duplicate_signers(¶ms.signers)?; - // Additional validation: depositor must be different from beneficiary if params.depositor == params.beneficiary { return Err(EscrowError::DepositorCannotBeBeneficiary); diff --git a/contracts/teachlink/tests/test_validation.rs b/contracts/teachlink/tests/test_validation.rs index 6712fcf7..8e3625a2 100644 --- a/contracts/teachlink/tests/test_validation.rs +++ b/contracts/teachlink/tests/test_validation.rs @@ -687,3 +687,54 @@ fn test_config_constants() { assert!(config::MIN_TIMEOUT_SECONDS > 0); assert!(config::MAX_TIMEOUT_SECONDS > config::MIN_TIMEOUT_SECONDS); } + +#[test] +fn test_multisig_threshold_boundaries() { + let env = Env::default(); + + let make_signer = |w: u32| EscrowSigner { + address: Address::generate(&env), + role: EscrowRole::Primary, + weight: w, + }; + + // threshold == 1 (minimum) should pass + let mut signers = Vec::new(&env); + signers.push_back(make_signer(5)); + assert!(EscrowValidator::validate_multisig(&signers, 1).is_ok()); + + // threshold == total_weight (maximum) should pass + assert!(EscrowValidator::validate_multisig(&signers, 5).is_ok()); + + // threshold == 0 should fail + assert!(EscrowValidator::validate_multisig(&signers, 0).is_err()); + + // threshold > total_weight should fail + assert!(EscrowValidator::validate_multisig(&signers, 6).is_err()); + + // zero-weight signer should fail + let mut zero_weight = Vec::new(&env); + zero_weight.push_back(make_signer(0)); + assert!(EscrowValidator::validate_multisig(&zero_weight, 1).is_err()); + + // weight overflow should fail + let mut overflow_signers = Vec::new(&env); + overflow_signers.push_back(make_signer(u32::MAX)); + overflow_signers.push_back(make_signer(1)); + assert!(EscrowValidator::validate_multisig(&overflow_signers, 1).is_err()); + + // duplicate signers should fail + let dup = Address::generate(&env); + let mut dup_signers = Vec::new(&env); + dup_signers.push_back(EscrowSigner { address: dup.clone(), role: EscrowRole::Primary, weight: 1 }); + dup_signers.push_back(EscrowSigner { address: dup.clone(), role: EscrowRole::Primary, weight: 1 }); + assert!(EscrowValidator::validate_multisig(&dup_signers, 1).is_err()); + + // multi-signer weighted threshold at exact boundary + let mut multi = Vec::new(&env); + multi.push_back(make_signer(3)); + multi.push_back(make_signer(2)); + assert!(EscrowValidator::validate_multisig(&multi, 5).is_ok()); // threshold == total + assert!(EscrowValidator::validate_multisig(&multi, 1).is_ok()); // threshold == min + assert!(EscrowValidator::validate_multisig(&multi, 6).is_err()); // threshold > total +}