forked from Veritas-Vaults-Network/soroban-guard-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
116 lines (97 loc) · 3.54 KB
/
Copy pathlib.rs
File metadata and controls
116 lines (97 loc) · 3.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! VULNERABLE: No Minimum Stake Threshold
//!
//! A staking contract that accepts any positive amount, including dust-sized
//! stakes like 1 stroop. An attacker can create thousands of tiny stake
//! entries at negligible cost, bloating persistent storage and increasing
//! ledger fees for everyone.
//!
//! VULNERABILITY: `stake()` never enforces a minimum stake threshold.
//! SECURE MIRROR: `secure::SecureStaking` stores a configurable minimum stake
//! at initialization and rejects amounts below it.
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};
#[cfg(not(target_family = "wasm"))]
pub mod secure;
/// Default minimum stake used by the secure contract when initialized.
pub const MIN_STAKE: i128 = 1_000_000;
#[contracttype]
pub enum DataKey {
Stake(Address),
Admin,
MinStake,
}
fn get_stake(env: &Env, staker: &Address) -> i128 {
env.storage()
.persistent()
.get(&DataKey::Stake(staker.clone()))
.unwrap_or(0)
}
fn set_stake(env: &Env, staker: &Address, amount: i128) {
env.storage()
.persistent()
.set(&DataKey::Stake(staker.clone()), &amount);
}
#[contract]
pub struct VulnerableStaking;
#[contractimpl]
impl VulnerableStaking {
/// VULNERABLE: accepts any positive amount, including dust.
///
/// This creates storage pollution opportunities because even tiny stakes
/// are recorded as persistent entries.
pub fn stake(env: Env, staker: Address, amount: i128) {
staker.require_auth();
assert!(amount > 0, "amount must be positive");
let current = get_stake(&env, &staker);
set_stake(&env, &staker, current + amount);
}
pub fn balance(env: Env, staker: Address) -> i128 {
get_stake(&env, &staker)
}
}
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{testutils::Address as _, Address, Env};
fn setup() -> (Env, VulnerableStakingClient<'static>, Address) {
let env = Env::default();
env.mock_all_auths();
let id = env.register_contract(None, VulnerableStaking);
let client = VulnerableStakingClient::new(&env, &id);
let staker = Address::generate(&env);
(env, client, staker)
}
/// Demonstrates the bug: a 1-stroop stake succeeds and records storage.
#[test]
fn test_one_stroop_succeeds() {
let (_env, client, staker) = setup();
client.stake(&staker, &1);
assert_eq!(client.balance(&staker), 1);
}
/// Secure version rejects stakes below the configured minimum.
#[test]
#[should_panic(expected = "stake below minimum")]
fn test_secure_rejects_below_minimum() {
let env = Env::default();
env.mock_all_auths();
let id = env.register_contract(None, secure::SecureStaking);
let client = secure::SecureStakingClient::new(&env, &id);
let admin = Address::generate(&env);
let staker = Address::generate(&env);
client.initialize(&admin, &MIN_STAKE);
client.stake(&staker, &(MIN_STAKE - 1));
}
/// Secure version accepts exactly MIN_STAKE.
#[test]
fn test_secure_accepts_exact_minimum() {
let env = Env::default();
env.mock_all_auths();
let id = env.register_contract(None, secure::SecureStaking);
let client = secure::SecureStakingClient::new(&env, &id);
let admin = Address::generate(&env);
let staker = Address::generate(&env);
client.initialize(&admin, &MIN_STAKE);
client.stake(&staker, &MIN_STAKE);
assert_eq!(client.balance(&staker), MIN_STAKE);
}
}