Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
56671cb
feat: upstream pallet-vaults
lrazovic Dec 19, 2025
49b0ae0
chore: removed deprecated warning on SubstrateWeight.
lrazovic Dec 19, 2025
054c6e6
chore: first feedback round
lrazovic Jan 5, 2026
c31d1b1
Merge branch 'master' into leo/vaults
lrazovic Jan 5, 2026
711b0b8
Merge branch 'leo/vaults' of github.com:amforc/polkadot-sdk into leo/…
lrazovic Jan 5, 2026
65030aa
feat: remove on_runtime_upgrade hook from the pallet
lrazovic Jan 5, 2026
8d87807
chore: move the closing logic into the do_close_vault
lrazovic Jan 5, 2026
ff5479b
chore: fix typo
lrazovic Jan 5, 2026
f41e606
feat: initial bechmark helper implementation
lrazovic Jan 12, 2026
be3b6c9
Merge branch 'master' into leo/vaults
lrazovic Jan 23, 2026
538c5b6
Merge branch 'leo/vaults' of github.com:amforc/polkadot-sdk into leo/…
lrazovic Jan 23, 2026
3694f61
feat: v1 initilizer migration
lrazovic Jan 23, 2026
9acbc8d
feat: introduce the `DebtComponents`
lrazovic Jan 23, 2026
2bf2d38
feat: align with latest design doc version
lrazovic Jan 23, 2026
38eb725
chore: let's be pedantic
lrazovic Jan 23, 2026
2b65c6d
fix: update function name
lrazovic Jan 23, 2026
9d8633c
fix: add ensure_max_position_amount in benchmarks
lrazovic Jan 23, 2026
77a0d86
chore: remove redundant tests
lrazovic Jan 23, 2026
10e805b
chore: add missing documentation on events
lrazovic Jan 23, 2026
a6e1ee0
chore: introduce the do_mint abstraction
lrazovic Jan 26, 2026
bca8dfc
feat: move all the parameters to storage
lrazovic Jan 26, 2026
ef7d15c
chore: fmt
lrazovic Jan 26, 2026
05a1fb5
feat: simplify the benchmark helper
lrazovic Jan 26, 2026
b9f9c18
chore: restore Cargo.lock
lrazovic Jan 26, 2026
68faafc
chore: add the new storage-based config items to the migration
lrazovic Jan 26, 2026
dc9719f
test: add migration test case
lrazovic Jan 26, 2026
b4d47f0
Merge branch 'master' into leo/vaults
lrazovic Feb 3, 2026
180af26
doc: Update README to align with the latest code changes
lrazovic Feb 4, 2026
aae9eca
feat: add missing set_max_position_amount benchmark
lrazovic Feb 4, 2026
27f543b
chore: fix calls order
lrazovic Feb 4, 2026
2b6abeb
Merge branch 'master' into leo/vaults
lrazovic Feb 4, 2026
547143d
chore: add header license
lrazovic Feb 5, 2026
b9ab9c3
chore: rename from Inner to VersionUnchecked
lrazovic Feb 5, 2026
b865c44
feat: specify principal and interest in the repaid event
lrazovic Feb 5, 2026
53345d3
chore: fmt
lrazovic Feb 5, 2026
a88206f
feat: parameters are now `OptionQuery`s
lrazovic Feb 11, 2026
27bf893
feat: introduce `integrity_test` and `try-state` hooks
lrazovic Feb 11, 2026
16d6cb9
chore: propagate features via zepeter
lrazovic Feb 11, 2026
c6f0696
chore: fix the markdown lint
lrazovic Feb 11, 2026
fd48e0a
feat: use `DefensiveSaturating`
lrazovic Feb 11, 2026
2ff40a0
feat: remove `new` from `DebtComponents`
lrazovic Feb 11, 2026
9d79ff8
tests: invariants do_try_state
lrazovic Feb 11, 2026
0754c19
tests: improve test coverage
lrazovic Feb 11, 2026
3e56060
chore: use ItemOf to represent the pUSD
lrazovic Mar 9, 2026
2831917
feat: let's be polite
lrazovic Mar 9, 2026
f1a9638
feat: accrual interest only if unit
lrazovic Mar 9, 2026
2d8e3ec
feat: heal the best effort
lrazovic Mar 9, 2026
cdb14d6
feat: heal extrinsic is now "free"
lrazovic Mar 9, 2026
61fd9e7
feat: liquidate_vault "free" if successful
lrazovic Mar 9, 2026
3fe62bf
feat: reduce CLA iff prinipal paid is not zero
lrazovic Mar 9, 2026
389590f
feat: liquidate_vault is now transactional
lrazovic Mar 10, 2026
7f9f3bd
feat: track bad debt as principal + interest
lrazovic Mar 10, 2026
e476d9b
test: add mcr raising test
lrazovic Mar 10, 2026
6d4abde
bench: compute `safe_mint_amount` using `MinimumMint`
lrazovic Mar 10, 2026
6cef850
chore: rename to `Collateral` and `StableAsset`
lrazovic Mar 19, 2026
755c773
bench: worst-case for `mint` and `withdraw`
lrazovic Mar 19, 2026
1868737
feat: free `heal` only if successful
lrazovic Mar 19, 2026
97d0989
feat: store the `PreviousStabilityFee`
lrazovic Mar 19, 2026
ae5aa00
chore: generate the weights
lrazovic Mar 19, 2026
f9487d6
Merge branch 'master' into leo/vaults
lrazovic Mar 22, 2026
8f02ed4
chore: move traits to `frame_support`
lrazovic Mar 22, 2026
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
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ members = [
"substrate/frame/tx-pause",
"substrate/frame/uniques",
"substrate/frame/utility",
"substrate/frame/vaults",
"substrate/frame/verify-signature",
"substrate/frame/vesting",
"substrate/frame/whitelist",
Expand Down Expand Up @@ -1089,6 +1090,7 @@ pallet-treasury = { path = "substrate/frame/treasury", default-features = false
pallet-tx-pause = { default-features = false, path = "substrate/frame/tx-pause" }
pallet-uniques = { path = "substrate/frame/uniques", default-features = false }
pallet-utility = { path = "substrate/frame/utility", default-features = false }
pallet-vaults = { path = "substrate/frame/vaults", default-features = false }
pallet-verify-signature = { path = "substrate/frame/verify-signature", default-features = false }
pallet-vesting = { path = "substrate/frame/vesting", default-features = false }
pallet-whitelist = { path = "substrate/frame/whitelist", default-features = false }
Expand Down
26 changes: 25 additions & 1 deletion substrate/bin/node/runtime/src/genesis_config_presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
constants::currency::*, frame_support::build_struct_json_patch, AccountId, AssetsConfig,
BabeConfig, Balance, BalancesConfig, ElectionsConfig, NominationPoolsConfig, ReviveConfig,
RuntimeGenesisConfig, SessionConfig, SessionKeys, SocietyConfig, StakerStatus, StakingConfig,
SudoConfig, TechnicalCommitteeConfig, BABE_GENESIS_EPOCH_CONFIG,
SudoConfig, TechnicalCommitteeConfig, VaultsConfig, BABE_GENESIS_EPOCH_CONFIG,
};
use alloc::{vec, vec::Vec};
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
Expand Down Expand Up @@ -98,6 +98,30 @@ pub fn kitchensink_genesis(
revive: ReviveConfig {
mapped_accounts: endowed_accounts.iter().filter(|x| ! is_eth_derived(x)).cloned().collect(),
},
vaults: VaultsConfig {
// 180% - Minimum safety margin before liquidation
minimum_collateralization_ratio: sp_runtime::FixedU128::from_rational(180, 100),
// 200% - Prevents immediate liquidation after minting
initial_collateralization_ratio: sp_runtime::FixedU128::from_rational(200, 100),
// 4% annual stability fee
stability_fee: sp_runtime::Permill::from_percent(4),
// 13% liquidation penalty
liquidation_penalty: sp_runtime::Permill::from_percent(13),
// 20M pUSD system-wide debt ceiling (6 decimals)
maximum_issuance: 20_000_000 * 1_000_000,
// 20M pUSD maximum concurrent liquidation exposure
max_liquidation_amount: 20_000_000 * 1_000_000,
// 10M pUSD maximum single vault debt
max_position_amount: 10_000_000 * 1_000_000,
// Minimum collateral deposit to create a vault: 100 DOT.
minimum_deposit: 100 * DOLLARS,
// Minimum mint amount: 5 pUSD.
minimum_mint: 5 * 1_000_000,
// 4 hours stale vault threshold (milliseconds)
stale_vault_threshold: 4 * 60 * 60 * 1000,
// 1 hour oracle staleness threshold (milliseconds)
oracle_staleness_threshold: 60 * 60 * 1000,
},
})
}

