diff --git a/investment_vault/src/events.rs b/investment_vault/src/events.rs index 03570e06..b4c046a7 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(), @@ -444,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] 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 } 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" + ); +}