diff --git a/veritixpay/contract/token/src/contract.rs b/veritixpay/contract/token/src/contract.rs index 044952d..fc6da06 100644 --- a/veritixpay/contract/token/src/contract.rs +++ b/veritixpay/contract/token/src/contract.rs @@ -1,6 +1,12 @@ use crate::admin::{check_admin, has_admin, read_admin, transfer_admin, write_admin}; use crate::allowance::{read_allowance, spend_allowance, write_allowance}; use crate::balance::{decrease_supply, increase_supply, read_balance, read_total_supply, receive_balance, spend_balance}; +use crate::dispute::{appeal_dispute, get_dispute as dispute_get, open_dispute, resolve_dispute, DisputeRecord}; +use crate::escrow::{admin_settle_escrow as escrow_admin_settle, create_escrow as escrow_create, get_escrow as escrow_get, refund_escrow as escrow_refund, release_escrow as escrow_release, EscrowRecord}; +use crate::freeze::{freeze_account, is_frozen as read_frozen_status, unfreeze_account}; +use crate::metadata::{read_decimal, read_name, read_symbol, validate_metadata, write_metadata, TokenMetadata}; +use crate::recurring::{cancel_recurring, execute_recurring, get_recurring, setup_recurring, RecurringRecord}; +use crate::splitter::{cancel_split as split_cancel, create_split as split_create, distribute as split_distribute, get_split as split_get, SplitRecord, SplitRecipient}; use crate::dispute::{get_dispute as dispute_get, get_dispute_history_for_escrow, open_dispute, resolve_dispute, DisputeRecord}; use crate::escrow::{admin_settle_escrow as escrow_admin_settle, create_escrow as escrow_create, get_escrow as escrow_get, refund_escrow as escrow_refund, release_escrow as escrow_release, EscrowRecord}; use crate::freeze::{freeze_account, is_frozen as read_frozen_status, unfreeze_account}; @@ -77,6 +83,38 @@ impl VeritixToken { spend_balance(&e, from.clone(), amount); receive_balance(&e, to.clone(), amount); e.events().publish((symbol_short!("transfer"), from, to), amount); } + pub fn initialize(e: Env, admin: Address, name: String, symbol: String, decimal: u32) { + if has_admin(&e) { panic!("already initialized"); } + admin.require_auth(); + let meta = TokenMetadata { name, symbol, decimal }; + validate_metadata(&meta); write_admin(&e, &admin); write_metadata(&e, meta); + } + pub fn set_admin(e: Env, new_admin: Address) { transfer_admin(&e, new_admin); } + pub fn clawback(e: Env, admin: Address, from: Address, amount: i128) { + check_admin(&e, &admin); require_positive_amount(amount); + spend_balance(&e, from.clone(), amount); decrease_supply(&e, amount); + e.events().publish((symbol_short!("clawback"), admin, from), amount); + } + pub fn freeze(e: Env, target: Address) { let admin = read_admin(&e); check_admin(&e, &admin); freeze_account(&e, admin, target); } + pub fn unfreeze(e: Env, target: Address) { let admin = read_admin(&e); check_admin(&e, &admin); unfreeze_account(&e, admin, target); } + pub fn mint(e: Env, admin: Address, to: Address, amount: i128) { + check_admin(&e, &admin); require_positive_amount(amount); require_not_frozen_account(&e, &to); + receive_balance(&e, to.clone(), amount); increase_supply(&e, amount); + e.events().publish((symbol_short!("mint"), admin, to), amount); + } + pub fn transfer(e: Env, from: Address, to: Address, amount: i128) { + from.require_auth(); require_positive_amount(amount); + require_not_frozen_account(&e, &from); require_not_frozen_account(&e, &to); + spend_balance(&e, from.clone(), amount); receive_balance(&e, to.clone(), amount); + e.events().publish((symbol_short!("transfer"), from, to), amount); + } + pub fn transfer_from(e: Env, spender: Address, from: Address, to: Address, amount: i128) { + spender.require_auth(); require_positive_amount(amount); + require_not_frozen_account(&e, &from); require_not_frozen_account(&e, &to); + spend_allowance(&e, from.clone(), spender.clone(), amount); + spend_balance(&e, from.clone(), amount); receive_balance(&e, to.clone(), amount); + e.events().publish((symbol_short!("transfer"), from, to), amount); + } pub fn initialize(e: Env, admin: Address, name: String, symbol: String, decimal: u32) { if has_admin(&e) { panic!("already initialized"); } pub fn initialize(e: Env, admin: Address, name: String, symbol: String, decimal: u32) { @@ -190,6 +228,15 @@ impl VeritixToken { spend_balance(&e, from.clone(), amount); decrease_supply(&e, amount); e.events().publish((symbol_short!("burn"), from), amount); } + pub fn approve(e: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32) { + from.require_auth(); require_positive_amount(amount); + write_allowance(&e, from.clone(), spender.clone(), amount, expiration_ledger); + e.events().publish((symbol_short!("approve"), from, spender), amount); + } + spend_allowance(&e, from.clone(), spender.clone(), amount); + spend_balance(&e, from.clone(), amount); decrease_supply(&e, amount); + e.events().publish((symbol_short!("burn"), from), amount); + } pub fn approve(e: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32) { from.require_auth(); require_positive_amount(amount); write_allowance(&e, from.clone(), spender.clone(), amount, expiration_ledger); @@ -285,6 +332,7 @@ impl VeritixToken { pub fn resolve_dispute(e: Env, resolver: Address, dispute_id: u32, release_to_beneficiary: bool) { resolve_dispute(&e, resolver, dispute_id, release_to_beneficiary) } pub fn get_dispute(e: Env, dispute_id: u32) -> DisputeRecord { dispute_get(&e, dispute_id) } pub fn dispute_count(e: Env) -> u32 { crate::storage_types::bump_instance(&e); crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::DisputeCount) } + pub fn appeal_dispute(e: Env, appellant: Address, dispute_id: u32, new_resolver: Address) { appeal_dispute(&e, appellant, dispute_id, new_resolver) } pub fn get_dispute_history_for_escrow(e: Env, escrow_id: u32) -> Vec { get_dispute_history_for_escrow(&e, escrow_id) } pub fn create_split(e: Env, sender: Address, recipients: Vec, total_amount: i128) -> u32 { split_create(&e, sender, recipients, total_amount) } pub fn distribute(e: Env, caller: Address, split_id: u32) { split_distribute(&e, caller, split_id) } diff --git a/veritixpay/contract/token/src/dispute.rs b/veritixpay/contract/token/src/dispute.rs index 3171f63..17c10d0 100644 --- a/veritixpay/contract/token/src/dispute.rs +++ b/veritixpay/contract/token/src/dispute.rs @@ -4,10 +4,14 @@ use crate::storage_types::{ increment_counter, write_persistent_record, DataKey, PERSISTENT_BUMP_AMOUNT, PERSISTENT_LIFETIME_THRESHOLD, }; +use soroban_sdk::{contracttype, symbol_short, Address, Env, Symbol}; use soroban_sdk::{contracttype, symbol_short, vec, Address, Env, Symbol, Vec}; +pub const APPEAL_WINDOW: u32 = 1000; + #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] +pub enum DisputeStatus { Open, ResolvedForBeneficiary, ResolvedForDepositor, Appealed } pub enum DisputeStatus { Open, ResolvedForBeneficiary, ResolvedForDepositor } #[contracttype] @@ -15,6 +19,7 @@ pub enum DisputeStatus { Open, ResolvedForBeneficiary, ResolvedForDepositor } pub struct DisputeRecord { pub id: u32, pub escrow_id: u32, pub claimant: Address, pub resolver: Address, pub status: DisputeStatus, + pub appeal_deadline_ledger: u32, } fn append_dispute_history(e: &Env, escrow_id: u32, dispute_id: u32) { @@ -59,6 +64,10 @@ pub fn open_dispute(e: &Env, claimant: Address, escrow_id: u32, resolver: Addres if resolver == escrow.beneficiary { panic!("InvalidResolver: resolver cannot be the beneficiary"); } if e.storage().persistent().has(&DataKey::EscrowDispute(escrow_id)) { panic!("DisputeAlreadyOpen: An open dispute already exists for this escrow"); } let count = increment_counter(e, &DataKey::DisputeCount); + let record = DisputeRecord { + id: count, escrow_id, claimant: claimant.clone(), resolver, + status: DisputeStatus::Open, appeal_deadline_ledger: 0, + let count = increment_counter(e, &DataKey::DisputeCount); let record = DisputeRecord { id: count, escrow_id, claimant: claimant.clone(), resolver, status: DisputeStatus::Open }; if v != id { updated.push_back(v); @@ -127,6 +136,28 @@ pub fn resolve_dispute(e: &Env, resolver: Address, dispute_id: u32, release_to_b if dispute.status != DisputeStatus::Open { panic!("AlreadyResolved: This dispute has already been resolved"); } if dispute.resolver != resolver { panic!("UnauthorizedResolver: Only the designated resolver can resolve this"); } settle_escrow_by_outcome(e, dispute.escrow_id, release_to_beneficiary); + dispute.status = if release_to_beneficiary { DisputeStatus::ResolvedForBeneficiary } else { DisputeStatus::ResolvedForDepositor }; + dispute.appeal_deadline_ledger = e.ledger().sequence() + APPEAL_WINDOW; + e.storage().persistent().set(&dispute_key, &dispute); + e.storage().persistent().extend_ttl(&dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); + e.storage().persistent().remove(&DataKey::EscrowDispute(dispute.escrow_id)); + e.events().publish((symbol_short!("dispute_resolved"), dispute_id, resolver), release_to_beneficiary); +} + +pub fn appeal_dispute(e: &Env, appellant: Address, dispute_id: u32, new_resolver: Address) { + appellant.require_auth(); + let dispute_key = DataKey::Dispute(dispute_id); + let mut dispute: DisputeRecord = e.storage().persistent().get(&dispute_key).expect("Dispute not found"); + if dispute.status == DisputeStatus::Open || dispute.status == DisputeStatus::Appealed { + panic!("InvalidState: dispute must be resolved before appeal"); + } + if e.ledger().sequence() > dispute.appeal_deadline_ledger { panic!("AppealWindowClosed: appeal window has passed"); } + if appellant != dispute.claimant { panic!("Unauthorized: only the claimant can appeal"); } + dispute.status = DisputeStatus::Appealed; + dispute.resolver = new_resolver.clone(); + e.storage().persistent().set(&dispute_key, &dispute); + e.storage().persistent().extend_ttl(&dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); + e.events().publish((symbol_short!("dispute_appealed"), dispute_id, appellant), new_resolver); remove_resolver_dispute(e, &resolver, dispute_id); dispute.status = if release_to_beneficiary { DisputeStatus::ResolvedForBeneficiary } else { DisputeStatus::ResolvedForDepositor }; e.storage().persistent().set(&dispute_key, &dispute);