From 562007063763e827d3d9607469787ff3feaf3a12 Mon Sep 17 00:00:00 2001 From: onahiOMOTI Date: Fri, 20 Feb 2026 13:25:27 +0000 Subject: [PATCH 1/2] optimize: conditional storage TTL extension (closes #16) --- .gitignore | 39 +++++++++++++++++ Cargo.lock | 12 +++--- src/lib.rs | 96 ++++++++++++++++++++++++++++++++++++----- target/.rustc_info.json | 2 +- 4 files changed, 131 insertions(+), 18 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a95523a --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index d6498f1..0ee83fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -615,9 +615,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] @@ -1281,9 +1281,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "version_check" diff --git a/src/lib.rs b/src/lib.rs index 587abf0..fd2022c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(unexpected_cfgs)] #![no_std] use soroban_sdk::{contract, contracttype, contractimpl, Address, Env, token}; @@ -24,39 +25,112 @@ 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 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, + }; + + 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"); + } + 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); } -} +} \ No newline at end of file diff --git a/target/.rustc_info.json b/target/.rustc_info.json index 3b99dce..c2ab9d9 100644 --- a/target/.rustc_info.json +++ b/target/.rustc_info.json @@ -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":{}} \ No newline at end of file +{"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":{}} \ No newline at end of file From 9bc3fe18e7a034a53d54f72c9fefcd6ea2d8a0c4 Mon Sep 17 00:00:00 2001 From: onahiOMOTI Date: Fri, 20 Feb 2026 15:14:22 +0000 Subject: [PATCH 2/2] feat: add dispute resolution hook with arbiter (closes #17) --- src/lib.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index fd2022c..a05d2bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,12 +12,14 @@ 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] @@ -38,6 +40,42 @@ impl GrantContract { } // ──────────────────────────────────────────────── + 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, @@ -68,6 +106,7 @@ impl GrantContract { last_claim_time: env.ledger().timestamp(), is_paused: false, token, + dispute_active: false, }; env.storage() @@ -93,6 +132,11 @@ impl GrantContract { 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;