Fix valuation to scale by asset decimals and Pyth exponent - #177
Merged
Merged
Conversation
Valuation assumed every asset had USDC's six decimals and every Pyth feed an exponent of -8: value = amount * price / 10^8, with nothing checking either. An eight-decimal asset was valued 100 times too high, so a later depositor bought almost no shares; Pyth's US equity feeds use exponent -5, a further factor of 1,000. load_price now reads the exponent, AssetConfig records the mint's decimals and Fund the USDC mint's, and deposit and rebalance value and size swaps by 10^(usdc_decimals + exponent - asset_decimals). The mock router's usdc_per_token is now USDC minor units per whole token. rebalance(sell_amount, usdc_to_invest) let the manager choose both legs, so a manager could trade a balanced fund back and forth and bleed it through slippage to whoever filled the trades. rebalance(sell_index, buy_index) is now permissionless and sizes its own trade: it values every asset, requires the asset sold to be above its target by a threshold fixed at creation (100..=2,000 bps, no setter) and the asset bought to be below its own, and trades the smaller gap, spending only the sale's proceeds. A fund at its targets has no trade to make. Anchor v2, Anchor v1 and Quasar change together, with tests for churn, the threshold, the buy side, retired assets, donations and mixed decimals, two new Kani harnesses, and the web apps' IDL, client and rebalance panel. From quicknode/solana-book review feedback on the Managed Fund chapter. Claude-Session: https://claude.ai/code/session_01Jw5mz4PJSs6GbyMY1zNeEr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The managed fund's valuation logic incorrectly assumed all assets had 6 decimals and Pyth feeds had an exponent of -8. This fix makes valuation decimal-aware by reading each asset's decimals from its
AssetConfigand each Pyth feed's exponent from the price account, then scaling appropriately during conversions between asset and USDC amounts.Key Changes
Oracle valuation: Updated
asset_value_in_usdc()andusdc_to_asset_amount()to accept and use the asset's decimals and Pyth feed's exponent. Introducedmul_pow10_div()helper to handle scaling by powers of 10 (positive or negative).Pyth feed reading: Added
PYTH_EXPONENT_OFFSETconstant (offset 89) to read the 4-byte exponent field from Pyth PriceUpdateV2 accounts. Updated all test helpers (write_price_feed,set_price_feed_posted_at) to accept and write the exponent.AssetConfig storage: Added
decimalsfield toAssetConfigto record each asset's mint decimals at registration time, enabling consistent valuation across the fund's lifetime.Fund state: Added
usdc_decimalsfield toFundfor consistency (USDC is always 6 decimals in practice, but stored for clarity).Rebalance refactoring: Simplified the
rebalanceinstruction to accept only the sell and buy asset indices; the program now computes the trade amounts from oracle prices and target weights using the corrected valuation. Changed the signer frommanagertocaller(anyone can rebalance). Removed explicit mint and config accounts from the instruction signature; they are now passed viaremaining_accounts.Router rate semantics: Updated mock router's
AssetRate.usdc_per_tokendocumentation and test constants to clarify it represents USDC minor units per whole token (e.g.,250_000_000for $250), not per base unit.Test coverage: Added
test_valuation_scales_by_decimals_and_exponentto verify correct scaling for an 8-decimal asset with a -5 exponent Pyth feed. Extended rebalance tests to cover drift detection, threshold validation, and edge cases.Threshold bounds: Added
MIN_REBALANCE_THRESHOLD_BPSandMAX_REBALANCE_THRESHOLD_BPSconstants;initialize_fundnow validates the rebalance threshold is in range.Implementation Details
asset_value_usdc = amount_asset × price_pyth × 10^(exponent - asset_decimals + usdc_decimals) / 10^8https://claude.ai/code/session_01Jw5mz4PJSs6GbyMY1zNeEr