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
39 changes: 39 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Rust / Cargo build artifacts
target/
Cargo.lock # ← Usually ignored in library crates; commit if it's a binary or workspace root with fixed deps
*.rs.bk
*.swp

# Soroban-specific
.soroban/ # ← soroban CLI cache, deployments, etc.
*.wasm # Compiled contract WASM (regenerate on build)
*.wasm.map
*.optimized.wasm

# Environment / secrets
.env
.env.*
!.env.example # Keep template if you have one

# IDE / editor files
.vscode/
.idea/
*.sublime-*
.DS_Store
Thumbs.db

# Testing / temp
tests/output/
coverage/
prof/

# If you have frontend/tests/scripts in the same repo (optional)
node_modules/
npm-debug.log
yarn-error.log
dist/
build/

# Misc
*.log
*.tmp
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

140 changes: 129 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![allow(unexpected_cfgs)]
#![no_std]
use soroban_sdk::{contract, contracttype, contractimpl, Address, Env, token};

Expand All @@ -11,52 +12,169 @@ pub struct Grant {
pub last_claim_time: u64,
pub is_paused: bool,
pub token: Address,
pub dispute_active: bool, // frozen if true
}

#[contracttype]
pub enum DataKey {
Grant(u64),
Count,
Arbiter,
}

#[contract]
pub struct GrantContract;

