diff --git a/soroban/contracts/peer-to-peer-energy-sharing/src/lib.rs b/soroban/contracts/peer-to-peer-energy-sharing/src/lib.rs index f555366..091ee5d 100644 --- a/soroban/contracts/peer-to-peer-energy-sharing/src/lib.rs +++ b/soroban/contracts/peer-to-peer-energy-sharing/src/lib.rs @@ -5,7 +5,7 @@ mod sharing; mod utils; #[cfg(test)] -mod test; +mod tests; use soroban_sdk::{contract, contractimpl, Address, Env, Map, Vec}; diff --git a/soroban/contracts/peer-to-peer-energy-sharing/src/test.rs b/soroban/contracts/peer-to-peer-energy-sharing/src/test.rs deleted file mode 100644 index 91c943e..0000000 --- a/soroban/contracts/peer-to-peer-energy-sharing/src/test.rs +++ /dev/null @@ -1 +0,0 @@ -// placeholder tests \ No newline at end of file diff --git a/soroban/contracts/peer-to-peer-energy-sharing/src/tests/agreement.rs b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/agreement.rs new file mode 100644 index 0000000..b9801cc --- /dev/null +++ b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/agreement.rs @@ -0,0 +1,95 @@ +#![cfg(test)] + +use crate::tests::utils::*; +use crate::utils::*; +use crate::{PeerToPeerEnergySharing, PeerToPeerEnergySharingClient}; +use soroban_sdk::{testutils::Address as _, Address, Env}; + +#[test] +fn test_valid_agreement_creation() { + let setup = TestSetup::new(); + + let agreement_id = setup.create_test_agreement(); + let agreement = setup.client.get_agreement(&agreement_id); + + // Verify data integrity + assert_eq!(agreement.provider, setup.provider); + assert_eq!(agreement.consumer, setup.consumer); + assert_eq!(agreement.energy_amount_kwh, 100); + assert_eq!(agreement.price_per_kwh, 50); + assert_eq!(agreement.total_amount, 5000); + assert_eq!(agreement.status, AgreementStatus::Active); +} + +#[test] +fn test_agreement_invalid_inputs() { + let setup = TestSetup::new(); + + // Zero energy amount + let result = setup.client.try_create_agreement( + &setup.provider, + &setup.consumer, + &0u64, + &50u64, + &setup.get_future_deadline(), + ); + assert_eq!(result, Err(Ok(SharingError::InvalidInput))); + + // Zero price + let result = setup.client.try_create_agreement( + &setup.provider, + &setup.consumer, + &100u64, + &0u64, + &setup.get_future_deadline(), + ); + assert_eq!(result, Err(Ok(SharingError::InvalidInput))); + + // Invalid deadline + let result = + setup + .client + .try_create_agreement(&setup.provider, &setup.consumer, &100u64, &50u64, &0u64); + assert_eq!(result, Err(Ok(SharingError::InvalidInput))); +} + +#[test] +fn test_agreement_authorization_failures() { + let setup = TestSetup::new(); + let unregistered = Address::generate(&setup.env); + + // Unregistered provider + let result = setup.client.try_create_agreement( + &unregistered, + &setup.consumer, + &100u64, + &50u64, + &setup.get_future_deadline(), + ); + assert_eq!(result, Err(Ok(SharingError::ProsumerNotRegistered))); + + // Self-sharing not allowed + let result = setup.client.try_create_agreement( + &setup.provider, + &setup.provider, + &100u64, + &50u64, + &setup.get_future_deadline(), + ); + assert_eq!(result, Err(Ok(SharingError::SelfSharingNotAllowed))); + + // Contract not initialized + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(PeerToPeerEnergySharing, ()); + let client = PeerToPeerEnergySharingClient::new(&env, &contract_id); + + let result = client.try_create_agreement( + &Address::generate(&env), + &Address::generate(&env), + &100u64, + &50u64, + &(env.ledger().timestamp() + 86400), + ); + assert_eq!(result, Err(Ok(SharingError::NotInitialized))); +} diff --git a/soroban/contracts/peer-to-peer-energy-sharing/src/tests/delivery.rs b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/delivery.rs new file mode 100644 index 0000000..442eb57 --- /dev/null +++ b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/delivery.rs @@ -0,0 +1,81 @@ +#![cfg(test)] + +use crate::tests::utils::*; +use crate::utils::*; + +#[test] +fn test_valid_energy_delivery() { + let setup = TestSetup::new(); + let agreement_id = setup.create_test_agreement(); + + let transaction_id = + setup + .client + .deliver_energy(&agreement_id, &100u64, &1500u64, &setup.provider); + + let transaction = setup.client.get_transaction(&transaction_id); + assert_eq!(transaction.energy_delivered_kwh, 100); + assert_eq!(transaction.meter_reading, 1500); + assert_eq!(transaction.payment_amount, 5000); + assert_eq!(transaction.status, TransactionStatus::Delivered); + + // Verify agreement status updated + let agreement = setup.client.get_agreement(&agreement_id); + assert_eq!(agreement.status, AgreementStatus::Delivered); +} + +#[test] +fn test_delivery_validation_failures() { + let setup = TestSetup::new(); + let agreement_id = setup.create_test_agreement(); + + // Unauthorized provider + let result = setup + .client + .try_deliver_energy(&agreement_id, &100u64, &1500u64, &setup.consumer); + assert_eq!(result, Err(Ok(SharingError::NotAuthorized))); + + // Excessive energy delivery + let result = setup + .client + .try_deliver_energy(&agreement_id, &150u64, &2250u64, &setup.provider); + assert_eq!(result, Err(Ok(SharingError::InsufficientEnergy))); + + // Non-existent agreement + let result = setup + .client + .try_deliver_energy(&999u64, &100u64, &1500u64, &setup.provider); + assert_eq!(result, Err(Ok(SharingError::AgreementNotFound))); +} + +#[test] +fn test_delivery_deadline_enforcement() { + let setup = TestSetup::new(); + let agreement_id = setup.create_test_agreement(); + + // Advance time past deadline + setup.advance_ledger_time(90000); + + let result = setup + .client + .try_deliver_energy(&agreement_id, &100u64, &1500u64, &setup.provider); + assert_eq!(result, Err(Ok(SharingError::DeliveryDeadlinePassed))); +} + +#[test] +fn test_partial_delivery_with_meter_verification() { + let setup = TestSetup::new(); + let agreement_id = setup.create_test_agreement(); + + // Partial delivery with specific meter reading + let meter_reading = 2750u64; + let transaction_id = + setup + .client + .deliver_energy(&agreement_id, &75u64, &meter_reading, &setup.provider); + + let transaction = setup.client.get_transaction(&transaction_id); + assert_eq!(transaction.energy_delivered_kwh, 75); + assert_eq!(transaction.meter_reading, meter_reading); + assert_eq!(transaction.payment_amount, 3750); // 75 * 50 +} diff --git a/soroban/contracts/peer-to-peer-energy-sharing/src/tests/mod.rs b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/mod.rs new file mode 100644 index 0000000..19c8f03 --- /dev/null +++ b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/mod.rs @@ -0,0 +1,6 @@ +#![cfg(test)] + +pub mod agreement; +pub mod delivery; +pub mod payment; +pub mod utils; diff --git a/soroban/contracts/peer-to-peer-energy-sharing/src/tests/payment.rs b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/payment.rs new file mode 100644 index 0000000..d316f7e --- /dev/null +++ b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/payment.rs @@ -0,0 +1,143 @@ +#![cfg(test)] + +use crate::tests::utils::*; +use crate::utils::*; + +#[test] +fn test_successful_payment_settlement() { + let setup = TestSetup::new(); + let agreement_id = setup.create_test_agreement(); + + // Deliver energy + let transaction_id = + setup + .client + .deliver_energy(&agreement_id, &100u64, &1500u64, &setup.provider); + + // Check balances before settlement + let consumer_balance_before = + get_token_balance(&setup.env, &setup.token_contract, &setup.consumer); + let provider_balance_before = + get_token_balance(&setup.env, &setup.token_contract, &setup.provider); + + // Settle payment + setup + .client + .settle_payment(&transaction_id, &setup.consumer); + + // Verify payment transferred correctly + let consumer_balance_after = + get_token_balance(&setup.env, &setup.token_contract, &setup.consumer); + let provider_balance_after = + get_token_balance(&setup.env, &setup.token_contract, &setup.provider); + + assert_eq!(consumer_balance_after, consumer_balance_before - 5000); + assert_eq!(provider_balance_after, provider_balance_before + 5000); + + // Verify transaction and agreement status updated + let transaction = setup.client.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Settled); + assert!(transaction.settled_at.is_some()); + + let agreement = setup.client.get_agreement(&agreement_id); + assert_eq!(agreement.status, AgreementStatus::Settled); +} + +#[test] +fn test_payment_authorization_failures() { + let setup = TestSetup::new(); + let agreement_id = setup.create_test_agreement(); + + let transaction_id = + setup + .client + .deliver_energy(&agreement_id, &100u64, &1500u64, &setup.provider); + + // Unauthorized settlement attempt + let result = setup + .client + .try_settle_payment(&transaction_id, &setup.prosumer3); + assert_eq!(result, Err(Ok(SharingError::NotAuthorized))); + + // Non-existent transaction + let result = setup.client.try_settle_payment(&999, &setup.consumer); + assert_eq!(result, Err(Ok(SharingError::TransactionNotFound))); + + // Settle successfully first time + setup + .client + .settle_payment(&transaction_id, &setup.consumer); + + // Duplicate settlement attempt + let result = setup + .client + .try_settle_payment(&transaction_id, &setup.consumer); + assert_eq!(result, Err(Ok(SharingError::TransactionAlreadySettled))); +} + +#[test] +fn test_payment_accuracy_partial_delivery() { + let setup = TestSetup::new(); + let agreement_id = setup.create_custom_agreement( + &setup.provider, + &setup.consumer, + 250, + 120, + setup.get_future_deadline(), + ); + + // Partial delivery: 200 out of 250 kWh at 120 per kWh + let transaction_id = + setup + .client + .deliver_energy(&agreement_id, &200u64, &3000u64, &setup.provider); + + let consumer_balance_before = + get_token_balance(&setup.env, &setup.token_contract, &setup.consumer); + let provider_balance_before = + get_token_balance(&setup.env, &setup.token_contract, &setup.provider); + + setup + .client + .settle_payment(&transaction_id, &setup.provider); + + let consumer_balance_after = + get_token_balance(&setup.env, &setup.token_contract, &setup.consumer); + let provider_balance_after = + get_token_balance(&setup.env, &setup.token_contract, &setup.provider); + + // Should pay exactly: 200 * 120 = 24000 + assert_eq!(consumer_balance_after, consumer_balance_before - 24000); + assert_eq!(provider_balance_after, provider_balance_before + 24000); +} + +#[test] +fn test_transaction_history_tracking() { + let setup = TestSetup::new(); + let agreement_id = setup.create_test_agreement(); + + // Initially empty + let history = setup.client.get_transaction_history(&setup.provider); + assert_eq!(history.len(), 0); + + // Create transaction + let transaction_id = + setup + .client + .deliver_energy(&agreement_id, &100u64, &1500u64, &setup.provider); + + // Verify transaction appears in both provider and consumer history + let provider_history = setup.client.get_transaction_history(&setup.provider); + let consumer_history = setup.client.get_transaction_history(&setup.consumer); + + assert_eq!(provider_history.len(), 1); + assert_eq!(consumer_history.len(), 1); + assert_eq!( + provider_history.get(0).unwrap().transaction_id, + transaction_id + ); + assert_eq!( + consumer_history.get(0).unwrap().transaction_id, + transaction_id + ); +} diff --git a/soroban/contracts/peer-to-peer-energy-sharing/src/tests/utils.rs b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/utils.rs new file mode 100644 index 0000000..10e33c3 --- /dev/null +++ b/soroban/contracts/peer-to-peer-energy-sharing/src/tests/utils.rs @@ -0,0 +1,115 @@ +#![cfg(test)] + +use crate::{utils::*, PeerToPeerEnergySharing, PeerToPeerEnergySharingClient}; +use soroban_sdk::{ + testutils::{Address as _, Ledger}, + Address, Env, +}; + +pub struct TestSetup { + pub env: Env, + pub client: PeerToPeerEnergySharingClient<'static>, + pub admin: Address, + pub token_contract: Address, + pub provider: Address, + pub consumer: Address, + pub prosumer3: Address, +} + +impl TestSetup { + pub fn new() -> Self { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let token_contract = create_test_token(&env, &admin); + let provider = Address::generate(&env); + let consumer = Address::generate(&env); + let prosumer3 = Address::generate(&env); + + let contract_id = env.register(PeerToPeerEnergySharing, ()); + let client = PeerToPeerEnergySharingClient::new(&env, &contract_id); + + // Initialize contract + client.initialize(&admin, &token_contract); + + // Register prosumers + client.register_prosumer(&provider); + client.register_prosumer(&consumer); + client.register_prosumer(&prosumer3); + + // Mint tokens for testing payments + mint_tokens(&env, &token_contract, &admin, &consumer, 10_000_000); + mint_tokens(&env, &token_contract, &admin, &prosumer3, 5_000_000); + + Self { + env, + client, + admin, + token_contract, + provider, + consumer, + prosumer3, + } + } + + pub fn advance_ledger_time(&self, seconds: u64) { + self.env + .ledger() + .set_timestamp(self.env.ledger().timestamp() + seconds); + } + + pub fn get_future_deadline(&self) -> u64 { + self.env.ledger().timestamp() + 86400 // 1 day from now + } + + pub fn create_test_agreement(&self) -> u64 { + self.client.create_agreement( + &self.provider, + &self.consumer, + &100u64, // 100 kWh + &50u64, // 50 units per kWh + &self.get_future_deadline(), + ) + } + + pub fn create_custom_agreement( + &self, + provider: &Address, + consumer: &Address, + energy_amount: u64, + price_per_kwh: u64, + deadline: u64, + ) -> u64 { + self.client.create_agreement( + provider, + consumer, + &energy_amount, + &price_per_kwh, + &deadline, + ) + } +} + +pub fn create_test_token(env: &Env, admin: &Address) -> Address { + env.register_stellar_asset_contract_v2(admin.clone()) + .address() +} + +pub fn mint_tokens( + env: &Env, + token_address: &Address, + _admin: &Address, + to: &Address, + amount: i128, +) { + use soroban_sdk::token; + let token = token::StellarAssetClient::new(env, token_address); + token.mint(to, &amount); +} + +pub fn get_token_balance(env: &Env, token_address: &Address, account: &Address) -> i128 { + use soroban_sdk::token; + let token = token::Client::new(env, token_address); + token.balance(account) +}