Expand description
§Peg Stability Module (PSM) Pallet
Instantiable Peg Stability Modules (PSMs). Each PSM enables 1:1 swaps between an internal stablecoin and one or more approved external stablecoins, typically to maintain a peg.
§Pallet API
See the pallet module for more information about the interfaces this pallet exposes,
including its configuration trait, dispatchables, storage items, events and errors.
§Terminology
Throughout this pallet two distinct token roles are referenced:
- Internal — the stablecoin a PSM issues and burns (e.g. a runtime’s own USD-pegged
stablecoin). Each PSM instance is keyed by its internal asset id; multiple instances can
coexist, each with its own reserve, debt ceiling, fee destination and approved externals. Mint
operations credit the user with the internal asset; redeem operations burn it. Fees are
collected in the internal asset and forwarded to that instance’s
PsmInfo::fee_destination. - External — third-party assets (e.g. USDC, USDT) approved on a specific PSM via
Pallet::add_external_assetand held in that PSM’s reserve. Users deposit external to mint internal, and burn internal to redeem external. A PSM may approve multiple externals, each identified byexternal_asset.
§Overview
A PSM strengthens its internal asset’s peg by providing arbitrage opportunities:
- When the internal asset trades above $1: Users swap external assets for the internal asset and sell for profit.
- When the internal asset trades below $1: Users buy cheap internal asset and swap for external assets.
This creates a price corridor bounded by the minting and redemption fees.
§Key Concepts
- PSM instance: A configured Peg Stability Module, keyed by its internal asset id and
described by
PsmInfo. Each instance has its own reserve account derived fromblake2_256((PalletId::TYPE_ID, PalletId, internal_asset).encode()). - Minting: Deposit external asset → receive internal asset (minus fee).
- Redemption: Burn internal asset → receive external asset (minus fee).
- Reserve: External asset balance held by a PSM’s reserve account (derived, not stored).
- PSM Debt: Total internal asset minted through a PSM, backed 1:1 by external assets in that PSM’s reserve.
- Circuit Breaker: Per-external emergency control to disable minting or all swaps.
§Storage Invariants
With the try-runtime feature, the pallet tests seventeen storage invariants in
each block. The function do_try_state does these tests. It confirms that:
- each decimals snapshot agrees with the live asset metadata;
- each reserve covers the debt that the PSM records against it;
- the issuance of the internal asset covers the debt of the instance;
- no storage row stays after the removal of its PSM;
- no debt exists for a pair that the PSM does not approve.
Five of the seventeen checks are advisory. An advisory check writes a warning to the log. It does not return an error. A permitted action can create these five states, so an error would stop the chain after a correct call:
- debt above a per-asset ceiling;
- debt above the ceiling of an instance;
- a reserve with a balance, but with zero weight and zero debt;
- live decimals of the internal asset that differ from the recorded value;
- live decimals of an external asset that differ from the recorded value.
The two decimal checks are advisory because an asset owner can change asset metadata at any time, and swaps use the recorded values. Drift does not change the arithmetic of the pallet. It changes what a reader of live metadata sees.
§Fee Structure
- Minting Fee (
MintingFee): Deducted from internal-asset output during minting, configured per(internal_asset, external_asset)pair. - Redemption Fee (
RedemptionFee): Deducted from external-asset output during redemption, configured per(internal_asset, external_asset)pair.
Fees are collected in the internal asset and transferred to the instance’s
PsmInfo::fee_destination.
§Example
// Mint internal asset by depositing USDC on the PSM
let max_fee = MintingFee::<Runtime>::get(INTERNAL_ASSET_ID, USDC_ASSET_ID);
Psm::mint(
RuntimeOrigin::signed(user),
INTERNAL_ASSET_ID,
USDC_ASSET_ID,
1000 * UNIT,
max_fee,
)?;
// Redeem USDC by burning the internal asset
let max_fee = RedemptionFee::<Runtime>::get(INTERNAL_ASSET_ID, USDC_ASSET_ID);
Psm::redeem(
RuntimeOrigin::signed(user),
INTERNAL_ASSET_ID,
USDC_ASSET_ID,
1000 * UNIT,
max_fee,
)?;Re-exports§
pub use weights::WeightInfo;pub use pallet::*;
Modules§
- pallet
- The
palletmodule in each FRAME pallet hosts the most important items needed to construct this pallet. - weights
- Autogenerated weights for
pallet_psm
Traits§
- Benchmark
Helper - Helper trait for benchmark setup.