From c507a94c8ee278868b82d234f2549a12f25da0c6 Mon Sep 17 00:00:00 2001 From: frankie-powers Date: Fri, 3 Oct 2025 23:15:38 +0100 Subject: [PATCH 1/5] lib.rs and utils --- .../src/lib.rs | 277 ++++++++++++++++++ .../src/utils.rs | 141 +++++++++ 2 files changed, 418 insertions(+) create mode 100644 soroban/contracts/renewable-energy-certificate-tracker/src/lib.rs create mode 100644 soroban/contracts/renewable-energy-certificate-tracker/src/utils.rs diff --git a/soroban/contracts/renewable-energy-certificate-tracker/src/lib.rs b/soroban/contracts/renewable-energy-certificate-tracker/src/lib.rs new file mode 100644 index 0000000..90a30d2 --- /dev/null +++ b/soroban/contracts/renewable-energy-certificate-tracker/src/lib.rs @@ -0,0 +1,277 @@ +#![no_std] + +use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, Vec, BytesN, String, Map}; + +pub mod certificate; +pub mod transfer; +pub mod utils; + +#[cfg(test)] +mod tests; + +// Core data structures for Renewable Energy Certificates (RECs) +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct REC { + pub id: BytesN<32>, + pub issuer: Address, + pub energy_source: EnergySource, + pub production_date: u64, + pub production_location: String, + pub capacity_mwh: i128, // Megawatt-hours + pub current_owner: Address, + pub status: RECStatus, + pub verification_standard: String, // I-REC, RE100, etc. + pub verification_hash: BytesN<32>, // Hash of verification documents + pub issuance_date: u64, + pub metadata: Map, // Additional metadata +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum EnergySource { + Solar, + Wind, + Hydro, + Geothermal, + Biomass, + Tidal, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum RECStatus { + Issued, + Transferred, + Retired, + Suspended, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RECEvent { + pub event_type: EventType, + pub rec_id: BytesN<32>, + pub timestamp: u64, + pub from: Option
, + pub to: Option
, + pub capacity_mwh: i128, + pub transaction_hash: BytesN<32>, + pub notes: String, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum EventType { + Issuance, + Transfer, + Retirement, + Suspension, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IssuerInfo { + pub address: Address, + pub name: String, + pub authorized: bool, + pub registration_date: u64, + pub total_issued: i128, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TransferParams { + pub rec_id: BytesN<32>, + pub from: Address, + pub to: Address, + pub capacity_mwh: i128, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RetirementParams { + pub rec_id: BytesN<32>, + pub owner: Address, + pub capacity_mwh: i128, + pub retirement_reason: String, +} + +// Storage keys +#[contracttype] +#[derive(Clone)] +pub enum DataKey { + Admin, + Initialized, + REC(BytesN<32>), + Issuer(Address), + RecHistory(BytesN<32>), + Stats, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ContractStats { + pub total_recs_issued: i128, + pub total_capacity_issued_mwh: i128, + pub total_capacity_transferred_mwh: i128, + pub total_capacity_retired_mwh: i128, +} + +#[contract] +pub struct RenewableEnergyCertificateTracker; + +#[contractimpl] +impl RenewableEnergyCertificateTracker { + /// Initialize the contract with admin address + pub fn initialize(env: Env, admin: Address) { + if env.storage().instance().has(&DataKey::Initialized) { + panic!("Already initialized"); + } + + admin.require_auth(); + + env.storage().instance().set(&DataKey::Admin, &admin); + env.storage().instance().set(&DataKey::Initialized, &true); + + let stats = ContractStats { + total_recs_issued: 0, + total_capacity_issued_mwh: 0, + total_capacity_transferred_mwh: 0, + total_capacity_retired_mwh: 0, + }; + env.storage().instance().set(&DataKey::Stats, &stats); + } + + /// Register an authorized issuer + pub fn register_issuer(env: Env, issuer: Address, name: String) { + let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + let issuer_info = IssuerInfo { + address: issuer.clone(), + name, + authorized: true, + registration_date: env.ledger().timestamp(), + total_issued: 0, + }; + + env.storage().persistent().set(&DataKey::Issuer(issuer), &issuer_info); + } + + /// Issue a new REC for verified renewable energy production + pub fn issue_rec( + env: Env, + issuer: Address, + energy_source: EnergySource, + production_date: u64, + production_location: String, + capacity_mwh: i128, + verification_standard: String, + verification_hash: BytesN<32>, + metadata: Map, + ) -> BytesN<32> { + certificate::issue_rec( + &env, + issuer, + energy_source, + production_date, + production_location, + capacity_mwh, + verification_standard, + verification_hash, + metadata, + ) + } + + /// Transfer a REC to a new owner + pub fn transfer_rec( + env: Env, + rec_id: BytesN<32>, + from: Address, + to: Address, + capacity_mwh: i128, + ) -> bool { + transfer::transfer_rec(&env, rec_id, from, to, capacity_mwh) + } + + /// Retire a REC to claim renewable energy usage + pub fn retire_rec( + env: Env, + rec_id: BytesN<32>, + owner: Address, + capacity_mwh: i128, + retirement_reason: String, + ) -> bool { + transfer::retire_rec(&env, rec_id, owner, capacity_mwh, retirement_reason) + } + + /// Get the status and details of a REC + pub fn get_rec_status(env: Env, rec_id: BytesN<32>) -> REC { + let rec_key = DataKey::REC(rec_id); + env.storage().persistent().get(&rec_key).unwrap() + } + + /// Get the history of a REC + pub fn get_rec_history(env: Env, rec_id: BytesN<32>) -> Vec { + let history_key = DataKey::RecHistory(rec_id); + env.storage() + .persistent() + .get(&history_key) + .unwrap_or(Vec::new(&env)) + } + + /// Get issuer information + pub fn get_issuer_info(env: Env, issuer: Address) -> IssuerInfo { + env.storage() + .persistent() + .get(&DataKey::Issuer(issuer)) + .unwrap() + } + + /// Get contract statistics + pub fn get_contract_stats(env: Env) -> (i128, i128, i128, i128) { + let stats: ContractStats = env.storage().instance().get(&DataKey::Stats).unwrap(); + ( + stats.total_recs_issued, + stats.total_capacity_issued_mwh, + stats.total_capacity_transferred_mwh, + stats.total_capacity_retired_mwh, + ) + } + + /// Suspend a REC (admin only) + pub fn suspend_rec(env: Env, rec_id: BytesN<32>) -> bool { + let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + let rec_key = DataKey::REC(rec_id.clone()); + let mut rec: REC = env.storage().persistent().get(&rec_key).unwrap(); + + rec.status = RECStatus::Suspended; + env.storage().persistent().set(&rec_key, &rec); + + // Log suspension event + let event = RECEvent { + event_type: EventType::Suspension, + rec_id, + timestamp: env.ledger().timestamp(), + from: Some(rec.current_owner.clone()), + to: None, + capacity_mwh: rec.capacity_mwh, + transaction_hash: BytesN::from_array(&env, &[0u8; 32]), + notes: String::from_str(&env, "REC suspended by admin"), + }; + + let history_key = DataKey::RecHistory(rec.id.clone()); + let mut history: Vec = env + .storage() + .persistent() + .get(&history_key) + .unwrap_or(Vec::new(&env)); + history.push_back(event); + env.storage().persistent().set(&history_key, &history); + + true + } +} diff --git a/soroban/contracts/renewable-energy-certificate-tracker/src/utils.rs b/soroban/contracts/renewable-energy-certificate-tracker/src/utils.rs new file mode 100644 index 0000000..a18b0f0 --- /dev/null +++ b/soroban/contracts/renewable-energy-certificate-tracker/src/utils.rs @@ -0,0 +1,141 @@ +use soroban_sdk::{Address, Bytes, BytesN, Env}; + +/// Generates a unique REC ID based on issuer and verification hash +pub fn generate_rec_id( + env: &Env, + _issuer: &Address, + verification_hash: &BytesN<32>, +) -> BytesN<32> { + let timestamp = env.ledger().timestamp(); + let timestamp_bytes = timestamp.to_be_bytes(); + + // Combine timestamp and verification hash to create unique ID + let mut combined = [0u8; 40]; + combined[0..8].copy_from_slice(×tamp_bytes); + combined[8..40].copy_from_slice(&verification_hash.to_array()); + + let bytes_data = Bytes::from_slice(env, &combined); + env.crypto().sha256(&bytes_data).into() +} + +/// Validates energy capacity value +pub fn validate_capacity(capacity_mwh: i128) -> bool { + capacity_mwh > 0 +} + +/// Validates production date (must not be in the future) +pub fn validate_production_date(env: &Env, production_date: u64) -> bool { + production_date <= env.ledger().timestamp() +} + +/// Validates verification hash (must not be empty) +pub fn validate_verification_hash(verification_hash: &BytesN<32>) -> bool { + let empty_hash = [0u8; 32]; + verification_hash.to_array() != empty_hash +} + +/// Checks if an address is authorized as an issuer +pub fn is_authorized_issuer(env: &Env, issuer: &Address) -> bool { + use crate::{DataKey, IssuerInfo}; + + let issuer_key = DataKey::Issuer(issuer.clone()); + if let Some(issuer_info) = env.storage().persistent().get::(&issuer_key) + { + issuer_info.authorized + } else { + false + } +} + +/// Validates REC transfer parameters +pub fn validate_transfer( + capacity_mwh: i128, + from: &Address, + to: &Address, +) -> bool { + capacity_mwh > 0 && from != to +} + +/// Validates retirement parameters +pub fn validate_retirement(capacity_mwh: i128) -> bool { + capacity_mwh > 0 +} + +/// Creates a transaction hash for events +pub fn create_transaction_hash( + env: &Env, + rec_id: &BytesN<32>, + timestamp: u64, +) -> BytesN<32> { + let timestamp_bytes = timestamp.to_be_bytes(); + + let mut combined = [0u8; 40]; + combined[0..32].copy_from_slice(&rec_id.to_array()); + combined[32..40].copy_from_slice(×tamp_bytes); + + let bytes_data = Bytes::from_slice(env, &combined); + env.crypto().sha256(&bytes_data).into() +} + +#[cfg(test)] +mod tests { + use super::*; + use soroban_sdk::testutils::Address as _; + + #[test] + fn test_validate_capacity() { + assert!(validate_capacity(100)); + assert!(validate_capacity(1)); + assert!(!validate_capacity(0)); + assert!(!validate_capacity(-100)); + } + + #[test] + fn test_validate_transfer() { + use soroban_sdk::testutils::Address as _; + + let env = Env::default(); + let addr1 = Address::generate(&env); + let addr2 = Address::generate(&env); + + assert!(validate_transfer(100, &addr1, &addr2)); + assert!(!validate_transfer(0, &addr1, &addr2)); + assert!(!validate_transfer(-100, &addr1, &addr2)); + assert!(!validate_transfer(100, &addr1, &addr1)); + } + + #[test] + fn test_validate_retirement() { + assert!(validate_retirement(100)); + assert!(validate_retirement(1)); + assert!(!validate_retirement(0)); + assert!(!validate_retirement(-100)); + } + + #[test] + fn test_validate_verification_hash() { + let env = Env::default(); + + let valid_hash = BytesN::from_array(&env, &[1u8; 32]); + assert!(validate_verification_hash(&valid_hash)); + + let empty_hash = BytesN::from_array(&env, &[0u8; 32]); + assert!(!validate_verification_hash(&empty_hash)); + } + + #[test] + fn test_generate_rec_id() { + use soroban_sdk::testutils::Ledger as _; + + let env = Env::default(); + let issuer = Address::generate(&env); + let verification_hash1 = BytesN::from_array(&env, &[1u8; 32]); + let verification_hash2 = BytesN::from_array(&env, &[2u8; 32]); + + let rec_id1 = generate_rec_id(&env, &issuer, &verification_hash1); + let rec_id2 = generate_rec_id(&env, &issuer, &verification_hash2); + + // IDs should be different due to different verification hashes + assert_ne!(rec_id1, rec_id2); + } +} From faf331469dacc976a1504c0af59b5545cea434b2 Mon Sep 17 00:00:00 2001 From: frankie-powers Date: Fri, 3 Oct 2025 23:16:38 +0100 Subject: [PATCH 2/5] transfer module and cargo files --- soroban/Cargo.lock | 7 + .../Cargo.toml | 16 ++ .../src/transfer.rs | 188 ++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 soroban/contracts/renewable-energy-certificate-tracker/Cargo.toml create mode 100644 soroban/contracts/renewable-energy-certificate-tracker/src/transfer.rs diff --git a/soroban/Cargo.lock b/soroban/Cargo.lock index 414e069..690d814 100644 --- a/soroban/Cargo.lock +++ b/soroban/Cargo.lock @@ -1078,6 +1078,13 @@ dependencies = [ "getrandom", ] +[[package]] +name = "renewable_energy_certificate_tracker" +version = "0.1.0" +dependencies = [ + "soroban-sdk 21.7.7", +] + [[package]] name = "rfc6979" version = "0.4.0" diff --git a/soroban/contracts/renewable-energy-certificate-tracker/Cargo.toml b/soroban/contracts/renewable-energy-certificate-tracker/Cargo.toml new file mode 100644 index 0000000..1786cc1 --- /dev/null +++ b/soroban/contracts/renewable-energy-certificate-tracker/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "renewable_energy_certificate_tracker" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = "21.0.0" + +[dev-dependencies] +soroban-sdk = { version = "21.0.0", features = ["testutils"] } + +[features] +testutils = ["soroban-sdk/testutils"] diff --git a/soroban/contracts/renewable-energy-certificate-tracker/src/transfer.rs b/soroban/contracts/renewable-energy-certificate-tracker/src/transfer.rs new file mode 100644 index 0000000..a5c679f --- /dev/null +++ b/soroban/contracts/renewable-energy-certificate-tracker/src/transfer.rs @@ -0,0 +1,188 @@ +use soroban_sdk::{Address, BytesN, Env, String, Vec}; + +use crate::{ContractStats, DataKey, EventType, REC, RECEvent, RECStatus}; + +/// Transfers a REC to a new owner +pub fn transfer_rec( + env: &Env, + rec_id: BytesN<32>, + from: Address, + to: Address, + capacity_mwh: i128, +) -> bool { + // Require authentication from sender + from.require_auth(); + + // Get the REC + let rec_key = DataKey::REC(rec_id.clone()); + let mut rec: REC = env + .storage() + .persistent() + .get(&rec_key) + .expect("REC not found"); + + // Verify ownership + if rec.current_owner != from { + panic!("Not the owner"); + } + + // Verify REC is not retired or suspended + if rec.status == RECStatus::Retired { + panic!("Cannot transfer retired REC"); + } + + if rec.status == RECStatus::Suspended { + panic!("Cannot transfer suspended REC"); + } + + // Verify capacity + if capacity_mwh <= 0 { + panic!("Capacity must be positive"); + } + + if capacity_mwh > rec.capacity_mwh { + panic!("Insufficient capacity"); + } + + // Verify sender and recipient are different + if from == to { + panic!("Cannot transfer to self"); + } + + // Update REC ownership and status + rec.current_owner = to.clone(); + rec.status = RECStatus::Transferred; + env.storage().persistent().set(&rec_key, &rec); + + // Create transfer event + let event = RECEvent { + event_type: EventType::Transfer, + rec_id: rec_id.clone(), + timestamp: env.ledger().timestamp(), + from: Some(from), + to: Some(to), + capacity_mwh, + transaction_hash: BytesN::from_array(env, &[2u8; 32]), + notes: String::from_str(env, "REC transferred"), + }; + + // Add event to history + let history_key = DataKey::RecHistory(rec_id); + let mut history: Vec = env + .storage() + .persistent() + .get(&history_key) + .unwrap_or(Vec::new(env)); + history.push_back(event); + env.storage().persistent().set(&history_key, &history); + + // Update contract statistics + let mut stats: ContractStats = env.storage().instance().get(&DataKey::Stats).unwrap(); + stats.total_capacity_transferred_mwh += capacity_mwh; + env.storage().instance().set(&DataKey::Stats, &stats); + + true +} + +/// Retires a REC to claim renewable energy usage +pub fn retire_rec( + env: &Env, + rec_id: BytesN<32>, + owner: Address, + capacity_mwh: i128, + retirement_reason: String, +) -> bool { + // Require authentication from owner + owner.require_auth(); + + // Get the REC + let rec_key = DataKey::REC(rec_id.clone()); + let mut rec: REC = env + .storage() + .persistent() + .get(&rec_key) + .expect("REC not found"); + + // Verify ownership + if rec.current_owner != owner { + panic!("Not the owner"); + } + + // Verify REC is not already retired + if rec.status == RECStatus::Retired { + panic!("REC already retired"); + } + + // Verify REC is not suspended + if rec.status == RECStatus::Suspended { + panic!("Cannot retire suspended REC"); + } + + // Verify capacity + if capacity_mwh <= 0 { + panic!("Capacity must be positive"); + } + + if capacity_mwh > rec.capacity_mwh { + panic!("Insufficient capacity"); + } + + // Update REC status + rec.status = RECStatus::Retired; + env.storage().persistent().set(&rec_key, &rec); + + // Create retirement event + let event = RECEvent { + event_type: EventType::Retirement, + rec_id: rec_id.clone(), + timestamp: env.ledger().timestamp(), + from: Some(owner), + to: None, + capacity_mwh, + transaction_hash: BytesN::from_array(env, &[3u8; 32]), + notes: retirement_reason, + }; + + // Add event to history + let history_key = DataKey::RecHistory(rec_id); + let mut history: Vec = env + .storage() + .persistent() + .get(&history_key) + .unwrap_or(Vec::new(env)); + history.push_back(event); + env.storage().persistent().set(&history_key, &history); + + // Update contract statistics + let mut stats: ContractStats = env.storage().instance().get(&DataKey::Stats).unwrap(); + stats.total_capacity_retired_mwh += capacity_mwh; + env.storage().instance().set(&DataKey::Stats, &stats); + + true +} + +/// Checks if a REC can be transferred +pub fn can_transfer(env: &Env, rec_id: BytesN<32>, owner: &Address) -> bool { + let rec_key = DataKey::REC(rec_id); + + if let Some(rec) = env.storage().persistent().get::(&rec_key) { + rec.current_owner == *owner + && rec.status != RECStatus::Retired + && rec.status != RECStatus::Suspended + } else { + false + } +} + +/// Checks if a REC can be retired +pub fn can_retire(env: &Env, rec_id: BytesN<32>, owner: &Address) -> bool { + let rec_key = DataKey::REC(rec_id); + + if let Some(rec) = env.storage().persistent().get::(&rec_key) { + rec.current_owner == *owner + && rec.status != RECStatus::Retired + && rec.status != RECStatus::Suspended + } else { + false + } +} From dbc450e5baa6482f811b2bdb45e430034ba51517 Mon Sep 17 00:00:00 2001 From: frankie-powers Date: Fri, 3 Oct 2025 23:16:57 +0100 Subject: [PATCH 3/5] certificate.rs --- .../src/certificate.rs | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 soroban/contracts/renewable-energy-certificate-tracker/src/certificate.rs diff --git a/soroban/contracts/renewable-energy-certificate-tracker/src/certificate.rs b/soroban/contracts/renewable-energy-certificate-tracker/src/certificate.rs new file mode 100644 index 0000000..d4e5035 --- /dev/null +++ b/soroban/contracts/renewable-energy-certificate-tracker/src/certificate.rs @@ -0,0 +1,143 @@ +use soroban_sdk::{Address, BytesN, Env, Map, String, Vec}; + +use crate::{ + utils, ContractStats, DataKey, EnergySource, EventType, IssuerInfo, REC, RECEvent, RECStatus, +}; + +/// Issues a new REC for verified renewable energy production +pub fn issue_rec( + env: &Env, + issuer: Address, + energy_source: EnergySource, + production_date: u64, + production_location: String, + capacity_mwh: i128, + verification_standard: String, + verification_hash: BytesN<32>, + metadata: Map, +) -> BytesN<32> { + // Verify issuer is authorized + issuer.require_auth(); + + let issuer_key = DataKey::Issuer(issuer.clone()); + let mut issuer_info: IssuerInfo = env + .storage() + .persistent() + .get(&issuer_key) + .expect("Issuer not registered"); + + if !issuer_info.authorized { + panic!("Issuer not authorized"); + } + + // Validate capacity + if capacity_mwh <= 0 { + panic!("Capacity must be positive"); + } + + // Generate unique REC ID + let rec_id = utils::generate_rec_id(env, &issuer, &verification_hash); + + // Check if REC already exists + let rec_key = DataKey::REC(rec_id.clone()); + if env.storage().persistent().has(&rec_key) { + panic!("REC already exists"); + } + + // Create the REC + let rec = REC { + id: rec_id.clone(), + issuer: issuer.clone(), + energy_source, + production_date, + production_location, + capacity_mwh, + current_owner: issuer.clone(), + status: RECStatus::Issued, + verification_standard, + verification_hash, + issuance_date: env.ledger().timestamp(), + metadata, + }; + + // Store the REC + env.storage().persistent().set(&rec_key, &rec); + + // Create issuance event + let event = RECEvent { + event_type: EventType::Issuance, + rec_id: rec_id.clone(), + timestamp: env.ledger().timestamp(), + from: None, + to: Some(issuer.clone()), + capacity_mwh, + transaction_hash: BytesN::from_array(env, &[1u8; 32]), + notes: String::from_str(env, "REC issued"), + }; + + // Store event in history + let history_key = DataKey::RecHistory(rec_id.clone()); + let mut history: Vec = Vec::new(env); + history.push_back(event); + env.storage().persistent().set(&history_key, &history); + + // Update issuer statistics + issuer_info.total_issued += capacity_mwh; + env.storage().persistent().set(&issuer_key, &issuer_info); + + // Update contract statistics + let mut stats: ContractStats = env.storage().instance().get(&DataKey::Stats).unwrap(); + stats.total_recs_issued += 1; + stats.total_capacity_issued_mwh += capacity_mwh; + env.storage().instance().set(&DataKey::Stats, &stats); + + rec_id +} + +/// Validates REC authenticity and ownership +pub fn verify_rec(env: &Env, rec_id: BytesN<32>) -> bool { + let rec_key = DataKey::REC(rec_id.clone()); + + if !env.storage().persistent().has(&rec_key) { + return false; + } + + let rec: REC = env.storage().persistent().get(&rec_key).unwrap(); + + // Verify issuer is still authorized + let issuer_key = DataKey::Issuer(rec.issuer.clone()); + if let Some(issuer_info) = env.storage().persistent().get::(&issuer_key) + { + if !issuer_info.authorized { + return false; + } + } else { + return false; + } + + // Verify REC is not suspended + if rec.status == RECStatus::Suspended { + return false; + } + + true +} + +/// Gets detailed information about a REC +pub fn get_rec_details(env: &Env, rec_id: BytesN<32>) -> REC { + let rec_key = DataKey::REC(rec_id); + env.storage() + .persistent() + .get(&rec_key) + .expect("REC not found") +} + +/// Checks if an address is the current owner of a REC +pub fn is_owner(env: &Env, rec_id: BytesN<32>, address: &Address) -> bool { + let rec_key = DataKey::REC(rec_id); + if let Some(rec) = env.storage().persistent().get::(&rec_key) { + rec.current_owner == *address + } else { + false + } +} From e6ef3696fe137916ccf215138e5bcc40b80cef1b Mon Sep 17 00:00:00 2001 From: frankie-powers Date: Fri, 3 Oct 2025 23:17:12 +0100 Subject: [PATCH 4/5] makefile and readme --- .../Makefile | 74 +++++ .../README.md | 299 ++++++++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 soroban/contracts/renewable-energy-certificate-tracker/Makefile create mode 100644 soroban/contracts/renewable-energy-certificate-tracker/README.md diff --git a/soroban/contracts/renewable-energy-certificate-tracker/Makefile b/soroban/contracts/renewable-energy-certificate-tracker/Makefile new file mode 100644 index 0000000..608e0ef --- /dev/null +++ b/soroban/contracts/renewable-energy-certificate-tracker/Makefile @@ -0,0 +1,74 @@ +# Renewable Energy Certificate Tracker Contract Makefile + +.PHONY: build test clean deploy + +# Build the contract +build: + cargo build --target wasm32-unknown-unknown --release + +# Build using Stellar CLI +stellar-build: + stellar contract build + +# Run tests +test: + cargo test + +# Clean build artifacts +clean: + cargo clean + rm -f *.wasm + +# Deploy contract (requires Stellar CLI and network configuration) +deploy: + stellar contract deploy --wasm target/wasm32-unknown-unknown/release/renewable_energy_certificate_tracker.wasm + +# Install dependencies +install: + cargo install --locked soroban-cli + +# Format code +fmt: + cargo fmt + +# Check code +check: + cargo check + +# Run clippy +clippy: + cargo clippy -- -D warnings + +# Build optimized WASM +build-optimized: + cargo build --target wasm32-unknown-unknown --release + wasm-opt -Oz target/wasm32-unknown-unknown/release/renewable_energy_certificate_tracker.wasm -o renewable_energy_certificate_tracker_optimized.wasm + +# Generate contract bindings +bindings: + stellar contract bindings typescript --output-dir ./bindings + +# Run integration tests +test-integration: + cargo test --test integration + +# All-in-one build and test +all: clean build test + +# Help target +help: + @echo "Available targets:" + @echo " build - Build the contract using cargo" + @echo " stellar-build - Build using Stellar CLI" + @echo " test - Run unit tests" + @echo " clean - Clean build artifacts" + @echo " deploy - Deploy contract to network" + @echo " install - Install Soroban CLI" + @echo " fmt - Format code" + @echo " check - Check code without building" + @echo " clippy - Run clippy linter" + @echo " build-optimized - Build optimized WASM" + @echo " bindings - Generate contract bindings" + @echo " test-integration - Run integration tests" + @echo " all - Clean, build, and test" + @echo " help - Show this help message" diff --git a/soroban/contracts/renewable-energy-certificate-tracker/README.md b/soroban/contracts/renewable-energy-certificate-tracker/README.md new file mode 100644 index 0000000..2f2857b --- /dev/null +++ b/soroban/contracts/renewable-energy-certificate-tracker/README.md @@ -0,0 +1,299 @@ +# Renewable Energy Certificate (REC) Tracker + +A comprehensive smart contract for tracking Renewable Energy Certificates (RECs) on the Stellar network using Soroban. This contract ensures transparent issuance, transfer, and retirement of RECs for renewable energy compliance, following standards like I-REC and RE100. + +## Overview + +The Renewable Energy Certificate Tracker provides a tamper-proof, decentralized system for managing RECs throughout their lifecycle. It enables authorized issuers to create certificates for verified renewable energy production, facilitates secure transfers between parties, and allows certificate retirement for renewable energy claims. + +## Features + +- **Certificate Issuance**: Authorized issuers can create RECs for verified renewable energy production +- **Ownership Transfer**: Secure transfer of RECs between parties with full audit trail +- **Certificate Retirement**: Retirement of RECs to claim renewable energy usage +- **Verification & Authentication**: Built-in verification mechanisms for REC authenticity +- **Role-Based Access Control**: Issuer authorization and owner-based permissions +- **Complete Audit Trail**: Full lifecycle tracking with timestamped events +- **Standards Compliance**: Support for I-REC, RE100, and other verification standards +- **Multiple Energy Sources**: Support for Solar, Wind, Hydro, Geothermal, Biomass, and Tidal + +## Contract Structure + +``` +renewable-energy-certificate-tracker/ +├── src/ +│ ├── lib.rs # Main contract, data structures, and exports +│ ├── certificate.rs # REC issuance and verification logic +│ ├── transfer.rs # Transfer and retirement operations +│ └── utils.rs # Validation and utility functions +├── Cargo.toml # Dependencies and package configuration +├── Makefile # Build and deployment automation +└── README.md # This file +``` + +## Data Structures + +### REC (Renewable Energy Certificate) +```rust +pub struct REC { + pub id: BytesN<32>, // Unique certificate ID + pub issuer: Address, // Issuer address + pub energy_source: EnergySource, // Type of renewable energy + pub production_date: u64, // Energy production timestamp + pub production_location: String, // Geographic location + pub capacity_mwh: i128, // Capacity in megawatt-hours + pub current_owner: Address, // Current owner + pub status: RECStatus, // Current status + pub verification_standard: String, // I-REC, RE100, etc. + pub verification_hash: BytesN<32>, // Verification document hash + pub issuance_date: u64, // Certificate issuance timestamp + pub metadata: Map, // Additional metadata +} +``` + +### Energy Source Types +- Solar +- Wind +- Hydro +- Geothermal +- Biomass +- Tidal + +### REC Status +- **Issued**: Newly created certificate +- **Transferred**: Certificate has been transferred +- **Retired**: Certificate retired for compliance claims +- **Suspended**: Certificate suspended by admin + +## Key Functions + +### Administrative Functions + +#### `initialize(env: Env, admin: Address)` +Initializes the contract with an admin address. Must be called once before any other operations. + +**Parameters:** +- `admin`: Address of the contract administrator + +#### `register_issuer(env: Env, issuer: Address, name: String)` +Registers an authorized issuer who can create RECs. + +**Parameters:** +- `issuer`: Address to authorize as an issuer +- `name`: Name of the issuing organization + +**Authorization:** Admin only + +### Certificate Operations + +#### `issue_rec(...) -> BytesN<32>` +Issues a new REC for verified renewable energy production. + +**Parameters:** +- `issuer`: Address of the authorized issuer +- `energy_source`: Type of renewable energy (Solar, Wind, etc.) +- `production_date`: Timestamp of energy production +- `production_location`: Geographic location of production +- `capacity_mwh`: Energy capacity in megawatt-hours +- `verification_standard`: Verification standard (I-REC, RE100, etc.) +- `verification_hash`: Hash of verification documents +- `metadata`: Additional certificate metadata + +**Returns:** Unique REC ID + +**Authorization:** Authorized issuer only + +#### `transfer_rec(env: Env, rec_id: BytesN<32>, from: Address, to: Address, capacity_mwh: i128) -> bool` +Transfers a REC to a new owner. + +**Parameters:** +- `rec_id`: ID of the REC to transfer +- `from`: Current owner address +- `to`: New owner address +- `capacity_mwh`: Capacity to transfer + +**Returns:** Success status + +**Authorization:** Current owner only + +#### `retire_rec(env: Env, rec_id: BytesN<32>, owner: Address, capacity_mwh: i128, retirement_reason: String) -> bool` +Retires a REC to claim renewable energy usage. + +**Parameters:** +- `rec_id`: ID of the REC to retire +- `owner`: Owner address +- `capacity_mwh`: Capacity to retire +- `retirement_reason`: Reason for retirement + +**Returns:** Success status + +**Authorization:** Current owner only + +### Query Functions + +#### `get_rec_status(env: Env, rec_id: BytesN<32>) -> REC` +Retrieves the current status and details of a REC. + +#### `get_rec_history(env: Env, rec_id: BytesN<32>) -> Vec` +Retrieves the complete event history for a REC. + +#### `get_issuer_info(env: Env, issuer: Address) -> IssuerInfo` +Retrieves information about an authorized issuer. + +#### `get_contract_stats(env: Env) -> (i128, i128, i128, i128)` +Retrieves contract-level statistics: +- Total RECs issued +- Total capacity issued (MWh) +- Total capacity transferred (MWh) +- Total capacity retired (MWh) + +#### `suspend_rec(env: Env, rec_id: BytesN<32>) -> bool` +Suspends a REC (admin only). + +## Building and Testing + +### Prerequisites +- Rust (latest stable) +- Soroban CLI +- wasm32-unknown-unknown target + +### Build +```bash +# Standard build +cargo build --target wasm32-unknown-unknown --release + +# Or using Stellar CLI +stellar contract build + +# Or using Makefile +make build +``` + +### Test +```bash +# Run all tests +cargo test + +# Or using Makefile +make test +``` + +### Deploy +```bash +# Deploy to network (requires network configuration) +stellar contract deploy --wasm target/wasm32-unknown-unknown/release/renewable_energy_certificate_tracker.wasm + +# Or using Makefile +make deploy +``` + +## Usage Examples + +### 1. Initialize Contract +```rust +let admin = Address::generate(&env); +contract.initialize(&admin); +``` + +### 2. Register Issuer +```rust +let issuer = Address::generate(&env); +contract.register_issuer(&issuer, String::from_str(&env, "Green Energy Co")); +``` + +### 3. Issue REC +```rust +let rec_id = contract.issue_rec( + &issuer, + EnergySource::Solar, + production_date, + String::from_str(&env, "California, USA"), + 1000, // 1000 MWh + String::from_str(&env, "I-REC"), + verification_hash, + metadata, +); +``` + +### 4. Transfer REC +```rust +let buyer = Address::generate(&env); +contract.transfer_rec(&rec_id, &issuer, &buyer, 1000); +``` + +### 5. Retire REC +```rust +contract.retire_rec( + &rec_id, + &buyer, + 1000, + String::from_str(&env, "2024 renewable energy compliance"), +); +``` + +## Security Features + +- **Authentication**: All sensitive operations require proper authorization +- **Ownership Verification**: Transfers and retirements require owner authentication +- **Issuer Authorization**: Only registered issuers can create RECs +- **Status Validation**: Prevents operations on retired or suspended RECs +- **Immutable History**: Complete audit trail cannot be modified +- **Hash Verification**: Verification documents secured with cryptographic hashes + +## Integration + +### Carbon Credit Integration +This contract can be integrated with the carbon-credit-registry contract for organizations using renewable energy to offset carbon emissions. + +### Energy Production Oracles +Designed to support integration with energy production data oracles for real-time verification of renewable energy generation. + +## Storage Optimization + +The contract uses efficient storage patterns to minimize transaction fees: +- Persistent storage for RECs and issuer information +- Instance storage for contract-level data +- Event history stored separately from REC data +- Optimized data structures for minimal storage footprint + +## Compliance Standards + +The contract supports multiple renewable energy compliance standards: +- **I-REC**: International Renewable Energy Certificate Standard +- **RE100**: 100% Renewable Energy Initiative +- Custom verification standards as needed + +## Development + +### Run Tests +```bash +make test +``` + +### Format Code +```bash +make fmt +``` + +### Check Code +```bash +make check +``` + +### Run Linter +```bash +make clippy +``` + +### All-in-One +```bash +make all # Clean, build, and test +``` + +## License + +This contract is provided as-is for renewable energy certificate tracking on the Stellar network. + +## Support + +For issues, questions, or contributions, please refer to the project repository. From 37b06afc958549096c0779e3794fc0a305113bb7 Mon Sep 17 00:00:00 2001 From: frankie-powers Date: Fri, 3 Oct 2025 23:28:54 +0100 Subject: [PATCH 5/5] ran cargo fmt --- .../src/certificate.rs | 7 ++++-- .../src/lib.rs | 6 +++-- .../src/tests.rs | 1 + .../src/transfer.rs | 2 +- .../src/utils.rs | 23 ++++++------------- 5 files changed, 18 insertions(+), 21 deletions(-) create mode 100644 soroban/contracts/renewable-energy-certificate-tracker/src/tests.rs diff --git a/soroban/contracts/renewable-energy-certificate-tracker/src/certificate.rs b/soroban/contracts/renewable-energy-certificate-tracker/src/certificate.rs index d4e5035..9ad9e43 100644 --- a/soroban/contracts/renewable-energy-certificate-tracker/src/certificate.rs +++ b/soroban/contracts/renewable-energy-certificate-tracker/src/certificate.rs @@ -1,7 +1,7 @@ use soroban_sdk::{Address, BytesN, Env, Map, String, Vec}; use crate::{ - utils, ContractStats, DataKey, EnergySource, EventType, IssuerInfo, REC, RECEvent, RECStatus, + utils, ContractStats, DataKey, EnergySource, EventType, IssuerInfo, RECEvent, RECStatus, REC, }; /// Issues a new REC for verified renewable energy production @@ -106,7 +106,10 @@ pub fn verify_rec(env: &Env, rec_id: BytesN<32>) -> bool { // Verify issuer is still authorized let issuer_key = DataKey::Issuer(rec.issuer.clone()); - if let Some(issuer_info) = env.storage().persistent().get::(&issuer_key) + if let Some(issuer_info) = env + .storage() + .persistent() + .get::(&issuer_key) { if !issuer_info.authorized { return false; diff --git a/soroban/contracts/renewable-energy-certificate-tracker/src/lib.rs b/soroban/contracts/renewable-energy-certificate-tracker/src/lib.rs index 90a30d2..0710215 100644 --- a/soroban/contracts/renewable-energy-certificate-tracker/src/lib.rs +++ b/soroban/contracts/renewable-energy-certificate-tracker/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, Vec, BytesN, String, Map}; +use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env, Map, String, Vec}; pub mod certificate; pub mod transfer; @@ -156,7 +156,9 @@ impl RenewableEnergyCertificateTracker { total_issued: 0, }; - env.storage().persistent().set(&DataKey::Issuer(issuer), &issuer_info); + env.storage() + .persistent() + .set(&DataKey::Issuer(issuer), &issuer_info); } /// Issue a new REC for verified renewable energy production diff --git a/soroban/contracts/renewable-energy-certificate-tracker/src/tests.rs b/soroban/contracts/renewable-energy-certificate-tracker/src/tests.rs new file mode 100644 index 0000000..ff7bd09 --- /dev/null +++ b/soroban/contracts/renewable-energy-certificate-tracker/src/tests.rs @@ -0,0 +1 @@ +// placeholder diff --git a/soroban/contracts/renewable-energy-certificate-tracker/src/transfer.rs b/soroban/contracts/renewable-energy-certificate-tracker/src/transfer.rs index a5c679f..d21307e 100644 --- a/soroban/contracts/renewable-energy-certificate-tracker/src/transfer.rs +++ b/soroban/contracts/renewable-energy-certificate-tracker/src/transfer.rs @@ -1,6 +1,6 @@ use soroban_sdk::{Address, BytesN, Env, String, Vec}; -use crate::{ContractStats, DataKey, EventType, REC, RECEvent, RECStatus}; +use crate::{ContractStats, DataKey, EventType, RECEvent, RECStatus, REC}; /// Transfers a REC to a new owner pub fn transfer_rec( diff --git a/soroban/contracts/renewable-energy-certificate-tracker/src/utils.rs b/soroban/contracts/renewable-energy-certificate-tracker/src/utils.rs index a18b0f0..9453244 100644 --- a/soroban/contracts/renewable-energy-certificate-tracker/src/utils.rs +++ b/soroban/contracts/renewable-energy-certificate-tracker/src/utils.rs @@ -1,11 +1,7 @@ use soroban_sdk::{Address, Bytes, BytesN, Env}; /// Generates a unique REC ID based on issuer and verification hash -pub fn generate_rec_id( - env: &Env, - _issuer: &Address, - verification_hash: &BytesN<32>, -) -> BytesN<32> { +pub fn generate_rec_id(env: &Env, _issuer: &Address, verification_hash: &BytesN<32>) -> BytesN<32> { let timestamp = env.ledger().timestamp(); let timestamp_bytes = timestamp.to_be_bytes(); @@ -39,7 +35,10 @@ pub fn is_authorized_issuer(env: &Env, issuer: &Address) -> bool { use crate::{DataKey, IssuerInfo}; let issuer_key = DataKey::Issuer(issuer.clone()); - if let Some(issuer_info) = env.storage().persistent().get::(&issuer_key) + if let Some(issuer_info) = env + .storage() + .persistent() + .get::(&issuer_key) { issuer_info.authorized } else { @@ -48,11 +47,7 @@ pub fn is_authorized_issuer(env: &Env, issuer: &Address) -> bool { } /// Validates REC transfer parameters -pub fn validate_transfer( - capacity_mwh: i128, - from: &Address, - to: &Address, -) -> bool { +pub fn validate_transfer(capacity_mwh: i128, from: &Address, to: &Address) -> bool { capacity_mwh > 0 && from != to } @@ -62,11 +57,7 @@ pub fn validate_retirement(capacity_mwh: i128) -> bool { } /// Creates a transaction hash for events -pub fn create_transaction_hash( - env: &Env, - rec_id: &BytesN<32>, - timestamp: u64, -) -> BytesN<32> { +pub fn create_transaction_hash(env: &Env, rec_id: &BytesN<32>, timestamp: u64) -> BytesN<32> { let timestamp_bytes = timestamp.to_be_bytes(); let mut combined = [0u8; 40];