Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BREAKING] Refactor: remove vault collateral interface #781

Merged
merged 2 commits into from
Nov 29, 2022
Merged
Changes from all commits
Commits
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
3 changes: 3 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 crates/issue/Cargo.toml
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ fee = { path = "../fee", default-features = false }
security = { path = "../security", default-features = false }
currency = { path = "../currency", default-features = false }
vault-registry = { path = "../vault-registry", default-features = false }
nomination = { path = "../nomination", default-features = false }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a dev-dependency right?


primitives = { package = "interbtc-primitives", path = "../../primitives", default-features = false }

@@ -74,6 +75,7 @@ std = [
"security/std",
"currency/std",
"vault-registry/std",
"nomination/std",

"primitives/std",

7 changes: 7 additions & 0 deletions crates/issue/src/mock.rs
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ frame_support::construct_runtime!(
Fee: fee::{Pallet, Call, Config<T>, Storage},
Staking: staking::{Pallet, Storage, Event<T>},
Currency: currency::{Pallet},
Nomination: nomination::{Pallet, Call, Config, Storage, Event<T>},
}
);

@@ -154,6 +155,12 @@ impl vault_registry::Config for Test {
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = Nomination;
}

impl nomination::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
}

pub struct CurrencyConvert;
1 change: 1 addition & 0 deletions crates/nomination/Cargo.toml
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ reward = { path = "../reward", default-features = false }
staking = { path = "../staking", default-features = false }

primitives = { package = "interbtc-primitives", path = "../../primitives", default-features = false }
traits = { path = "../../crates/traits", default-features = false }

# Orml dependencies
orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "f4ef99fb108232c4c1fe9ed54d08be3681b77669", default-features = false, optional = true }
38 changes: 24 additions & 14 deletions crates/nomination/src/lib.rs
Original file line number Diff line number Diff line change
@@ -245,9 +245,10 @@ impl<T: Config> Pallet<T> {
Error::<T>::CannotWithdrawCollateral
);

ensure!(Self::is_nomination_enabled(), Error::<T>::VaultNominationDisabled);
ensure!(Self::is_opted_in(vault_id)?, Error::<T>::VaultNotOptedInToNomination);

if &vault_id.account_id != nominator_id {
ensure!(Self::is_nomination_enabled(), Error::<T>::VaultNominationDisabled);
ensure!(Self::is_opted_in(vault_id)?, Error::<T>::VaultNotOptedInToNomination);
}
ext::vault_registry::decrease_total_backing_collateral(&vault_id.currencies, &amount)?;
}

@@ -271,25 +272,28 @@ impl<T: Config> Pallet<T> {
nominator_id: &T::AccountId,
amount: BalanceOf<T>,
) -> DispatchResult {
ensure!(Self::is_nomination_enabled(), Error::<T>::VaultNominationDisabled);
ensure!(Self::is_opted_in(vault_id)?, Error::<T>::VaultNotOptedInToNomination);

let amount = Amount::new(amount, vault_id.collateral_currency());

let total_nominated_collateral = Self::get_total_nominated_collateral(vault_id)?;
let new_nominated_collateral = total_nominated_collateral.checked_add(&amount)?;
let max_nominatable_collateral = Self::get_nomination_limit(vault_id);
ensure!(
new_nominated_collateral.le(&max_nominatable_collateral)?,
Error::<T>::NominationExceedsLimit
);
if &vault_id.account_id != nominator_id {
let total_nominated_collateral = Self::get_total_nominated_collateral(vault_id)?;
let new_nominated_collateral = total_nominated_collateral.checked_add(&amount)?;
let max_nominatable_collateral = Self::get_nomination_limit(vault_id);

ensure!(Self::is_nomination_enabled(), Error::<T>::VaultNominationDisabled);
ensure!(Self::is_opted_in(vault_id)?, Error::<T>::VaultNotOptedInToNomination);

ensure!(
new_nominated_collateral.le(&max_nominatable_collateral)?,
Error::<T>::NominationExceedsLimit
);
amount.transfer(&nominator_id, &vault_id.account_id)?;
}

// Withdraw all vault rewards first, to prevent the nominator from withdrawing past rewards
ext::fee::withdraw_all_vault_rewards::<T>(vault_id)?;

// Deposit `amount` of stake into the vault staking pool
ext::staking::deposit_stake::<T>(vault_id, nominator_id, amount.amount())?;
amount.transfer(&nominator_id, &vault_id.account_id)?;
amount.lock_on(&vault_id.account_id)?;
ext::vault_registry::try_increase_total_backing_collateral(&vault_id.currencies, &amount)?;

@@ -366,3 +370,9 @@ impl<T: Config> Pallet<T> {
Ok(Amount::new(amount, vault_id.collateral_currency()))
}
}

