Introduce PSM pallet (part of the pUSD Project)#11068
Introduce PSM pallet (part of the pUSD Project)#11068muharem merged 37 commits intoparitytech:masterfrom
Conversation
muharem
left a comment
There was a problem hiding this comment.
Looks good.
One issue I see is that the pallet assumes both pUSD and the external stablecoin share the same decimal precision. The asset pallet does not know the precision. Either we only add assets with the same precision (how we don’t forget this), or we make the pallet aware of the precision and normalize the values.
|
@lrazovic actually I was wrong, there is the metadata inspect trait that fetches the I think we should add the support for assets with different I would snapshot the what you think? |
I agree we could make the PSM generic over assets with different decimal precisions, since the asset owner could technical change the decimals of the asset, but I don’t think that extra complexity is justified for the current scope. The pallet is intended for a small, governance-curated set of stables (e.g. USDC and USDT), so I’d prefer to keep the accounting simple and make the assumption explicit. We can enforce an invariant when adding a new external asset: its decimals must match pUSD decimals, i.e. Another small issue: unfortunately there's no |
|
I will provide |
|
we need prdoc |
| /// Whether this level allows modifying per-asset ceiling weights. | ||
| /// Both Full and Emergency levels can set asset ceilings. | ||
| pub const fn can_set_asset_ceiling(&self) -> bool { | ||
| true |
There was a problem hiding this comment.
contradicts with
polkadot-sdk/substrate/frame/psm/src/lib.rs
Line 182 in c4d7de6
also if there only a single external asset, we cannot really change ceiling with set_asset_ceiling_weight. only disable asset by setting 0
| /// - [`Event::ExternalAssetAdded`]: Emitted on successful addition | ||
| #[pallet::call_index(7)] | ||
| #[pallet::weight(T::WeightInfo::add_external_asset())] | ||
| pub fn add_external_asset(origin: OriginFor<T>, asset_id: T::AssetId) -> DispatchResult { |
There was a problem hiding this comment.
Not a big issue, just a suggestion. Maybe it would be a good idea to accept minting_fee and redemption_fee as parameters here.
These fees have a default value and it seems pretty possible to forget to call the separate extrinsics to set them, which would result in charging the default 0.5% which might not be the wanted behaviour.
rockbmb
left a comment
There was a problem hiding this comment.
Here are some suggestions for additional do_try_state checks; a summary below, and the proposed diff in the pallet's source.
Current (checks 1-4):
| # | Check | Severity |
|---|---|---|
| 1 | All approved assets have decimals matching the stable asset | error |
| 2 | Per-asset reserve >= per-asset debt | error |
| 3 | total_psm_debt() equals sum of all per-asset PsmDebt entries |
error |
| 4 | Per-asset debt does not exceed its ceiling (when minting is enabled) | error |
Proposed additions (checks 5-11):
| # | Check | Severity | Rationale |
|---|---|---|---|
| 5 | No non-zero PsmDebt entry for a non-approved asset |
error | Orphan debt after asset removal |
| 6 | Warn on MintingFee/RedemptionFee/AssetCeilingWeight for non-approved asset |
warn | May be intentional pre-configuration |
| 7 | PSM account exists | error | Required for transfers |
| 8 | ExternalAssets count <= MaxExternalAssets |
error | Bounded storage |
| 9 | Zero ceiling weight + zero debt implies zero reserve | warn | Non-zero reserve with no debt/ceiling is likely a donation or bug |
| 10 | Total PSM debt <= MaxPsmDebtOfTotal ceiling |
warn | May be transiently violated if governance lowers ceiling |
| 11 | Fee destination account exists | error | Required for fee transfers |
The #[cfg] gate should also be widened from try-runtime || test to try-runtime || test || fuzzing to support cargo-fuzz harnesses. It will not be in time for this PR's merger; I am working on such a fuzzer.
dd79f96
| assert!( | ||
| T::Fungibles::decimals(*asset_id) == stable_decimals, | ||
| "PSM migration: asset {:?} decimals do not match stable asset decimals", | ||
| asset_id, | ||
| ); |
There was a problem hiding this comment.
Panicing in a runtime upgrade is a sure way to brick the chain. Please only panic in pre and post upgrade checks. I am changing it now. Upgrades themselves must be infallible.
…11807) Addresses issue @ggwpez raised in this comment: #11068 (comment) ## Summary Replaces the `assert!` in the PSM `InitializePsm` migration with a log + skip when an asset's decimals don't match the stable asset. Panicking in a runtime upgrade bricks the chain. Migrations must be infallible. ### Test Adds `initialize_psm_skips_assets_with_wrong_decimals` which verifies that assets with mismatched decimals are skipped while correctly-configured assets in the same migration are still added.
…11807) Addresses issue @ggwpez raised in this comment: #11068 (comment) ## Summary Replaces the `assert!` in the PSM `InitializePsm` migration with a log + skip when an asset's decimals don't match the stable asset. Panicking in a runtime upgrade bricks the chain. Migrations must be infallible. ### Test Adds `initialize_psm_skips_assets_with_wrong_decimals` which verifies that assets with mismatched decimals are skipped while correctly-configured assets in the same migration are still added.
Description
This PR introduces
pallet-psm, a new FRAME pallet that implements a Peg Stability Module (PSM) for pUSD. The pallet enables 1:1 swaps between pUSD and approved external stablecoins (e.g. USDC/USDT), with configurable mint/redeem fees and per-asset circuit breakers.The pallet enforces a three-tier debt ceiling model before minting:
MaximumIssuance)MaxPsmDebtOfTotal)AssetCeilingWeight)It also adds cross-pallet interfaces in
frame_support::traits::tokens::stable:VaultsInterface(PSM -> Vaults): query system issuance ceilingPsmInterface(Vaults/others -> PSM): query reserved PSM capacityIntegration
For Runtime Developers
To integrate
pallet-psminto your runtime:Cargo.toml:construct_runtime!:For Pallet Developers
Other pallets can query PSM-reserved issuance capacity via
PsmInterface:This can be used to account for PSM-reserved issuance when computing vault minting headroom.
Review Notes
Key Features
mint(external -> pUSD) andredeem(pUSD -> external)add_external_asset/remove_external_asset)AllEnabled->MintingDisabled->AllDisabledFull: all parameter and asset-management operationsEmergency: can only set circuit breaker statusFeeHandlerFeeHandlerPsmDebt(not just raw reserve), preventing withdrawal of donated reservesSwap Lifecycle
Mint (External -> pUSD):
mint(asset_id, external_amount)FeeHandlerPsmDebt[asset_id]Redeem (pUSD -> External):
redeem(asset_id, pusd_amount)FeeHandlerPsmDebt[asset_id]Governance/Operations
set_minting_feeset_redemption_feeset_max_psm_debtset_asset_ceiling_weightset_asset_statusadd_external_assetremove_external_asset(requires zero debt; cleans up config storage)Config Trait
FungiblesAssetIdVaultsInterfaceManagerOriginPsmManagerLevel(Full/Emergency).WeightInfoStableAssetfungibletype (typicallyItemOf<Assets, StablecoinAssetId>). Must implementFungibleMutate+FungibleBalanced.FeeHandlerOnUnbalancedhandler for fee credits.PalletIdMinSwapAmountMaxExternalAssetsTesting
The pallet includes comprehensive coverage for:
v0 -> v1and skip-when-already-v1)