Skip to content

Commit 4642bfb

Browse files
committed
Managed Fund: reject Pyth prices with a wide confidence interval
load_price in all three copies (Anchor v2, Anchor v1, Quasar) now reads the PriceUpdateV2 `conf` field (offset 81) and rejects a price whose interval exceeds MAX_CONFIDENCE_BPS (100 bps, 1% of the price) with the new OracleConfidenceTooWide error. Deposit prices shares and rebalance sets its swap floor from the oracle, so a wide band moved value between depositors or loosened the floor. Withdraw reads no price and still pays out in kind. test_wide_confidence_price_rejected in each copy widens NVDAx's band to 2% and asserts deposit and rebalance fail, withdraw succeeds, and a band of exactly 1% is accepted. The app IDLs gain the error; READMEs and CHANGELOGs follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ji7D1gLxYnd1cdd6NMXQL
1 parent b3f2248 commit 4642bfb

18 files changed

Lines changed: 363 additions & 25 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ All notable changes to this repository are documented here.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [2026-10-03] - Managed Fund rejects wide-confidence prices
8+
9+
### Fixed
10+
11+
- `finance/managed-fund` (Anchor v2, Anchor v1, Quasar) ignored the
12+
confidence interval on its Pyth prices. `load_price` now rejects a price
13+
whose interval exceeds 1% of the price (`MAX_CONFIDENCE_BPS`, new
14+
`OracleConfidenceTooWide` error), so deposit and rebalance are refused while
15+
publishers disagree; withdraw reads no price and still pays out in kind.
16+
Tested by `test_wide_confidence_price_rejected` in each copy. The web apps'
17+
IDLs gain the error.
18+
719
## [2026-10-03] - Fundraiser: `close_contributor` is `close_contribution`
820

921
### Changed

‎finance/managed-fund/anchor-v1/CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 2026-10-03
4+
5+
- **Prices with a wide confidence interval are rejected.** Pyth reports each price with a confidence interval (`conf`, offset 81), and `load_price` ignored it, so a price the publishers disagreed on by several percent was used as if it were exact. Deposits price shares from that price and rebalance sets its swap floor from it, so a wide band moves value between depositors or loosens the floor by the same amount. `load_price` now rejects a price whose interval exceeds `MAX_CONFIDENCE_BPS` (100 bps, 1% of the price) with the new `OracleConfidenceTooWide` error. `withdraw` reads no price and is unaffected, so investors can still leave in kind. The limit is a program constant, like the 60-second staleness window; prop-amm and perpetual-futures store theirs per market. Tested by `test_wide_confidence_price_rejected`. The web app's IDL gains the error.
6+
37
## 2026-10-01
48

59
- **Valuation scales by each asset's decimals and each feed's exponent.** The fund valued an asset as `amount × price / 10⁸`, which is right only when the asset has USDC's 6 decimals and its Pyth feed has exponent −8, and nothing checked either. An eight-decimal asset would have been valued 100 times too high, so a later depositor would have bought almost no shares; Pyth's US equity feeds use exponent −5, a further factor of 1,000. `load_price` now reads the exponent (offset 89) and returns an `OraclePrice`, `AssetConfig` records the mint's `decimals` and `Fund` the USDC mint's `usdc_decimals`, and `deposit` and `rebalance` value and size swaps with `asset_value_in_usdc` and `usdc_to_asset_amount`, which scale by `10^(usdc_decimals + exponent − asset_decimals)`. `PYTH_PRICE_PRECISION` is removed. The mock router's `usdc_per_token` is now USDC minor units per whole token, and its swaps scale by the asset mint's decimals. Tested by `test_valuation_scales_by_decimals_and_exponent`, which runs the story with an eight-decimal TSLAx on an exponent −5 feed.

‎finance/managed-fund/anchor-v1/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Because the asset set is dynamic, `deposit` must value *every* asset. The assets
3333

