Skip to content

Commit

Permalink
Merge pull request #597 from sander2/fix/liquidation-vault-migration
Browse files Browse the repository at this point in the history
fix: migration to fix invalid liquidation vault state
  • Loading branch information
gregdhill authored May 12, 2022
2 parents 75c16ad + 31e74a3 commit a8c0a15
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 217 deletions.
4 changes: 0 additions & 4 deletions crates/vault-registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,6 @@ pub mod pallet {
log::info!("Off-chain worker started on block {:?}", n);
Self::_offchain_worker();
}

fn on_runtime_upgrade() -> Weight {
crate::types::v2::migrate_v2_to_v3::<T>()
}
}

#[pallet::validate_unsigned]
Expand Down
251 changes: 53 additions & 198 deletions crates/vault-registry/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub enum Version {
V2,
/// moved public_key out of the vault struct
V3,
/// Fixed liquidation vault
V4,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -104,229 +106,82 @@ pub type DefaultVaultId<T> = VaultId<<T as frame_system::Config>::AccountId, Cur

pub type DefaultVaultCurrencyPair<T> = VaultCurrencyPair<CurrencyId<T>>;

pub mod v2 {
pub mod liquidation_vault_fix {
use super::*;
use primitives::{
CurrencyId::Token,
TokenSymbol::{KBTC, KSM},
VaultCurrencyPair,
};

#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, Default, TypeInfo)]
struct WalletV2 {
// store all addresses for `report_vault_theft` checks
pub addresses: BTreeSet<BtcAddress>,
// we use this public key to generate new addresses
pub public_key: BtcPublicKey,
}

#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
#[cfg_attr(feature = "std", derive(Debug))]
struct VaultV2<AccountId, BlockNumber, Balance, CurrencyId: Copy> {
/// Account identifier of the Vault
pub id: VaultId<AccountId, CurrencyId>,
/// Bitcoin address of this Vault (P2PKH, P2SH, P2WPKH, P2WSH)
pub wallet: WalletV2,
/// Current status of the vault
pub status: super::VaultStatus,
/// Block height until which this Vault is banned from being used for
/// Issue, Redeem (except during automatic liquidation) and Replace.
pub banned_until: Option<BlockNumber>,
/// Number of tokens pending issue
pub to_be_issued_tokens: Balance,
/// Number of issued tokens
pub issued_tokens: Balance,
/// Number of tokens pending redeem
pub to_be_redeemed_tokens: Balance,
/// Number of tokens that have been requested for a replace through
/// `request_replace`, but that have not been accepted yet by a new_vault.
pub to_be_replaced_tokens: Balance,
/// Amount of collateral that is available as griefing collateral to vaults accepting
/// a replace request. It is to be payed out if the old_vault fails to call execute_replace.
pub replace_collateral: Balance,
/// Amount of collateral locked for accepted replace requests.
pub active_replace_collateral: Balance,
/// Amount of collateral that is locked for remaining to_be_redeemed
/// tokens upon liquidation.
pub liquidated_collateral: Balance,
}

type DefaultVaultV2<T> = VaultV2<
<T as frame_system::Config>::AccountId,
<T as frame_system::Config>::BlockNumber,
BalanceOf<T>,
CurrencyId<T>,
>;

pub fn migrate_v2_to_v3<T: Config>() -> frame_support::weights::Weight {
use crate::types::Version;

const TO_BE_REDEEMED_DECREASE: u32 = 6_354_070;

pub fn fix_liquidation_vault<T: Config>() -> Result<frame_support::weights::Weight, ()> {
use sp_runtime::traits::Saturating;

if !matches!(
crate::StorageVersion::<T>::get(),
Version::V0 | Version::V1 | Version::V2
) {
log::info!("Not running vault storage migration");
return T::DbWeight::get().reads(1); // already upgraded; don't run migration
if !matches!(crate::StorageVersion::<T>::get(), Version::V3) {
log::info!("Not running liquidation vault fix");
return Err(());
}
let mut num_migrated_vaults = 0u64;

crate::Vaults::<T>::translate::<DefaultVaultV2<T>, _>(|_key, vault_v2| {
num_migrated_vaults.saturating_inc();
let currency_pair = VaultCurrencyPair {
collateral: Token(KSM),
wrapped: Token(KBTC),
};
<crate::LiquidationVault<T>>::try_mutate(&currency_pair, |maybe_liquidation_vault| {
if let Some(ref mut liquidation_vault) = maybe_liquidation_vault {
liquidation_vault
.to_be_redeemed_tokens
.saturating_reduce(TO_BE_REDEEMED_DECREASE.into());
Ok(())
} else {
log::info!("Failed to fetch liquidation vault");
Err(())
}
})?;

crate::VaultBitcoinPublicKey::<T>::insert(
vault_v2.id.account_id.clone(),
vault_v2.wallet.public_key.clone(),
);
Some(Vault {
id: vault_v2.id,
wallet: Wallet {
addresses: vault_v2.wallet.addresses,
},
status: vault_v2.status,
banned_until: vault_v2.banned_until,
to_be_issued_tokens: vault_v2.to_be_issued_tokens,
issued_tokens: vault_v2.issued_tokens,
to_be_redeemed_tokens: vault_v2.to_be_redeemed_tokens,
to_be_replaced_tokens: vault_v2.to_be_replaced_tokens,
replace_collateral: vault_v2.replace_collateral,
active_replace_collateral: vault_v2.active_replace_collateral,
liquidated_collateral: vault_v2.liquidated_collateral,
})
});
crate::StorageVersion::<T>::put(Version::V3);
log::info!("LiquidationVault migration finished");

log::info!("Migrated {num_migrated_vaults} vaults");
crate::StorageVersion::<T>::put(Version::V4);

T::DbWeight::get().reads_writes(num_migrated_vaults, num_migrated_vaults)
Ok(T::DbWeight::get().reads_writes(2, 2))
}

#[cfg(test)]
#[test]
fn test_migration() {
use crate::mock::Test;
use bitcoin::types::H160;
use frame_support::{storage::migration, Blake2_128Concat, StorageHasher};
use primitives::{CurrencyId::Token, KBTC, KSM};
use sp_runtime::traits::TrailingZeroInput;

crate::mock::run_test(|| {
crate::StorageVersion::<Test>::put(Version::V2);

let vault1: DefaultVaultV2<Test> = VaultV2 {
id: VaultId {
account_id: Decode::decode(&mut TrailingZeroInput::new(&[1u8; 32])).unwrap(),
currencies: VaultCurrencyPair {
collateral: Token(KSM),
wrapped: Token(KBTC),
},
},
wallet: WalletV2 {
addresses: [
BtcAddress::P2PKH(H160::from([1; 20])),
BtcAddress::P2PKH(H160::from([2; 20])),
]
.into(),
public_key: BtcPublicKey::from([2u8; 33]),
},
banned_until: None,
status: VaultStatus::Active(true),
issued_tokens: Default::default(),
liquidated_collateral: Default::default(),
replace_collateral: Default::default(),
to_be_issued_tokens: Default::default(),
to_be_redeemed_tokens: Default::default(),
to_be_replaced_tokens: Default::default(),
active_replace_collateral: Default::default(),
};
let vault2: DefaultVaultV2<crate::mock::Test> = VaultV2 {
id: VaultId {
account_id: Decode::decode(&mut TrailingZeroInput::new(&[2u8; 32])).unwrap(),
currencies: VaultCurrencyPair {
collateral: Token(KBTC),
wrapped: Token(KBTC),
},
},
wallet: WalletV2 {
addresses: [
BtcAddress::P2PKH(H160::from([3; 20])),
BtcAddress::P2PKH(H160::from([4; 20])),
]
.into(),
public_key: BtcPublicKey::from([3u8; 33]),
},
banned_until: Some(123),
status: VaultStatus::Active(false),
issued_tokens: 1,
liquidated_collateral: 2,
replace_collateral: 3,
to_be_issued_tokens: 4,
to_be_redeemed_tokens: 5,
to_be_replaced_tokens: 6,
active_replace_collateral: 7,
};
migration::put_storage_value(
b"VaultRegistry",
b"Vaults",
&Blake2_128Concat::hash(&vault1.id.encode()),
&vault1,
);
migration::put_storage_value(
b"VaultRegistry",
b"Vaults",
&Blake2_128Concat::hash(&vault2.id.encode()),
&vault2,
);
crate::StorageVersion::<Test>::put(Version::V3);

migrate_v2_to_v3::<Test>();

let expected_migrated_vault1 = Vault {
id: vault1.id.clone(),
wallet: Wallet {
addresses: vault1.wallet.addresses.clone(),
},
status: vault1.status.clone(),
banned_until: vault1.banned_until,
to_be_issued_tokens: vault1.to_be_issued_tokens,
issued_tokens: vault1.issued_tokens,
to_be_redeemed_tokens: vault1.to_be_redeemed_tokens,
to_be_replaced_tokens: vault1.to_be_replaced_tokens,
replace_collateral: vault1.replace_collateral,
active_replace_collateral: vault1.active_replace_collateral,
liquidated_collateral: vault1.liquidated_collateral,
let currency_pair = VaultCurrencyPair {
collateral: Token(KSM),
wrapped: Token(KBTC),
};

let expected_migrated_vault2 = Vault {
id: vault2.id.clone(),
wallet: Wallet {
addresses: vault2.wallet.addresses.clone(),
},
status: vault2.status.clone(),
banned_until: vault2.banned_until,
to_be_issued_tokens: vault2.to_be_issued_tokens,
issued_tokens: vault2.issued_tokens,
to_be_redeemed_tokens: vault2.to_be_redeemed_tokens,
to_be_replaced_tokens: vault2.to_be_replaced_tokens,
replace_collateral: vault2.replace_collateral,
active_replace_collateral: vault2.active_replace_collateral,
liquidated_collateral: vault2.liquidated_collateral,
let test_liquidaiton_vault = DefaultSystemVault::<Test> {
to_be_issued_tokens: 123,
issued_tokens: 234,
to_be_redeemed_tokens: TO_BE_REDEEMED_DECREASE.into(),
collateral: 345,
currency_pair: currency_pair.clone(),
};

// check that vault struct was migrated correctly
assert_eq!(
crate::Vaults::<Test>::iter().collect::<Vec<_>>(),
vec![
(expected_migrated_vault2.id.clone(), expected_migrated_vault2),
(expected_migrated_vault1.id.clone(), expected_migrated_vault1),
]
);
crate::LiquidationVault::<Test>::insert(&currency_pair, &test_liquidaiton_vault);

fix_liquidation_vault::<Test>().unwrap();

// check that public key is set
assert_eq!(
crate::VaultBitcoinPublicKey::<Test>::iter().collect::<Vec<_>>(),
vec![
(vault2.id.account_id.clone(), vault2.wallet.public_key),
(vault1.id.account_id.clone(), vault1.wallet.public_key),
]
crate::LiquidationVault::<Test>::get(&currency_pair).unwrap(),
DefaultSystemVault::<Test> {
to_be_redeemed_tokens: 0,
..test_liquidaiton_vault
}
);

// check that storage version is bumped
assert!(crate::StorageVersion::<Test>::get() == Version::V3);
});
})
}
}

Expand Down
29 changes: 14 additions & 15 deletions parachain/runtime/kintsugi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,21 +408,20 @@ impl pallet_scheduler::Config for Runtime {
type NoPreimagePostponement = NoPreimagePostponement;
}

// Migration for scheduler pallet to move from a plain Call to a CallOrHash.
pub struct SchedulerMigrationV3;
impl frame_support::traits::OnRuntimeUpgrade for SchedulerMigrationV3 {
pub struct LiquidationVaultFixMigration;
impl frame_support::traits::OnRuntimeUpgrade for LiquidationVaultFixMigration {
fn on_runtime_upgrade() -> frame_support::weights::Weight {
Scheduler::migrate_v2_to_v3()
}

#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<(), &'static str> {
Scheduler::pre_migrate_to_v3()
}

#[cfg(feature = "try-runtime")]
fn post_upgrade() -> Result<(), &'static str> {
Scheduler::post_migrate_to_v3()
use orml_traits::MultiReservableCurrency;
use sp_runtime::AccountId32;
if let Ok(weight) = vault_registry::types::liquidation_vault_fix::fix_liquidation_vault::<Runtime>() {
// a3b3EwCtmURY7K3d6aoWzouHriGfTsvCP2axuMVGpRpkPoxg8
let account_raw = hex_literal::hex!("24ac7fb5407f270d807425ecc6352305c0a21b9e7a1ba9812a1785a2af9b955a");
let account_id = AccountId32::from(account_raw);
Tokens::unreserve(Token(KSM), &account_id, 42335659763394);
weight
} else {
0
}
}
}

Expand Down Expand Up @@ -1041,7 +1040,7 @@ pub type Executive = frame_executive::Executive<
frame_system::ChainContext<Runtime>,
Runtime,
AllPalletsWithSystem,
SchedulerMigrationV3,
LiquidationVaultFixMigration,
>;

#[cfg(not(feature = "disable-runtime-api"))]
Expand Down

0 comments on commit a8c0a15

Please sign in to comment.