diff --git a/crates/vault-registry/src/default_weights.rs b/crates/vault-registry/src/default_weights.rs index e0694cf746..56161bee7d 100644 --- a/crates/vault-registry/src/default_weights.rs +++ b/crates/vault-registry/src/default_weights.rs @@ -47,6 +47,8 @@ pub trait WeightInfo { fn set_premium_redeem_threshold() -> Weight; fn set_liquidation_collateral_threshold() -> Weight; fn report_undercollateralized_vault() -> Weight; + fn set_current_client_release() -> Weight; + fn set_pending_client_release() -> Weight; fn recover_vault_id() -> Weight; } @@ -189,6 +191,15 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(16 as Weight)) } + fn set_current_client_release() -> Weight { + (4_130_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + + fn set_pending_client_release() -> Weight { + (4_130_000 as Weight) + } + // Storage: VaultRegistry Vaults (r:2 w:1) fn recover_vault_id() -> Weight { (14_679_000 as Weight) @@ -335,6 +346,15 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(16 as Weight)) } + fn set_current_client_release() -> Weight { + (4_130_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + + fn set_pending_client_release() -> Weight { + (4_130_000 as Weight) + } + // Storage: VaultRegistry Vaults (r:2 w:1) fn recover_vault_id() -> Weight { (14_679_000 as Weight) diff --git a/crates/vault-registry/src/lib.rs b/crates/vault-registry/src/lib.rs index 05659428b9..e44f1b9ce3 100644 --- a/crates/vault-registry/src/lib.rs +++ b/crates/vault-registry/src/lib.rs @@ -29,8 +29,8 @@ use mocktopus::macros::mockable; use primitives::VaultCurrencyPair; use crate::types::{ - BalanceOf, BtcAddress, CurrencyId, DefaultSystemVault, RichSystemVault, RichVault, SignedInner, UnsignedFixedPoint, - UpdatableVault, Version, + BalanceOf, BtcAddress, ClientRelease, CurrencyId, DefaultSystemVault, RichSystemVault, RichVault, SignedInner, + UnsignedFixedPoint, UpdatableVault, Version, }; use crate::types::DefaultVaultCurrencyPair; @@ -134,6 +134,7 @@ pub mod pallet { fn on_runtime_upgrade() -> frame_support::weights::Weight { crate::types::v4::migrate_v4_to_v5::() + + crate::types::upgrade_vault_release::try_upgrade_current_vault_release::() } } @@ -443,6 +444,37 @@ pub mod pallet { Ok(()) } + /// Sets the current client release version, in case of a bug fix or patch. + /// + /// # Arguments + /// * `uri` - URI to the client release binary + /// * `code_hash` - The runtime code hash associated with this client release + #[pallet::weight(::WeightInfo::set_current_client_release())] + #[transactional] + pub fn set_current_client_release(origin: OriginFor, uri: Vec, code_hash: T::Hash) -> DispatchResult { + ensure_root(origin)?; + let release = ClientRelease { uri, code_hash }; + CurrentClientRelease::::put(release.clone()); + Self::deposit_event(Event::::ApplyClientRelease { release }); + Ok(()) + } + + /// Sets the pending client release version. To be batched alongside the + /// `parachainSystem.enactAuthorizedUpgrade` relay chain xcm call. + /// + /// # Arguments + /// * `uri` - URI to the client release binary + /// * `code_hash` - The runtime code hash associated with this client release + #[pallet::weight(::WeightInfo::set_pending_client_release())] + #[transactional] + pub fn set_pending_client_release(origin: OriginFor, uri: Vec, code_hash: T::Hash) -> DispatchResult { + ensure_root(origin)?; + let release = ClientRelease { uri, code_hash }; + PendingClientRelease::::put(Some(release.clone())); + Self::deposit_event(Event::::NotifyClientRelease { release }); + Ok(()) + } + /// Recover vault ID from a liquidated status. /// /// # Arguments @@ -573,6 +605,12 @@ pub mod pallet { vault_id: DefaultVaultId, banned_until: T::BlockNumber, }, + NotifyClientRelease { + release: ClientRelease, + }, + ApplyClientRelease { + release: ClientRelease, + }, } #[pallet::error] @@ -700,6 +738,16 @@ pub mod pallet { pub(super) type TotalUserVaultCollateral = StorageMap<_, Blake2_128Concat, DefaultVaultCurrencyPair, BalanceOf, ValueQuery>; + /// Tuple of (release_uri, code_hash) indicating the current vault client release. + #[pallet::storage] + #[pallet::getter(fn current_client_release)] + pub(super) type CurrentClientRelease = StorageValue<_, ClientRelease, ValueQuery>; + + /// Tuple of (release_uri, code_hash) indicating the pending vault client release. + #[pallet::storage] + #[pallet::getter(fn pending_client_release)] + pub(super) type PendingClientRelease = StorageValue<_, Option>, ValueQuery>; + #[pallet::type_value] pub(super) fn DefaultForStorageVersion() -> Version { Version::V0 diff --git a/crates/vault-registry/src/types.rs b/crates/vault-registry/src/types.rs index 6af4037b35..3b091cac8f 100644 --- a/crates/vault-registry/src/types.rs +++ b/crates/vault-registry/src/types.rs @@ -11,7 +11,7 @@ pub use primitives::{VaultCurrencyPair, VaultId}; use scale_info::TypeInfo; use sp_core::H256; use sp_runtime::traits::{CheckedAdd, CheckedSub, Zero}; -use sp_std::collections::btree_set::BTreeSet; +use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; #[cfg(test)] use mocktopus::macros::mockable; @@ -35,6 +35,14 @@ pub enum Version { V5, } +#[derive(Encode, Decode, Eq, PartialEq, Clone, Default, TypeInfo, Debug)] +pub struct ClientRelease { + /// URI to the client release binary. + pub uri: Vec, + /// The runtime code hash associated with this client release. + pub code_hash: Hash, +} + #[derive(Debug, PartialEq)] pub enum CurrencySource { /// Used by vault to back issued tokens @@ -108,6 +116,89 @@ pub type DefaultVaultId = VaultId<::AccountId, Cur pub type DefaultVaultCurrencyPair = VaultCurrencyPair>; +pub mod upgrade_vault_release { + + use super::*; + use frame_support::weights::Weight; + + /// If a pending client release exists, set the current release to that. + /// The pending release becomes `None`. + pub fn try_upgrade_current_vault_release() -> Weight { + let writes: Weight = if let Some(pending_release) = crate::PendingClientRelease::::take() { + log::info!("Upgrading current vault release."); + crate::CurrentClientRelease::::put(pending_release.clone()); + Pallet::::deposit_event(crate::Event::::ApplyClientRelease { + release: pending_release, + }); + 2 + } else { + log::info!("No pending vault release, skipping migration."); + 0 + }; + T::DbWeight::get().reads_writes(1, writes) + } + + #[cfg(test)] + #[test] + fn test_vault_release_migration_is_skipped() { + use crate::mock::Test; + + crate::mock::run_test(|| { + let pre_migration_pending_release = None; + crate::PendingClientRelease::::put(pre_migration_pending_release.clone()); + + let pre_migration_current_release = ClientRelease { + uri: b"https://github.com/interlay/interbtc-clients/releases/download/1.15.0/vault-standalone-metadata" + .to_vec(), + code_hash: H256::default(), + }; + crate::CurrentClientRelease::::put(pre_migration_current_release.clone()); + + try_upgrade_current_vault_release::(); + + assert_eq!( + crate::PendingClientRelease::::get(), + pre_migration_pending_release + ); + assert_eq!( + crate::CurrentClientRelease::::get(), + pre_migration_current_release + ); + }); + } + + #[cfg(test)] + #[test] + fn test_vault_release_migration_executes() { + use crate::mock::Test; + + crate::mock::run_test(|| { + let pre_migration_pending_release = ClientRelease { + uri: b"https://github.com/interlay/interbtc-clients/releases/download/1.14.0/vault-standalone-metadata" + .to_vec(), + code_hash: H256::default(), + }; + crate::PendingClientRelease::::put(Some(pre_migration_pending_release.clone())); + + let pre_migration_current_release = ClientRelease { + uri: b"https://github.com/interlay/interbtc-clients/releases/download/1.15.0/vault-standalone-metadata" + .to_vec(), + code_hash: H256::default(), + }; + crate::CurrentClientRelease::::put(pre_migration_current_release.clone()); + + try_upgrade_current_vault_release::(); + + let no_release: Option> = None; + assert_eq!(crate::PendingClientRelease::::get(), no_release); + assert_eq!( + crate::CurrentClientRelease::::get(), + pre_migration_pending_release + ); + }); + } +} + pub mod liquidation_vault_fix { use super::*; use primitives::{ diff --git a/standalone/runtime/tests/test_vault_registry.rs b/standalone/runtime/tests/test_vault_registry.rs index 3fbda5c20b..14d070d21a 100644 --- a/standalone/runtime/tests/test_vault_registry.rs +++ b/standalone/runtime/tests/test_vault_registry.rs @@ -532,6 +532,45 @@ fn integration_test_vault_registry_theft_recovery_works() { }); } +mod client_release { + use super::{assert_eq, *}; + use vault_registry::types::ClientRelease; + + #[test] + fn integration_test_vault_registry_set_current_client_release_works() { + test_with(|_vault_id| { + let new_release = ClientRelease { + uri: b"https://github.com/interlay/interbtc-clients/releases/download/1.14.0/vault-standalone-metadata" + .to_vec(), + code_hash: H256::default(), + }; + assert_ok!(Call::VaultRegistry(VaultRegistryCall::set_current_client_release { + uri: new_release.uri.clone(), + code_hash: new_release.code_hash.clone() + }) + .dispatch(root())); + assert_eq!(VaultRegistryPallet::current_client_release(), new_release); + }); + } + + #[test] + fn integration_test_vault_registry_set_pending_client_release_works() { + test_with(|_vault_id| { + let new_release = ClientRelease { + uri: b"https://github.com/interlay/interbtc-clients/releases/download/1.15.0/vault-standalone-metadata" + .to_vec(), + code_hash: H256::default(), + }; + assert_ok!(Call::VaultRegistry(VaultRegistryCall::set_pending_client_release { + uri: new_release.uri.clone(), + code_hash: new_release.code_hash.clone() + }) + .dispatch(root())); + assert_eq!(VaultRegistryPallet::pending_client_release(), Some(new_release)); + }); + } +} + #[test] fn integration_test_vault_registry_theft_recovery_with_executed_redeem_works() { test_with(|vault_id| {