From 29fc37c3dd166f0e91d8e81a722219d358521fd0 Mon Sep 17 00:00:00 2001 From: Victor Edeh Date: Mon, 27 Jul 2026 15:06:27 +0100 Subject: [PATCH 1/5] feat: add query_price helper and protocol fee boundary tests (#572) --- creator-keys/src/lib.rs | 18 +++++ creator-keys/src/test_issues.rs | 117 ++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/creator-keys/src/lib.rs b/creator-keys/src/lib.rs index 1adb1f23..74a56011 100644 --- a/creator-keys/src/lib.rs +++ b/creator-keys/src/lib.rs @@ -2710,6 +2710,24 @@ impl CreatorKeysContract { checked_format_quote_response(price, creator_fee, protocol_fee, true) } + /// Read-only price query helper for a given creator and supply step. + /// + /// Computes the bonding curve price for `supply` without requiring authorization + /// or mutating contract state. + /// + /// Returns `Err(ContractError::KeyPriceNotSet)` if base key price is not set, + /// or `Err(ContractError::Overflow)` if arithmetic overflows or supply exceeds `u32::MAX`. + pub fn query_price(env: Env, creator: Address, supply: u64) -> Result { + let supply_u32 = u32::try_from(supply).map_err(|_| ContractError::Overflow)?; + let base_price: i128 = env + .storage() + .persistent() + .get(&constants::storage::KEY_PRICE) + .ok_or(ContractError::KeyPriceNotSet)?; + + compute_bonding_curve_price(&env, &creator, base_price, supply_u32) + } + /// Read-only view: returns the total creator buyback cost for a given amount. /// /// The returned value is `base_price(amount) + protocol_fee(amount)` because the diff --git a/creator-keys/src/test_issues.rs b/creator-keys/src/test_issues.rs index 18b0d992..6dd27189 100644 --- a/creator-keys/src/test_issues.rs +++ b/creator-keys/src/test_issues.rs @@ -701,4 +701,121 @@ mod issue_tests { "different creators must produce different locked allocation keys" ); } + + // ============================================================================= + // Tests for Issue #572 + // ============================================================================= + + #[test] + fn test_query_price_at_boundary_supplies() { + let env = Env::default(); + let contract_id = env.register(CreatorKeysContract, ()); + let client = CreatorKeysContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + + let base_price = 100i128; + client.set_key_price(&admin, &base_price); + + // Test for Linear curve + let creator = register_creator(&env, &client, None); + for supply in [0u64, 1u64, 50u64] { + let queried_price = client.query_price(&creator, &supply); + let expected_price = + compute_bonding_curve_price(&env, &creator, base_price, supply as u32).unwrap(); + assert_eq!( + queried_price, expected_price, + "query_price output must match buy-path bonding curve formula for supply {}", + supply + ); + } + + // Test for Flat curve + let flat_creator = Address::generate(&env); + let handle = String::from_str(&env, "bob"); + client.register_creator( + &crate::RegisterCreatorParams { + creator: flat_creator.clone(), + handle, + }, + &None, + &None, + &None, + &Some(CurvePreset::Flat), + &None, + &None, + ); + for supply in [0u64, 1u64, 50u64] { + let price = client.query_price(&flat_creator, &supply); + assert_eq!(price, base_price); + } + + // Test for Quadratic curve + let quad_creator = Address::generate(&env); + let handle_q = String::from_str(&env, "charlie"); + client.register_creator( + &crate::RegisterCreatorParams { + creator: quad_creator.clone(), + handle: handle_q, + }, + &None, + &None, + &None, + &Some(CurvePreset::Quadratic), + &None, + &None, + ); + for supply in [0u64, 1u64, 50u64] { + let queried_price = client.query_price(&quad_creator, &supply); + let expected_price = + compute_bonding_curve_price(&env, &quad_creator, base_price, supply as u32).unwrap(); + assert_eq!(queried_price, expected_price); + } + } + + #[test] + fn test_protocol_fee_calculation_at_boundary_supplies() { + let env = Env::default(); + let contract_id = env.register(CreatorKeysContract, ()); + let client = CreatorKeysContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + + let base_price = 100i128; + client.set_key_price(&admin, &base_price); + + let protocol_bps = 250u32; // 2.5% + let creator_bps = 500u32; // 5.0% + client.set_fee_config(&admin, &creator_bps, &protocol_bps); + + let creator = register_creator(&env, &client, None); + + // Boundary supply values: 0, 1, 1000 + for supply in [0u64, 1u64, 1000u64] { + let price = client.query_price(&creator, &supply); + let computed_fee = crate::fee::apply_percentage_fee(price, protocol_bps).unwrap(); + let expected_fee = (price * protocol_bps as i128) / crate::fee::BPS_MAX as i128; + + assert_eq!( + computed_fee, expected_fee, + "Protocol fee for supply {} must match expected BPS calculation", + supply + ); + } + } + + #[test] + fn test_protocol_fee_floors_on_non_whole_stroops() { + // Price = 1050 stroops, protocol_bps = 50 bps (0.5%) + // Exact: 1050 * 50 / 10000 = 5.25 stroops -> Floors to 5 stroops + let price_a = 1050i128; + let bps_a = 50u32; + let fee_a = crate::fee::apply_percentage_fee(price_a, bps_a).unwrap(); + assert_eq!(fee_a, 5, "Fee 5.25 stroops must floor to 5 stroops"); + + // Price = 105 stroops, protocol_bps = 50 bps (0.5%) + // Exact: 105 * 50 / 10000 = 0.525 stroops -> Floors to 0 stroops + let price_b = 105i128; + let bps_b = 50u32; + let fee_b = crate::fee::apply_percentage_fee(price_b, bps_b).unwrap(); + assert_eq!(fee_b, 0, "Fee 0.525 stroops must floor to 0 stroops"); + } } From 7a3c23bccf303485d119e7e7f8c0c18898e4f586 Mon Sep 17 00:00:00 2001 From: Chucks1093 Date: Thu, 30 Jul 2026 05:09:51 +0100 Subject: [PATCH 2/5] fix: apply cargo fmt to test_issues.rs --- creator-keys/src/test_issues.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/creator-keys/src/test_issues.rs b/creator-keys/src/test_issues.rs index 6dd27189..4a9c2589 100644 --- a/creator-keys/src/test_issues.rs +++ b/creator-keys/src/test_issues.rs @@ -767,7 +767,8 @@ mod issue_tests { for supply in [0u64, 1u64, 50u64] { let queried_price = client.query_price(&quad_creator, &supply); let expected_price = - compute_bonding_curve_price(&env, &quad_creator, base_price, supply as u32).unwrap(); + compute_bonding_curve_price(&env, &quad_creator, base_price, supply as u32) + .unwrap(); assert_eq!(queried_price, expected_price); } } @@ -783,7 +784,7 @@ mod issue_tests { client.set_key_price(&admin, &base_price); let protocol_bps = 250u32; // 2.5% - let creator_bps = 500u32; // 5.0% + let creator_bps = 500u32; // 5.0% client.set_fee_config(&admin, &creator_bps, &protocol_bps); let creator = register_creator(&env, &client, None); From 348882d3d37468621a294008f73d0deef5dab83f Mon Sep 17 00:00:00 2001 From: Chucks1093 Date: Thu, 30 Jul 2026 05:25:28 +0100 Subject: [PATCH 3/5] fix: add env.mock_all_auths() to boundary supply tests --- creator-keys/src/test_issues.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/creator-keys/src/test_issues.rs b/creator-keys/src/test_issues.rs index 4a9c2589..ee81c5fc 100644 --- a/creator-keys/src/test_issues.rs +++ b/creator-keys/src/test_issues.rs @@ -709,6 +709,7 @@ mod issue_tests { #[test] fn test_query_price_at_boundary_supplies() { let env = Env::default(); + env.mock_all_auths(); let contract_id = env.register(CreatorKeysContract, ()); let client = CreatorKeysContractClient::new(&env, &contract_id); let admin = Address::generate(&env); @@ -776,6 +777,7 @@ mod issue_tests { #[test] fn test_protocol_fee_calculation_at_boundary_supplies() { let env = Env::default(); + env.mock_all_auths(); let contract_id = env.register(CreatorKeysContract, ()); let client = CreatorKeysContractClient::new(&env, &contract_id); let admin = Address::generate(&env); From 09eef3c0e1241fda8636aa85a04e8611c3958f04 Mon Sep 17 00:00:00 2001 From: Chucks1093 Date: Thu, 30 Jul 2026 05:39:58 +0100 Subject: [PATCH 4/5] fix: wrap compute_bonding_curve_price calls in env.as_contract Storage access is only valid inside a contract execution context; calling compute_bonding_curve_price bare in test code panics with "this function is not accessible outside of a contract". --- creator-keys/src/test_issues.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/creator-keys/src/test_issues.rs b/creator-keys/src/test_issues.rs index ee81c5fc..9cf2ee06 100644 --- a/creator-keys/src/test_issues.rs +++ b/creator-keys/src/test_issues.rs @@ -721,8 +721,9 @@ mod issue_tests { let creator = register_creator(&env, &client, None); for supply in [0u64, 1u64, 50u64] { let queried_price = client.query_price(&creator, &supply); - let expected_price = - compute_bonding_curve_price(&env, &creator, base_price, supply as u32).unwrap(); + let expected_price = env.as_contract(&contract_id, || { + compute_bonding_curve_price(&env, &creator, base_price, supply as u32).unwrap() + }); assert_eq!( queried_price, expected_price, "query_price output must match buy-path bonding curve formula for supply {}", @@ -767,9 +768,10 @@ mod issue_tests { ); for supply in [0u64, 1u64, 50u64] { let queried_price = client.query_price(&quad_creator, &supply); - let expected_price = + let expected_price = env.as_contract(&contract_id, || { compute_bonding_curve_price(&env, &quad_creator, base_price, supply as u32) - .unwrap(); + .unwrap() + }); assert_eq!(queried_price, expected_price); } } From 8a36d3ac1293767fb274beb0f45f66e892825150 Mon Sep 17 00:00:00 2001 From: Chucks1093 Date: Thu, 30 Jul 2026 05:45:44 +0100 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20cargo=20fmt=20=E2=80=94=20collapse?= =?UTF-8?q?=20unwrap=20onto=20single=20line=20inside=20closure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- creator-keys/src/test_issues.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/creator-keys/src/test_issues.rs b/creator-keys/src/test_issues.rs index 9cf2ee06..189084fa 100644 --- a/creator-keys/src/test_issues.rs +++ b/creator-keys/src/test_issues.rs @@ -769,8 +769,7 @@ mod issue_tests { for supply in [0u64, 1u64, 50u64] { let queried_price = client.query_price(&quad_creator, &supply); let expected_price = env.as_contract(&contract_id, || { - compute_bonding_curve_price(&env, &quad_creator, base_price, supply as u32) - .unwrap() + compute_bonding_curve_price(&env, &quad_creator, base_price, supply as u32).unwrap() }); assert_eq!(queried_price, expected_price); }