diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..0eb087e --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["-D", "warnings"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 252f6a2..63c5e06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,69 +1,29 @@ -name: Veritix Pay CI +name: CI on: - push: - branches: [ "main" ] pull_request: - branches: [ "main" ] + branches: [main] jobs: - build-and-test: - name: Build, Test, and Format + contract-ci: runs-on: ubuntu-latest - - # Set the working directory for all run steps in this job defaults: run: working-directory: veritixpay/contract/token - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.84.0" - # wasm32-unknown-unknown is needed for `cargo test` and clippy. - # `stellar contract build` uses wasm32v1-none internally and manages - # that target itself; we do not need to install it here. - targets: wasm32-unknown-unknown - components: rustfmt, clippy - - - name: Cache Cargo registry and build artifacts - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - workspaces: "veritixpay/contract/token -> target" - + targets: wasm32-unknown-unknown,wasm32v1-none - name: Install Stellar CLI - run: cargo install --locked stellar-cli - - - name: Check for orphaned source files - run: | - LIB=src/lib.rs - EXIT=0 - for f in src/*.rs; do - mod=$(basename "$f" .rs) - [ "$mod" = "lib" ] && continue - if ! grep -qE "^\s*(pub\s+)?mod\s+${mod}\s*;" "$LIB"; then - echo "ORPHAN: $f is not declared in lib.rs" - EXIT=1 - fi - done - exit $EXIT - - - name: Check Formatting - # Uses cargo fmt directly, or make fmt if it passes the args in your Makefile - run: cargo fmt -- --check - - - name: Run Clippy - run: cargo clippy --lib -- -D warnings - - - name: Compile-only check (locked) - run: cargo check --locked - - - name: Build Contract + run: cargo install stellar-cli --locked + - name: Preflight + run: make preflight + - name: Build run: make build - - - name: Run Tests + - name: Test run: make test + - name: Clippy + run: cargo clippy --all -- -D warnings + - name: Rustfmt + run: cargo fmt --all --check diff --git a/veritixpay/contract/token/src/allowance.rs b/veritixpay/contract/token/src/allowance.rs index 1ca1166..5c11738 100644 --- a/veritixpay/contract/token/src/allowance.rs +++ b/veritixpay/contract/token/src/allowance.rs @@ -1,5 +1,6 @@ use crate::storage_types::{ AllowanceDataKey, AllowanceValue, DataKey, ALLOWANCE_BUMP_AMOUNT, ALLOWANCE_LIFETIME_THRESHOLD, + PERSISTENT_BUMP_AMOUNT, PERSISTENT_LIFETIME_THRESHOLD, }; use crate::validation::{require_current_or_future_ledger, require_non_negative_amount}; use soroban_sdk::{Address, Env, Vec}; @@ -73,6 +74,12 @@ pub fn write_allowance( } } e.storage().persistent().set(&index_key, &updated); + // Keep spender index alive for long-lived delegated payment lookups. + e.storage().persistent().extend_ttl( + &index_key, + PERSISTENT_LIFETIME_THRESHOLD, + PERSISTENT_BUMP_AMOUNT, + ); } else { let mut exists = false; for i in 0..spenders_from.len() { @@ -84,12 +91,23 @@ pub fn write_allowance( if !exists { spenders_from.push_back(from.clone()); e.storage().persistent().set(&index_key, &spenders_from); + // Keep spender index alive for long-lived delegated payment lookups. + e.storage().persistent().extend_ttl( + &index_key, + PERSISTENT_LIFETIME_THRESHOLD, + PERSISTENT_BUMP_AMOUNT, + ); } let allowance = AllowanceValue { amount, expiration_ledger, }; e.storage().persistent().set(&key, &allowance); + e.storage().persistent().extend_ttl( + &key, + ALLOWANCE_LIFETIME_THRESHOLD, + ALLOWANCE_BUMP_AMOUNT, + ); } } diff --git a/veritixpay/contract/token/src/contract.rs b/veritixpay/contract/token/src/contract.rs index 55db23c..1503da5 100644 --- a/veritixpay/contract/token/src/contract.rs +++ b/veritixpay/contract/token/src/contract.rs @@ -172,13 +172,19 @@ impl VeritixToken { decrease_supply(&e, amount); e.events().publish((symbol_short!("clawback"), admin, from), amount); } + /// Admin-only batch clawback over `(from, amount)` tuples. pub fn clawback_batch(e: Env, admin: Address, targets: Vec<(Address, i128)>) { clawback_batch(&e, admin, targets); } + // --- Freeze controls --- + + /// Admin-only freeze for a single account. pub fn freeze(e: Env, target: Address) { let admin = read_admin(&e); check_admin(&e, &admin); freeze_account(&e, admin, target); } + + /// Admin-only unfreeze for a single account. pub fn unfreeze(e: Env, target: Address) { let admin = read_admin(&e); check_admin(&e, &admin); unfreeze_account(&e, admin, target); } @@ -187,10 +193,12 @@ impl VeritixToken { receive_balance(&e, to.clone(), amount); increase_supply(&e, amount); e.events().publish((symbol_short!("mint"), admin, to), amount); } + /// Admin-only batch freeze for multiple accounts. pub fn freeze_batch(e: Env, admin: Address, targets: Vec
) { freeze_batch(&e, admin, targets); } + /// Admin-only batch unfreeze for multiple accounts. pub fn unfreeze_batch(e: Env, admin: Address, targets: Vec
) { unfreeze_batch(&e, admin, targets); } @@ -330,24 +338,30 @@ impl VeritixToken { // --- Read-only views --- + /// Returns current total token supply. pub fn total_supply(e: Env) -> i128 { read_total_supply(&e) } + /// Returns token balance for `id`. pub fn balance(e: Env, id: Address) -> i128 { read_balance(&e, id) } + /// Returns allowance from `from` to `spender`. pub fn allowance(e: Env, from: Address, spender: Address) -> i128 { read_allowance(&e, from, spender).amount } + /// Returns all owners that granted non-zero allowance to `spender`. pub fn allowances_for_spender(e: Env, spender: Address) -> Vec
{ get_allowances_for_spender(&e, spender) } + /// Returns current admin address. pub fn admin(e: Env) -> Address { read_admin(&e) } + /// Returns compact admin metadata for clients. pub fn admin_info(e: Env) -> AdminInfo { AdminInfo { admin: read_admin(&e), @@ -355,18 +369,22 @@ impl VeritixToken { } } + /// Returns freeze state for account `id`. pub fn is_frozen(e: Env, id: Address) -> bool { read_frozen_status(&e, &id) } + /// Returns token decimal precision. pub fn decimals(e: Env) -> u32 { read_decimal(&e) } + /// Returns token display name. pub fn name(e: Env) -> String { read_name(&e) } + /// Returns token symbol. pub fn symbol(e: Env) -> String { read_symbol(&e) } @@ -403,6 +421,7 @@ impl VeritixToken { 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) } + /// Creates an escrow and returns its ID. pub fn create_escrow(e: Env, depositor: Address, beneficiary: Address, amount: i128, expiry_ledger: u32) -> u32 { escrow_create(&e, depositor, beneficiary, amount, expiry_ledger) } @@ -410,14 +429,17 @@ impl VeritixToken { 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) } + /// Releases escrow funds to beneficiary. pub fn release_escrow(e: Env, caller: Address, escrow_id: u32) { escrow_release(&e, caller, escrow_id) } + /// Refunds escrow funds to depositor. pub fn refund_escrow(e: Env, caller: Address, escrow_id: u32) { escrow_refund(&e, caller, escrow_id) } + /// Returns escrow record for `escrow_id`. pub fn get_escrow(e: Env, escrow_id: u32) -> EscrowRecord { escrow_get(&e, escrow_id) } @@ -434,6 +456,7 @@ impl VeritixToken { escrow_admin_settle(&e, admin, escrow_id, recipient) } + /// Opens a dispute for an escrow and returns dispute ID. pub fn open_dispute(e: Env, claimant: Address, escrow_id: u32, resolver: Address) -> u32 { open_dispute(&e, claimant, escrow_id, resolver) } @@ -451,10 +474,18 @@ impl VeritixToken { } pub fn get_dispute(e: Env, dispute_id: u32) -> DisputeRecord { dispute_get(&e, dispute_id) } + /// Resolves dispute by releasing to beneficiary or refunding depositor. + pub fn resolve_dispute( + e: Env, + resolver: Address, + dispute_id: u32, + release_to_beneficiary: bool, + ) { pub fn resolve_dispute(e: Env, resolver: Address, dispute_id: u32, release_to_beneficiary: bool) { resolve_dispute(&e, resolver, dispute_id, release_to_beneficiary) } + /// Returns dispute record for `dispute_id`. pub fn get_dispute(e: Env, dispute_id: u32) -> DisputeRecord { dispute_get(&e, dispute_id) } @@ -486,14 +517,30 @@ impl VeritixToken { pub fn get_split(e: Env, split_id: u32) -> SplitRecord { split_get(&e, split_id) } // --- Splitter --- + /// Creates a split payment plan and returns split ID. + pub fn create_split( + e: Env, + sender: Address, + recipients: Vec, + total_amount: i128, + ) -> u32 { pub fn create_split(e: Env, sender: Address, recipients: Vec, total_amount: i128) -> u32 { split_create(&e, sender, recipients, total_amount) } + /// Executes split distribution to recipients. pub fn distribute(e: Env, caller: Address, split_id: u32) { split_distribute(&e, caller, split_id) } + /// Cancels an active split and returns remainder to sender. + pub fn cancel_split(e: Env, caller: Address, split_id: u32) { + split_cancel(&e, caller, split_id) + } + + /// Returns split record for `split_id`. + pub fn get_split(e: Env, split_id: u32) -> SplitRecord { + split_get(&e, split_id) pub fn create_split(e: Env, sender: Address, recipients: Vec, total_amount: i128) -> u32 { split_create(&e, sender, recipients, total_amount) } @@ -530,18 +577,29 @@ impl VeritixToken { pub fn get_recurring(e: Env, recurring_id: u32) -> RecurringRecord { get_recurring(&e, recurring_id) } // --- Recurring Payments --- + /// Creates a recurring payment and returns recurring ID. + pub fn setup_recurring( + e: Env, + payer: Address, + payee: Address, + amount: i128, + interval: u32, + ) -> u32 { pub fn setup_recurring(e: Env, payer: Address, payee: Address, amount: i128, interval: u32) -> u32 { setup_recurring(&e, payer, payee, amount, interval) } + /// Executes one interval payment for a recurring plan. pub fn execute_recurring(e: Env, recurring_id: u32) { execute_recurring(&e, recurring_id) } + /// Cancels recurring payment plan. pub fn cancel_recurring(e: Env, caller: Address, recurring_id: u32) { cancel_recurring(&e, caller, recurring_id) } + /// Returns recurring payment record for `recurring_id`. pub fn get_recurring(e: Env, recurring_id: u32) -> RecurringRecord { get_recurring(&e, recurring_id) }