diff --git a/backend/src/services/mod.rs b/backend/src/services/mod.rs index 78bbc58..a641267 100644 --- a/backend/src/services/mod.rs +++ b/backend/src/services/mod.rs @@ -26,3 +26,4 @@ pub mod compilation; pub mod dependency_analyzer; pub mod contract_call_logger; pub mod compliance; +pub mod rollback; diff --git a/backend/src/services/rollback.rs b/backend/src/services/rollback.rs new file mode 100644 index 0000000..3914d2a --- /dev/null +++ b/backend/src/services/rollback.rs @@ -0,0 +1,154 @@ +/// Contract Rollback Service +/// +/// Handles contract state rollback and transaction reversal capability +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ContractState { + pub contract_id: String, + pub storage: HashMap>, + pub ledger_seq: u64, + pub timestamp: i64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RollbackCheckpoint { + pub checkpoint_id: String, + pub states: HashMap, + pub created_at: i64, +} + +#[derive(Debug, Clone)] +pub struct RollbackManager { + checkpoints: HashMap, + active_transactions: HashMap>, +} + +impl RollbackManager { + pub fn new() -> Self { + Self { + checkpoints: HashMap::new(), + active_transactions: HashMap::new(), + } + } + + /// Create a checkpoint of current contract states + pub fn create_checkpoint( + &mut self, + contract_ids: Vec, + states: HashMap, + ) -> Result { + let checkpoint_id = format!("cp_{}", chrono::Utc::now().timestamp_millis()); + + let checkpoint = RollbackCheckpoint { + checkpoint_id: checkpoint_id.clone(), + states, + created_at: chrono::Utc::now().timestamp(), + }; + + self.checkpoints.insert(checkpoint_id.clone(), checkpoint); + Ok(checkpoint_id) + } + + /// Rollback contract to a specific checkpoint + pub fn rollback_to_checkpoint(&self, checkpoint_id: &str) -> Result { + self.checkpoints + .get(checkpoint_id) + .cloned() + .ok_or_else(|| format!("Checkpoint {} not found", checkpoint_id)) + } + + /// Start transaction tracking for rollback capability + pub fn begin_transaction(&mut self, tx_id: String) -> Result<(), String> { + self.active_transactions.insert(tx_id, vec![]); + Ok(()) + } + + /// Record a state change in the transaction + pub fn record_state_change(&mut self, tx_id: &str, contract_id: String) -> Result<(), String> { + if let Some(tx) = self.active_transactions.get_mut(tx_id) { + tx.push(contract_id); + Ok(()) + } else { + Err(format!("Transaction {} not found", tx_id)) + } + } + + /// Commit transaction + pub fn commit_transaction(&mut self, tx_id: &str) -> Result<(), String> { + self.active_transactions.remove(tx_id); + Ok(()) + } + + /// Rollback transaction + pub fn rollback_transaction(&mut self, tx_id: &str) -> Result, String> { + self.active_transactions + .remove(tx_id) + .ok_or_else(|| format!("Transaction {} not found", tx_id)) + } + + /// Get list of all checkpoints + pub fn list_checkpoints(&self) -> Vec { + self.checkpoints.keys().cloned().collect() + } + + /// Validate rollback safety + pub fn validate_rollback(&self, checkpoint_id: &str) -> Result { + if !self.checkpoints.contains_key(checkpoint_id) { + return Err(format!("Checkpoint {} not found", checkpoint_id)); + } + + // Validate checkpoint integrity + if let Some(checkpoint) = self.checkpoints.get(checkpoint_id) { + Ok(!checkpoint.states.is_empty()) + } else { + Ok(false) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_checkpoint() { + let mut manager = RollbackManager::new(); + let states = HashMap::new(); + let result = manager.create_checkpoint(vec!["contract1".to_string()], states); + assert!(result.is_ok()); + } + + #[test] + fn test_begin_transaction() { + let mut manager = RollbackManager::new(); + let result = manager.begin_transaction("tx1".to_string()); + assert!(result.is_ok()); + } + + #[test] + fn test_record_state_change() { + let mut manager = RollbackManager::new(); + manager.begin_transaction("tx1".to_string()).unwrap(); + let result = manager.record_state_change("tx1", "contract1".to_string()); + assert!(result.is_ok()); + } + + #[test] + fn test_commit_transaction() { + let mut manager = RollbackManager::new(); + manager.begin_transaction("tx1".to_string()).unwrap(); + let result = manager.commit_transaction("tx1"); + assert!(result.is_ok()); + } + + #[test] + fn test_rollback_transaction() { + let mut manager = RollbackManager::new(); + manager.begin_transaction("tx1".to_string()).unwrap(); + manager.record_state_change("tx1", "contract1".to_string()).unwrap(); + let result = manager.rollback_transaction("tx1"); + assert!(result.is_ok()); + } +} diff --git a/contracts/governance/Cargo.toml b/contracts/governance/Cargo.toml new file mode 100644 index 0000000..16f1c8e --- /dev/null +++ b/contracts/governance/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "governance" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = { version = "21.0.0", features = ["alloc"] } + +[profile.release] +opt-level = "z" +overflow-checks = false +lto = true +codegen-units = 1 +strip = true diff --git a/contracts/governance/src/lib.rs b/contracts/governance/src/lib.rs new file mode 100644 index 0000000..d269347 --- /dev/null +++ b/contracts/governance/src/lib.rs @@ -0,0 +1,227 @@ +#![no_std] +#![allow(deprecated)] +use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, Map}; + +#[contracttype] +#[derive(Clone)] +struct Proposal { + id: u64, + title: String, + description: String, + proposer: Address, + votes_for: i128, + votes_against: i128, + created_at: u64, + deadline: u64, + executed: bool, +} + +#[contracttype] +#[derive(Clone)] +struct Vote { + voter: Address, + proposal_id: u64, + amount: i128, + direction: bool, // true = for, false = against +} + +#[contracttype] +enum DataKey { + Admin, + TotalSupply, + Balance(Address), + ProposalCounter, + Proposal(u64), + Vote(Address, u64), + VotingPower(Address), +} + +/// DAO Governance Contract with voting capabilities +#[contract] +#[derive(Default)] +pub struct Governance; + +#[contractimpl] +impl Governance { + /// Initialize governance with admin and initial token supply + pub fn initialize(env: Env, admin: Address, initial_supply: i128) { + let storage = env.storage().instance(); + storage.set(&DataKey::Admin, &admin); + storage.set(&DataKey::TotalSupply, &initial_supply); + storage.set(&DataKey::ProposalCounter, &0u64); + storage.set(&DataKey::Balance(admin.clone()), &initial_supply); + } + + /// Get voting power of an address + pub fn voting_power(env: Env, account: Address) -> i128 { + let storage = env.storage().instance(); + storage + .get(&DataKey::VotingPower(account.clone())) + .unwrap_or(0) + } + + /// Create a new proposal + pub fn create_proposal( + env: Env, + proposer: Address, + title: String, + description: String, + deadline: u64, + ) -> Result { + let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap(); + proposer.require_auth(); + + let storage = env.storage().instance(); + let mut counter: u64 = storage.get(&DataKey::ProposalCounter).unwrap_or(0); + counter += 1; + + let proposal = Proposal { + id: counter, + title, + description, + proposer, + votes_for: 0, + votes_against: 0, + created_at: env.ledger().timestamp(), + deadline, + executed: false, + }; + + storage.set(&DataKey::Proposal(counter), &proposal); + storage.set(&DataKey::ProposalCounter, &counter); + + env.events() + .publish((symbol_short!("prop"), counter), proposal.title); + + Ok(counter) + } + + /// Cast vote on a proposal + pub fn vote( + env: Env, + voter: Address, + proposal_id: u64, + amount: i128, + direction: bool, + ) -> Result<(), &'static str> { + voter.require_auth(); + + let storage = env.storage().instance(); + + // Get proposal + let mut proposal: Proposal = storage + .get(&DataKey::Proposal(proposal_id)) + .ok_or("Proposal not found")?; + + if env.ledger().timestamp() > proposal.deadline { + return Err("Voting period ended"); + } + + if amount <= 0 { + return Err("Vote amount must be positive"); + } + + // Check voting power + let voting_power: i128 = storage + .get(&DataKey::VotingPower(voter.clone())) + .unwrap_or(0); + + if voting_power < amount { + return Err("Insufficient voting power"); + } + + // Record vote + let vote = Vote { + voter: voter.clone(), + proposal_id, + amount, + direction, + }; + + storage.set(&DataKey::Vote(voter.clone(), proposal_id), &vote); + + // Update proposal vote counts + if direction { + proposal.votes_for += amount; + } else { + proposal.votes_against += amount; + } + + storage.set(&DataKey::Proposal(proposal_id), &proposal); + + env.events() + .publish((symbol_short!("vote"), proposal_id), amount); + + Ok(()) + } + + /// Execute passed proposal + pub fn execute_proposal(env: Env, proposal_id: u64) -> Result { + let storage = env.storage().instance(); + + let mut proposal: Proposal = storage + .get(&DataKey::Proposal(proposal_id)) + .ok_or("Proposal not found")?; + + if env.ledger().timestamp() <= proposal.deadline { + return Err("Voting period not ended"); + } + + if proposal.executed { + return Err("Proposal already executed"); + } + + let passed = proposal.votes_for > proposal.votes_against; + + if passed { + proposal.executed = true; + storage.set(&DataKey::Proposal(proposal_id), &proposal); + env.events().publish((symbol_short!("exec"), proposal_id), true); + } + + Ok(passed) + } + + /// Get proposal details + pub fn get_proposal(env: Env, proposal_id: u64) -> Result { + env.storage() + .instance() + .get(&DataKey::Proposal(proposal_id)) + .ok_or("Proposal not found") + } + + /// Delegate voting power + pub fn delegate_voting_power( + env: Env, + from: Address, + to: Address, + amount: i128, + ) -> Result<(), &'static str> { + from.require_auth(); + + if amount <= 0 { + return Err("Amount must be positive"); + } + + let storage = env.storage().instance(); + let current_power: i128 = storage + .get(&DataKey::VotingPower(from.clone())) + .unwrap_or(0); + + if current_power < amount { + return Err("Insufficient power to delegate"); + } + + // Update from power + storage.set(&DataKey::VotingPower(from.clone()), &(current_power - amount)); + + // Update to power + let to_power: i128 = storage.get(&DataKey::VotingPower(to.clone())).unwrap_or(0); + storage.set(&DataKey::VotingPower(to.clone()), &(to_power + amount)); + + env.events() + .publish((symbol_short!("deleg"), from), amount); + + Ok(()) + } +} diff --git a/contracts/insurance/Cargo.toml b/contracts/insurance/Cargo.toml new file mode 100644 index 0000000..3595211 --- /dev/null +++ b/contracts/insurance/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "insurance" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = { version = "21.0.0", features = ["alloc"] } + +[profile.release] +opt-level = "z" +overflow-checks = false +lto = true +codegen-units = 1 +strip = true diff --git a/contracts/insurance/src/lib.rs b/contracts/insurance/src/lib.rs new file mode 100644 index 0000000..1fa0efc --- /dev/null +++ b/contracts/insurance/src/lib.rs @@ -0,0 +1,248 @@ +#![no_std] +#![allow(deprecated)] +use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env}; + +#[contracttype] +#[derive(Clone)] +struct InsurancePolicy { + policy_id: u64, + holder: Address, + contract_address: Address, + coverage_amount: i128, + premium: i128, + active: bool, + created_at: u64, + expires_at: u64, +} + +#[contracttype] +#[derive(Clone)] +struct Claim { + claim_id: u64, + policy_id: u64, + amount: i128, + status: u32, // 0 = pending, 1 = approved, 2 = rejected + failure_reason: String, + created_at: u64, +} + +#[contracttype] +enum DataKey { + Admin, + TotalReserves, + PolicyCounter, + ClaimCounter, + Policy(u64), + Claim(u64), + PolicyBalance(Address), +} + +/// Insurance Contract for smart contract failures +#[contract] +#[derive(Default)] +pub struct Insurance; + +#[contractimpl] +impl Insurance { + /// Initialize insurance contract + pub fn initialize(env: Env, admin: Address, initial_reserves: i128) { + let storage = env.storage().instance(); + storage.set(&DataKey::Admin, &admin); + storage.set(&DataKey::TotalReserves, &initial_reserves); + storage.set(&DataKey::PolicyCounter, &0u64); + storage.set(&DataKey::ClaimCounter, &0u64); + } + + /// Create an insurance policy for a contract + pub fn create_policy( + env: Env, + holder: Address, + contract_address: Address, + coverage_amount: i128, + premium: i128, + duration_days: u64, + ) -> Result { + holder.require_auth(); + + if coverage_amount <= 0 || premium <= 0 { + return Err("Amounts must be positive"); + } + + let storage = env.storage().instance(); + let mut counter: u64 = storage.get(&DataKey::PolicyCounter).unwrap_or(0); + counter += 1; + + let policy = InsurancePolicy { + policy_id: counter, + holder: holder.clone(), + contract_address, + coverage_amount, + premium, + active: true, + created_at: env.ledger().timestamp(), + expires_at: env.ledger().timestamp() + (duration_days * 86400), + }; + + storage.set(&DataKey::Policy(counter), &policy); + storage.set(&DataKey::PolicyCounter, &counter); + + // Update balance + let balance: i128 = storage + .get(&DataKey::PolicyBalance(holder.clone())) + .unwrap_or(0); + storage.set(&DataKey::PolicyBalance(holder), &(balance + premium)); + + env.events() + .publish((symbol_short!("policy"), counter), coverage_amount); + + Ok(counter) + } + + /// File a claim for contract failure + pub fn file_claim( + env: Env, + policy_id: u64, + amount: i128, + failure_reason: String, + ) -> Result { + let storage = env.storage().instance(); + + // Verify policy exists and is active + let policy: InsurancePolicy = storage + .get(&DataKey::Policy(policy_id)) + .ok_or("Policy not found")?; + + if !policy.active { + return Err("Policy is not active"); + } + + if env.ledger().timestamp() > policy.expires_at { + return Err("Policy expired"); + } + + if amount <= 0 || amount > policy.coverage_amount { + return Err("Invalid claim amount"); + } + + policy.holder.require_auth(); + + let mut counter: u64 = storage.get(&DataKey::ClaimCounter).unwrap_or(0); + counter += 1; + + let claim = Claim { + claim_id: counter, + policy_id, + amount, + status: 0, // pending + failure_reason, + created_at: env.ledger().timestamp(), + }; + + storage.set(&DataKey::Claim(counter), &claim); + storage.set(&DataKey::ClaimCounter, &counter); + + env.events() + .publish((symbol_short!("claim"), counter), amount); + + Ok(counter) + } + + /// Approve a claim (admin only) + pub fn approve_claim(env: Env, claim_id: u64) -> Result<(), &'static str> { + let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + let storage = env.storage().instance(); + + let mut claim: Claim = storage + .get(&DataKey::Claim(claim_id)) + .ok_or("Claim not found")?; + + if claim.status != 0 { + return Err("Claim already processed"); + } + + claim.status = 1; // approved + storage.set(&DataKey::Claim(claim_id), &claim); + + // Deduct from reserves + let reserves: i128 = storage.get(&DataKey::TotalReserves).unwrap_or(0); + storage.set(&DataKey::TotalReserves, &(reserves - claim.amount)); + + env.events() + .publish((symbol_short!("apprv"), claim_id), claim.amount); + + Ok(()) + } + + /// Reject a claim (admin only) + pub fn reject_claim(env: Env, claim_id: u64) -> Result<(), &'static str> { + let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + let storage = env.storage().instance(); + + let mut claim: Claim = storage + .get(&DataKey::Claim(claim_id)) + .ok_or("Claim not found")?; + + if claim.status != 0 { + return Err("Claim already processed"); + } + + claim.status = 2; // rejected + storage.set(&DataKey::Claim(claim_id), &claim); + + env.events() + .publish((symbol_short!("rejct"), claim_id), 0); + + Ok(()) + } + + /// Get policy details + pub fn get_policy(env: Env, policy_id: u64) -> Result { + env.storage() + .instance() + .get(&DataKey::Policy(policy_id)) + .ok_or("Policy not found") + } + + /// Get claim details + pub fn get_claim(env: Env, claim_id: u64) -> Result { + env.storage() + .instance() + .get(&DataKey::Claim(claim_id)) + .ok_or("Claim not found") + } + + /// Get total reserves + pub fn get_reserves(env: Env) -> i128 { + env.storage() + .instance() + .get(&DataKey::TotalReserves) + .unwrap_or(0) + } + + /// Renew policy + pub fn renew_policy(env: Env, policy_id: u64, extension_days: u64) -> Result<(), &'static str> { + let storage = env.storage().instance(); + + let mut policy: InsurancePolicy = storage + .get(&DataKey::Policy(policy_id)) + .ok_or("Policy not found")?; + + policy.holder.require_auth(); + + if !policy.active { + return Err("Policy is not active"); + } + + policy.expires_at += extension_days * 86400; + storage.set(&DataKey::Policy(policy_id), &policy); + + env.events() + .publish((symbol_short!("renew"), policy_id), extension_days as i128); + + Ok(()) + } +} diff --git a/contracts/oracle/Cargo.toml b/contracts/oracle/Cargo.toml new file mode 100644 index 0000000..a393206 --- /dev/null +++ b/contracts/oracle/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "oracle" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = { version = "21.0.0", features = ["alloc"] } + +[profile.release] +opt-level = "z" +overflow-checks = false +lto = true +codegen-units = 1 +strip = true diff --git a/contracts/oracle/src/lib.rs b/contracts/oracle/src/lib.rs new file mode 100644 index 0000000..ae29a24 --- /dev/null +++ b/contracts/oracle/src/lib.rs @@ -0,0 +1,227 @@ +#![no_std] +#![allow(deprecated)] +use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, Vec}; + +#[contracttype] +#[derive(Clone)] +struct PriceData { + symbol: String, + price: i128, + timestamp: u64, + source: String, +} + +#[contracttype] +#[derive(Clone)] +struct DataSource { + source_id: u64, + address: Address, + name: String, + active: bool, + last_update: u64, +} + +#[contracttype] +enum DataKey { + Admin, + SourceCounter, + DataSource(u64), + Price(String), + PriceHistory(String, u64), + SourceWhitelist(Address), +} + +/// Oracle Contract with multiple data sources +#[contract] +#[derive(Default)] +pub struct Oracle; + +#[contractimpl] +impl Oracle { + /// Initialize oracle contract + pub fn initialize(env: Env, admin: Address) { + let storage = env.storage().instance(); + storage.set(&DataKey::Admin, &admin); + storage.set(&DataKey::SourceCounter, &0u64); + } + + /// Register a new data source + pub fn register_source(env: Env, address: Address, name: String) -> Result { + let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + let storage = env.storage().instance(); + let mut counter: u64 = storage.get(&DataKey::SourceCounter).unwrap_or(0); + counter += 1; + + let source = DataSource { + source_id: counter, + address: address.clone(), + name, + active: true, + last_update: env.ledger().timestamp(), + }; + + storage.set(&DataKey::DataSource(counter), &source); + storage.set(&DataKey::SourceCounter, &counter); + storage.set(&DataKey::SourceWhitelist(address), &true); + + env.events() + .publish((symbol_short!("source"), counter), 1); + + Ok(counter) + } + + /// Submit price data from a source + pub fn submit_price( + env: Env, + symbol: String, + price: i128, + source_name: String, + ) -> Result<(), &'static str> { + let sender = env.current_contract_address(); + + let storage = env.storage().instance(); + + // Verify sender is whitelisted + let is_whitelisted: bool = storage + .get(&DataKey::SourceWhitelist(sender.clone())) + .unwrap_or(false); + + if !is_whitelisted { + return Err("Source not whitelisted"); + } + + if price <= 0 { + return Err("Price must be positive"); + } + + let timestamp = env.ledger().timestamp(); + + // Store latest price + let price_data = PriceData { + symbol: symbol.clone(), + price, + timestamp, + source: source_name, + }; + + storage.set(&DataKey::Price(symbol.clone()), &price_data); + + // Store in history + storage.set( + &DataKey::PriceHistory(symbol.clone(), timestamp), + &price, + ); + + env.events() + .publish((symbol_short!("price"), symbol), price); + + Ok(()) + } + + /// Get latest price for a symbol + pub fn get_price(env: Env, symbol: String) -> Result { + env.storage() + .instance() + .get::<_, PriceData>(&DataKey::Price(symbol)) + .map(|data| data.price) + .ok_or("Price not found") + } + + /// Get price data with source info + pub fn get_price_data(env: Env, symbol: String) -> Result { + env.storage() + .instance() + .get(&DataKey::Price(symbol)) + .ok_or("Price not found") + } + + /// Aggregate prices from multiple sources (average) + pub fn aggregate_price(env: Env, symbol: String, num_sources: u64) -> Result { + if num_sources == 0 { + return Err("num_sources must be positive"); + } + + let storage = env.storage().instance(); + + // For MVP, return the latest price + // In production, this would aggregate from multiple sources + let price_data: PriceData = storage + .get(&DataKey::Price(symbol)) + .ok_or("Price not found")?; + + Ok(price_data.price) + } + + /// Get data source details + pub fn get_source(env: Env, source_id: u64) -> Result { + env.storage() + .instance() + .get(&DataKey::DataSource(source_id)) + .ok_or("Source not found") + } + + /// Deactivate a data source + pub fn deactivate_source(env: Env, source_id: u64) -> Result<(), &'static str> { + let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + let storage = env.storage().instance(); + + let mut source: DataSource = storage + .get(&DataKey::DataSource(source_id)) + .ok_or("Source not found")?; + + source.active = false; + storage.set(&DataKey::DataSource(source_id), &source); + + // Remove from whitelist + storage.set(&DataKey::SourceWhitelist(source.address), &false); + + env.events() + .publish((symbol_short!("deact"), source_id), 0); + + Ok(()) + } + + /// Activate a data source + pub fn activate_source(env: Env, source_id: u64) -> Result<(), &'static str> { + let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + let storage = env.storage().instance(); + + let mut source: DataSource = storage + .get(&DataKey::DataSource(source_id)) + .ok_or("Source not found")?; + + source.active = true; + source.last_update = env.ledger().timestamp(); + storage.set(&DataKey::DataSource(source_id), &source); + + // Add to whitelist + storage.set(&DataKey::SourceWhitelist(source.address), &true); + + env.events() + .publish((symbol_short!("actv"), source_id), 1); + + Ok(()) + } + + /// Validate price data freshness + pub fn validate_price_freshness( + env: Env, + symbol: String, + max_age_seconds: u64, + ) -> Result { + let storage = env.storage().instance(); + + let price_data: PriceData = storage + .get(&DataKey::Price(symbol)) + .ok_or("Price not found")?; + + let age = env.ledger().timestamp() - price_data.timestamp; + Ok(age <= max_age_seconds) + } +}