Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions investment_vault/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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]
Expand Down
14 changes: 14 additions & 0 deletions investment_vault/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -1570,6 +1582,8 @@ impl InvestmentVault {
&(prev + calc.credits),
);

events::carbon_credits_issued(&env, &to, project_id, calc.credits);

calc.credits
}

Expand Down
27 changes: 27 additions & 0 deletions investment_vault/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
}
Loading