diff --git a/contracts/carbon-credit/src/events.rs b/contracts/carbon-credit/src/events.rs new file mode 100644 index 0000000..280b02b --- /dev/null +++ b/contracts/carbon-credit/src/events.rs @@ -0,0 +1,36 @@ +use soroban_sdk::{Env, Address, Symbol, symbol_short, i128}; + +pub fn emit_initialized(env: &Env, admin: Address) { + env.events().publish( + (symbol_short!("carbon"), symbol_short!("init")), + admin, + ); +} + +pub fn emit_carbon_registered(env: &Env, invoice_id: Symbol, credits: i128) { + env.events().publish( + (symbol_short!("carbon"), symbol_short!("reg")), + (invoice_id, credits), + ); +} + +pub fn emit_invoice_linked(env: &Env, invoice_id: Symbol, wallet: Address, credits: i128) { + env.events().publish( + (symbol_short!("carbon"), symbol_short!("link")), + (invoice_id, wallet, credits), + ); +} + +pub fn emit_carbon_verified(env: &Env, invoice_id: Symbol) { + env.events().publish( + (symbol_short!("carbon"), symbol_short!("verify")), + invoice_id, + ); +} + +pub fn emit_credits_redeemed(env: &Env, wallet: Address, amount: i128) { + env.events().publish( + (symbol_short!("carbon"), symbol_short!("redeem")), + (wallet, amount), + ); +} diff --git a/contracts/carbon-credit/src/lib.rs b/contracts/carbon-credit/src/lib.rs new file mode 100644 index 0000000..1213f0b --- /dev/null +++ b/contracts/carbon-credit/src/lib.rs @@ -0,0 +1,127 @@ +#![no_std] + +use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, Symbol, Vec, i128}; + +mod events; +mod storage; +mod types; + +pub use events::*; +pub use storage::*; +pub use types::*; + +#[contract] +pub struct CarbonCreditContract; + +#[contractimpl] +impl CarbonCreditContract { + pub fn initialize(env: Env, admin: Address) { + admin.require_auth(); + storage::set_admin(&env, &admin); + emit_initialized(&env, admin); + } + + pub fn register_invoice_carbon( + env: Env, + invoice_id: Symbol, + credits: i128, + source: Symbol, + ) -> Result<(), Error> { + let admin: Address = storage::get_admin(&env)?; + admin.require_auth(); + + assert!(credits > 0, "credits must be positive"); + + let record = CarbonRecord { + invoice_id: invoice_id.clone(), + credits, + source, + timestamp: env.ledger().timestamp(), + verified: false, + }; + + storage::store_carbon_record(&env, &invoice_id, &record); + emit_carbon_registered(&env, invoice_id, credits); + + Ok(()) + } + + pub fn link_invoice_credits( + env: Env, + invoice_id: Symbol, + wallet: Address, + ) -> Result { + let record = storage::get_carbon_record(&env, &invoice_id) + .ok_or(Error::NotFound)?; + + storage::link_wallet_credits(&env, &wallet, record.credits); + emit_invoice_linked(&env, invoice_id, wallet, record.credits); + + Ok(record.credits) + } + + pub fn get_wallet_total_credits(env: Env, wallet: Address) -> i128 { + storage::get_wallet_credits(&env, &wallet).unwrap_or(0) + } + + pub fn verify_carbon_record(env: Env, invoice_id: Symbol) -> Result<(), Error> { + let admin: Address = storage::get_admin(&env)?; + admin.require_auth(); + + let mut record = storage::get_carbon_record(&env, &invoice_id) + .ok_or(Error::NotFound)?; + + record.verified = true; + storage::store_carbon_record(&env, &invoice_id, &record); + emit_carbon_verified(&env, invoice_id); + + Ok(()) + } + + pub fn get_carbon_record(env: Env, invoice_id: Symbol) -> Result { + storage::get_carbon_record(&env, &invoice_id) + .ok_or(Error::NotFound) + } + + pub fn list_wallet_credits(env: Env, wallet: Address, limit: u32) -> Vec { + storage::list_wallet_records(&env, &wallet, limit) + } + + pub fn redeem_credits( + env: Env, + wallet: Address, + amount: i128, + ) -> Result<(), Error> { + wallet.require_auth(); + + let current = storage::get_wallet_credits(&env, &wallet) + .ok_or(Error::InsufficientFunds)?; + + assert!(amount <= current, "insufficient credits"); + + let remaining = current - amount; + storage::update_wallet_credits(&env, &wallet, remaining); + emit_credits_redeemed(&env, wallet, amount); + + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub enum Error { + NotFound, + InsufficientFunds, + Unauthorized, + InvalidAmount, +} + +impl From for soroban_sdk::Error { + fn from(err: Error) -> Self { + match err { + Error::NotFound => soroban_sdk::Error::from_contract_error(1), + Error::InsufficientFunds => soroban_sdk::Error::from_contract_error(2), + Error::Unauthorized => soroban_sdk::Error::from_contract_error(3), + Error::InvalidAmount => soroban_sdk::Error::from_contract_error(4), + } + } +} diff --git a/contracts/carbon-credit/src/storage.rs b/contracts/carbon-credit/src/storage.rs new file mode 100644 index 0000000..ccf9c07 --- /dev/null +++ b/contracts/carbon-credit/src/storage.rs @@ -0,0 +1,62 @@ +use soroban_sdk::{Env, Address, Symbol, Vec, symbol_short, i128}; +use crate::{CarbonRecord, Error}; + +const ADMIN_KEY: &str = "admin"; +const CARBON_RECORDS_PREFIX: &str = "carbon_"; +const WALLET_CREDITS_PREFIX: &str = "wallet_"; + +pub fn set_admin(env: &Env, admin: &Address) { + env.storage().instance().set(&symbol_short!("admin"), admin); +} + +pub fn get_admin(env: &Env) -> Result { + env.storage() + .instance() + .get(&symbol_short!("admin")) + .ok_or(Error::NotFound) +} + +pub fn store_carbon_record(env: &Env, invoice_id: &Symbol, record: &CarbonRecord) { + let key = format!("carbon_{}", invoice_id.to_string()); + env.storage().persistent().set(&Symbol::new(env, &key), record); +} + +pub fn get_carbon_record(env: &Env, invoice_id: &Symbol) -> Option { + let key = format!("carbon_{}", invoice_id.to_string()); + env.storage() + .persistent() + .get(&Symbol::new(env, &key)) +} + +pub fn link_wallet_credits(env: &Env, wallet: &Address, amount: i128) { + let key = format!("wallet_{}", wallet.to_string()); + let current = env.storage() + .persistent() + .get::(&Symbol::new(env, &key)) + .unwrap_or(0); + + env.storage() + .persistent() + .set(&Symbol::new(env, &key), &(current + amount)); +} + +pub fn get_wallet_credits(env: &Env, wallet: &Address) -> Option { + let key = format!("wallet_{}", wallet.to_string()); + env.storage() + .persistent() + .get(&Symbol::new(env, &key)) +} + +pub fn update_wallet_credits(env: &Env, wallet: &Address, amount: i128) { + let key = format!("wallet_{}", wallet.to_string()); + env.storage() + .persistent() + .set(&Symbol::new(env, &key), &amount); +} + +pub fn list_wallet_records(env: &Env, wallet: &Address, limit: u32) -> Vec { + let mut records = Vec::new(env); + // Implementation would iterate through storage with prefix matching + // Simplified for this example + records +} diff --git a/contracts/carbon-credit/src/types.rs b/contracts/carbon-credit/src/types.rs new file mode 100644 index 0000000..487490e --- /dev/null +++ b/contracts/carbon-credit/src/types.rs @@ -0,0 +1,29 @@ +use soroban_sdk::{Symbol, contracttype, i128}; + +#[contracttype] +#[derive(Clone, Debug)] +pub struct CarbonRecord { + pub invoice_id: Symbol, + pub credits: i128, + pub source: Symbol, + pub timestamp: u64, + pub verified: bool, +} + +#[contracttype] +#[derive(Clone, Debug)] +pub struct WalletCarbonBalance { + pub wallet: soroban_sdk::Address, + pub total_credits: i128, + pub redeemed_credits: i128, + pub available_credits: i128, +} + +#[contracttype] +#[derive(Clone, Debug)] +pub struct CarbonSummary { + pub total_registered: i128, + pub total_verified: i128, + pub total_linked: i128, + pub total_redeemed: i128, +}