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
1,497 changes: 1,240 additions & 257 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
[workspace]
members = [
"contracts/donation",
"contracts/withdrawal",
"contracts/campaign",
"sdk",
"worker",
]

[workspace.profile.release]
opt-level = "z"
lto = true

[package]
name = "contract-fox"
version = "0.1.0"
Expand Down
92 changes: 58 additions & 34 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,52 +1,76 @@
### Makefile for Stellar / Soroban contract development
### Makefile for Contract Fox - Soroban Smart Contracts
# Usage:
# make build # build workspace
# make wasm # build contract WASM (package: $(CONTRACT_PKG))
# make deploy # deploy WASM using soroban CLI (requires `soroban`)
# make fund ADDR=G... # fund an address on testnet using Friendbot (curl)
# make invoke FUNC=ping # invoke a function on deployed contract (requires CONTRACT_ID)
# make test # run cargo test
# make fmt # run cargo fmt
# make lint # run cargo clippy (strict)
# make clean # cargo clean
# make build # build entire workspace
# make build-contracts # build all contract WASMs
# make test # run all tests
# make deploy-testnet # deploy contracts to testnet
# make deploy-mainnet # deploy contracts to mainnet
# make fmt # format code
# make lint # lint code with clippy
# make clean # clean build artifacts

# --- Configuration ---
CONTRACT_PKG ?= stellaraid-core
WASM_TARGET ?= wasm32-unknown-unknown
RELEASE_FLAG ?= --release
NETWORK ?= testnet
WASM_FILE ?= target/$(WASM_TARGET)/release/$(CONTRACT_PKG).wasm
CONTRACT_ID_FILE ?= .contract_id
CONTRACTS = donation-contract withdrawal-contract campaign-contract

.PHONY: all help build wasm deploy fund invoke test fmt lint clean
.PHONY: all help build build-contracts test deploy-testnet deploy-mainnet fmt lint clean

all: build

help:
@echo "Makefile targets:"
@echo " build Build the entire workspace"
@echo " wasm Build contract WASM for $(CONTRACT_PKG)"
@echo " deploy Deploy $(WASM_FILE) to $(NETWORK) via soroban"
@echo " fund ADDR=... Fund a testnet address using Friendbot"
@echo " invoke FUNC=... Invoke a method on deployed contract (set CONTRACT_ID or CONTRACT_ID_FILE)"
@echo " test Run cargo test"
@echo " fmt Run cargo fmt"
@echo " lint Run cargo clippy"
@echo " clean Run cargo clean"
@echo "Contract Fox - Soroban Smart Contracts"
@echo ""
@echo "Available targets:"
@echo " build Build the entire workspace"
@echo " build-contracts Build all contract WASMs for Soroban"
@echo " test Run all tests (workspace + contracts)"
@echo " deploy-testnet Deploy contracts to Soroban testnet"
@echo " deploy-mainnet Deploy contracts to Soroban mainnet"
@echo " fmt Format code with cargo fmt"
@echo " lint Lint code with cargo clippy"
@echo " clean Clean all build artifacts"
@echo ""
@echo "Configuration:"
@echo " NETWORK Network for deployment (default: testnet)"
@echo " RELEASE_FLAG Build flag for contracts (default: --release)"

build:
cargo build --workspace

wasm:
@which cargo >/dev/null 2>&1 || (echo "cargo not found"; exit 1)
@rustup target add $(WASM_TARGET) >/dev/null 2>&1 || true
cargo build -p $(CONTRACT_PKG) --target $(WASM_TARGET) $(RELEASE_FLAG)
build-contracts:
@echo "Building all contract WASMs..."
@for contract in $(CONTRACTS); do \
echo "Building $$contract..."; \
rustup target add $(WASM_TARGET) 2>/dev/null || true; \
cargo build -p $$contract --target $(WASM_TARGET) $(RELEASE_FLAG) || exit 1; \
done
@echo "All contracts built successfully"

deploy: wasm
deploy-testnet: build-contracts
@command -v soroban >/dev/null 2>&1 || (echo "soroban CLI not found; install via 'cargo install soroban-cli'"; exit 1)
@echo "Deploying $(WASM_FILE) to network=$(NETWORK)"
@soroban contract deploy --wasm $(WASM_FILE) --network $(NETWORK) | tee $(CONTRACT_ID_FILE)
@echo "Contract ID stored in $(CONTRACT_ID_FILE)"
@echo "Deploying contracts to Soroban testnet..."
@for contract in $(CONTRACTS); do \
WASM_FILE="target/$(WASM_TARGET)/release/$${contract}.wasm"; \
if [ -f "$$WASM_FILE" ]; then \
echo "Deploying $$WASM_FILE..."; \
soroban contract deploy --wasm "$$WASM_FILE" --network testnet || exit 1; \
fi; \
done
@echo "All contracts deployed to testnet"