Expand Down
183 changes: 182 additions & 1 deletion substrate/bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use pallet_treasury::ArgumentsFactory as PalletTreasuryArgumentsFactory;
#[cfg(feature = "runtime-benchmarks")]
use polkadot_sdk::sp_core::crypto::FromEntropy;

use polkadot_sdk::*;
use polkadot_sdk::{cumulus_primitives_core::Location, *};

use alloc::{vec, vec::Vec};
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
Expand Down Expand Up @@ -2881,6 +2881,9 @@ mod runtime {
#[runtime::pallet_index(85)]
pub type Oracle = pallet_oracle::Pallet<Runtime>;

#[runtime::pallet_index(86)]
pub type Vaults = pallet_vaults::Pallet<Runtime>;

#[runtime::pallet_index(89)]
pub type MetaTx = pallet_meta_tx::Pallet<Runtime>;

Expand Down Expand Up @@ -3062,6 +3065,183 @@ impl pallet_oracle::Config for Runtime {
type BenchmarkHelper = OracleBenchmarkingHelper;
}

parameter_types! {
/// The pUSD stablecoin asset ID.
pub const VaultsStablecoinAssetId: u32 = 1;
/// DOT collateral location.
pub VaultsCollateralLocation: Location = Location::here();
/// Maximum vaults to process in on_idle per block.
pub const VaultsMaxOnIdleItems: u32 = 16;
}

type VaultsAsset = ItemOf<Assets, VaultsStablecoinAssetId, AccountId>;

/// Insurance fund account that receives protocol revenue (interest and penalties).
pub struct InsuranceFundAccount;
impl frame_support::traits::Get<AccountId> for InsuranceFundAccount {
fn get() -> AccountId {
// Use a deterministic insurance fund account
sp_runtime::traits::AccountIdConversion::<AccountId>::into_account_truncating(
&frame_support::PalletId(*b"py/insur"),
)
}
}

/// Mock oracle adapter that provides a fixed price for now.
///
/// This adapter implements `ProvidePrice` with a hardcoded DOT price.
/// For production, replace this with a real oracle integration.
///
/// **Price format (normalized):** smallest_pUSD_units / smallest_collateral_unit
///
/// Default DOT price: $4.21 normalized for DOT (10 decimals) and pUSD (6 decimals)
/// Price = 4.21 * 10^6 / 10^10 = 0.000421
/// As FixedU128: 421_000_000_000_000 (0.000421 * 10^18)
pub const DEFAULT_DOT_PRICE: u128 = 421_000_000_000_000;

#[cfg(feature = "runtime-benchmarks")]
frame_support::parameter_types! {
/// Storage for benchmark price override.
/// When set, MockOracleAdapter will use this price instead of the default.
pub storage BenchmarkOraclePrice: Option<FixedU128> = None;
}

/// Example calculation for DOT at $4.21:
/// - DOT has 10 decimals, pUSD has 6 decimals
/// - 1 DOT = 4.21 pUSD
/// - Normalized price = 4.21 × 10^6 / 10^10 = 0.000421
/// - As FixedU128 (18 decimals): 0.000421 × 10^18 = 421_000_000_000_000
pub struct MockOracleAdapter;
impl pallet_vaults::ProvidePrice for MockOracleAdapter {
type Moment = u64;

fn get_price(asset: &Location) -> Option<(FixedU128, Self::Moment)> {
// Only support DOT (native asset) for now
if *asset != Location::here() {
return None;
}

// Check for benchmark price override
#[cfg(feature = "runtime-benchmarks")]
let price = BenchmarkOraclePrice::get().unwrap_or(FixedU128::from_inner(DEFAULT_DOT_PRICE));

#[cfg(not(feature = "runtime-benchmarks"))]
let price = FixedU128::from_inner(DEFAULT_DOT_PRICE);

// Use current timestamp from the Timestamp pallet
let now = <Timestamp as frame_support::traits::Time>::now();

Some((price, now))
}
}

/// Stub implementation for the Auctions handler.
///
/// This is a placeholder until a proper Auctions pallet is implemented.
/// Currently, liquidations will fail with `Unimplemented` error.
///
/// TODO: Replace with actual pallet_auctions integration when available.
pub struct AuctionAdapter;
impl pallet_vaults::AuctionsHandler<AccountId, Balance> for AuctionAdapter {
fn start_auction(
_vault_owner: AccountId,
_collateral_amount: Balance,
_debt: frame_support::traits::DebtComponents<Balance>,
_keeper: AccountId,
) -> Result<u32, frame_support::pallet_prelude::DispatchError> {
// During benchmarks, return success to allow liquidation benchmarks to complete
#[cfg(feature = "runtime-benchmarks")]
return Ok(1);

// TODO: Implement actual auction logic when pallet_auctions is available
// For now, liquidations are disabled
#[cfg(not(feature = "runtime-benchmarks"))]
Err(frame_support::pallet_prelude::DispatchError::Other("Auctions not yet implemented"))
}
}

/// EnsureOrigin implementation for vaults management that supports privilege levels.
///
/// - Root origin → `VaultsManagerLevel::Full` (can modify all parameters)
///
/// TODO: In the future, this can be extended to support Emergency privilege level
/// via a (new) specific governance origin that can only lower the debt ceiling.
pub struct EnsureVaultsManager;
impl frame_support::traits::EnsureOrigin<RuntimeOrigin> for EnsureVaultsManager {
type Success = pallet_vaults::VaultsManagerLevel;

fn try_origin(o: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
use frame_system::RawOrigin;

match o.clone().into() {
Ok(RawOrigin::Root) => Ok(pallet_vaults::VaultsManagerLevel::Full),
_ => Err(o),
}
}

#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<RuntimeOrigin, ()> {
Ok(RuntimeOrigin::root())
}
}