3434
Referencing every asset has a transaction-size cost: `deposit` pulls in `14 + 5N` accounts and `withdraw` `10 + 4N`, where `N` is the asset count. That stays within Solana's 128-account transaction lock limit at the `MAX_ASSETS` cap of 16 (94 accounts for `deposit`), but a basket beyond roughly three assets no longer fits a legacy transaction's 1232-byte limit, so the client must send a v0 transaction with an [Address Lookup Table](https://docs.anza.xyz/proposals/versioned-transactions).
3535

36-
Prices come from [Pyth Network](https://pyth.network/) `PriceUpdateV2` accounts. A 60-second staleness window is enforced; zero or negative prices are rejected, and so is any price posted at or before the last cluster restart (`PricePredatesRestart`), which the seconds check alone cannot catch after a halt.
36+
Prices come from [Pyth Network](https://pyth.network/) `PriceUpdateV2` accounts. A 60-second staleness window is enforced; zero or negative prices are rejected, and so is any price posted at or before the last cluster restart (`PricePredatesRestart`), which the seconds check alone cannot catch after a halt. A price whose confidence interval is wider than 1% of the price (`MAX_CONFIDENCE_BPS`, 100) is rejected too (`OracleConfidenceTooWide`): deposits price shares from the oracle and rebalance sets its swap floor from it, so a price the publishers disagree on by more than a typical slippage tolerance is not one to trade on. `withdraw` reads no price, so investors can always leave in kind while deposits and rebalances wait for the band to narrow.
3737

3838
### Shares
3939

@@ -161,7 +161,7 @@ cargo build-sbf --manifest-path programs/managed-fund/Cargo.toml
161161
cargo test --manifest-path programs/managed-fund/Cargo.toml
162162
```
163163

164-
Tests live in `programs/managed-fund/tests/managed_fund.rs` and use [LiteSVM](https://github.com/LiteSVM/litesvm). Both `.so` files are loaded from `target/deploy/`, so build before testing. The suite covers the full lifecycle end to end (deposit with auto-deployment, a price move, rebalance back to target, a second depositor priced at the new NAV, a year's fee, in-kind withdrawal), retiring an asset with `set_weight` and reallocating to reopen deposits, and the rejection paths: unapproved asset, weight overflow, over-cap fee and slippage, oracle-bounded deposit slippage, an under-allocated fund, non-manager `set_weight`, unregistered router, and incomplete asset accounts on deposit and rebalance. The rebalance tests sign as a stranger, since anyone may call it: `test_rebalance_refuses_fund_at_target`, `test_rebalance_refuses_drift_below_threshold` and `test_rebalance_cannot_churn` check that a fund at its targets, or within its threshold, or just rebalanced, cannot be traded; `test_rebalance_refuses_buying_overweight_asset`, `test_rebalance_sells_retired_asset` and `test_initialize_rejects_threshold_out_of_range` cover the rest of its rules. `test_valuation_scales_by_decimals_and_exponent` runs the story with an eight-decimal TSLAx priced by an exponent −5 feed and gets the same share counts. `test_full_lifecycle` checks after every step that the recorded holdings equal the vaults' balances. `test_donation_does_not_inflate_share_price` runs the first-depositor attack (a one-minor-unit deposit, a 1,000 USDC transfer straight into the USDC vault, then a 1,000 USDC deposit with no `minimum_shares` floor) and checks the victim gets exactly the shares they would have got without the donation. `test_deposit_rejects_leg_that_buys_nothing` and `test_rebalance_ignores_donations` pin the other two guards: the second checks that donated tokens can neither force a rebalance nor be spent by one.
164+
Tests live in `programs/managed-fund/tests/managed_fund.rs` and use [LiteSVM](https://github.com/LiteSVM/litesvm). Both `.so` files are loaded from `target/deploy/`, so build before testing. The suite covers the full lifecycle end to end (deposit with auto-deployment, a price move, rebalance back to target, a second depositor priced at the new NAV, a year's fee, in-kind withdrawal), retiring an asset with `set_weight` and reallocating to reopen deposits, and the rejection paths: unapproved asset, weight overflow, over-cap fee and slippage, oracle-bounded deposit slippage, an under-allocated fund, non-manager `set_weight`, unregistered router, and incomplete asset accounts on deposit and rebalance. The rebalance tests sign as a stranger, since anyone may call it: `test_rebalance_refuses_fund_at_target`, `test_rebalance_refuses_drift_below_threshold` and `test_rebalance_cannot_churn` check that a fund at its targets, or within its threshold, or just rebalanced, cannot be traded; `test_rebalance_refuses_buying_overweight_asset`, `test_rebalance_sells_retired_asset` and `test_initialize_rejects_threshold_out_of_range` cover the rest of its rules. `test_valuation_scales_by_decimals_and_exponent` runs the story with an eight-decimal TSLAx priced by an exponent −5 feed and gets the same share counts. `test_wide_confidence_price_rejected` widens NVDAx's confidence interval to 2% of its price and checks that deposit and rebalance fail with `OracleConfidenceTooWide`, that withdraw still pays out in kind, and that a band of exactly 1% is accepted. `test_full_lifecycle` checks after every step that the recorded holdings equal the vaults' balances. `test_donation_does_not_inflate_share_price` runs the first-depositor attack (a one-minor-unit deposit, a 1,000 USDC transfer straight into the USDC vault, then a 1,000 USDC deposit with no `minimum_shares` floor) and checks the victim gets exactly the shares they would have got without the donation. `test_deposit_rejects_leg_that_buys_nothing` and `test_rebalance_ignores_donations` pin the other two guards: the second checks that donated tokens can neither force a rebalance nor be spent by one.
165165

166166
## FAQ
167167

‎finance/managed-fund/anchor-v1/app/src/idl/managed_fund.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,11 @@
10471047
"code": 6031,
10481048
"name": "NotUnderweight",
10491049
"msg": "The asset to buy is not below its target weight"
1050+
},
1051+
{
1052+
"code": 6032,
1053+
"name": "OracleConfidenceTooWide",
1054+
"msg": "Pyth price confidence interval is too wide to trust"
10501055
}
10511056
],
10521057
"types": [

‎finance/managed-fund/anchor-v1/programs/managed-fund/src/error.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,6 @@ pub enum FundError {
6666
DriftBelowThreshold,
6767
#[msg("The asset to buy is not below its target weight")]
6868
NotUnderweight,
69+
#[msg("Pyth price confidence interval is too wide to trust")]
70+
OracleConfidenceTooWide,
6971
}

‎finance/managed-fund/anchor-v1/programs/managed-fund/src/oracle.rs‎

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use crate::error::FundError;
66
/// Byte offset of `price` (i64) inside a Pyth PriceUpdateV2 account:
77
/// 8 discriminator + 32 write_authority + 1 verification_level + 32 feed_id = 73
88
const PYTH_PRICE_OFFSET: usize = 73;
9+
/// Byte offset of `conf` (u64), the confidence interval: +8 bytes after price.
10+
const PYTH_CONF_OFFSET: usize = PYTH_PRICE_OFFSET + 8; // 81
911
/// Byte offset of `exponent` (i32): price(8) + conf(8) = +16 bytes after price.
1012
const PYTH_EXPONENT_OFFSET: usize = PYTH_PRICE_OFFSET + 8 + 8; // 89
1113
/// Byte offset of `publish_time` (i64):
@@ -16,6 +18,12 @@ const PYTH_PUBLISH_TIME_OFFSET: usize = PYTH_PRICE_OFFSET + 8 + 8 + 4; // 93
1618
const PYTH_POSTED_SLOT_OFFSET: usize = PYTH_PUBLISH_TIME_OFFSET + 8 + 8 + 8 + 8; // 125
1719
/// Prices older than this (seconds) are rejected.
1820
const MAX_PRICE_AGE_SECONDS: i64 = 60;
21+
/// Widest confidence interval accepted, in basis points of the price (1%).
22+
/// Deposits price shares and rebalance sets its swap floor from the Pyth
23+
/// price, so a price that may be off by more than a typical 1% slippage
24+
/// tolerance would quietly widen that tolerance. Major feeds usually quote
25+
/// well under 0.1%; a band past 1% means the publishers disagree.
26+
const MAX_CONFIDENCE_BPS: u128 = 100;
1927

2028
/// SPL token account layout: amount is a u64 at bytes 64..72. The base layout is
2129
/// shared by the Classic Token Program and the Token Extensions Program, so this
@@ -35,8 +43,8 @@ pub struct OraclePrice {
3543
pub exponent: i32,
3644
}
3745

38-
/// Returns `(price, exponent, publish_time, posted_slot)`.
39-
fn read_pyth_raw(account_data: &[u8]) -> Result<(i64, i32, i64, u64)> {
46+
/// Returns `(price, conf, exponent, publish_time, posted_slot)`.
47+
fn read_pyth_raw(account_data: &[u8]) -> Result<(i64, u64, i32, i64, u64)> {
4048
if account_data.len() < PYTH_POSTED_SLOT_OFFSET + 8 {
4149
return err!(FundError::InvalidPriceFeed);
4250
}
@@ -45,6 +53,11 @@ fn read_pyth_raw(account_data: &[u8]) -> Result<(i64, i32, i64, u64)> {
4553
.try_into()
4654
.map_err(|_| FundError::InvalidPriceFeed)?,
4755
);
56+
let conf = u64::from_le_bytes(
57+
account_data[PYTH_CONF_OFFSET..PYTH_CONF_OFFSET + 8]
58+
.try_into()
59+
.map_err(|_| FundError::InvalidPriceFeed)?,
60+
);
4861
let exponent = i32::from_le_bytes(
4962
account_data[PYTH_EXPONENT_OFFSET..PYTH_EXPONENT_OFFSET + 4]
5063
.try_into()
@@ -60,11 +73,12 @@ fn read_pyth_raw(account_data: &[u8]) -> Result<(i64, i32, i64, u64)> {
6073
.try_into()
6174
.map_err(|_| FundError::InvalidPriceFeed)?,
6275
);
63-
Ok((price, exponent, publish_time, posted_slot))
76+
Ok((price, conf, exponent, publish_time, posted_slot))
6477
}
6578

6679
/// Validate a price feed account against the one the fund registered, then
6780
/// return its positive, fresh price. `now` is the current unix timestamp.
81+
/// A price whose confidence interval exceeds `MAX_CONFIDENCE_BPS` is rejected.
6882
/// A price posted at or before the last cluster restart is rejected too.
6983
pub fn load_price(
7084
price_feed: &AccountInfo,
@@ -74,9 +88,19 @@ pub fn load_price(
7488
require_keys_eq!(price_feed.key(), *expected_key, FundError::InvalidPriceFeed);
7589

7690
let data = price_feed.try_borrow_data()?;
77-
let (price, exponent, publish_time, posted_slot) = read_pyth_raw(&data)?;
91+
let (price, conf, exponent, publish_time, posted_slot) = read_pyth_raw(&data)?;
7892

7993
require!(price > 0, FundError::NegativePrice);
94+
95+
// Reject a price the oracle itself is unsure of: the confidence interval,
96+
// as a fraction of the price, must not exceed MAX_CONFIDENCE_BPS. `conf`
97+
// shares the price's exponent, so the ratio needs no scaling, and in u128
98+
// neither product can overflow.
99+
require!(
100+
(conf as u128) * 10_000 <= (price as u128) * MAX_CONFIDENCE_BPS,
101+
FundError::OracleConfidenceTooWide
102+
);
103+
80104
require!(
81105
now.checked_sub(publish_time)
82106
.ok_or(FundError::MathOverflow)?

‎finance/managed-fund/anchor-v1/programs/managed-fund/tests/managed_fund.rs‎

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey {
4747
}
4848

4949
/// Mock PriceUpdateV2 layout (see pyth-solana-receiver-sdk): price i64 at 73,
50-
/// publish_time i64 at 93, posted_slot u64 at 125. Exponent -8.
50+
/// conf u64 at 81, publish_time i64 at 93, posted_slot u64 at 125. Exponent -8.
5151
fn build_mock_price_update_account(
5252
price: i64,
53+
confidence: u64,
5354
exponent: i32,
5455
publish_time: i64,
5556
posted_slot: u64,
@@ -61,7 +62,7 @@ fn build_mock_price_update_account(
6162
data.push(1u8);
6263
data.extend_from_slice(&[0xEFu8; 32]);
6364
data.extend_from_slice(&price.to_le_bytes());
64-
data.extend_from_slice(&100_000u64.to_le_bytes());
65+
data.extend_from_slice(&confidence.to_le_bytes());
6566
data.extend_from_slice(&exponent.to_le_bytes());
6667
data.extend_from_slice(&publish_time.to_le_bytes());
6768
data.extend_from_slice(&(publish_time - 1).to_le_bytes());
@@ -82,7 +83,20 @@ fn set_price_feed_posted_at(svm: &mut LiteSVM, key: Pubkey, price: i64, posted_s
8283

8384
/// Write a Pyth feed with its own exponent: Pyth's US equity feeds use -5.
8485
fn write_price_feed(svm: &mut LiteSVM, key: Pubkey, price: i64, exponent: i32, posted_slot: u64) {
85-
let data = build_mock_price_update_account(price, exponent, PUBLISH_TIME, posted_slot);
86+
write_price_feed_with_confidence(svm, key, price, DEFAULT_CONFIDENCE, exponent, posted_slot);
87+
}
88+
89+
/// Write a Pyth feed with its own confidence interval, in the price's units.
90+
fn write_price_feed_with_confidence(
91+
svm: &mut LiteSVM,
92+
key: Pubkey,
93+
price: i64,
94+
confidence: u64,
95+
exponent: i32,
96+
posted_slot: u64,
97+
) {
98+
let data =
99+
build_mock_price_update_account(price, confidence, exponent, PUBLISH_TIME, posted_slot);
86100
let rent = svm.minimum_balance_for_rent_exemption(data.len());
87101
svm.set_account(
88102
key,
@@ -98,6 +112,8 @@ fn write_price_feed(svm: &mut LiteSVM, key: Pubkey, price: i64, exponent: i32, p
98112
}
99113

100114
const PUBLISH_TIME: i64 = 1_700_000_000;
115+
/// A tight confidence interval, $0.001 at exponent -8, far inside the 1% limit.
116+
const DEFAULT_CONFIDENCE: u64 = 100_000;
101117
const TOKEN_DECIMALS: u8 = 6;
102118
const SECONDS_PER_YEAR: i64 = 31_536_000;
103119

@@ -2057,3 +2073,68 @@ fn test_valuation_scales_by_decimals_and_exponent() {
20572073
assert_eq!(read_fund(&ctx).asset_holdings[1], 4_320_000);
20582074
assert_holdings_match_vaults(&ctx);
20592075
}
2076+
2077+
/// A price the oracle is unsure of is not traded on. With NVDAx's confidence
2078+
/// interval at 2% of its price, past the 1% limit, deposit and rebalance both
2079+
/// refuse, but withdraw still pays out in kind: it reads no price, so investors
2080+
/// can always leave. A band of exactly 1% is accepted.
2081+
#[test]
2082+
fn test_wide_confidence_price_rejected() {
2083+
let mut ctx = setup_full();
2084+
standard_fund(&mut ctx);
2085+
2086+
// Alice deposits 900 USDC: 1.44 TSLAx + 3.0 NVDAx at 40/60.
2087+
let alice = fund_user(&mut ctx, 900_000_000);
2088+
do_deposit(&mut ctx, &alice, 900_000_000, 1);
2089+
2090+
// NVDAx rises to $200, so the fund has drifted and needs a rebalance. Then
2091+
// its feed reports a $4 confidence interval: 2% of the price.
2092+
set_nvda_price(&mut ctx, 20_000_000_000, 200_000_000);
2093+
write_price_feed_with_confidence(
2094+
&mut ctx.svm,
2095+
ctx.price_feed_nvda,
2096+
20_000_000_000,
2097+
400_000_000,
2098+
-8,
2099+
1,
2100+
);
2101+
2102+
let bob = fund_user(&mut ctx, 480_000_000);
2103+
let ix = deposit_instruction(&ctx, &bob, 480_000_000, 1, deposit_remaining(&ctx));
2104+
assert_program_error(
2105+
send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[&bob], &bob.pubkey()),
2106+
FundError::OracleConfidenceTooWide,
2107+
"a deposit priced from a wide-confidence feed must fail",
2108+
);
2109+
assert_program_error(
2110+
try_rebalance(&mut ctx, 1, 0),
2111+
FundError::OracleConfidenceTooWide,
2112+
"a rebalance priced from a wide-confidence feed must fail",
2113+
);
2114+
2115+
// Withdraw reads no price: Alice takes half her shares out in kind.
2116+
do_withdraw(&mut ctx, &alice, 450_000_000, 0);
2117+
assert_eq!(
2118+
get_token_account_balance(&ctx.svm, &derive_ata(&alice.pubkey(), &ctx.tsla_mint)).unwrap(),
2119+
720_000
2120+
);
2121+
assert_eq!(
2122+
get_token_account_balance(&ctx.svm, &derive_ata(&alice.pubkey(), &ctx.nvda_mint)).unwrap(),
2123+
1_500_000
2124+
);
2125+
2126+
// A $2 interval is exactly 1% of the price, which is accepted: the
2127+
// rebalance sells 0.06 NVDAx for 12 USDC and buys 0.048 TSLAx.
2128+
write_price_feed_with_confidence(
2129+
&mut ctx.svm,
2130+
ctx.price_feed_nvda,
2131+
20_000_000_000,
2132+
200_000_000,
2133+
-8,
2134+
1,
2135+
);
2136+
do_rebalance(&mut ctx, 1, 0);
2137+
assert_eq!(read_fund(&ctx).asset_holdings[0], 768_000);
2138+
assert_eq!(read_fund(&ctx).asset_holdings[1], 1_440_000);
2139+
assert_holdings_match_vaults(&ctx);
2140+
}

‎finance/managed-fund/anchor/CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 2026-10-03
4+
5+
- **Prices with a wide confidence interval are rejected.** Pyth reports each price with a confidence interval (`conf`, offset 81), and `load_price` ignored it, so a price the publishers disagreed on by several percent was used as if it were exact. Deposits price shares from that price and rebalance sets its swap floor from it, so a wide band moves value between depositors or loosens the floor by the same amount. `load_price` now rejects a price whose interval exceeds `MAX_CONFIDENCE_BPS` (100 bps, 1% of the price) with the new `OracleConfidenceTooWide` error. `withdraw` reads no price and is unaffected, so investors can still leave in kind. The limit is a program constant, like the 60-second staleness window; prop-amm and perpetual-futures store theirs per market. Tested by `test_wide_confidence_price_rejected`. The web app's IDL gains the error.
6+
37
## 2026-10-01
48

59
- **Valuation scales by each asset's decimals and each feed's exponent.** The fund valued an asset as `amount × price / 10⁸`, which is right only when the asset has USDC's 6 decimals and its Pyth feed has exponent −8, and nothing checked either. An eight-decimal asset would have been valued 100 times too high, so a later depositor would have bought almost no shares; Pyth's US equity feeds use exponent −5, a further factor of 1,000. `load_price` now reads the exponent (offset 89) and returns an `OraclePrice`, `AssetConfig` records the mint's `decimals` and `Fund` the USDC mint's `usdc_decimals`, and `deposit` and `rebalance` value and size swaps with `asset_value_in_usdc` and `usdc_to_asset_amount`, which scale by `10^(usdc_decimals + exponent − asset_decimals)`. `PYTH_PRICE_PRECISION` is removed. The mock router's `usdc_per_token` is now USDC minor units per whole token, and its swaps scale by the asset mint's decimals. Tested by `test_valuation_scales_by_decimals_and_exponent`, which runs the story with an eight-decimal TSLAx on an exponent −5 feed.

0 commit comments

Comments
 (0)