Skip to content

Commit 392613c

Browse files
Merge pull request #114 from meshackyaro/fix/issues-84-85-86-87
fix: resolve risk-pool security and storage issues (#84, #85, #86, #87)
2 parents 9f02601 + 47f605c commit 392613c

5 files changed

Lines changed: 276 additions & 119 deletions

File tree

‎contracts/risk-pool/src/lib.rs‎

Lines changed: 79 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
//! v2 — full implementation; Risk Pool is now deployable and testable.
1414
#![no_std]
1515
extern crate alloc;
16-
use alloc::string::ToString;
1716

1817
use soroban_sdk::{
1918
contract, contractimpl, contracttype, contracterror, panic_with_error,
20-
token, Address, BytesN, Env, Symbol, Vec,
19+
token, Address, Env, Symbol, Vec,
2120
};
2221

2322
pub mod types;
@@ -47,12 +46,16 @@ enum StorageKey {
4746
TotalLocked,
4847
TotalShares,
4948
AccumulatedPremium,
49+
AccumulatedBackstop,
5050
Status,
5151
LpPosition(Address),
52-
LpList,
52+
LpCount,
53+
LpAddress(u32),
5354
Lock(u128),
5455
AdminWithdrawalRequest,
5556
PendingAdmin,
57+
PolicyEngine,
58+
ClaimsProcessor,
5659
}
5760

5861
#[contracterror]
@@ -71,10 +74,10 @@ pub enum Error {
7174
AlreadyReleased = 10,
7275
Undercollateralized = 11,
7376
PoolCapExceeded = 12,
74-
TimelockPending = 13,
75-
TimelockNotReady = 14,
76-
NoPendingWithdrawal = 15,
7777
InsufficientShares = 13,
78+
TimelockPending = 14,
79+
TimelockNotReady = 15,
80+
NoPendingWithdrawal = 16,
7881
}
7982

8083
#[contract]
@@ -90,6 +93,8 @@ impl RiskPool {
9093
treasury: Address,
9194
backstop: Address,
9295
category: Symbol,
96+
policy_engine: Address,
97+
claims_processor: Address,
9398
) {
9499
if env.storage().instance().has(&StorageKey::Initialized) {
95100
panic_with_error!(&env, Error::AlreadyInitialized);
@@ -128,29 +133,33 @@ impl RiskPool {
128133
}
129134

130135
admin.require_auth();
131-
env.storage().instance().set(&StorageKey::Initialized, &true);
132-
env.storage().instance().set(&StorageKey::Admin, &admin);
133-
env.storage().instance().set(&StorageKey::UsdcToken, &usdc_token);
134-
env.storage().instance().set(&StorageKey::Treasury, &treasury);
135-
env.storage().instance().set(&StorageKey::Backstop, &backstop);
136-
env.storage().instance().set(&StorageKey::Category, &category);
137-
env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
138-
// No pending admin initially
139-
env.storage().instance().set(&StorageKey::PendingAdmin, &Address::from_uint(&env, 0));
140-
env.storage().instance().set(&StorageKey::TotalLocked, &0i128);
141-
env.storage().instance().set(&StorageKey::TotalShares, &0i128);
142-
env.storage().instance().set(&StorageKey::AccumulatedPremium, &0i128);
143-
env.storage().instance().set(&StorageKey::Status, &PoolStatus::Active);
144-
env.storage().instance().set(&StorageKey::LpList, &Vec::<Address>::new(&env));
136+
env.storage().instance().set(&StorageKey::Initialized, &true);
137+
env.storage().instance().set(&StorageKey::Admin, &admin);
138+
env.storage().instance().set(&StorageKey::UsdcToken, &usdc_token);
139+
env.storage().instance().set(&StorageKey::Treasury, &treasury);
140+
env.storage().instance().set(&StorageKey::Backstop, &backstop);
141+
env.storage().instance().set(&StorageKey::Category, &category);
142+
env.storage().instance().set(&StorageKey::PolicyEngine, &policy_engine);
143+
env.storage().instance().set(&StorageKey::ClaimsProcessor, &claims_processor);
144+
env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
145+
env.storage().instance().set(&StorageKey::TotalLocked, &0i128);
146+
env.storage().instance().set(&StorageKey::TotalShares, &0i128);
147+
env.storage().instance().set(&StorageKey::AccumulatedPremium, &0i128);
148+
env.storage().instance().set(&StorageKey::AccumulatedBackstop, &0i128);
149+
env.storage().instance().set(&StorageKey::Status, &PoolStatus::Active);
150+
env.storage().instance().set(&StorageKey::LpCount, &0u32);
151+
// PendingAdmin is absent until propose_new_admin is called; no init needed.
145152

146153
env.events().publish(
147154
(Symbol::new(&env, "initialized"),),
148155
Initialized {
149-
admin: admin.clone(),
150-
usdc_token: usdc_token.clone(),
151-
treasury: treasury.clone(),
152-
backstop: backstop.clone(),
153-
category: category.clone(),
156+
admin: admin.clone(),
157+
usdc_token: usdc_token.clone(),
158+
treasury: treasury.clone(),
159+
backstop: backstop.clone(),
160+
category: category.clone(),
161+
policy_engine: policy_engine.clone(),
162+
claims_processor: claims_processor.clone(),
154163
},
155164
);
156165
}
@@ -195,10 +204,10 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
195204
pos
196205
}
197206
None => {
198-
let mut lp_list: Vec<Address> = env.storage().instance()
199-
.get(&StorageKey::LpList).unwrap_or_else(|| Vec::new(&env));
200-
lp_list.push_back(provider.clone());
201-
env.storage().instance().set(&StorageKey::LpList, &lp_list);
207+
let count: u32 = env.storage().instance()
208+
.get(&StorageKey::LpCount).unwrap_or(0);
209+
env.storage().persistent().set(&StorageKey::LpAddress(count), &provider);
210+
env.storage().instance().set(&StorageKey::LpCount, &(count + 1));
202211
LpPosition {
203212
provider: provider.clone(),
204213
deposited: amount,
@@ -229,6 +238,7 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
229238
provider.require_auth();
230239
// Guard: check for zero or negative shares input
231240
if shares <= 0 { panic_with_error!(&env, Error::ZeroAmount); }
241+
Self::assert_active(&env);
232242

233243
let lp_key = StorageKey::LpPosition(provider.clone());
234244
let mut position: LpPosition = env.storage().persistent()
@@ -293,6 +303,10 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
293303
.get(&StorageKey::AccumulatedPremium).unwrap_or(0);
294304
env.storage().instance().set(&StorageKey::AccumulatedPremium, &(acc + lp_share));
295305

306+
let acc_backstop: i128 = env.storage().instance()
307+
.get(&StorageKey::AccumulatedBackstop).unwrap_or(0);
308+
env.storage().instance().set(&StorageKey::AccumulatedBackstop, &(acc_backstop + backstop_share));
309+
296310
env.events().publish(
297311
(Symbol::new(&env, "premium_distributed"),),
298312
PremiumDistributed {
@@ -377,7 +391,7 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
377391
// ── Capital locks ─────────────────────────────────────────────────────────
378392

379393
pub fn lock_for_policy(env: Env, caller: Address, policy_id: u128, amount: i128) {
380-
Self::require_admin(&env, &caller);
394+
Self::require_protocol_caller(&env, &caller);
381395
// Guard: check for zero or negative lock amount input
382396
if amount <= 0 { panic_with_error!(&env, Error::ZeroAmount); }
383397

@@ -404,7 +418,7 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
404418
}
405419

406420
pub fn release_for_claim(env: Env, caller: Address, policy_id: u128) {
407-
Self::require_admin(&env, &caller);
421+
Self::require_protocol_caller(&env, &caller);
408422
let mut lock: CapitalLock = env.storage().persistent()
409423
.get(&StorageKey::Lock(policy_id))
410424
.unwrap_or_else(|| panic_with_error!(&env, Error::LockNotFound));
@@ -428,7 +442,7 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
428442
}
429443

430444
pub fn release_for_expiry(env: Env, caller: Address, policy_id: u128) {
431-
Self::require_admin(&env, &caller);
445+
Self::require_protocol_caller(&env, &caller);
432446
let mut lock: CapitalLock = env.storage().persistent()
433447
.get(&StorageKey::Lock(policy_id))
434448
.unwrap_or_else(|| panic_with_error!(&env, Error::LockNotFound));
@@ -443,12 +457,13 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
443457

444458
pub fn get_stats(env: Env) -> PoolStats {
445459
PoolStats {
446-
category: env.storage().instance().get(&StorageKey::Category).unwrap(),
447-
total_deposited: env.storage().instance().get(&StorageKey::TotalDeposited).unwrap_or(0),
448-
total_locked: env.storage().instance().get(&StorageKey::TotalLocked).unwrap_or(0),
449-
total_shares: env.storage().instance().get(&StorageKey::TotalShares).unwrap_or(0),
450-
accumulated_premium: env.storage().instance().get(&StorageKey::AccumulatedPremium).unwrap_or(0),
451-
status: env.storage().instance().get(&StorageKey::Status).unwrap_or(PoolStatus::Active),
460+
category: env.storage().instance().get(&StorageKey::Category).unwrap(),
461+
total_deposited: env.storage().instance().get(&StorageKey::TotalDeposited).unwrap_or(0),
462+
total_locked: env.storage().instance().get(&StorageKey::TotalLocked).unwrap_or(0),
463+
total_shares: env.storage().instance().get(&StorageKey::TotalShares).unwrap_or(0),
464+
accumulated_premium: env.storage().instance().get(&StorageKey::AccumulatedPremium).unwrap_or(0),
465+
accumulated_backstop: env.storage().instance().get(&StorageKey::AccumulatedBackstop).unwrap_or(0),
466+
status: env.storage().instance().get(&StorageKey::Status).unwrap_or(PoolStatus::Active),
452467
}
453468
}
454469

@@ -469,31 +484,28 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
469484
}
470485

471486
pub fn get_lp_count(env: Env) -> u32 {
472-
let list: Vec<Address> = env.storage().instance()
473-
.get(&StorageKey::LpList)
474-
.unwrap_or_else(|| Vec::new(&env));
475-
list.len()
487+
env.storage().instance().get(&StorageKey::LpCount).unwrap_or(0)
476488
}
477489

478490
pub fn get_lp_list(env: Env, offset: Option<u32>, limit: Option<u32>) -> PaginatedLps {
479-
let list: Vec<Address> = env.storage().instance()
480-
.get(&StorageKey::LpList)
481-
.unwrap_or_else(|| Vec::new(&env));
482-
let total_count = list.len();
483-
491+
let total_count: u32 = env.storage().instance()
492+
.get(&StorageKey::LpCount).unwrap_or(0);
493+
484494
let offset_val = offset.unwrap_or(0);
485495
let limit_val = core::cmp::min(limit.unwrap_or(100), 500);
486-
496+
487497
let mut paginated = Vec::new(&env);
488498
if offset_val < total_count {
489499
let end = core::cmp::min(offset_val + limit_val, total_count);
490500
for i in offset_val..end {
491-
if let Some(addr) = list.get(i) {
501+
if let Some(addr) = env.storage().persistent()
502+
.get::<_, Address>(&StorageKey::LpAddress(i))
503+
{
492504
paginated.push_back(addr);
493505
}
494506
}
495507
}
496-
508+
497509
PaginatedLps {
498510
lps: paginated,
499511
total_count,
@@ -643,19 +655,16 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
643655
pub fn accept_admin(env: Env, admin: Address) {
644656
let pending_admin: Address = env.storage().instance()
645657
.get(&StorageKey::PendingAdmin)
646-
.unwrap_or_else(|| Address::from_uint(&env, 0));
658+
.unwrap_or_else(|| panic_with_error!(&env, Error::Unauthorized));
647659
// Only the pending admin can accept
648660
if admin != pending_admin {
649661
panic_with_error!(&env, Error::Unauthorized);
650662
}
651663
admin.require_auth();
652-
let current_admin: Address = env.storage().instance()
653-
.get(&StorageKey::Admin)
654-
.unwrap_or_else(|| panic_with_error!(env, Error::NotInitialized));
655664
// Update admin
656665
env.storage().instance().set(&StorageKey::Admin, &admin);
657666
// Clear the proposal
658-
env.storage().instance().set(&StorageKey::PendingAdmin, &Address::from_uint(&env, 0));
667+
env.storage().instance().remove(&StorageKey::PendingAdmin);
659668
// Emit event
660669
env.events().publish(
661670
(Symbol::new(&env, "admin_updated"),),
@@ -665,6 +674,21 @@ env.storage().instance().set(&StorageKey::TotalDeposited, &0i128);
665674
);
666675
}
667676

677+
/// Enforces that only admin, the registered policy engine, or the registered
678+
/// claims processor may call capital-lock functions.
679+
fn require_protocol_caller(env: &Env, caller: &Address) {
680+
let admin: Address = env.storage().instance().get(&StorageKey::Admin)
681+
.unwrap_or_else(|| panic_with_error!(env, Error::NotInitialized));
682+
let pe: Address = env.storage().instance().get(&StorageKey::PolicyEngine)
683+
.unwrap_or_else(|| panic_with_error!(env, Error::NotInitialized));
684+
let cp: Address = env.storage().instance().get(&StorageKey::ClaimsProcessor)
685+
.unwrap_or_else(|| panic_with_error!(env, Error::NotInitialized));
686+
if *caller != admin && *caller != pe && *caller != cp {
687+
panic_with_error!(env, Error::Unauthorized);
688+
}
689+
caller.require_auth();
690+
}
691+
668692
fn require_admin(env: &Env, caller: &Address) {
669693
let admin: Address = env.storage().instance().get(&StorageKey::Admin)
670694
.unwrap_or_else(|| panic_with_error!(env, Error::NotInitialized));

0 commit comments

Comments
 (0)