Skip to content
Closed
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
1 change: 1 addition & 0 deletions investment_vault/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,7 @@ impl InvestmentVault {
.get(&VaultKey::VolumeTierFeeBps)
.unwrap_or(0);
(threshold, bps)
}
// ── Per-project investment cap (#32) ──────────────────────────────────────

/// Set the maximum total USDC the vault may invest in any single project. Admin-only.
Expand Down
4 changes: 2 additions & 2 deletions investment_vault/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ pub enum VaultError {
/// batch_deposit received an empty investor list (#178).
EmptyBatchDeposit = 41,
/// Share transfers are blocked because a funding round is active (#38).
FundingRoundActive = 41,
FundingRoundActive = 42,
/// Funding would push cumulative investment in a project above its per-project cap (#32).
InvestmentCapExceeded = 41,
InvestmentCapExceeded = 43,
}

#[contracttype]
Expand Down
74 changes: 0 additions & 74 deletions project_registry/src/logic.rs
Original file line number Diff line number Diff line change
@@ -1,75 +1 @@
use crate::types::{CertificationStatus, ProjectData};
use soroban_sdk::{Address, Env, String};

/// Interest-rate calculation for green-bond projects.
///
/// Heliobond uses a two-dimensional scoring model to determine each project's
/// borrowing cost:
///
/// # Credit Quality vs Green Impact
///
/// | Dimension | Range | Purpose |
/// |----------------- |--------|---------|
/// | `credit_quality` | 0–100 | Reflects the borrower's ability to repay — backed by on-chain collateral, creator reputation, and certification status. |
/// | `green_impact` | 0–100 | Reflects the environmental benefit of the project — verified via oracle data and third-party certification. |
///
/// Both scores are 0–100 unsigned integers and are always set and updated
/// together via `update_impact_score`, or independently via
/// `update_credit_quality_score`.
///
/// # How each score affects the interest rate
///
/// The two scores are averaged into a single 0–100 combined value:
///
/// ```text
/// combined_score = (credit_quality + green_impact) / 2 // integer division
/// ```
///
/// This combined score drives the annualised interest rate via the formula:
///
/// ```text
/// discount = combined_score × MAX_DISCOUNT_BPS / 100
/// rate = max(BASE_RATE_BPS − discount, 0)
/// ```
///
/// - A project with **both scores at 100** earns the maximum discount
/// (MAX_DISCOUNT_BPS = 500 bps) and pays the minimum rate (500 bps = 5 %).
/// - A project with **both scores at 0** pays the base rate (BASE_RATE_BPS =
/// 1 000 bps = 10 %).
/// - A project with credit_quality = 100 and green_impact = 0 (or vice versa)
/// earns half the maximum discount (250 bps) and pays 7.5 %.
///
/// The averaging means neither score alone can drive the rate to its floor —
/// a project must excel at both creditworthiness *and* environmental impact to
/// unlock the lowest borrowing cost.
///
/// # How each score affects the funding limit
///
/// The `get_creator_funding_limit_bps` function uses `creator reputation`
/// (a separate 0–100 score set by the admin/whitelister) to cap the fraction
/// of vault assets a creator's projects may receive. The *credit_quality*
/// score is **not** used to compute the funding limit — that depends only on
/// reputation. However, `credit_quality` indirectly affects funding eligibility
/// through the interest rate: a higher credit quality lowers the rate, making
/// the project more attractive to vault LPs and therefore more likely to
/// reach its funding target.
///
/// The `green_impact` score has no direct effect on funding limits, but
/// projects with higher green impact earn lower interest rates, which in turn
/// makes them more attractive to vault LPs who may choose projects based on
/// both yield and environmental impact.
pub fn calculate_interest_rate(
base_rate_bps: u32,
max_discount_bps: u32,
credit_quality: u32,
green_impact: u32,
) -> u32 {
let combined_score = (credit_quality + green_impact) / 2;
let discount = (combined_score * max_discount_bps) / 100;

if discount > base_rate_bps {
0
} else {
base_rate_bps - discount
}
}
6 changes: 6 additions & 0 deletions project_registry/src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
use crate::types::{DataKey, ProjectData, Proposal};
use soroban_sdk::{Address, Env};

#[allow(dead_code)]
pub fn read_project(env: &Env, id: u32) -> Option<ProjectData> {
env.storage().persistent().get(&DataKey::Project(id))
}

#[allow(dead_code)]
pub fn write_project(env: &Env, id: u32, project: &ProjectData) {
env.storage()
.persistent()
.set(&DataKey::Project(id), project);
}

#[allow(dead_code)]
pub fn read_proposal(env: &Env, id: u32) -> Option<Proposal> {
env.storage().persistent().get(&DataKey::Proposal(id))
}

#[allow(dead_code)]
pub fn write_proposal(env: &Env, id: u32, proposal: &Proposal) {
env.storage()
.persistent()
.set(&DataKey::Proposal(id), proposal);
}

#[allow(dead_code)]
pub fn read_whitelist(env: &Env, account: Address) -> bool {
env.storage()
.persistent()
.get(&DataKey::Whitelist(account))
.unwrap_or(false)
}

#[allow(dead_code)]
pub fn write_whitelist(env: &Env, account: Address, status: bool) {
env.storage()
.persistent()
Expand Down
Loading