/// Benchmark helper for the Vaults pallet.
#[cfg(feature = "runtime-benchmarks")]
pub struct VaultsBenchmarkHelper;

#[cfg(feature = "runtime-benchmarks")]
impl pallet_vaults::BenchmarkHelper for VaultsBenchmarkHelper {
fn create_stablecoin_asset() {
use frame_support::traits::fungibles::Create;

// Ensure the owner account exists
let owner = InsuranceFundAccount::get();
let _ = frame_system::Pallet::<Runtime>::inc_providers(&owner);

// Create the asset if it doesn't exist (ignore errors if already exists)
let _ = <Assets as Create<AccountId>>::create(
VaultsStablecoinAssetId::get(),
owner.clone(),
true, // is_sufficient
1, // min_balance
);
}

fn advance_time(millis: u64) {
let current = pallet_timestamp::Now::<Runtime>::get();
let new_time = current.saturating_add(millis);
pallet_timestamp::Now::<Runtime>::put(new_time);
}

fn set_price(price: sp_runtime::FixedU128) {
BenchmarkOraclePrice::set(&Some(price));
log::info!(
target: "runtime::vaults::benchmark",
"set_price: {:?}",
price
);
}
}

/// Configure the Vaults pallet.
impl pallet_vaults::Config for Runtime {
type Collateral = Balances;
type RuntimeHoldReason = RuntimeHoldReason;
type StableAsset = VaultsAsset;
type InsuranceFund = InsuranceFundAccount;
type FeeHandler = ResolveTo<TreasuryAccount, Balances>;
type SurplusHandler = ResolveTo<TreasuryAccount, VaultsAsset>;
type TimeProvider = Timestamp;
type ManagerOrigin = EnsureVaultsManager;
type MaxOnIdleItems = VaultsMaxOnIdleItems;
type Oracle = MockOracleAdapter;
type CollateralLocation = VaultsCollateralLocation;
type AuctionsHandler = AuctionAdapter;
type WeightInfo = pallet_vaults::weights::SubstrateWeight<Runtime>;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = VaultsBenchmarkHelper;
}

