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
84 changes: 82 additions & 2 deletions src/v16_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10077,6 +10077,43 @@ pub mod processor {
.ok_or(PercolatorError::EngineArithmeticOverflow.into())
}

/// #433: reconstruct the accounting baseline for a backing domain that
/// was funded before its canonical BackingDomainLedger existed.
///
/// This is deliberately migration-specific. `read_or_new_backing_domain_ledger`
/// must keep its zero-principal semantics for other callers (notably LP-vault
/// accounting, where an uninitialized ledger means that vault has not funded
/// the domain).
fn seed_legacy_backing_domain_ledger(
ledger: &mut state::BackingDomainLedgerAccountV16,
bucket: &percolator::BackingBucketV16,
) -> ProgramResult {
let gross_principal_num = bucket
.fresh_unliened_backing_num
.checked_add(bucket.valid_liened_backing_num)
.and_then(|v| v.checked_add(bucket.consumed_liened_backing_num))
.and_then(|v| v.checked_add(bucket.impaired_liened_backing_num))
.ok_or(PercolatorError::EngineArithmeticOverflow)?;

let gross_principal_atoms = gross_principal_num / BOUND_SCALE;
let unavailable_atoms = backing_unavailable_principal_atoms(bucket)?;

// Seed principal and impairment together. Seeding principal alone would
// make consumed/impaired backing appear available under:
//
// available = principal - (loss - recovery)
ledger.total_principal_atoms = gross_principal_atoms;
ledger.cumulative_loss_atoms = unavailable_atoms;
ledger.cumulative_recovery_atoms = 0;
Comment on lines +10105 to +10107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Seed outstanding legacy backing earnings

When a legacy ledgerless bucket already has nonzero utilization_fee_earnings, this migration reconstructs principal and impairment but leaves total_earnings_atoms at zero while read_or_new_backing_domain_ledger has already pinned the earnings watermark to the bucket's current value. Subsequent syncs therefore never recognize those earnings; withdrawing them instead increments total_earnings_withdrawn_atoms above total earnings and leaves the canonical ledger inconsistent. Seed the currently outstanding earnings as part of the migration baseline.

Useful? React with 👍 / 👎.


// The unavailable amount above is a migration baseline, not a newly
// observed loss. Pin the watermark to the same snapshot so the first
// normal sync cannot book it again.
ledger.last_observed_unavailable_principal_atoms = unavailable_atoms;

Ok(())
}

fn sync_backing_domain_ledger(
ledger: &mut state::BackingDomainLedgerAccountV16,
bucket: &percolator::BackingBucketV16,
Expand Down Expand Up @@ -10535,15 +10572,19 @@ pub mod processor {
domain,
&bucket,
)?;
sync_backing_domain_ledger(&mut ledger, &bucket)?;
if initialized {
sync_backing_domain_ledger(&mut ledger, &bucket)?;
} else {
seed_legacy_backing_domain_ledger(&mut ledger, &bucket)?;
}
Some((ledger, initialized))
} else {
None
};
group
.deposit_fresh_counterparty_backing_not_atomic(domain_usize, amount, expiry_slot)
.map_err(map_v16_error)?;
if let Some((ledger, _)) = ledger_state.as_mut() {
if let Some((ledger, initialized)) = ledger_state.as_mut() {
ledger.total_principal_atoms = ledger
.total_principal_atoms
.checked_add(amount)
Expand All @@ -10552,6 +10593,18 @@ pub mod processor {
.total_deposited_atoms
.checked_add(amount)
.ok_or(PercolatorError::EngineArithmeticOverflow)?;

// #433 migration only. A top-up may satisfy provider receivable
// and reduce consumed backing. For a ledger reconstructed from
// the PRE-top-up bucket, that reduction is new capital refill,
// not a newly observed recovery. Re-baseline it so the next
// sync cannot count the refill twice.
if !*initialized {
let (_, bucket_after) =
backing_domain_parts_view(&group, domain as usize)?;
ledger.last_observed_unavailable_principal_atoms =
backing_unavailable_principal_atoms(&bucket_after)?;
}
}
group.validate_shape().map_err(map_v16_error)?;
if let (Some(data), Some((ledger, initialized))) =
Expand All @@ -10560,6 +10613,33 @@ pub mod processor {
write_or_init_backing_domain_ledger(data, ledger, *initialized)?;
}
}
if amount == 0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow zero-capital reconciliation after resolution

When a legacy ledgerless domain is already in Resolved mode, this migration branch is unreachable because the shared preflight rejects every non-Live market at line 10537. WithdrawBackingBucket explicitly supports mode 1 but now requires the ledger account, so backing in such a market cannot be reconciled or withdrawn. Permit this zero-capital initialization in the resolved wind-down state, or provide an equivalent ledger-creation path during withdrawal.

Useful? React with 👍 / 👎.

let mut market_data = market_ai.try_borrow_mut_data()?;
let (cfg, group) = state::market_view_mut(&mut market_data)?;
let authorities = domain_authorities_from_view(&group, &cfg, domain_usize)?;
expect_live_authority(&authorities.backing_bucket_authority, signer.key)?;

let mut ledger_data = ledger_ai.try_borrow_mut_data()?;
if !state::is_initialized(&ledger_data) {
let (_, bucket) = backing_domain_parts_view(&group, domain_usize)?;
let (mut ledger, initialized) = read_or_new_backing_domain_ledger(
&ledger_data,
market_ai.key.to_bytes(),
authorities.backing_bucket_authority,
domain,
&bucket,
)?;

seed_legacy_backing_domain_ledger(&mut ledger, &bucket)?;
group.validate_shape().map_err(map_v16_error)?;
write_or_init_backing_domain_ledger(
&mut ledger_data,
&ledger,
initialized,
)?;
}
}

transfer_tokens(token_program, source_token, vault_token, signer, amount_u64)?;
Ok(())
}
Expand Down
Loading
Loading