deploy-mainnet: build-contracts
@command -v soroban >/dev/null 2>&1 || (echo "soroban CLI not found; install via 'cargo install soroban-cli'"; exit 1)
@echo "Deploying contracts to Soroban mainnet..."
@for contract in $(CONTRACTS); do \
WASM_FILE="target/$(WASM_TARGET)/release/$${contract}.wasm"; \
if [ -f "$$WASM_FILE" ]; then \
echo "Deploying $$WASM_FILE..."; \
soroban contract deploy --wasm "$$WASM_FILE" --network mainnet || exit 1; \
fi; \
done
@echo "All contracts deployed to mainnet"

fund:
@if [ -z "$(ADDR)" ]; then echo "Usage: make fund ADDR=G..."; exit 1; fi
Expand All @@ -57,8 +81,8 @@ fund:
invoke:
@command -v soroban >/dev/null 2>&1 || (echo "soroban CLI not found; install via 'cargo install soroban-cli'"; exit 1)
@if [ -z "$(FUNC)" ]; then echo "Usage: make invoke FUNC=<method> [CONTRACT_ID=<id>] [ARGS='arg1 arg2']"; exit 1; fi
@CONTRACT_ID=$${CONTRACT_ID:-$$(cat $(CONTRACT_ID_FILE) 2>/dev/null || true)}; \
if [ -z "$$CONTRACT_ID" ]; then echo "Contract ID not set and $(CONTRACT_ID_FILE) missing"; exit 1; fi; \
@CONTRACT_ID=$${CONTRACT_ID:-$$(cat .contract_id 2>/dev/null || true)}; \
if [ -z "$$CONTRACT_ID" ]; then echo "Contract ID not set and .contract_id missing"; exit 1; fi; \
ARGS=$${ARGS:-}; \
set -x; soroban contract invoke --id "$$CONTRACT_ID" --network $(NETWORK) --fn $(FUNC) --args $$ARGS

Expand Down
15 changes: 15 additions & 0 deletions contracts/campaign/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "campaign-contract"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[dependencies]
soroban-sdk = "20.5.0"
stellar-strkey = "0.0.8"
ed25519-dalek = "2"

[dev-dependencies]
soroban-sdk = { version = "20.5.0", features = ["testutils"] }
33 changes: 33 additions & 0 deletions contracts/campaign/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#![no_std]

use soroban_sdk::{Address, Env, Symbol, contract, contractimpl};

#[contract]
pub struct CampaignContract;

#[contractimpl]
impl CampaignContract {
/// Create a new campaign
pub fn create(env: Env, campaign_id: Symbol, title: Symbol, target: i128, deadline: u64) {
let key = Symbol::new(&env, "campaign_data");
env.storage()
.instance()
.set(&key, &(campaign_id, title, target, deadline));
}

/// Get campaign status
pub fn get_status(env: Env) -> (Symbol, Symbol, i128, u64) {
let key = Symbol::new(&env, "campaign_data");
env.storage().instance().get(&key).unwrap()
}

/// Check if campaign is active
pub fn is_active(env: Env) -> bool {
let key = Symbol::new(&env, "campaign_data");
let (_id, _title, _target, deadline): (Symbol, Symbol, i128, u64) =
env.storage().instance().get(&key).unwrap();

let current_time = env.ledger().timestamp();
current_time < deadline
}
}
15 changes: 15 additions & 0 deletions contracts/donation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "donation-contract"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[dependencies]
soroban-sdk = "20.5.0"
stellar-strkey = "0.0.8"
ed25519-dalek = "2"

[dev-dependencies]
soroban-sdk = { version = "20.5.0", features = ["testutils"] }
35 changes: 35 additions & 0 deletions contracts/donation/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![no_std]

use soroban_sdk::{Address, Env, Symbol, contract, contractimpl};

#[contract]
pub struct DonationContract;

