Skip to content
Open
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
4 changes: 4 additions & 0 deletions investment_vault/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,8 @@ pub struct WithdrawalWindowSet {

pub fn withdrawal_window_set(env: &Env, ledgers: u32) {
WithdrawalWindowSet { ledgers }.publish(env);
}

/// Emitted when the admin opens a funding round (#38).
#[contractevent]
pub struct FundingRoundStarted {}
Expand All @@ -518,6 +520,8 @@ pub struct FundingRoundEnded {}

pub fn funding_round_ended(env: &Env) {
FundingRoundEnded {}.publish(env);
}

/// Emitted when the admin changes the per-project investment cap (#32).
#[contractevent]
pub struct InvestmentCapSet {
Expand Down
24 changes: 16 additions & 8 deletions investment_vault/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,8 @@ impl InvestmentVault {
.instance()
.get(&VaultKey::WithdrawalWindowLedgers)
.unwrap_or(1)
}

// ── Dynamic fee structure (#39) ───────────────────────────────────────────

/// Configure a two-tier volume-discount fee schedule for deposits (#39).
Expand Down Expand Up @@ -1179,6 +1181,8 @@ 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 Expand Up @@ -1475,8 +1479,18 @@ impl InvestmentVault {
panic!("flash loan callback failed");
}

Base::transfer(&env, &borrower, &MuxedAddress::from(&vault), amount + fee);
Base::burn(&env, &vault, amount);
// Repayment (#315): pull `amount + fee` shares back from the borrower.
// `Base::transfer` would call `borrower.require_auth()`, which a
// *contract* borrower cannot satisfy once its `flash_loan_callback` has
// already returned — the repayment therefore failed on a real network
// and only passed under `mock_all_auths()`. The borrower's consent is
// established by returning `true` from the callback in this same
// transaction, so the vault (as the token contract) moves the shares
// directly via the unauthenticated `Base::update` primitive.
Base::update(&env, Some(&borrower), Some(&vault), amount + fee);
// Burn the principal from the vault's own balance, leaving the fee as
// the vault's flash-loan revenue.
Base::update(&env, Some(&vault), None, amount);

events::flash_loan(&env, &initiator, &borrower, amount, fee);
}
Expand Down Expand Up @@ -2092,12 +2106,6 @@ fn check_deposit_lock(env: &Env, address: &Address) {
.persistent()
.get::<_, u64>(&VaultKey::LastDeposit(address.clone()))
{
let window: u32 = env
.storage()
.instance()
.get(&VaultKey::WithdrawalWindowLedgers)
.unwrap_or(1);
if env.ledger().sequence() < last_seq.saturating_add(window) {
if env.ledger().timestamp() < deposited_at + MIN_LOCK_PERIOD {
panic_with_error!(env, VaultError::DepositLocked);
}
Expand Down
2 changes: 2 additions & 0 deletions investment_vault/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2586,6 +2586,8 @@ fn test_volume_fee_tier_is_admin_only() {
},
}]);
s.vault_client.set_volume_fee_tier(&500_0000000i128, &50u32);
}

// ── #179: convert_to_shares() overflow guard on extremely large deposits ──────

/// Verify that `convert_to_shares` panics (rather than silently wrapping) when
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
Loading