#[contractimpl]
impl GrantContract {
pub fn create_grant(env: Env, admin: Address, grantee: Address, deposit: i128, flow_rate: i128, token: Address) -> u64 {
// ────────────────────────────────────────────────
// Added for Issue #16: Optimize Storage Bumps
// Only extend TTL when remaining lifetime is low → saves gas on most calls
fn ensure_sufficient_ttl(env: &Env) {
const THRESHOLD: u32 = 1000; // ~few hours — bump only if below this
let max_ttl = env.storage().max_ttl(); // network maximum TTL

// extend_ttl() is conditional: does nothing if current TTL ≥ threshold
// It extends both instance storage and the contract WASM code entry
env.storage().instance().extend_ttl(THRESHOLD, max_ttl);
}
// ────────────────────────────────────────────────

pub fn set_arbiter(env: Env, admin: Address, arbiter: Address) {
Self::ensure_sufficient_ttl(&env);

admin.require_auth();

// Only callable if arbiter not set yet (or add admin check)
if env.storage().instance().has(&DataKey::Arbiter) {
panic!("Arbiter already set");
}

env.storage().instance().set(&DataKey::Arbiter, &arbiter);
}

// ─── NEW: Core function for #17 ───
pub fn set_dispute_state(env: Env, grant_id: u64, active: bool) {
Self::ensure_sufficient_ttl(&env);

// Only the designated arbiter can call this
let arbiter: Address = env.storage().instance()
.get(&DataKey::Arbiter)
.unwrap_or_else(|| panic!("No arbiter set"));

arbiter.require_auth();

let mut grant: Grant = env.storage().instance()
.get(&DataKey::Grant(grant_id))
.unwrap_or_else(|| panic!("Grant not found"));

grant.dispute_active = active;

env.storage().instance().set(&DataKey::Grant(grant_id), &grant);

// Optional: emit event for frontend
// env.events().publish(("DisputeUpdated", grant_id), active);
}

pub fn create_grant(
env: Env,
admin: Address,
grantee: Address,
deposit: i128,
flow_rate: i128,
token: Address,
) -> u64 {
Self::ensure_sufficient_ttl(&env); // Added for #16

admin.require_auth();
let mut count: u64 = env.storage().instance().get(&DataKey::Count).unwrap_or(0);

let mut count: u64 = env
.storage()
.instance()
.get(&DataKey::Count)
.unwrap_or(0);
count += 1;

let client = token::Client::new(&env, &token);
client.transfer(&admin, &env.current_contract_address(), &deposit);
let grant = Grant { admin, grantee, flow_rate, balance: deposit, last_claim_time: env.ledger().timestamp(), is_paused: false, token };
env.storage().instance().set(&DataKey::Grant(count), &grant);

let grant = Grant {
admin,
grantee,
flow_rate,
balance: deposit,
last_claim_time: env.ledger().timestamp(),
is_paused: false,
token,
dispute_active: false,
};

env.storage()
.instance()
.set(&DataKey::Grant(count), &grant);
env.storage().instance().set(&DataKey::Count, &count);

count
}

pub fn withdraw(env: Env, grant_id: u64) {
let mut grant: Grant = env.storage().instance().get(&DataKey::Grant(grant_id)).unwrap();
Self::ensure_sufficient_ttl(&env); // Added for #16

let mut grant: Grant = env
.storage()
.instance()
.get(&DataKey::Grant(grant_id))
.unwrap();

grant.grantee.require_auth();
if grant.is_paused { panic!("Grant PAUSED by admin"); }

if grant.is_paused {
panic!("Grant PAUSED by admin");
}

if grant.dispute_active {
panic!("Grant is under dispute - withdrawals blocked");
}


let current_time = env.ledger().timestamp();
let seconds_passed = current_time - grant.last_claim_time;
let amount_due = grant.flow_rate * seconds_passed as i128;
let payout = if grant.balance >= amount_due { amount_due } else { grant.balance };

let payout = if grant.balance >= amount_due {
amount_due
} else {
grant.balance
};

if payout > 0 {
let client = token::Client::new(&env, &grant.token);
client.transfer(&env.current_contract_address(), &grant.grantee, &payout);

grant.balance -= payout;
grant.last_claim_time = current_time;
env.storage().instance().set(&DataKey::Grant(grant_id), &grant);

env.storage()
.instance()
.set(&DataKey::Grant(grant_id), &grant);
}
}

pub fn set_pause(env: Env, grant_id: u64, pause_state: bool) {
let mut grant: Grant = env.storage().instance().get(&DataKey::Grant(grant_id)).unwrap();
Self::ensure_sufficient_ttl(&env); // Added for #16

let mut grant: Grant = env
.storage()
.instance()
.get(&DataKey::Grant(grant_id))
.unwrap();

grant.admin.require_auth();

grant.is_paused = pause_state;
env.storage().instance().set(&DataKey::Grant(grant_id), &grant);

env.storage()
.instance()
.set(&DataKey::Grant(grant_id), &grant);
}
}
}
2 changes: 1 addition & 1 deletion target/.rustc_info.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"rustc_fingerprint":9417614391007597337,"outputs":{"11652014622397750202":{"success":true,"status":"","code":0,"stdout":"___.wasm\nlib___.rlib\n___.wasm\nlib___.a\n/home/codespace/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\n___\ndebug_assertions\npanic=\"abort\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"wasm32\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"wasm\"\ntarget_feature=\"bulk-memory\"\ntarget_feature=\"multivalue\"\ntarget_feature=\"mutable-globals\"\ntarget_feature=\"nontrapping-fptoint\"\ntarget_feature=\"reference-types\"\ntarget_feature=\"sign-ext\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"unknown\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"unknown\"\n","stderr":"warning: dropping unsupported crate type `dylib` for target `wasm32-unknown-unknown`\n\nwarning: dropping unsupported crate type `proc-macro` for target `wasm32-unknown-unknown`\n\nwarning: 2 warnings emitted\n\n"},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.93.1 (01f6ddf75 2026-02-11)\nbinary: rustc\ncommit-hash: 01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf\ncommit-date: 2026-02-11\nhost: x86_64-unknown-linux-gnu\nrelease: 1.93.1\nLLVM version: 21.1.8\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/codespace/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":111714449869969592,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/codespace/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.93.1 (01f6ddf75 2026-02-11)\nbinary: rustc\ncommit-hash: 01f6ddf7588f42ae2d7eb0a2f21d44e8e96674cf\ncommit-date: 2026-02-11\nhost: x86_64-unknown-linux-gnu\nrelease: 1.93.1\nLLVM version: 21.1.8\n","stderr":""}},"successes":{}}
Loading