diff --git a/veritixpay/contract/token/src/contract.rs b/veritixpay/contract/token/src/contract.rs index c50daf5..044952d 100644 --- a/veritixpay/contract/token/src/contract.rs +++ b/veritixpay/contract/token/src/contract.rs @@ -1,5 +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::{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}; +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::balance::{ decrease_supply, increase_supply, read_balance, read_total_supply, receive_balance, spend_balance, @@ -38,6 +45,38 @@ pub struct VeritixToken; #[contractimpl] impl VeritixToken { + 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) { @@ -142,6 +181,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); @@ -227,6 +275,27 @@ impl VeritixToken { pub fn decimals(e: Env) -> u32 { read_decimal(&e) } pub fn name(e: Env) -> String { read_name(&e) } pub fn symbol(e: Env) -> String { read_symbol(&e) } + pub fn create_escrow(e: Env, depositor: Address, beneficiary: Address, amount: i128, expiry_ledger: u32) -> u32 { escrow_create(&e, depositor, beneficiary, amount, expiry_ledger) } + pub fn release_escrow(e: Env, caller: Address, escrow_id: u32) { escrow_release(&e, caller, escrow_id) } + pub fn refund_escrow(e: Env, caller: Address, escrow_id: u32) { escrow_refund(&e, caller, escrow_id) } + pub fn get_escrow(e: Env, escrow_id: u32) -> EscrowRecord { escrow_get(&e, escrow_id) } + pub fn escrow_count(e: Env) -> u32 { crate::storage_types::bump_instance(&e); crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::EscrowCount) } + pub fn admin_settle_escrow(e: Env, admin: Address, escrow_id: u32, recipient: Address) { escrow_admin_settle(&e, admin, escrow_id, recipient) } + pub fn open_dispute(e: Env, claimant: Address, escrow_id: u32, resolver: Address) -> u32 { open_dispute(&e, claimant, escrow_id, resolver) } + 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 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) } + pub fn cancel_split(e: Env, caller: Address, split_id: u32) { split_cancel(&e, caller, split_id) } + pub fn get_split(e: Env, split_id: u32) -> SplitRecord { split_get(&e, split_id) } + pub fn split_count(e: Env) -> u32 { crate::storage_types::bump_instance(&e); crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::SplitCount) } + pub fn setup_recurring(e: Env, payer: Address, payee: Address, amount: i128, interval: u32) -> u32 { setup_recurring(&e, payer, payee, amount, interval) } + pub fn execute_recurring(e: Env, recurring_id: u32) { execute_recurring(&e, recurring_id) } + pub fn cancel_recurring(e: Env, caller: Address, recurring_id: u32) { cancel_recurring(&e, caller, recurring_id) } + pub fn get_recurring(e: Env, recurring_id: u32) -> RecurringRecord { get_recurring(&e, recurring_id) } + pub fn recurring_count(e: Env) -> u32 { crate::storage_types::bump_instance(&e); crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::RecurringCount) } pub fn create_escrow(e: Env, depositor: Address, beneficiary: Address, amount: i128, expiry_ledger: u32) -> u32 { escrow_create(&e, depositor, beneficiary, amount, expiry_ledger) diff --git a/veritixpay/contract/token/src/dispute.rs b/veritixpay/contract/token/src/dispute.rs index 23fed13..3171f63 100644 --- a/veritixpay/contract/token/src/dispute.rs +++ b/veritixpay/contract/token/src/dispute.rs @@ -8,20 +8,21 @@ use soroban_sdk::{contracttype, symbol_short, vec, Address, Env, Symbol, Vec}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub enum DisputeStatus { - Open, - ResolvedForBeneficiary, - ResolvedForDepositor, -} +pub enum DisputeStatus { Open, ResolvedForBeneficiary, ResolvedForDepositor } #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub struct DisputeRecord { - pub id: u32, - pub escrow_id: u32, - pub claimant: Address, - pub resolver: Address, - pub status: DisputeStatus, + pub id: u32, pub escrow_id: u32, pub claimant: Address, + pub resolver: Address, pub status: DisputeStatus, +} + +fn append_dispute_history(e: &Env, escrow_id: u32, dispute_id: u32) { + let key = DataKey::EscrowDisputeHistory(escrow_id); + let mut ids: Vec = e.storage().persistent().get(&key).unwrap_or_else(|| vec![e]); + ids.push_back(dispute_id); + e.storage().persistent().set(&key, &ids); + e.storage().persistent().extend_ttl(&key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); } fn append_resolver_dispute(e: &Env, resolver: &Address, id: u32) { @@ -52,6 +53,13 @@ pub fn open_dispute(e: &Env, claimant: Address, escrow_id: u32, resolver: Addres claimant.require_auth(); let escrow = get_escrow(e, escrow_id); if escrow.released || escrow.refunded { panic!("InvalidState: Cannot open dispute on a settled escrow"); } + if claimant != escrow.depositor && claimant != escrow.beneficiary { panic!("Unauthorized: Only depositor or beneficiary can open a dispute"); } + if resolver == claimant { panic!("InvalidResolver: resolver cannot be the claimant"); } + if resolver == escrow.depositor { panic!("InvalidResolver: resolver cannot be the depositor"); } + 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 }; if v != id { updated.push_back(v); } @@ -86,6 +94,7 @@ pub fn open_dispute(e: &Env, claimant: Address, escrow_id: u32, resolver: Addres e.storage().persistent().extend_ttl(&dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); e.storage().persistent().set(&escrow_dispute_key, &count); e.storage().persistent().extend_ttl(&escrow_dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); + append_dispute_history(e, escrow_id, count); append_resolver_dispute(e, &resolver, count); append_open_dispute(e, count); e.events().publish((symbol_short!("dispute_opened"), escrow_id, claimant.clone()), ()); @@ -134,6 +143,8 @@ pub fn get_dispute(e: &Env, dispute_id: u32) -> DisputeRecord { record } +pub fn get_dispute_history_for_escrow(e: &Env, escrow_id: u32) -> Vec { + let key = DataKey::EscrowDisputeHistory(escrow_id); pub fn get_disputes_by_resolver(e: &Env, resolver: Address) -> Vec { let key = DataKey::ResolverDisputes(resolver); pub fn get_open_disputes(e: &Env) -> Vec { diff --git a/veritixpay/contract/token/src/storage_types.rs b/veritixpay/contract/token/src/storage_types.rs index 9bea610..b486ec3 100644 --- a/veritixpay/contract/token/src/storage_types.rs +++ b/veritixpay/contract/token/src/storage_types.rs @@ -11,21 +11,19 @@ pub const PERSISTENT_BUMP_AMOUNT: u32 = 535000; #[derive(Clone)] #[contracttype] -pub struct AllowanceDataKey { - pub from: Address, - pub spender: Address, -} +pub struct AllowanceDataKey { pub from: Address, pub spender: Address } #[derive(Clone)] #[contracttype] -pub struct AllowanceValue { - pub amount: i128, - pub expiration_ledger: u32, -} +pub struct AllowanceValue { pub amount: i128, pub expiration_ledger: u32 } #[derive(Clone)] #[contracttype] pub enum DataKey { + Admin, Allowance(AllowanceDataKey), Balance(Address), Metadata, TotalSupply, + EscrowCount, Escrow(u32), RecurringCount, Recurring(u32), + SplitCount, Split(u32), DisputeCount, Dispute(u32), + EscrowDispute(u32), EscrowDisputeHistory(u32), Freeze(Address), Admin, Allowance(AllowanceDataKey), Balance(Address), @@ -47,35 +45,25 @@ pub enum DataKey { } pub fn read_persistent_record(e: &Env, key: &DataKey, missing_message: &'static str) -> T -where - T: TryFromVal, -{ +where T: TryFromVal { let storage = e.storage().persistent(); - let value = storage - .get::(key) - .unwrap_or_else(|| panic!("{}", missing_message)); + let value = storage.get::(key).unwrap_or_else(|| panic!("{}", missing_message)); storage.extend_ttl(key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); value } pub fn write_persistent_record(e: &Env, key: &DataKey, value: &T) -where - T: IntoVal, -{ +where T: IntoVal { let storage = e.storage().persistent(); storage.set(key, value); storage.extend_ttl(key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); } pub fn bump_instance(e: &Env) { - e.storage() - .instance() - .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); + e.storage().instance().extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); } -pub fn read_counter(e: &Env, key: &DataKey) -> u32 { - e.storage().instance().get(key).unwrap_or(0) -} +pub fn read_counter(e: &Env, key: &DataKey) -> u32 { e.storage().instance().get(key).unwrap_or(0) } pub fn increment_counter(e: &Env, key: &DataKey) -> u32 { let next = read_counter(e, key) + 1;