/// MMR helper types.
mod mmr {
use super::*;
Expand Down Expand Up @@ -3200,6 +3380,7 @@ mod benches {
[pallet_nft_fractionalization, NftFractionalization]
[pallet_utility, Utility]
[pallet_vesting, Vesting]
[pallet_vaults, Vaults]
[pallet_whitelist, Whitelist]
[pallet_tx_pause, TxPause]
[pallet_safe_mode, SafeMode]
Expand Down
8 changes: 6 additions & 2 deletions substrate/frame/support/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ pub use tokens::{
},
fungible, fungibles,
imbalance::{Imbalance, OnUnbalanced, SignedImbalance},
nonfungible, nonfungible_v2, nonfungibles, nonfungibles_v2, BalanceStatus,
ExistenceRequirement, Locker, WithdrawReasons,
nonfungible, nonfungible_v2, nonfungibles, nonfungibles_v2,
stable::{
AuctionsHandler, CollateralManager, DebtComponents, PaymentBreakdown, PsmInterface,
VaultsInterface,
},
BalanceStatus, ExistenceRequirement, Locker, WithdrawReasons,
};

mod members;
Expand Down
1 change: 1 addition & 0 deletions substrate/frame/support/src/traits/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod nonfungibles;
pub mod nonfungibles_v2;
pub use imbalance::Imbalance;
pub mod pay;
pub mod stable;
pub mod transfer;
pub use misc::{
AssetId, Balance, BalanceStatus, ConversionFromAssetBalance, ConversionToAssetBalance,
Expand Down
Loading