impl<T: Config> traits::NominationApi<DefaultVaultId<T>, Amount<T>> for Pallet<T> {
fn deposit_vault_collateral(vault_id: &DefaultVaultId<T>, amount: &Amount<T>) -> Result<(), DispatchError> {
Pallet::<T>::_deposit_collateral(vault_id, &vault_id.account_id, amount.amount())
}
}
1 change: 1 addition & 0 deletions crates/nomination/src/mock.rs
Original file line number Diff line number Diff line change
@@ -159,6 +159,7 @@ impl vault_registry::Config for Test {
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = Nomination;
}

pub struct CurrencyConvert;
2 changes: 2 additions & 0 deletions crates/redeem/Cargo.toml
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@ frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch =
reward = { path = "../reward" }
staking = { path = "../staking" }
currency = { path = "../currency", features = ["testing-utils"] }
nomination = { path = "../nomination" }

# Orml dependencies
orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "f4ef99fb108232c4c1fe9ed54d08be3681b77669" }
@@ -73,6 +74,7 @@ std = [
"fee/std",
"security/std",
"vault-registry/std",
"nomination/std",
"primitives/std",

"orml-tokens/std",
7 changes: 7 additions & 0 deletions crates/redeem/src/mock.rs
Original file line number Diff line number Diff line change
@@ -46,6 +46,7 @@ frame_support::construct_runtime!(
Fee: fee::{Pallet, Call, Config<T>, Storage},
Staking: staking::{Pallet, Storage, Event<T>},
Currency: currency::{Pallet},
Nomination: nomination::{Pallet, Call, Config, Storage, Event<T>},
}
);

@@ -151,6 +152,12 @@ impl vault_registry::Config for Test {
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = Nomination;
}

impl nomination::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
}

pub struct CurrencyConvert;
11 changes: 6 additions & 5 deletions crates/replace/src/mock.rs
Original file line number Diff line number Diff line change
@@ -181,6 +181,12 @@ impl vault_registry::Config for Test {
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = Nomination;
}

impl nomination::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
}

impl staking::Config for Test {
@@ -205,11 +211,6 @@ impl security::Config for Test {
type RuntimeEvent = RuntimeEvent;
}

impl nomination::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
}

parameter_types! {
pub const MinimumPeriod: Moment = 5;
}
4 changes: 4 additions & 0 deletions crates/traits/src/lib.rs
Original file line number Diff line number Diff line change
@@ -25,3 +25,7 @@ impl ConvertToBigUint for u128 {
pub trait OracleApi<Amount, CurrencyId> {
fn convert(amount: &Amount, to: CurrencyId) -> Result<Amount, DispatchError>;
}

pub trait NominationApi<VaultId, Amount> {
fn deposit_vault_collateral(vault_id: &VaultId, amount: &Amount) -> Result<(), DispatchError>;
}
3 changes: 2 additions & 1 deletion crates/vault-registry/Cargo.toml
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ currency = { path = "../currency", default-features = false }
reward = { path = "../reward", default-features = false }
staking = { path = "../staking", default-features = false }
primitives = { package = "interbtc-primitives", path = "../../primitives", default-features = false }
traits = { path = "../../crates/traits", default-features = false }

# Orml dependencies
orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "f4ef99fb108232c4c1fe9ed54d08be3681b77669", default-features = false }
@@ -44,8 +45,8 @@ orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-li
mocktopus = "0.7.0"
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
currency = { path = "../currency", default-features = false, features = ["testing-utils"] }
traits = { path = "../../crates/traits", default-features = false }
pretty_assertions = "0.7.2"
visibility = { version = "0.0.1" }

[features]
default = ["std"]
20 changes: 0 additions & 20 deletions crates/vault-registry/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -58,26 +58,6 @@ benchmarks! {
VaultRegistry::<T>::register_public_key(origin.clone().into(), public_key).unwrap();
}: _(origin, vault_id.currencies.clone(), amount.into())

deposit_collateral {
let vault_id = get_vault_id::<T>();
mint_collateral::<T>(&vault_id.account_id, (1u32 << 31).into());
let amount = 100u32.into();
register_vault_with_collateral::<T>(vault_id.clone(), 100000000);
Oracle::<T>::_set_exchange_rate(get_collateral_currency_id::<T>(),
UnsignedFixedPoint::<T>::one()
).unwrap();
}: _(RawOrigin::Signed(vault_id.account_id), vault_id.currencies.clone(), amount)

withdraw_collateral {
let vault_id = get_vault_id::<T>();
mint_collateral::<T>(&vault_id.account_id, (1u32 << 31).into());
let amount = 100u32.into();
register_vault_with_collateral::<T>(vault_id.clone(), 100000000);
Oracle::<T>::_set_exchange_rate(get_collateral_currency_id::<T>(),
UnsignedFixedPoint::<T>::one()
).unwrap();
}: _(RawOrigin::Signed(vault_id.account_id), vault_id.currencies.clone(), amount)

