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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"bounty",
"grants",
"treasury",
"math",
"governance",
"vetoken",
"pool",
Expand All @@ -31,7 +32,6 @@ exclude = [
"timelock",
"multisig",
"governance",
"math",
"snapshot_harness",
"xtask",
]
Expand Down
1 change: 1 addition & 0 deletions backstop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ crate-type = ["lib", "cdylib", "rlib"]
[dependencies]
soroban-sdk = { workspace = true }
refract-common = { path = "../common" }
refract-math = { path = "../math" }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
Expand Down
49 changes: 23 additions & 26 deletions backstop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
#![no_std]
#![allow(clippy::disallowed_methods)]

use refract_math::{mul_div_floor, saturating_add, saturating_sub, split_bps, BPS_DENOM, SCALE_1E7};
use soroban_sdk::{
contract, contracterror, contractimpl, contracttype, symbol_short, token, Address, Env,
IntoVal, Symbol, Vec,
Expand All @@ -78,9 +79,6 @@ pub const SHARE_SCALE: i128 = 10_000_000;
/// typical amounts.
pub const LOSS_INDEX_SCALE: i128 = 100_000_000_000_000;

/// Basis-point denominator used for premium-share and ratio configuration.
const BPS_DENOM: i128 = 10_000;

/// 1e7 fixed-point scaling factor used for intermediate ratio math.
const FP_SCALE: i128 = 10_000_000;

Expand Down Expand Up @@ -762,13 +760,17 @@ impl RefractBackstop {

pub fn tranche_share_price(env: Env, tranche: Tranche) -> Result<i128, BackstopError> {
let state = Self::_tranche_state(&env, tranche)?;
Ok(Self::quote_withdrawal(10_000_000, state.total_shares, state.total_assets))
Ok(Self::quote_withdrawal(SCALE_1E7, state.total_shares, state.total_assets))
}

pub fn senior_coverage_ratio(env: Env) -> Result<i128, BackstopError> {
let junior = Self::_tranche_state(&env, Tranche::Junior)?.total_assets;
let senior = Self::_tranche_state(&env, Tranche::Senior)?.total_assets;
Ok(if senior == 0 { 0 } else { junior * BPS_DENOM / senior })
Ok(if senior == 0 {
0
} else {
mul_div_floor(junior, BPS_DENOM, senior).unwrap_or(i128::MAX)
})
}

pub fn unabsorbed_shortfall(env: Env) -> i128 {
Expand All @@ -785,13 +787,21 @@ impl RefractBackstop {
let config = Self::get_config(&env)?;
let mut state = Self::get_state(&env)?;

let cut = amount * config.premium_share_bps / BPS_DENOM;
let junior_bps: i128 = env.storage().instance().get(&DataKey::JuniorPremiumBps).unwrap_or(BPS_DENOM / 2);
let junior_credit = Self::_credit_mature_premium(&env, Tranche::Junior, cut * junior_bps / BPS_DENOM)?;
let senior_credit = Self::_credit_mature_premium(&env, Tranche::Senior, cut - cut * junior_bps / BPS_DENOM)?;
let credited = junior_credit + senior_credit;
state.premium_income += credited;
state.total_capital += credited;
let cut = split_bps(amount, config.premium_share_bps)
.map_err(|_| BackstopError::InvalidPremiumShare)?
.cut;
let junior_bps: i128 = env
.storage()
.instance()
.get(&DataKey::JuniorPremiumBps)
.unwrap_or(BPS_DENOM / 2);
let tranche_split = split_bps(cut, junior_bps)
.map_err(|_| BackstopError::InvalidPremiumSplit)?;
let junior_credit = Self::_credit_mature_premium(&env, Tranche::Junior, tranche_split.cut)?;
let senior_credit = Self::_credit_mature_premium(&env, Tranche::Senior, tranche_split.rest)?;
let credited = saturating_add(junior_credit, senior_credit);
state.premium_income = saturating_add(state.premium_income, credited);
state.total_capital = saturating_add(state.total_capital, credited);
env.storage().instance().set(&DataKey::State, &state);

// Interactions: pull the stakers' premium slice while the lock is held.
Expand All @@ -810,17 +820,4 @@ impl RefractBackstop {

pub fn shares_of(env: Env, staker: Address) -> i128 {
let mut total = 0;
for tranche in [Tranche::Junior, Tranche::Senior] {
for mature in [false, true] {
let shares: i128 = env
.storage()
.persistent()
.get(&DataKey::Shares(staker.clone(), tranche, mature))
.unwrap_or(0);
total += shares;
}
}
total
}

pub fn state(env: Env) -> Result<BackstopState, BackstopError>
for tranche in [Tranche
1 change: 1 addition & 0 deletions bounty/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ crate-type = ["lib", "cdylib", "rlib"]

[dependencies]
soroban-sdk = { workspace = true }
refract-math = { path = "../math" }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
Expand Down
49 changes: 23 additions & 26 deletions bounty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
//! is intentionally no admin override capable of redirecting an awarded
//! commitment payout.

use refract_math::{checked_add, checked_sub};
use soroban_sdk::{
contract, contracterror, contractimpl, contracttype, symbol_short, token, Address, BytesN, Env,
};
Expand Down Expand Up @@ -304,9 +305,10 @@ impl RefractBountyEscrow {
return Err(BountyError::InvalidAmount);
}
let token = Self::token_addr(&env)?;
token::Client::new(&env, &token).transfer(&from, &env.current_contract_address(), &amount);
let balance = Self::raw_balance(&env)?;
env.storage().instance().set(&DataKey::Balance, &(balance + amount));
let new_balance = checked_add(balance, amount).map_err(|_| BountyError::InvalidAmount)?;
token::Client::new(&env, &token).transfer(&from, &env.current_contract_address(), &amount);
env.storage().instance().set(&DataKey::Balance, &new_balance);
Ok(())
}

Expand Down Expand Up @@ -346,6 +348,8 @@ impl RefractBountyEscrow {

// Deduct from total balance.
let balance = Self::raw_balance(&env)?;
let new_balance =
checked_sub(balance, amount).map_err(|_| BountyError::InsufficientBalance)?;
// Transfer before updating state to surface transfer errors early,
// but the balance is updated after so a revert leaves state consistent.
let token = Self::token_addr(&env)?;
Expand All @@ -354,7 +358,7 @@ impl RefractBountyEscrow {
&researcher,
&amount,
);
env.storage().instance().set(&DataKey::Balance, &(balance - amount));
env.storage().instance().set(&DataKey::Balance, &new_balance);

let record = AwardRecord {
researcher,
Expand Down Expand Up @@ -406,9 +410,11 @@ impl RefractBountyEscrow {

// Reserve the funds.
let reserved = Self::raw_reserved(&env)?;
let new_reserved =
checked_add(reserved, amount).map_err(|_| BountyError::InvalidAmount)?;
env.storage()
.instance()
.set(&DataKey::Reserved, &(reserved + amount));
.set(&DataKey::Reserved, &new_reserved);

let payout_id: u64 = env
.storage()
Expand Down Expand Up @@ -469,15 +475,19 @@ impl RefractBountyEscrow {

// Release reservation.
let reserved = Self::raw_reserved(&env)?;
let new_reserved =
checked_sub(reserved, amount).map_err(|_| BountyError::InsufficientBalance)?;
env.storage()
.instance()
.set(&DataKey::Reserved, &(reserved - amount));
.set(&DataKey::Reserved, &new_reserved);

// Deduct from total balance.
let balance = Self::raw_balance(&env)?;
let new_balance =
checked_sub(balance, amount).map_err(|_| BountyError::InsufficientBalance)?;
env.storage()
.instance()
.set(&DataKey::Balance, &(balance - amount));
.set(&DataKey::Balance, &new_balance);

// Transfer last — if it fails the state update above will revert with
// it (Soroban's atomic transaction model), leaving the record Pending.
Expand Down Expand Up @@ -523,15 +533,19 @@ impl RefractBountyEscrow {

// Release reservation.
let reserved = Self::raw_reserved(&env)?;
let new_reserved =
checked_sub(reserved, amount).map_err(|_| BountyError::InsufficientBalance)?;
env.storage()
.instance()
.set(&DataKey::Reserved, &(reserved - amount));
.set(&DataKey::Reserved, &new_reserved);

// Deduct from total balance.
let balance = Self::raw_balance(&env)?;
let new_balance =
checked_sub(balance, amount).map_err(|_| BountyError::InsufficientBalance)?;
env.storage()
.instance()
.set(&DataKey::Balance, &(balance - amount));
.set(&DataKey::Balance, &new_balance);

// Transfer swept funds back to governance.
token::Client::new(&env, &record.token).transfer(
Expand Down Expand Up @@ -881,21 +895,4 @@ impl RefractBountyEscrow {
}

/// Immutable record of a rewarded disclosure, if any.
pub fn get_award(env: Env, report_hash: BytesN<32>) -> Option<AwardRecord> {
env.storage().persistent().get(&DataKey::Award(report_hash))
}

/// Pull-payout record by id.
pub fn get_payout(env: Env, payout_id: u64) -> Option<PayoutRecord> {
env.storage().persistent().get(&DataKey::Payout(payout_id))
}

/// Registered payout-address binding for a commitment, if any.
pub fn get_payout_binding(env: Env, commitment: BytesN<32>) -> Option<PayoutBindingRecord> {
env.storage()
.persistent()
.get(&DataKey::PayoutBinding(commitment))
}

/// Commitment-award record for a commitment, if any.
pub fn get_commitment_award(env: Env, commitment: BytesN<
pub fn get_award(
4 changes: 4 additions & 0 deletions coverage-floor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ branches = 80.00
[params]
lines = 90.00
branches = 82.00

[math]
lines = 95.00
branches = 90.00
1 change: 1 addition & 0 deletions grants/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ crate-type = ["lib", "cdylib"]
[dependencies]
soroban-sdk = { workspace = true }
sha2 = "0.10"
refract-math = { path = "../math" }

[dev-dependencies]
soroban-sdk = { version = "21.0.0", features = ["testutils", "alloc"] }
24 changes: 12 additions & 12 deletions grants/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,16 @@ impl GrantsModel {
pub fn accounted_liabilities(&self) -> i128 {
self.grants
.values()
.map(|grant| {
grant
.fold(0i128, |acc, grant| {
let grant_owed = grant
.milestones
.iter()
.filter(|milestone| !milestone.claimed)
.map(|milestone| milestone.amount)
.sum::<i128>()
.fold(0i128, |m_acc, milestone| {
refract_math::saturating_add(m_acc, milestone.amount)
});
refract_math::saturating_add(acc, grant_owed)
})
.sum()
}

/// Approves a new grant with `total_amount` split across `milestones`.
Expand Down Expand Up @@ -212,7 +213,8 @@ impl GrantsModel {
if *amount <= 0 {
return Err(GrantsError::InvalidMilestoneAmount);
}
sum = sum.checked_add(*amount).ok_or(GrantsError::MilestoneSumMismatch)?;
sum = refract_math::checked_add(sum, *amount)
.map_err(|_| GrantsError::MilestoneSumMismatch)?;
}
if sum != total_amount {
return Err(GrantsError::MilestoneSumMismatch);
Expand Down Expand Up @@ -279,10 +281,8 @@ impl GrantsModel {
}

milestone.confirmed = true;
grant.claimable = grant
.claimable
.checked_add(milestone.amount)
.ok_or(GrantsError::MilestoneSumMismatch)?;
grant.claimable = refract_math::checked_add(grant.claimable, milestone.amount)
.map_err(|_| GrantsError::MilestoneSumMismatch)?;

Ok(())
}
Expand Down Expand Up @@ -371,11 +371,11 @@ impl GrantsModel {
if amount <= 0 {
return Err(GrantsError::InvalidRescueAmount);
}
let excess = live_balance - self.accounted_liabilities();
let excess = refract_math::saturating_sub(live_balance, self.accounted_liabilities());
if amount > excess {
return Err(GrantsError::NoExcessFunds);
}
Ok(excess - amount)
refract_math::checked_sub(excess, amount).map_err(|_| GrantsError::NoExcessFunds)
}

fn require_governance(&self, caller: Address) -> Result<(), GrantsError> {
Expand Down
Loading
Loading