Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
rustflags = ["-D", "warnings"]
70 changes: 15 additions & 55 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions veritixpay/contract/token/src/allowance.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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() {
Expand All @@ -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,
);
}
}

Expand Down
58 changes: 58 additions & 0 deletions veritixpay/contract/token/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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<Address>) {
freeze_batch(&e, admin, targets);
}

/// Admin-only batch unfreeze for multiple accounts.
pub fn unfreeze_batch(e: Env, admin: Address, targets: Vec<Address>) {
unfreeze_batch(&e, admin, targets);
}
Expand Down Expand Up @@ -330,43 +338,53 @@ 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<Address> {
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),
paused: false,
}
}

/// 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)
}
Expand Down Expand Up @@ -403,21 +421,25 @@ 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)
}
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) }

/// 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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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<SplitRecipient>,
total_amount: i128,
) -> u32 {
pub fn create_split(e: Env, sender: Address, recipients: Vec<SplitRecipient>, 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<SplitRecipient>, total_amount: i128) -> u32 {
split_create(&e, sender, recipients, total_amount)
}
Expand Down Expand Up @@ -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)
}
Expand Down