#[contractimpl]
impl DonationContract {
/// Initialize a donation campaign
pub fn initialize(env: Env, campaign_id: Symbol, target_amount: i128, beneficiary: Address) {
let key = Symbol::new(&env, "campaign");
env.storage()
.instance()
.set(&key, &(campaign_id, target_amount, beneficiary));
}

/// Donate funds to the campaign
pub fn donate(env: Env, donor: Address, amount: i128) -> i128 {
donor.require_auth();

let key = Symbol::new(&env, "total_donated");
let total: i128 = env.storage().instance().get(&key).unwrap_or(0);
let new_total = total + amount;

env.storage().instance().set(&key, &new_total);
new_total
}

/// Get total donations received
pub fn get_total_donated(env: Env) -> i128 {
let key = Symbol::new(&env, "total_donated");
env.storage().instance().get(&key).unwrap_or(0)
}
}
15 changes: 15 additions & 0 deletions contracts/withdrawal/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "withdrawal-contract"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[dependencies]
soroban-sdk = "20.5.0"
stellar-strkey = "0.0.8"
ed25519-dalek = "2"

[dev-dependencies]
soroban-sdk = { version = "20.5.0", features = ["testutils"] }
44 changes: 44 additions & 0 deletions contracts/withdrawal/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#![no_std]

use soroban_sdk::{Address, Env, Symbol, contract, contractimpl};

#[contract]
pub struct WithdrawalContract;

#[contractimpl]
impl WithdrawalContract {
/// Initialize withdrawal settings
pub fn initialize(env: Env, beneficiary: Address, max_withdrawal: i128) {
let key = Symbol::new(&env, "settings");
env.storage()
.instance()
.set(&key, &(beneficiary, max_withdrawal));
}

/// Withdraw funds from the contract
pub fn withdraw(env: Env, amount: i128) -> bool {
let key = Symbol::new(&env, "settings");
let (beneficiary, max_withdrawal): (Address, i128) =
env.storage().instance().get(&key).unwrap();

beneficiary.require_auth();

if amount > max_withdrawal {
return false;
}

let withdrawn_key = Symbol::new(&env, "total_withdrawn");
let total: i128 = env.storage().instance().get(&withdrawn_key).unwrap_or(0);
env.storage()
.instance()
.set(&withdrawn_key, &(total + amount));

true
}

/// Get total withdrawn
pub fn get_total_withdrawn(env: Env) -> i128 {
let key = Symbol::new(&env, "total_withdrawn");
env.storage().instance().get(&key).unwrap_or(0)
}
}
9 changes: 9 additions & 0 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "contract-fox-sdk"
version = "0.1.0"
edition = "2024"

[dependencies]
soroban-sdk = "20.5.0"
stellar-strkey = "0.0.8"
ed25519-dalek = "2"
6 changes: 6 additions & 0 deletions sdk/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Contract Fox SDK - Common utilities for smart contracts
pub mod types;
pub mod utils;

pub use types::*;
pub use utils::*;
18 changes: 18 additions & 0 deletions sdk/src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use soroban_sdk::{Address, Symbol};

/// Campaign information structure
#[derive(Clone, Debug)]
pub struct Campaign {
pub id: Symbol,
pub title: Symbol,
pub target_amount: i128,
pub deadline: u64,
}

/// Donation record structure
#[derive(Clone, Debug)]
pub struct Donation {
pub donor: Address,
pub amount: i128,
pub campaign_id: Symbol,
}
40 changes: 40 additions & 0 deletions sdk/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/// Utility functions for contract operations

/// Validate amount is positive
pub fn validate_amount(amount: i128) -> bool {
amount > 0
}

/// Calculate fee from amount (1% default)
pub fn calculate_fee(amount: i128) -> i128 {
(amount * 1) / 100
}

/// Calculate net amount after fee
pub fn calculate_net_amount(amount: i128) -> i128 {
amount - calculate_fee(amount)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_validate_amount() {
assert!(validate_amount(100));
assert!(!validate_amount(0));
assert!(!validate_amount(-100));
}

#[test]
fn test_calculate_fee() {
assert_eq!(calculate_fee(100), 1);
assert_eq!(calculate_fee(1000), 10);
}

#[test]
fn test_calculate_net_amount() {
assert_eq!(calculate_net_amount(100), 99);
assert_eq!(calculate_net_amount(1000), 990);
}
}
17 changes: 17 additions & 0 deletions worker/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "contract-fox-worker"
version = "0.1.0"
edition = "2024"

[[bin]]
name = "worker"
path = "src/main.rs"

[dependencies]
reqwest = { version = "0.12", features = ["blocking", "json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
stellar-strkey = "0.0.8"
ed25519-dalek = "2"
Loading