register_public_key {
let vault_id = get_vault_id::<T>();
mint_collateral::<T>(&vault_id.account_id, (1u32 << 31).into());
101 changes: 4 additions & 97 deletions crates/vault-registry/src/lib.rs
Original file line number Diff line number Diff line change
@@ -65,6 +65,7 @@ use sp_std::{
fmt::Debug,
vec::Vec,
};
use traits::NominationApi;

// value taken from https://github.com/substrate-developer-hub/recipes/blob/master/pallets/ocw-demo/src/lib.rs
pub const UNSIGNED_TXS_PRIORITY: u64 = 100;
@@ -123,6 +124,8 @@ pub mod pallet {
/// Currency used for griefing collateral, e.g. DOT.
#[pallet::constant]
type GetGriefingCollateralCurrencyId: Get<CurrencyId<Self>>;

type NominationApi: NominationApi<DefaultVaultId<Self>, Amount<Self>>;
}

#[pallet::hooks]
@@ -190,74 +193,6 @@ pub mod pallet {
Ok(().into())
}

/// Deposit collateral as a security against stealing the
/// Bitcoin locked with the caller.
///
/// # Arguments
/// * `amount` - the amount of extra collateral to lock
#[pallet::weight(<T as Config>::WeightInfo::deposit_collateral())]
#[transactional]
pub fn deposit_collateral(
origin: OriginFor<T>,
currency_pair: DefaultVaultCurrencyPair<T>,
#[pallet::compact] amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let account_id = ensure_signed(origin)?;

let vault_id = VaultId::new(account_id, currency_pair.collateral, currency_pair.wrapped);

let vault = Self::get_active_rich_vault_from_id(&vault_id)?;

let amount = Amount::new(amount, currency_pair.collateral);

Self::try_deposit_collateral(&vault_id, &amount)?;

Self::deposit_event(Event::<T>::DepositCollateral {
vault_id: vault.id(),
new_collateral: amount.amount(),
total_collateral: vault.get_total_collateral()?.amount(),
free_collateral: vault.get_free_collateral()?.amount(),
});
Ok(().into())
}

/// Withdraws `amount` of the collateral from the amount locked by
/// the vault corresponding to the origin account
/// The collateral left after withdrawal must be more
/// (free or used in collateral issued tokens) than MinimumCollateralVault
/// and above the SecureCollateralThreshold. Collateral that is currently
/// being used to back issued tokens remains locked until the Vault
/// is used for a redeem request (full release can take multiple redeem requests).
///
/// # Arguments
/// * `amount` - the amount of collateral to withdraw
///
/// # Errors
/// * `VaultNotFound` - if no vault exists for the origin account
/// * `InsufficientCollateralAvailable` - if the vault does not own enough collateral
#[pallet::weight(<T as Config>::WeightInfo::withdraw_collateral())]
#[transactional]
pub fn withdraw_collateral(
origin: OriginFor<T>,
currency_pair: DefaultVaultCurrencyPair<T>,
#[pallet::compact] amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let account_id = ensure_signed(origin)?;

let vault_id = VaultId::new(account_id, currency_pair.collateral, currency_pair.wrapped);
let vault = Self::get_rich_vault_from_id(&vault_id)?;

let amount = Amount::new(amount, currency_pair.collateral);
Self::try_withdraw_collateral(&vault_id, &amount)?;

Self::deposit_event(Event::<T>::WithdrawCollateral {
vault_id: vault.id(),
withdrawn_amount: amount.amount(),
total_collateral: vault.get_total_collateral()?.amount(),
});
Ok(().into())
}

/// Registers a new Bitcoin address for the vault.
///
/// # Arguments
@@ -864,21 +799,7 @@ impl<T: Config> Pallet<T> {
/// * `vault_id` - the id of the vault
/// * `amount` - the amount of collateral
pub fn try_deposit_collateral(vault_id: &DefaultVaultId<T>, amount: &Amount<T>) -> DispatchResult {
// ensure the vault is active
let _vault = Self::get_active_rich_vault_from_id(vault_id)?;

// will fail if collateral ceiling exceeded
Self::try_increase_total_backing_collateral(&vault_id.currencies, amount)?;
// will fail if free_balance is insufficient
amount.lock_on(&vault_id.account_id)?;

// withdraw first such that past rewards don't get changed by this deposit
ext::fee::withdraw_all_vault_rewards::<T>(vault_id)?;

// Deposit `amount` of stake in the pool
ext::staking::deposit_stake::<T>(vault_id, &vault_id.account_id, amount)?;

Ok(())
T::NominationApi::deposit_vault_collateral(&vault_id, &amount)
}

/// Withdraw an `amount` of collateral without checking collateralization
@@ -900,20 +821,6 @@ impl<T: Config> Pallet<T> {
Ok(())
}

/// Withdraw an `amount` of collateral, ensuring that the vault is sufficiently
/// over-collateralized
///
/// # Arguments
/// * `vault_id` - the id of the vault
/// * `amount` - the amount of collateral
pub fn try_withdraw_collateral(vault_id: &DefaultVaultId<T>, amount: &Amount<T>) -> DispatchResult {
ensure!(
Self::is_allowed_to_withdraw_collateral(vault_id, amount)?,
Error::<T>::InsufficientCollateral
);
Self::force_withdraw_collateral(vault_id, amount)
}

/// Checks if the vault would be above the secure threshold after withdrawing collateral
pub fn is_allowed_to_withdraw_collateral(
vault_id: &DefaultVaultId<T>,
28 changes: 27 additions & 1 deletion crates/vault-registry/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate as vault_registry;
use crate::{Config, Error};
use crate::{ext, Config, Error};
use frame_support::{
parameter_types,
traits::{ConstU32, Everything, GenesisBuild},
@@ -14,6 +14,7 @@ use sp_core::H256;
use sp_runtime::{
testing::{Header, TestXt},
traits::{BlakeTwo256, IdentityLookup, One, Zero},
DispatchError,
};
use traits::OracleApi;

@@ -204,13 +205,38 @@ impl fee::Config for Test {
parameter_types! {
pub const VaultPalletId: PalletId = PalletId(*b"mod/vreg");
}
pub struct MockDeposit;

impl traits::NominationApi<VaultId<AccountId, CurrencyId>, currency::Amount<Test>> for MockDeposit {
fn deposit_vault_collateral(
vault_id: &VaultId<AccountId, CurrencyId>,
amount: &currency::Amount<Test>,
) -> Result<(), DispatchError> {
// ensure the vault is active
let _vault = VaultRegistry::get_active_rich_vault_from_id(vault_id)?;

// will fail if collateral ceiling exceeded
VaultRegistry::try_increase_total_backing_collateral(&vault_id.currencies, amount)?;
// will fail if free_balance is insufficient
amount.lock_on(&vault_id.account_id)?;

// withdraw first such that past rewards don't get changed by this deposit
ext::fee::withdraw_all_vault_rewards::<Test>(vault_id)?;

// Deposit `amount` of stake in the pool
ext::staking::deposit_stake::<Test>(vault_id, &vault_id.account_id, amount)?;

Ok(())
}
}

impl Config for Test {
type PalletId = VaultPalletId;
type RuntimeEvent = RuntimeEvent;
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = MockDeposit;
}

impl<C> frame_system::offchain::SendTransactionTypes<C> for Test
265 changes: 1 addition & 264 deletions crates/vault-registry/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::{
ext,
mock::*,
types::{BalanceOf, UpdatableVault},
BtcPublicKey, CurrencySource, DefaultVaultId, DispatchError, Vault, VaultStatus,
BtcPublicKey, CurrencySource, DefaultVaultId, DispatchError, Vault,
};
use codec::Decode;
use currency::Amount;
@@ -217,70 +216,6 @@ fn register_vault_fails_when_already_registered() {
});
}

#[test]
fn deposit_collateral_succeeds() {
run_test(|| {
let id = create_vault(RICH_ID);
let additional = RICH_COLLATERAL - DEFAULT_COLLATERAL;
let res =
VaultRegistry::deposit_collateral(RuntimeOrigin::signed(id.account_id), DEFAULT_CURRENCY_PAIR, additional);
assert_ok!(res);
let new_collateral = ext::currency::get_reserved_balance::<Test>(Token(DOT), &id.account_id);
assert_eq!(new_collateral, amount(DEFAULT_COLLATERAL + additional));
assert_emitted!(Event::DepositCollateral {
vault_id: id,
new_collateral: additional,
total_collateral: RICH_COLLATERAL,
free_collateral: RICH_COLLATERAL
});
});
}

#[test]
fn deposit_collateral_fails_when_vault_does_not_exist() {
run_test(|| {
let res = VaultRegistry::deposit_collateral(RuntimeOrigin::signed(3), DEFAULT_CURRENCY_PAIR, 50);
assert_err!(res, TestError::VaultNotFound);
})
}

#[test]
fn withdraw_collateral_succeeds() {
run_test(|| {
let id = create_sample_vault();
let res = VaultRegistry::withdraw_collateral(RuntimeOrigin::signed(id.account_id), id.currencies.clone(), 50);
assert_ok!(res);
let new_collateral = ext::currency::get_reserved_balance::<Test>(Token(DOT), &id.account_id);
assert_eq!(new_collateral, amount(DEFAULT_COLLATERAL - 50));
assert_emitted!(Event::WithdrawCollateral {
vault_id: id,
withdrawn_amount: 50,
total_collateral: DEFAULT_COLLATERAL - 50
});
});
}

#[test]
fn withdraw_collateral_fails_when_vault_does_not_exist() {
run_test(|| {
let res = VaultRegistry::withdraw_collateral(RuntimeOrigin::signed(3), DEFAULT_CURRENCY_PAIR, 50);
assert_err!(res, TestError::VaultNotFound);
})
}

#[test]
fn withdraw_collateral_fails_when_not_enough_collateral() {
run_test(|| {
let id = create_sample_vault();
let res = VaultRegistry::withdraw_collateral(
RuntimeOrigin::signed(id.account_id),
id.currencies,
DEFAULT_COLLATERAL + 1,
);
assert_err!(res, TestError::InsufficientCollateral);
})
}

#[test]
fn try_increase_to_be_issued_tokens_succeeds() {
run_test(|| {
@@ -717,204 +652,6 @@ fn cancel_replace_tokens_succeeds() {
});
}

#[test]
fn liquidate_at_most_liquidation_threshold() {
run_test(|| {
let vault_id = DEFAULT_ID;

let issued_tokens = 100;
let to_be_issued_tokens = 25;
let to_be_redeemed_tokens = 40;

let backing_collateral = 10000;
let liquidated_collateral = 1875; // 125 * 10 * 1.5
let liquidated_for_issued = 1275; // 125 * 10 * 1.5

create_vault_with_collateral(&vault_id, backing_collateral);
let liquidation_vault_before = VaultRegistry::get_rich_liquidation_vault(&DEFAULT_CURRENCY_PAIR);

VaultRegistry::_set_liquidation_collateral_threshold(
DEFAULT_CURRENCY_PAIR,
FixedU128::checked_from_rational(150, 100).unwrap(), // 150%
);

VaultRegistry::_set_premium_redeem_threshold(
DEFAULT_CURRENCY_PAIR,
FixedU128::checked_from_rational(175, 100).unwrap(), // 175%
);

VaultRegistry::_set_secure_collateral_threshold(
DEFAULT_CURRENCY_PAIR,
FixedU128::checked_from_rational(200, 100).unwrap(), // 200%
);

let collateral_before = ext::currency::get_reserved_balance::<Test>(Token(DOT), &vault_id.account_id);
assert_eq!(collateral_before, amount(backing_collateral)); // sanity check

// required for `issue_tokens` to work
assert_ok!(VaultRegistry::try_increase_to_be_issued_tokens(
&vault_id,
&wrapped(issued_tokens)
));
assert_ok!(VaultRegistry::issue_tokens(&vault_id, &wrapped(issued_tokens)));
assert_ok!(VaultRegistry::try_increase_to_be_issued_tokens(
&vault_id,
&wrapped(to_be_issued_tokens)
));
assert_ok!(VaultRegistry::try_increase_to_be_redeemed_tokens(
&vault_id,
&wrapped(to_be_redeemed_tokens)
));

let vault_orig = <crate::Vaults<Test>>::get(&vault_id).unwrap();
let backing_collateral_orig = VaultRegistry::get_backing_collateral(&vault_id).unwrap();

// set exchange rate
convert_to.mock_safe(convert_with_exchange_rate(10));

assert_ok!(VaultRegistry::liquidate_vault(&vault_id));

// should only be able to withdraw excess above secure threshold
assert_err!(
VaultRegistry::withdraw_collateral(
RuntimeOrigin::signed(vault_id.account_id),
vault_id.currencies.clone(),
backing_collateral
),
TestError::InsufficientCollateral
);
assert_ok!(VaultRegistry::withdraw_collateral(
RuntimeOrigin::signed(vault_id.account_id),
vault_id.currencies.clone(),
backing_collateral - liquidated_collateral
));

let liquidation_vault_after = VaultRegistry::get_rich_liquidation_vault(&DEFAULT_CURRENCY_PAIR);
let liquidated_vault = <crate::Vaults<Test>>::get(&vault_id).unwrap();
assert!(matches!(liquidated_vault.status, VaultStatus::Liquidated));
assert_emitted!(Event::LiquidateVault {
vault_id: vault_id.clone(),
issued_tokens: vault_orig.issued_tokens,
to_be_issued_tokens: vault_orig.to_be_issued_tokens,
to_be_redeemed_tokens: vault_orig.to_be_redeemed_tokens,
to_be_replaced_tokens: vault_orig.to_be_replaced_tokens,
backing_collateral: backing_collateral_orig.amount(),
status: VaultStatus::Liquidated,
replace_collateral: vault_orig.replace_collateral,
});

// check liquidation_vault tokens & collateral
assert_eq!(
liquidation_vault_after.data.issued_tokens,
liquidation_vault_before.data.issued_tokens + issued_tokens
);
assert_eq!(
liquidation_vault_after.data.to_be_issued_tokens,
liquidation_vault_before.data.to_be_issued_tokens + to_be_issued_tokens
);
assert_eq!(
liquidation_vault_after.data.to_be_redeemed_tokens,
liquidation_vault_before.data.to_be_redeemed_tokens + to_be_redeemed_tokens
);
assert_eq!(
ext::currency::get_reserved_balance::<Test>(Token(DOT), &liquidation_vault_before.id().account_id),
amount(liquidated_for_issued)
);

// check vault tokens & collateral
let user_vault_after = VaultRegistry::get_rich_vault_from_id(&vault_id).unwrap();
assert_eq!(user_vault_after.data.issued_tokens, 0);
assert_eq!(user_vault_after.data.to_be_issued_tokens, 0);
assert_eq!(user_vault_after.data.to_be_redeemed_tokens, to_be_redeemed_tokens);
assert_eq!(
ext::currency::get_reserved_balance::<Test>(Token(DOT), &vault_id.account_id),
amount(liquidated_collateral - liquidated_for_issued)
);
assert_eq!(
ext::currency::get_free_balance::<Test>(Token(DOT), &vault_id.account_id),
amount(DEFAULT_COLLATERAL - liquidated_collateral)
);
});
}

#[test]
fn can_withdraw_only_up_to_custom_threshold() {
run_test(|| {
let vault_id = DEFAULT_ID;

let issued_tokens = 100;
let exchange_rate = 10;
let secure_threshold = 200u32; // %
let custom_threshold = 300u32; // %
let minimum_backing_collateral = 100 * 10 * 2;
let backing_collateral = 100 * 10 * 3 + 1;

create_vault_with_collateral(&vault_id, backing_collateral);

VaultRegistry::_set_secure_collateral_threshold(
DEFAULT_CURRENCY_PAIR,
FixedU128::checked_from_rational(secure_threshold, 100).unwrap(), // 200%
);

// set custom threshold for vault
assert_ok!(VaultRegistry::set_custom_secure_threshold(
RuntimeOrigin::signed(vault_id.account_id),
DEFAULT_CURRENCY_PAIR,
UnsignedFixedPoint::checked_from_rational(custom_threshold, 100),
));

// set exchange rate
convert_to.mock_safe(convert_with_exchange_rate(exchange_rate));

// issue
assert_ok!(VaultRegistry::try_increase_to_be_issued_tokens(
&vault_id,
&wrapped(issued_tokens)
));
assert_ok!(VaultRegistry::issue_tokens(&vault_id, &wrapped(issued_tokens)));

// should not be able to withdraw below custom threshold
assert_err!(
VaultRegistry::withdraw_collateral(
RuntimeOrigin::signed(vault_id.account_id),
vault_id.currencies.clone(),
2
),
TestError::InsufficientCollateral
);
// should be able to withdraw up to custom threshold
assert_ok!(VaultRegistry::withdraw_collateral(
RuntimeOrigin::signed(vault_id.account_id),
vault_id.currencies.clone(),
1
));

// reset custom threshold
assert_ok!(VaultRegistry::set_custom_secure_threshold(
RuntimeOrigin::signed(vault_id.account_id),
DEFAULT_CURRENCY_PAIR,
None
));

// should not be able to withdraw below base secure threshold now
assert_err!(
VaultRegistry::withdraw_collateral(
RuntimeOrigin::signed(vault_id.account_id),
vault_id.currencies.clone(),
backing_collateral - minimum_backing_collateral // will be 1 over
),
TestError::InsufficientCollateral
);

// should be able to withdraw to the secure threshold
assert_ok!(VaultRegistry::withdraw_collateral(
RuntimeOrigin::signed(vault_id.account_id),
vault_id.currencies.clone(),
backing_collateral - minimum_backing_collateral - 1
));
});
}

#[test]
fn is_collateral_below_threshold_true_succeeds() {
run_test(|| {
12 changes: 1 addition & 11 deletions crates/vault-registry/src/types.rs
Original file line number Diff line number Diff line change
@@ -230,7 +230,7 @@ pub type DefaultVault<T> = Vault<

pub type DefaultSystemVault<T> = SystemVault<BalanceOf<T>, CurrencyId<T>>;

#[cfg_attr(feature = "integration-tests", visibility::make(pub))]
#[cfg_attr(any(test, feature = "integration-tests"), visibility::make(pub))]
trait UpdatableVault<T: Config> {
fn increase_issued(&mut self, tokens: &Amount<T>) -> DispatchResult;

@@ -701,16 +701,6 @@ pub(crate) struct RichSystemVault<T: Config> {

#[cfg_attr(test, mockable)]
impl<T: Config> RichSystemVault<T> {
#[cfg(test)]
pub(crate) fn id(&self) -> DefaultVaultId<T> {
let account_id = Pallet::<T>::liquidation_vault_account_id();
VaultId::new(
account_id,
self.data.currency_pair.collateral,
self.data.currency_pair.wrapped,
)
}

pub(crate) fn burn_issued(&mut self, tokens: &Amount<T>) -> DispatchResult {
self.decrease_issued(tokens)
}
1 change: 1 addition & 0 deletions parachain/runtime/interlay/src/lib.rs
Original file line number Diff line number Diff line change
@@ -961,6 +961,7 @@ impl vault_registry::Config for Runtime {
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = Nomination;
}

impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
1 change: 1 addition & 0 deletions parachain/runtime/kintsugi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -961,6 +961,7 @@ impl vault_registry::Config for Runtime {
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = Nomination;
}

impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
1 change: 1 addition & 0 deletions parachain/runtime/testnet-interlay/src/lib.rs
Original file line number Diff line number Diff line change
@@ -931,6 +931,7 @@ impl vault_registry::Config for Runtime {
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = Nomination;
}

impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
1 change: 1 addition & 0 deletions parachain/runtime/testnet-kintsugi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -931,6 +931,7 @@ impl vault_registry::Config for Runtime {
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = Nomination;
}

impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
1 change: 1 addition & 0 deletions standalone/runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -885,6 +885,7 @@ impl vault_registry::Config for Runtime {
type Balance = Balance;
type WeightInfo = ();
type GetGriefingCollateralCurrencyId = GetNativeCurrencyId;
type NominationApi = Nomination;
}

impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
5 changes: 2 additions & 3 deletions standalone/runtime/tests/mock/mod.rs
Original file line number Diff line number Diff line change
@@ -1064,9 +1064,8 @@ pub fn get_register_vault_result(vault_id: &VaultId, collateral: Amount<Runtime>

pub fn try_register_vault(collateral: Amount<Runtime>, vault_id: &VaultId) {
if VaultRegistryPallet::get_vault_from_id(vault_id).is_err() {
if TokensPallet::accounts(vault_id.account_id.clone(), vault_id.collateral_currency()).free
< collateral.amount()
{
let q = TokensPallet::accounts(vault_id.account_id.clone(), vault_id.collateral_currency());
if q.free < collateral.amount() {
// register vault if not yet registered
assert_ok!(RuntimeCall::Tokens(TokensCall::set_balance {
who: vault_id.account_id.clone(),
5 changes: 3 additions & 2 deletions standalone/runtime/tests/mock/nomination_testing_utils.rs
Original file line number Diff line number Diff line change
@@ -74,8 +74,9 @@ pub fn assert_nominate_collateral(vault_id: &VaultId, nominator_id: AccountId, a
}

pub fn withdraw_vault_collateral(vault_id: &VaultId, amount_collateral: Amount<Runtime>) -> DispatchResultWithPostInfo {
RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount: amount_collateral.amount(),
})
.dispatch(origin_of(vault_id.account_id.clone()))
9 changes: 5 additions & 4 deletions standalone/runtime/tests/test_fee_pool.rs
Original file line number Diff line number Diff line change
@@ -590,8 +590,8 @@ fn do_random_nomination_sequence() {
let max_amount =
CoreVaultData::vault(vault_id.clone()).free_balance[&vault_id.collateral_currency()].amount();
let amount = rng.gen_range(0..max_amount);
assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::deposit_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::deposit_collateral {
vault_id: vault_id.clone(),
amount,
})
.dispatch(origin_of(vault_id.account_id.clone())));
@@ -616,8 +616,9 @@ fn do_random_nomination_sequence() {
continue;
}
let amount = rng.gen_range(0..max_amount);
assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount,
})
.dispatch(origin_of(vault_id.account_id.clone())));
5 changes: 3 additions & 2 deletions standalone/runtime/tests/test_issue.rs
Original file line number Diff line number Diff line change
@@ -562,8 +562,9 @@ fn integration_test_withdraw_after_request_issue() {
.is_err());

// should not be possible to withdraw the collateral now
assert!(RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
assert!(RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount: collateral_vault.amount()
})
.dispatch(origin_of(account_of(vault)))
12 changes: 7 additions & 5 deletions standalone/runtime/tests/test_nomination.rs
Original file line number Diff line number Diff line change
@@ -344,8 +344,9 @@ mod spec_based_tests {
fn integration_test_withdraw_collateral_preconditions_collateralization() {
// PRECONDITION: The Vault MUST remain above the secure collateralization threshold.
test_with_nomination_enabled(|vault_id| {
assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount: 750000
})
.dispatch(origin_of(account_of(VAULT))));
@@ -456,7 +457,7 @@ fn integration_test_vaults_cannot_withdraw_nominated_collateral() {
&vault_id,
default_backing_collateral(vault_id.collateral_currency()).with_amount(|x| x + 1)
),
VaultRegistryError::InsufficientCollateral
NominationError::CannotWithdrawCollateral
);
});
}
@@ -551,8 +552,9 @@ fn integration_test_nominator_withdrawal_request_reduces_issuable_tokens() {
#[test]
fn integration_test_nominator_withdrawal_below_collateralization_threshold_fails() {
test_with_nomination_enabled(|vault_id| {
assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount: 750000
})
.dispatch(origin_of(account_of(VAULT))));
68 changes: 37 additions & 31 deletions standalone/runtime/tests/test_vault_registry.rs
Original file line number Diff line number Diff line change
@@ -39,8 +39,8 @@ fn test_with<R>(execute: impl Fn(VaultId) -> R) {

fn deposit_collateral_and_issue(vault_id: VaultId) {
let new_collateral = 10_000;
assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::deposit_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::deposit_collateral {
vault_id: vault_id.clone(),
amount: new_collateral,
})
.dispatch(origin_of(account_of(VAULT))));
@@ -58,8 +58,8 @@ mod deposit_collateral_test {
let currency_id = vault_id.collateral_currency();
let amount = Amount::new(1_000, currency_id);

assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::deposit_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::deposit_collateral {
vault_id: vault_id.clone(),
amount: amount.amount(),
})
.dispatch(origin_of(account_of(VAULT))));
@@ -80,8 +80,8 @@ mod deposit_collateral_test {
let currency_id = vault_id.collateral_currency();
let amount = default_vault_free_balance(currency_id);

assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::deposit_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::deposit_collateral {
vault_id: vault_id.clone(),
amount: amount.amount()
})
.dispatch(origin_of(account_of(VAULT))));
@@ -103,8 +103,8 @@ mod deposit_collateral_test {
let amount = default_vault_free_balance(currency_id).amount() + 1;

assert_noop!(
RuntimeCall::VaultRegistry(VaultRegistryCall::deposit_collateral {
currency_pair: vault_id.currencies.clone(),
RuntimeCall::Nomination(NominationCall::deposit_collateral {
vault_id: vault_id.clone(),
amount: amount
})
.dispatch(origin_of(account_of(VAULT))),
@@ -141,8 +141,8 @@ mod deposit_collateral_test {
.dispatch(origin_of(vault_id.account_id.clone())));

assert_noop!(
RuntimeCall::VaultRegistry(VaultRegistryCall::deposit_collateral {
currency_pair: vault_id.currencies.clone(),
RuntimeCall::Nomination(NominationCall::deposit_collateral {
vault_id: vault_id.clone(),
amount: amount_1
})
.dispatch(origin_of(vault_id.account_id.clone())),
@@ -163,16 +163,16 @@ mod deposit_collateral_test {
let remaining = FUND_LIMIT_CEILING - current.amount();

assert_noop!(
RuntimeCall::VaultRegistry(VaultRegistryCall::deposit_collateral {
currency_pair: vault_id.currencies.clone(),
RuntimeCall::Nomination(NominationCall::deposit_collateral {
vault_id: vault_id.clone(),
amount: remaining + 1
})
.dispatch(origin_of(account_of(VAULT))),
VaultRegistryError::CurrencyCeilingExceeded
);

assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::deposit_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::deposit_collateral {
vault_id: vault_id.clone(),
amount: remaining,
})
.dispatch(origin_of(account_of(VAULT))));
@@ -194,9 +194,10 @@ mod withdraw_collateral_test {
let currency_id = vault_id.collateral_currency();
let amount = Amount::new(1_000, currency_id);

assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
amount: amount.amount()
assert_ok!(RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
amount: amount.amount(),
index: None,
})
.dispatch(origin_of(account_of(VAULT))));

@@ -216,8 +217,9 @@ mod withdraw_collateral_test {
let currency_id = vault_id.collateral_currency();
let amount = default_vault_backing_collateral(currency_id) - required_collateral(vault_id.clone());

assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount: amount.amount()
})
.dispatch(origin_of(account_of(VAULT))));
@@ -241,12 +243,13 @@ mod withdraw_collateral_test {
+ 1;

assert_noop!(
RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount: amount
})
.dispatch(origin_of(account_of(VAULT))),
VaultRegistryError::InsufficientCollateral
NominationError::CannotWithdrawCollateral
);
});
}
@@ -266,12 +269,13 @@ mod withdraw_collateral_test {
);

assert_err!(
RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount: amount.amount()
})
.dispatch(origin_of(account_of(VAULT))),
VaultRegistryError::InsufficientCollateral
NominationError::CannotWithdrawCollateral
);

assert_ok!(
@@ -282,8 +286,9 @@ mod withdraw_collateral_test {
.dispatch(origin_of(vault_id.account_id.clone()))
);

assert_ok!(RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
assert_ok!(RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount: amount.amount()
})
.dispatch(origin_of(account_of(VAULT))));
@@ -313,16 +318,17 @@ fn integration_test_vault_registry_with_parachain_shutdown_fails() {
SystemError::CallFiltered
);
assert_noop!(
RuntimeCall::VaultRegistry(VaultRegistryCall::deposit_collateral {
currency_pair: vault_id.currencies.clone(),
RuntimeCall::Nomination(NominationCall::deposit_collateral {
vault_id: vault_id.clone(),
amount: 0
})
.dispatch(origin_of(account_of(VAULT))),
SystemError::CallFiltered
);
assert_noop!(
RuntimeCall::VaultRegistry(VaultRegistryCall::withdraw_collateral {
currency_pair: vault_id.currencies.clone(),
RuntimeCall::Nomination(NominationCall::withdraw_collateral {
vault_id: vault_id.clone(),
index: None,
amount: 0
})
.dispatch(origin_of(account_of(VAULT))),