-
Notifications
You must be signed in to change notification settings - Fork 22
fixed #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fixed #64
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| {"rustc_fingerprint":6167044578903928928,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.88.0 (6b00bc388 2025-06-23)\nbinary: rustc\ncommit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc\ncommit-date: 2025-06-23\nhost: x86_64-pc-windows-msvc\nrelease: 1.88.0\nLLVM version: 20.1.5\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\pc\\.rustup\\toolchains\\1.88.0-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"6027984484328994041":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\nlib___.a\n___.dll\nC:\\Users\\pc\\.rustup\\toolchains\\1.88.0-x86_64-pc-windows-msvc\noff\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}} | ||
| {"rustc_fingerprint":9382291209947408725,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Godsm\\.rustup\\toolchains\\1.88.0-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.88.0 (6b00bc388 2025-06-23)\nbinary: rustc\ncommit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc\ncommit-date: 2025-06-23\nhost: x86_64-pc-windows-msvc\nrelease: 1.88.0\nLLVM version: 20.1.5\n","stderr":""}},"successes":{}} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win Remove generated compiler metadata from version control. This 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,11 +9,16 @@ | |
|
|
||
| use soroban_sdk::{ | ||
| contract, contracterror, contractevent, contractimpl, contracttype, symbol_short, Address, | ||
| BytesN, Env, Symbol, | ||
| BytesN, Env, IntoVal, Symbol, Val, | ||
| }; | ||
|
|
||
| const NAME: &str = "Wrapped Pi"; | ||
| const SYMBOL: &str = "wPI"; | ||
| const LEDGERS_PER_DAY: u32 = 17_280; | ||
| const PERSISTENT_ENTRY_TTL_THRESHOLD: u32 = 30 * LEDGERS_PER_DAY; | ||
| const PERSISTENT_ENTRY_TTL_EXTEND_TO: u32 = 180 * LEDGERS_PER_DAY; | ||
| const INSTANCE_TTL_THRESHOLD: u32 = 7 * LEDGERS_PER_DAY; | ||
| const INSTANCE_TTL_EXTEND_TO: u32 = 30 * LEDGERS_PER_DAY; | ||
| /// Pi Network is an SCP fork of Stellar and exposes the same Horizon REST | ||
| /// API. Native Pi amounts use 7 decimal places (1 Pi = 10_000_000 stroops), | ||
| /// identical to Stellar's native asset convention. | ||
|
|
@@ -148,14 +153,31 @@ fn is_redemption_processed(env: &Env, redemption_id: &BytesN<32>) -> bool { | |
| } | ||
|
|
||
| fn mark_redemption_processed(env: &Env, redemption_id: &BytesN<32>) { | ||
| env.storage() | ||
| .persistent() | ||
| .set(&DataKey::ProcessedRedemption(redemption_id.clone()), &true); | ||
| let key = DataKey::ProcessedRedemption(redemption_id.clone()); | ||
| env.storage().persistent().set(&key, &true); | ||
| bump_persistent_ttl(env, &key); | ||
|
Comment on lines
+156
to
+158
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift Do not let replay-protection markers expire. A processed ID is written once and then ages out after 180 days. Once absent, an old deposit can be minted again and an old redemption can be replayed. Use replay state that remains enforceable for the bridge’s full replay horizon, such as an ordered source-chain checkpoint/nonce design; periodic per-ID renewal is not sufficient at scale.
📍 Affects 1 file
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| #[contract] | ||
| pub struct WpiToken; | ||
|
|
||
| fn bump_instance_storage_ttl(env: &Env) { | ||
| env.storage() | ||
| .instance() | ||
| .extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND_TO); | ||
| } | ||
|
|
||
| fn bump_persistent_ttl<K>(env: &Env, key: &K) | ||
| where | ||
| K: IntoVal<Env, Val>, | ||
| { | ||
| env.storage().persistent().extend_ttl( | ||
| key, | ||
| PERSISTENT_ENTRY_TTL_THRESHOLD, | ||
| PERSISTENT_ENTRY_TTL_EXTEND_TO, | ||
| ); | ||
| } | ||
|
|
||
| fn read_admin(env: &Env) -> Address { | ||
| env.storage() | ||
| .instance() | ||
|
|
@@ -196,6 +218,7 @@ fn write_volume_limit_admin(env: &Env, admin: &Address) { | |
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::VolumeLimitAdmin, admin); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn read_minter(env: &Env) -> Address { | ||
|
|
@@ -218,6 +241,7 @@ fn require_minter(env: &Env) -> Address { | |
|
|
||
| fn write_minter(env: &Env, minter: &Address) { | ||
| env.storage().instance().set(&DataKey::Minter, minter); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn read_pauser(env: &Env) -> Address { | ||
|
|
@@ -240,10 +264,12 @@ fn require_pauser(env: &Env) -> Address { | |
|
|
||
| fn write_pauser(env: &Env, pauser: &Address) { | ||
| env.storage().instance().set(&DataKey::Pauser, pauser); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn write_admin(env: &Env, admin: &Address) { | ||
| env.storage().instance().set(&DataKey::Admin, admin); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn read_proposed_admin(env: &Env) -> Option<Address> { | ||
|
|
@@ -256,10 +282,12 @@ fn write_proposed_admin(env: &Env, proposed: &Address) { | |
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::ProposedAdmin, proposed); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn remove_proposed_admin(env: &Env) { | ||
| env.storage().instance().remove(&DataKey::ProposedAdmin); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn is_paused(env: &Env) -> bool { | ||
|
|
@@ -271,6 +299,7 @@ fn is_paused(env: &Env) -> bool { | |
|
|
||
| fn set_paused(env: &Env, paused: bool) { | ||
| env.storage().instance().set(&DataKey::Paused, &paused); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn is_circuit_breaker_active(env: &Env) -> bool { | ||
|
|
@@ -284,26 +313,35 @@ fn set_circuit_breaker(env: &Env, active: bool) { | |
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::CircuitBreaker, &active); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn read_balance(env: &Env, address: &Address) -> i128 { | ||
| let key = DataKey::Balance(address.clone()); | ||
| env.storage() | ||
| .instance() | ||
| .get::<DataKey, i128>(&DataKey::Balance(address.clone())) | ||
| .persistent() | ||
| .get::<DataKey, i128>(&key) | ||
| .or_else(|| env.storage().instance().get::<DataKey, i128>(&key)) | ||
| .unwrap_or(0) | ||
| } | ||
|
|
||
| fn write_balance(env: &Env, address: &Address, amount: i128) { | ||
| let key = DataKey::Balance(address.clone()); | ||
| env.storage().persistent().set(&key, &amount); | ||
| bump_persistent_ttl(env, &key); | ||
| env.storage().instance().remove(&key); | ||
| } | ||
|
|
||
| fn read_allowance_data(env: &Env, owner: &Address, spender: &Address) -> Option<AllowanceData> { | ||
| let key = DataKey::Allowance(owner.clone(), spender.clone()); | ||
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::Balance(address.clone()), &amount); | ||
| .persistent() | ||
| .get::<DataKey, AllowanceData>(&key) | ||
| .or_else(|| env.storage().instance().get::<DataKey, AllowanceData>(&key)) | ||
|
Comment on lines
319
to
+340
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Migrate existing account state before instance expiry. The fallback only preserves legacy balances/allowances until the shared instance namespace expires; inactive pre-upgrade accounts are never copied to persistent storage. Provide a one-time migration/snapshot-claim path before rollout rather than relying on each holder to perform a future write. 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| fn read_allowance(env: &Env, owner: &Address, spender: &Address) -> i128 { | ||
| let allowance = env | ||
| .storage() | ||
| .instance() | ||
| .get::<DataKey, AllowanceData>(&DataKey::Allowance(owner.clone(), spender.clone())); | ||
| let allowance = read_allowance_data(env, owner, spender); | ||
| match allowance { | ||
| Some(data) if data.expiration_ledger >= env.ledger().sequence() => data.amount, | ||
| _ => 0, | ||
|
|
@@ -317,13 +355,16 @@ fn write_allowance( | |
| amount: i128, | ||
| expiration_ledger: u32, | ||
| ) { | ||
| env.storage().instance().set( | ||
| &DataKey::Allowance(owner.clone(), spender.clone()), | ||
| let key = DataKey::Allowance(owner.clone(), spender.clone()); | ||
| env.storage().persistent().set( | ||
| &key, | ||
| &AllowanceData { | ||
| amount, | ||
| expiration_ledger, | ||
| }, | ||
| ); | ||
| bump_persistent_ttl(env, &key); | ||
| env.storage().instance().remove(&key); | ||
| } | ||
|
|
||
| fn read_total_supply(env: &Env) -> i128 { | ||
|
|
@@ -335,6 +376,7 @@ fn read_total_supply(env: &Env) -> i128 { | |
|
|
||
| fn write_total_supply(env: &Env, amount: i128) { | ||
| env.storage().instance().set(&DataKey::TotalSupply, &amount); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn is_deposit_processed(env: &Env, deposit_id: &BytesN<32>) -> bool { | ||
|
|
@@ -345,9 +387,9 @@ fn is_deposit_processed(env: &Env, deposit_id: &BytesN<32>) -> bool { | |
| } | ||
|
|
||
| fn mark_deposit_processed(env: &Env, deposit_id: &BytesN<32>) { | ||
| env.storage() | ||
| .persistent() | ||
| .set(&DataKey::ProcessedDeposit(deposit_id.clone()), &true); | ||
| let key = DataKey::ProcessedDeposit(deposit_id.clone()); | ||
| env.storage().persistent().set(&key, &true); | ||
| bump_persistent_ttl(env, &key); | ||
| } | ||
|
|
||
| fn next_redemption_nonce(env: &Env) -> Result<u64, Error> { | ||
|
|
@@ -360,6 +402,7 @@ fn next_redemption_nonce(env: &Env) -> Result<u64, Error> { | |
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::RedemptionNonce, &next); | ||
| bump_instance_storage_ttl(env); | ||
| Ok(next) | ||
| } | ||
|
|
||
|
|
@@ -384,6 +427,7 @@ fn advance_volume_generation(env: &Env) -> Result<u32, Error> { | |
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::VolumeGeneration, &next); | ||
| bump_instance_storage_ttl(env); | ||
| Ok(next) | ||
| } | ||
|
|
||
|
|
@@ -418,6 +462,7 @@ fn write_volume_bucket(env: &Env, bucket: &VolumeBucket) { | |
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::VolumeBucket(slot), bucket); | ||
| bump_instance_storage_ttl(env); | ||
| } | ||
|
|
||
| fn read_volume_window(env: &Env, config: &VolumeLimitConfig) -> Result<VolumeWindow, Error> { | ||
|
|
@@ -611,6 +656,7 @@ impl WpiToken { | |
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::VolumeLimitConfig, &config); | ||
| bump_instance_storage_ttl(&env); | ||
| VolumeLimitsConfigured { | ||
| mint_limit, | ||
| burn_limit, | ||
|
|
@@ -687,8 +733,16 @@ impl WpiToken { | |
| transfer_internal(&env, &from, &to, amount)?; | ||
| let expiration_ledger = env | ||
| .storage() | ||
| .instance() | ||
| .persistent() | ||
| .get::<DataKey, AllowanceData>(&DataKey::Allowance(from.clone(), spender.clone())) | ||
| .or_else(|| { | ||
| env.storage() | ||
| .instance() | ||
| .get::<DataKey, AllowanceData>(&DataKey::Allowance( | ||
| from.clone(), | ||
| spender.clone(), | ||
| )) | ||
| }) | ||
| .map(|data| data.expiration_ledger) | ||
| .unwrap_or(0); | ||
| write_allowance(&env, &from, &spender, allowance - amount, expiration_ledger); | ||
|
|
@@ -723,8 +777,16 @@ impl WpiToken { | |
| } | ||
| let expiration_ledger = env | ||
| .storage() | ||
| .instance() | ||
| .persistent() | ||
| .get::<DataKey, AllowanceData>(&DataKey::Allowance(from.clone(), spender.clone())) | ||
| .or_else(|| { | ||
| env.storage() | ||
| .instance() | ||
| .get::<DataKey, AllowanceData>(&DataKey::Allowance( | ||
| from.clone(), | ||
| spender.clone(), | ||
| )) | ||
| }) | ||
| .map(|data| data.expiration_ledger) | ||
| .unwrap_or(0); | ||
| write_allowance(&env, &from, &spender, allowance - amount, expiration_ledger); | ||
|
|
@@ -865,6 +927,14 @@ impl WpiToken { | |
| Ok(()) | ||
| } | ||
|
|
||
| /// Admin-only keeper hook for periods where the contract is idle and | ||
| /// instance storage would not otherwise be refreshed by normal writes. | ||
| pub fn bump_instance_ttl(env: Env) -> Result<(), Error> { | ||
| require_admin(&env); | ||
| bump_instance_storage_ttl(&env); | ||
| Ok(()) | ||
| } | ||
|
|
||
| pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) -> Result<(), Error> { | ||
| require_admin(&env); | ||
| env.deployer().update_current_contract_wasm(new_wasm_hash); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Document individual persistent-entry expiry and recovery.
Persistent storage avoids the shared instance cliff, but balances and allowances still expire after their own TTL if they are not written. State the renewal period, that reads do not renew it, and the required archival/restore and rent-funding procedure.
🤖 Prompt for AI Agents