diff --git a/Cargo.lock b/Cargo.lock index 842cc0f..5013c78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1754,6 +1754,13 @@ dependencies = [ "indexmap-nostd", ] +[[package]] +name = "waste-management" +version = "0.0.1" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "windows-core" version = "0.61.0" diff --git a/contracts/waste-management/Cargo.toml b/contracts/waste-management/Cargo.toml new file mode 100644 index 0000000..f1cd61d --- /dev/null +++ b/contracts/waste-management/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "waste-management" +description = "Waste tracking and recycling management contract" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version.workspace = true + +[package.metadata.stellar] +cargo_inherit = true + +[lib] +crate-type = ["cdylib"] +doctest = false + +[dependencies] +soroban-sdk.workspace = true + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/contracts/waste-management/README.md b/contracts/waste-management/README.md new file mode 100644 index 0000000..99e4bbf --- /dev/null +++ b/contracts/waste-management/README.md @@ -0,0 +1,98 @@ +# Waste Management Smart Contract + +A Soroban smart contract for tracking and managing waste recycling on the Stellar blockchain. + +## Overview + +This contract provides a robust system for recording waste entries with comprehensive tracking capabilities including location data, ownership, and confirmation status. + +## Waste Struct + +The main `Waste` struct contains the following fields: + +| Field | Type | Description | +|-------|------|-------------| +| `waste_id` | `u128` | Unique identifier for the waste entry | +| `waste_type` | `WasteType` | Type of waste (Plastic, Glass, Metal, etc.) | +| `weight` | `u128` | Weight of the waste in grams | +| `current_owner` | `Address` | Current owner of the waste | +| `latitude` | `i128` | Latitude coordinate (multiplied by 10^7 for precision) | +| `longitude` | `i128` | Longitude coordinate (multiplied by 10^7 for precision) | +| `recycled_timestamp` | `u64` | Timestamp when the waste was recycled | +| `is_active` | `bool` | Whether the waste entry is active | +| `is_confirmed` | `bool` | Whether the waste has been confirmed | +| `confirmer` | `Address` | Address of the confirmer | + +## Waste Types + +The contract supports the following waste types: + +- `Plastic` +- `Glass` +- `Metal` +- `Paper` +- `Organic` +- `Electronic` +- `Hazardous` +- `Mixed` + +## Builder Pattern + +The contract implements a builder pattern for convenient waste creation: + +```rust +let waste = WasteBuilder::new() + .waste_id(1) + .waste_type(WasteType::Plastic) + .weight(1000) + .current_owner(owner) + .latitude(404850000) // 40.4850000 * 10^7 + .longitude(-740600000) // -74.0600000 * 10^7 + .recycled_timestamp(timestamp) + .is_active(true) + .is_confirmed(false) + .confirmer(confirmer) + .build(); +``` + +## Contract Methods + +### `create_waste` + +Creates a new waste entry with the specified parameters. + +**Parameters:** +- `waste_id`: Unique identifier +- `waste_type`: Type of waste +- `weight`: Weight in grams +- `current_owner`: Owner address (requires auth) +- `latitude`: Latitude coordinate +- `longitude`: Longitude coordinate + +**Returns:** `Waste` struct + +## Location Coordinates + +Latitude and longitude values are stored as `i128` integers multiplied by 10^7 for precision: +- Example: 40.4850000° → 404850000 +- Example: -74.0600000° → -740600000 + +## Building + +```bash +cargo build --manifest-path contracts/waste-management/Cargo.toml --release --target wasm32-unknown-unknown +``` + +## Testing + +```bash +cargo test --manifest-path contracts/waste-management/Cargo.toml +``` + +## Acceptance Criteria + +✅ Struct compiles and can be stored +✅ All fields are properly typed +✅ Builder pattern works correctly +✅ All tests pass +✅ Contract can be built for WASM deployment diff --git a/contracts/waste-management/src/lib.rs b/contracts/waste-management/src/lib.rs new file mode 100644 index 0000000..9a6870d --- /dev/null +++ b/contracts/waste-management/src/lib.rs @@ -0,0 +1,44 @@ +#![no_std] + +mod waste; + +pub use waste::{Waste, WasteBuilder, WasteType}; + +use soroban_sdk::{contract, contractimpl, Address, Env}; + +#[contract] +pub struct WasteManagement; + +#[contractimpl] +impl WasteManagement { + /// Create a new waste entry + pub fn create_waste( + env: Env, + waste_id: u128, + waste_type: WasteType, + weight: u128, + current_owner: Address, + latitude: i128, + longitude: i128, + ) -> Waste { + current_owner.require_auth(); + + let waste = WasteBuilder::new() + .waste_id(waste_id) + .waste_type(waste_type) + .weight(weight) + .current_owner(current_owner.clone()) + .latitude(latitude) + .longitude(longitude) + .recycled_timestamp(env.ledger().timestamp()) + .is_active(true) + .is_confirmed(false) + .confirmer(current_owner) + .build(); + + waste + } +} + +#[cfg(test)] +mod test; diff --git a/contracts/waste-management/src/test.rs b/contracts/waste-management/src/test.rs new file mode 100644 index 0000000..07a9f2b --- /dev/null +++ b/contracts/waste-management/src/test.rs @@ -0,0 +1,169 @@ +#![cfg(test)] +extern crate std; + +use crate::{Waste, WasteBuilder, WasteManagement, WasteManagementClient, WasteType}; +use soroban_sdk::{testutils::Address as _, Address, Env}; +use std::vec; + +#[test] +fn test_waste_struct_creation() { + let env = Env::default(); + let owner = Address::generate(&env); + let confirmer = Address::generate(&env); + + let waste = Waste { + waste_id: 1, + waste_type: WasteType::Plastic, + weight: 1000, + current_owner: owner.clone(), + latitude: 404850000, // 40.4850000 * 10^7 + longitude: -740600000, // -74.0600000 * 10^7 + recycled_timestamp: 1234567890, + is_active: true, + is_confirmed: false, + confirmer: confirmer.clone(), + }; + + assert_eq!(waste.waste_id, 1); + assert_eq!(waste.waste_type, WasteType::Plastic); + assert_eq!(waste.weight, 1000); + assert_eq!(waste.current_owner, owner); + assert_eq!(waste.latitude, 404850000); + assert_eq!(waste.longitude, -740600000); + assert_eq!(waste.recycled_timestamp, 1234567890); + assert_eq!(waste.is_active, true); + assert_eq!(waste.is_confirmed, false); + assert_eq!(waste.confirmer, confirmer); +} + +#[test] +fn test_waste_builder_pattern() { + let env = Env::default(); + let owner = Address::generate(&env); + let confirmer = Address::generate(&env); + + let waste = WasteBuilder::new() + .waste_id(42) + .waste_type(WasteType::Glass) + .weight(2500) + .current_owner(owner.clone()) + .latitude(337700000) // 33.7700000 * 10^7 + .longitude(-842800000) // -84.2800000 * 10^7 + .recycled_timestamp(9876543210) + .is_active(true) + .is_confirmed(true) + .confirmer(confirmer.clone()) + .build(); + + assert_eq!(waste.waste_id, 42); + assert_eq!(waste.waste_type, WasteType::Glass); + assert_eq!(waste.weight, 2500); + assert_eq!(waste.current_owner, owner); + assert_eq!(waste.latitude, 337700000); + assert_eq!(waste.longitude, -842800000); + assert_eq!(waste.recycled_timestamp, 9876543210); + assert_eq!(waste.is_active, true); + assert_eq!(waste.is_confirmed, true); + assert_eq!(waste.confirmer, confirmer); +} + +#[test] +fn test_all_waste_types() { + let env = Env::default(); + let owner = Address::generate(&env); + + let waste_types = vec![ + WasteType::Plastic, + WasteType::Glass, + WasteType::Metal, + WasteType::Paper, + WasteType::Organic, + WasteType::Electronic, + WasteType::Hazardous, + WasteType::Mixed, + ]; + + for (i, waste_type) in waste_types.iter().enumerate() { + let waste = WasteBuilder::new() + .waste_id(i as u128) + .waste_type(waste_type.clone()) + .weight(1000) + .current_owner(owner.clone()) + .latitude(0) + .longitude(0) + .recycled_timestamp(0) + .is_active(true) + .is_confirmed(false) + .confirmer(owner.clone()) + .build(); + + assert_eq!(waste.waste_type, *waste_type); + } +} + +#[test] +fn test_contract_create_waste() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(WasteManagement, ()); + let client = WasteManagementClient::new(&env, &contract_id); + + let owner = Address::generate(&env); + + let waste = client.create_waste( + &123, + &WasteType::Metal, + &5000, + &owner, + &515000000, // 51.5000000 * 10^7 (London) + &-1270000, // -0.1270000 * 10^7 + ); + + assert_eq!(waste.waste_id, 123); + assert_eq!(waste.waste_type, WasteType::Metal); + assert_eq!(waste.weight, 5000); + assert_eq!(waste.current_owner, owner); + assert_eq!(waste.latitude, 515000000); + assert_eq!(waste.longitude, -1270000); + assert_eq!(waste.is_active, true); + assert_eq!(waste.is_confirmed, false); +} + +#[test] +#[should_panic(expected = "waste_id is required")] +fn test_builder_missing_waste_id() { + let env = Env::default(); + let owner = Address::generate(&env); + + WasteBuilder::new() + .waste_type(WasteType::Plastic) + .weight(1000) + .current_owner(owner.clone()) + .latitude(0) + .longitude(0) + .recycled_timestamp(0) + .is_active(true) + .is_confirmed(false) + .confirmer(owner) + .build(); +} + +#[test] +#[should_panic(expected = "waste_type is required")] +fn test_builder_missing_waste_type() { + let env = Env::default(); + let owner = Address::generate(&env); + + WasteBuilder::new() + .waste_id(1) + .weight(1000) + .current_owner(owner.clone()) + .latitude(0) + .longitude(0) + .recycled_timestamp(0) + .is_active(true) + .is_confirmed(false) + .confirmer(owner) + .build(); +} diff --git a/contracts/waste-management/src/waste.rs b/contracts/waste-management/src/waste.rs new file mode 100644 index 0000000..893fc77 --- /dev/null +++ b/contracts/waste-management/src/waste.rs @@ -0,0 +1,157 @@ +use soroban_sdk::{contracttype, Address}; + +/// Enum representing different types of waste +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum WasteType { + Plastic, + Glass, + Metal, + Paper, + Organic, + Electronic, + Hazardous, + Mixed, +} + +/// Main Waste struct with all required fields +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Waste { + /// Unique identifier for the waste entry + pub waste_id: u128, + /// Type of waste (Plastic, Glass, Metal, etc.) + pub waste_type: WasteType, + /// Weight of the waste in grams + pub weight: u128, + /// Current owner of the waste + pub current_owner: Address, + /// Latitude coordinate (multiplied by 10^7 for precision) + pub latitude: i128, + /// Longitude coordinate (multiplied by 10^7 for precision) + pub longitude: i128, + /// Timestamp when the waste was recycled + pub recycled_timestamp: u64, + /// Whether the waste entry is active + pub is_active: bool, + /// Whether the waste has been confirmed + pub is_confirmed: bool, + /// Address of the confirmer + pub confirmer: Address, +} + +/// Builder pattern for constructing Waste instances +#[derive(Clone, Debug)] +pub struct WasteBuilder { + waste_id: Option, + waste_type: Option, + weight: Option, + current_owner: Option
, + latitude: Option, + longitude: Option, + recycled_timestamp: Option, + is_active: Option, + is_confirmed: Option, + confirmer: Option
, +} + +impl WasteBuilder { + /// Create a new WasteBuilder instance + pub fn new() -> Self { + Self { + waste_id: None, + waste_type: None, + weight: None, + current_owner: None, + latitude: None, + longitude: None, + recycled_timestamp: None, + is_active: None, + is_confirmed: None, + confirmer: None, + } + } + + /// Set the waste_id + pub fn waste_id(mut self, waste_id: u128) -> Self { + self.waste_id = Some(waste_id); + self + } + + /// Set the waste_type + pub fn waste_type(mut self, waste_type: WasteType) -> Self { + self.waste_type = Some(waste_type); + self + } + + /// Set the weight + pub fn weight(mut self, weight: u128) -> Self { + self.weight = Some(weight); + self + } + + /// Set the current_owner + pub fn current_owner(mut self, current_owner: Address) -> Self { + self.current_owner = Some(current_owner); + self + } + + /// Set the latitude + pub fn latitude(mut self, latitude: i128) -> Self { + self.latitude = Some(latitude); + self + } + + /// Set the longitude + pub fn longitude(mut self, longitude: i128) -> Self { + self.longitude = Some(longitude); + self + } + + /// Set the recycled_timestamp + pub fn recycled_timestamp(mut self, recycled_timestamp: u64) -> Self { + self.recycled_timestamp = Some(recycled_timestamp); + self + } + + /// Set the is_active flag + pub fn is_active(mut self, is_active: bool) -> Self { + self.is_active = Some(is_active); + self + } + + /// Set the is_confirmed flag + pub fn is_confirmed(mut self, is_confirmed: bool) -> Self { + self.is_confirmed = Some(is_confirmed); + self + } + + /// Set the confirmer + pub fn confirmer(mut self, confirmer: Address) -> Self { + self.confirmer = Some(confirmer); + self + } + + /// Build the Waste instance + /// Panics if any required field is missing + pub fn build(self) -> Waste { + Waste { + waste_id: self.waste_id.expect("waste_id is required"), + waste_type: self.waste_type.expect("waste_type is required"), + weight: self.weight.expect("weight is required"), + current_owner: self.current_owner.expect("current_owner is required"), + latitude: self.latitude.expect("latitude is required"), + longitude: self.longitude.expect("longitude is required"), + recycled_timestamp: self.recycled_timestamp.expect("recycled_timestamp is required"), + is_active: self.is_active.expect("is_active is required"), + is_confirmed: self.is_confirmed.expect("is_confirmed is required"), + confirmer: self.confirmer.expect("confirmer is required"), + } + } +} + +impl Default for WasteBuilder { + fn default() -> Self { + Self::new() + } +}