From 6d0c3fdc413c3d0cd5b3d35d6aac943b58f2dc67 Mon Sep 17 00:00:00 2001 From: laurentketterle-hub Date: Wed, 12 Aug 2026 21:22:10 +0200 Subject: [PATCH 1/4] fix: gate issue_carbon_credits behind carbon-oracle auth (#312) --- investment_vault/src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/investment_vault/src/lib.rs b/investment_vault/src/lib.rs index 1f384090..d3f23c4d 100644 --- a/investment_vault/src/lib.rs +++ b/investment_vault/src/lib.rs @@ -1552,8 +1552,20 @@ impl InvestmentVault { } /// Issue carbon credits to a specified recipient (#184). + /// + /// Only the carbon credit oracle may mint credits (#312): this closes the + /// hole where any caller could credit an arbitrary address with freely + /// transferable carbon-credit balances. The authorization gate mirrors + /// `set_carbon_credit_price`. pub fn issue_carbon_credits(env: Env, to: Address, project_id: u32, amount: i128) -> i128 { require_current_state(&env); + let oracle: Address = env + .storage() + .instance() + .get(&VaultKey::CarbonOracle) + .expect("carbon oracle not set"); + oracle.require_auth(); + let calc = Self::calculate_carbon_credits(env.clone(), project_id, amount); if calc.credits <= 0 { @@ -1570,6 +1582,8 @@ impl InvestmentVault { &(prev + calc.credits), ); + events::carbon_credits_issued(&env, &to, project_id, calc.credits); + calc.credits } From df7d2cd9b08e6801c64bf19e3e5a51bf28dd53bc Mon Sep 17 00:00:00 2001 From: laurentketterle-hub Date: Wed, 12 Aug 2026 21:22:11 +0200 Subject: [PATCH 2/4] feat: add CarbonCreditsIssued event (#312) --- investment_vault/src/events.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/investment_vault/src/events.rs b/investment_vault/src/events.rs index 03570e06..6e70b2cf 100644 --- a/investment_vault/src/events.rs +++ b/investment_vault/src/events.rs @@ -415,6 +415,15 @@ pub struct CarbonCreditsTransferred { pub amount: i128, } +#[contractevent] +pub struct CarbonCreditsIssued { + #[topic] + pub to: Address, + #[topic] + pub project_id: u32, + pub credits: i128, +} + pub fn carbon_oracle_set(env: &Env, oracle: &Address) { CarbonOracleSet { oracle: oracle.clone(), From aae7e88257a6c5f7def5f6fd2f03fbab7a5f60da Mon Sep 17 00:00:00 2001 From: laurentketterle-hub Date: Wed, 12 Aug 2026 21:22:13 +0200 Subject: [PATCH 3/4] feat: publish carbon_credits_issued event (#312) --- investment_vault/src/events.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/investment_vault/src/events.rs b/investment_vault/src/events.rs index 6e70b2cf..b4c046a7 100644 --- a/investment_vault/src/events.rs +++ b/investment_vault/src/events.rs @@ -453,6 +453,15 @@ pub fn carbon_credits_transferred(env: &Env, from: &Address, to: &Address, amoun .publish(env); } +pub fn carbon_credits_issued(env: &Env, to: &Address, project_id: u32, credits: i128) { + CarbonCreditsIssued { + to: to.clone(), + project_id, + credits, + } + .publish(env); +} + // ── Compliance / regulatory events ─────────────────────────────────────── #[contractevent] From d7ba987cc5798281681fd1526418415aaf1db760 Mon Sep 17 00:00:00 2001 From: laurentketterle-hub Date: Wed, 12 Aug 2026 21:22:14 +0200 Subject: [PATCH 4/4] test: assert non-oracle caller cannot mint carbon credits (#312) --- investment_vault/src/test.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/investment_vault/src/test.rs b/investment_vault/src/test.rs index 4be8ed36..123bd4a8 100644 --- a/investment_vault/src/test.rs +++ b/investment_vault/src/test.rs @@ -2733,3 +2733,30 @@ fn test_flash_loan_fails_without_repayment() { &soroban_sdk::Bytes::new(&s.env), ); } + + +#[test] +fn test_issue_carbon_credits_rejects_non_oracle_caller() { + let s = setup(); + let oracle = Address::generate(&s.env); + s.vault_client.set_carbon_oracle(&oracle); + + let stranger = Address::generate(&s.env); + s.env.mock_auths(&[soroban_sdk::testutils::MockAuth { + address: &stranger, + invoke: &soroban_sdk::testutils::MockAuthInvoke { + contract: &s.vault_address, + fn_name: "issue_carbon_credits", + args: soroban_sdk::vec![&s.env, stranger.clone(), 1u32, 1_000_0000000i128], + sub_invokes: &[], + }, + }]); + + let res = s + .vault_client + .try_issue_carbon_credits(&stranger, &1u32, &1_000_0000000i128); + assert!( + res.is_err(), + "a non-oracle caller must not be able to mint carbon credits" + ); +}