diff --git a/Cargo.lock b/Cargo.lock index face9e081b..01ca6a2a91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11607,6 +11607,7 @@ name = "redeem-rpc" version = "1.2.0" dependencies = [ "jsonrpsee", + "oracle-rpc-runtime-api", "parity-scale-codec", "redeem-rpc-runtime-api", "sp-api", @@ -11619,6 +11620,7 @@ name = "redeem-rpc-runtime-api" version = "1.2.0" dependencies = [ "frame-support", + "oracle-rpc-runtime-api", "parity-scale-codec", "sp-api", "sp-std", diff --git a/crates/fee/src/lib.rs b/crates/fee/src/lib.rs index c172324475..d98f8dfa4e 100644 --- a/crates/fee/src/lib.rs +++ b/crates/fee/src/lib.rs @@ -435,6 +435,13 @@ impl Pallet { >::get() } + /// Get the fee share that users need to pay to redeem tokens. + /// + /// # Returns + /// Returns the redeem fee. + pub fn get_redeem_fee_value() -> UnsignedFixedPoint { + >::get() + } /// Calculate punishment fee for a Vault that fails to execute a redeem /// request before the expiry. /// diff --git a/crates/redeem/rpc/Cargo.toml b/crates/redeem/rpc/Cargo.toml index fa03559d88..523683e9ea 100644 --- a/crates/redeem/rpc/Cargo.toml +++ b/crates/redeem/rpc/Cargo.toml @@ -11,3 +11,6 @@ sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkad sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" } sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" } redeem-rpc-runtime-api = { path = "runtime-api" } + +[dependencies.oracle-rpc-runtime-api] +path = '../../oracle/rpc/runtime-api' diff --git a/crates/redeem/rpc/runtime-api/Cargo.toml b/crates/redeem/rpc/runtime-api/Cargo.toml index be10d50822..a3ba7f6253 100644 --- a/crates/redeem/rpc/runtime-api/Cargo.toml +++ b/crates/redeem/rpc/runtime-api/Cargo.toml @@ -10,6 +10,10 @@ frame-support = { git = "https://github.com/paritytech/substrate", branch = "pol sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +[dependencies.oracle-rpc-runtime-api] +default-features = false +path = '../../../oracle/rpc/runtime-api' + [features] default = ["std"] std = [ diff --git a/crates/redeem/rpc/runtime-api/src/lib.rs b/crates/redeem/rpc/runtime-api/src/lib.rs index cdcc70a605..0b3dd1c080 100644 --- a/crates/redeem/rpc/runtime-api/src/lib.rs +++ b/crates/redeem/rpc/runtime-api/src/lib.rs @@ -3,10 +3,14 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::Codec; +use frame_support::dispatch::DispatchError; +use oracle_rpc_runtime_api::BalanceWrapper; use sp_std::vec::Vec; sp_api::decl_runtime_apis! { - pub trait RedeemApi where + pub trait RedeemApi where + VaultId: Codec, + Balance: Codec, AccountId: Codec, H256: Codec, RedeemRequest: Codec, @@ -16,5 +20,8 @@ sp_api::decl_runtime_apis! { /// Get all redeem requests for a particular vault fn get_vault_redeem_requests(vault_id: AccountId) -> Vec; + + /// Get all vaults below the premium redeem threshold, ordered in descending order of this amount + fn get_premium_redeem_vaults() -> Result)>, DispatchError>; } } diff --git a/crates/redeem/rpc/src/lib.rs b/crates/redeem/rpc/src/lib.rs index 6e0e2c1146..0003008009 100644 --- a/crates/redeem/rpc/src/lib.rs +++ b/crates/redeem/rpc/src/lib.rs @@ -6,20 +6,36 @@ use jsonrpsee::{ proc_macros::rpc, types::error::{CallError, ErrorCode, ErrorObject}, }; +use oracle_rpc_runtime_api::BalanceWrapper; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; -use sp_runtime::traits::Block as BlockT; +use sp_runtime::{ + traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}, + DispatchError, +}; use std::sync::Arc; pub use redeem_rpc_runtime_api::RedeemApi as RedeemRuntimeApi; +fn handle_response(result: Result, E>, msg: String) -> RpcResult { + result + .map_err(|err| internal_err(format!("Runtime error: {:?}: {:?}", msg, err)))? + .map_err(|err| internal_err(format!("Execution error: {:?}: {:?}", msg, err))) +} + #[rpc(client, server)] -pub trait RedeemApi { +pub trait RedeemApi +where + Balance: Codec + MaybeDisplay + MaybeFromStr, +{ #[method(name = "redeem_getRedeemRequests")] fn get_redeem_requests(&self, account_id: AccountId, at: Option) -> RpcResult>; #[method(name = "redeem_getVaultRedeemRequests")] fn get_vault_redeem_requests(&self, vault_id: AccountId, at: Option) -> RpcResult>; + + #[method(name = "redeem_getPremiumRedeemVaults")] + fn get_premium_redeem_vaults(&self, at: Option) -> RpcResult)>>; } fn internal_err(message: T) -> JsonRpseeError { @@ -47,12 +63,14 @@ impl Redeem { } #[async_trait] -impl RedeemApiServer<::Hash, AccountId, H256, RedeemRequest> - for Redeem +impl + RedeemApiServer<::Hash, VaultId, Balance, AccountId, H256, RedeemRequest> for Redeem where Block: BlockT, C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: RedeemRuntimeApi, + C::Api: RedeemRuntimeApi, + VaultId: Codec, + Balance: Codec + MaybeDisplay + MaybeFromStr, AccountId: Codec, H256: Codec, RedeemRequest: Codec, @@ -76,4 +94,17 @@ where api.get_vault_redeem_requests(at, vault_id) .map_err(|e| internal_err(format!("Unable to fetch redeem requests: {:?}", e))) } + + fn get_premium_redeem_vaults( + &self, + at: Option<::Hash>, + ) -> RpcResult)>> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + handle_response( + api.get_premium_redeem_vaults(at), + "Unable to find a vault below the premium redeem threshold".into(), + ) + } } diff --git a/crates/redeem/src/benchmarking.rs b/crates/redeem/src/benchmarking.rs index fd46579e84..c6a58c2453 100644 --- a/crates/redeem/src/benchmarking.rs +++ b/crates/redeem/src/benchmarking.rs @@ -18,10 +18,10 @@ use vault_registry::{ // Pallets use crate::Pallet as Redeem; use btc_relay::Pallet as BtcRelay; -use oracle::Pallet as Oracle; +use oracle::{OracleKey, Pallet as Oracle}; use security::Pallet as Security; +use sp_runtime::FixedPointNumber; use vault_registry::Pallet as VaultRegistry; - type UnsignedFixedPoint = ::UnsignedFixedPoint; fn collateral(amount: u32) -> Amount { diff --git a/crates/redeem/src/ext.rs b/crates/redeem/src/ext.rs index 32a0f25ff7..ecc3eaf666 100644 --- a/crates/redeem/src/ext.rs +++ b/crates/redeem/src/ext.rs @@ -42,8 +42,22 @@ pub(crate) mod vault_registry { use crate::DefaultVaultId; use currency::Amount; use frame_support::dispatch::{DispatchError, DispatchResult}; + use sp_std::vec::Vec; use vault_registry::types::{CurrencyId, CurrencySource, DefaultVault}; + pub fn calculate_inclusion_fee( + wrapped_currency: CurrencyId, + redeem_transaction_size: u32, + ) -> Result, DispatchError> { + >::calculate_inclusion_fee(wrapped_currency, redeem_transaction_size) + } + + pub fn get_premium_redeem_vaults( + redeem_transaction_size: u32, + ) -> Result, Amount)>, DispatchError> { + >::get_premium_redeem_vaults(redeem_transaction_size) + } + pub fn get_vault_max_premium_redeem( vault_id: &DefaultVaultId, ) -> Result, DispatchError> { @@ -199,17 +213,6 @@ pub(crate) mod security { } } -#[cfg_attr(test, mockable)] -pub(crate) mod oracle { - use crate::OracleKey; - use frame_support::dispatch::DispatchError; - use oracle::types::UnsignedFixedPoint; - - pub fn get_price(key: OracleKey) -> Result, DispatchError> { - >::get_price(key) - } -} - #[cfg_attr(test, mockable)] pub(crate) mod fee { use currency::Amount; diff --git a/crates/redeem/src/lib.rs b/crates/redeem/src/lib.rs index 34f4568d84..d93c05ccea 100644 --- a/crates/redeem/src/lib.rs +++ b/crates/redeem/src/lib.rs @@ -40,9 +40,7 @@ use frame_support::{ transactional, }; use frame_system::{ensure_root, ensure_signed}; -use oracle::OracleKey; use sp_core::H256; -use sp_runtime::{ArithmeticError, FixedPointNumber}; use sp_std::{convert::TryInto, vec::Vec}; use types::DefaultVaultId; use vault_registry::{ @@ -456,7 +454,6 @@ mod self_redeem { Ok(()) } } - // "Internal" functions, callable by code. #[cfg_attr(test, mockable)] impl Pallet { @@ -505,6 +502,9 @@ impl Pallet { let below_premium_redeem = ext::vault_registry::is_vault_below_premium_threshold::(&vault_id)?; let currency_id = vault_id.collateral_currency(); + // Calculate the premium collateral amount based on whether the redemption is below the premium redeem + // threshold. This should come before increasing the `to_be_redeemed` tokens and locking the amount to + // ensure accurate premium redeem calculations. let premium_collateral = if below_premium_redeem { let redeem_amount_wrapped_in_collateral = user_to_be_received_btc.convert_to(currency_id)?; let premium_redeem_rate = ext::fee::premium_redeem_reward_rate::(); @@ -797,13 +797,7 @@ impl Pallet { /// the inclusion fee rate reported by the oracle pub fn get_current_inclusion_fee(wrapped_currency: CurrencyId) -> Result, DispatchError> { let size: u32 = Self::redeem_transaction_size(); - let satoshi_per_bytes = ext::oracle::get_price::(OracleKey::FeeEstimation)?; - - let fee = satoshi_per_bytes - .checked_mul_int(size) - .ok_or(ArithmeticError::Overflow)?; - let amount = fee.try_into().map_err(|_| Error::::TryIntoIntError)?; - Ok(Amount::new(amount, wrapped_currency)) + ext::vault_registry::calculate_inclusion_fee::(wrapped_currency, size) } pub fn get_dust_value(currency_id: CurrencyId) -> Amount { @@ -821,6 +815,11 @@ impl Pallet { .collect::>() } + pub fn get_premium_redeem_vaults() -> Result, Amount)>, DispatchError> { + let size: u32 = Self::redeem_transaction_size(); + ext::vault_registry::get_premium_redeem_vaults::(size) + } + /// Fetch all redeem requests for the specified vault. /// /// # Arguments diff --git a/crates/vault-registry/rpc/runtime-api/src/lib.rs b/crates/vault-registry/rpc/runtime-api/src/lib.rs index 838142f72d..b752770357 100644 --- a/crates/vault-registry/rpc/runtime-api/src/lib.rs +++ b/crates/vault-registry/rpc/runtime-api/src/lib.rs @@ -24,9 +24,6 @@ sp_api::decl_runtime_apis! { /// Get the vault's collateral (including nomination) fn get_vault_total_collateral(vault_id: VaultId) -> Result, DispatchError>; - /// Get all vaults below the premium redeem threshold, ordered in descending order of this amount - fn get_premium_redeem_vaults() -> Result)>, DispatchError>; - /// Get all vaults with non-zero issuable tokens, ordered in descending order of this amount fn get_vaults_with_issuable_tokens() -> Result)>, DispatchError>; diff --git a/crates/vault-registry/rpc/src/lib.rs b/crates/vault-registry/rpc/src/lib.rs index c538d9284c..b3e479c5ac 100644 --- a/crates/vault-registry/rpc/src/lib.rs +++ b/crates/vault-registry/rpc/src/lib.rs @@ -38,9 +38,6 @@ where at: Option, ) -> RpcResult>; - #[method(name = "vaultRegistry_getPremiumRedeemVaults")] - fn get_premium_redeem_vaults(&self, at: Option) -> RpcResult)>>; - #[method(name = "vaultRegistry_getVaultsWithIssuableTokens")] fn get_vaults_with_issuable_tokens( &self, @@ -179,19 +176,6 @@ where ) } - fn get_premium_redeem_vaults( - &self, - at: Option<::Hash>, - ) -> RpcResult)>> { - let api = self.client.runtime_api(); - let at = at.unwrap_or_else(|| self.client.info().best_hash); - - handle_response( - api.get_premium_redeem_vaults(at), - "Unable to find a vault below the premium redeem threshold".into(), - ) - } - fn get_vaults_with_issuable_tokens( &self, at: Option<::Hash>, diff --git a/crates/vault-registry/src/ext.rs b/crates/vault-registry/src/ext.rs index 83494f4675..0bf4f5de5b 100644 --- a/crates/vault-registry/src/ext.rs +++ b/crates/vault-registry/src/ext.rs @@ -110,6 +110,16 @@ pub(crate) mod capacity { } } +#[cfg_attr(test, mockable)] +pub(crate) mod oracle { + use frame_support::dispatch::DispatchError; + use oracle::{types::UnsignedFixedPoint, OracleKey}; + + pub fn get_price(key: OracleKey) -> Result, DispatchError> { + >::get_price(key) + } +} + #[cfg_attr(test, mockable)] pub(crate) mod fee { use crate::DefaultVaultId; @@ -123,4 +133,8 @@ pub(crate) mod fee { pub fn premium_redeem_reward_rate() -> UnsignedFixedPoint { >::premium_redeem_reward_rate() } + + pub fn get_redeem_fee_value() -> UnsignedFixedPoint { + >::get_redeem_fee_value() + } } diff --git a/crates/vault-registry/src/lib.rs b/crates/vault-registry/src/lib.rs index 8e22089fd1..14fe02bd7f 100644 --- a/crates/vault-registry/src/lib.rs +++ b/crates/vault-registry/src/lib.rs @@ -52,11 +52,12 @@ use frame_system::{ ensure_signed, offchain::{SendTransactionTypes, SubmitTransaction}, }; +use oracle::OracleKey; use sp_core::{H256, U256}; use sp_runtime::{ traits::*, transaction_validity::{InvalidTransaction, TransactionSource, TransactionValidity, ValidTransaction}, - ArithmeticError, + ArithmeticError, FixedPointNumber, }; use sp_std::{convert::TryInto, vec::Vec}; use traits::NominationApi; @@ -782,9 +783,6 @@ impl Pallet { /// # Returns /// Returns a `Result` containing the calculated maximum premium as an `Amount`. pub fn get_vault_max_premium_redeem(vault_id: &DefaultVaultId) -> Result, DispatchError> { - // The goal of premium redeems is to get the vault back the a healthy collateralization ratio. As such, - // we only award a premium for the amount of tokens required to get the vault back to secure threshold. - // The goal of premium redeems is to get the vault back the a healthy collateralization ratio. As such, // we only award a premium for the amount of tokens required to get the vault back to secure threshold. // The CollateralizationRate is defined as `totalCollateral / convertToCollateral(totalTokens)` @@ -804,7 +802,8 @@ impl Pallet { let global_secure_threshold = Self::get_global_secure_threshold(&vault_id.currencies)?; let premium_redeem_rate = ext::fee::premium_redeem_reward_rate::(); - let required_collateral = Self::required_collateral(&vault_id, &to_be_backed_tokens, global_secure_threshold)?; + let required_collateral = + Self::get_required_collateral_for_wrapped(&to_be_backed_tokens, vault_id.collateral_currency())?; let current_collateral = Self::get_backing_collateral(&vault_id)?; let missing_collateral = required_collateral.saturating_sub(¤t_collateral)?; @@ -1146,7 +1145,8 @@ impl Pallet { /// /// # Errors /// * `ThresholdNotSet` - If the secure collateral threshold for the given `currency_pair` is not set. - pub fn get_global_secure_threshold( + #[cfg_attr(feature = "integration-tests", visibility::make(pub))] + fn get_global_secure_threshold( currency_pair: &VaultCurrencyPair>, ) -> Result, DispatchError> { let global_secure_threshold = @@ -1154,24 +1154,6 @@ impl Pallet { Ok(global_secure_threshold) } - /// Calculate the required collateral for a vault given the specified parameters. - /// - /// # Arguments - /// * `vault_id` - The identifier of the vault for which to calculate the required collateral. - /// * `to_be_backed_tokens` - The amount of tokens to be backed by collateral. - /// * `secure_threshold` - The secure collateral threshold to be applied in the calculation. - /// - /// # Returns - /// Returns the required collateral amount or an error if the calculation fails. - pub fn required_collateral( - vault_id: &DefaultVaultId, - to_be_backed_tokens: &Amount, - secure_threshold: UnsignedFixedPoint, - ) -> Result, DispatchError> { - let issued_tokens_in_collateral = to_be_backed_tokens.convert_to(vault_id.collateral_currency())?; // oldTokens * EXCH - issued_tokens_in_collateral.checked_rounded_mul(&secure_threshold, Rounding::NearestPrefUp) - } - /// Adds an amount tokens to the to-be-redeemed tokens balance of a vault. /// This function serves as a prevention against race conditions in the /// redeem and replace procedures. If, for example, a vault would receive @@ -1574,7 +1556,7 @@ impl Pallet { pub fn is_vault_below_secure_threshold(vault_id: &DefaultVaultId) -> Result { let vault = Self::get_rich_vault_from_id(&vault_id)?; let threshold = vault.get_secure_threshold()?; - Self::is_vault_below_threshold(vault_id, threshold) + Self::is_vault_below_certain_threshold(vault_id, threshold) } pub fn is_vault_liquidated(vault_id: &DefaultVaultId) -> Result { @@ -1674,25 +1656,59 @@ impl Pallet { .collect(); Ok(vaults) } + + /// Calculates the inclusion fee for a redeem transaction based on the provided parameters. + pub fn calculate_inclusion_fee( + wrapped_currency: CurrencyId, + redeem_tx_size: u32, + ) -> Result, DispatchError> { + let satoshi_per_bytes = ext::oracle::get_price::(OracleKey::FeeEstimation)?; + + let fee = satoshi_per_bytes + .checked_mul_int(redeem_tx_size) + .ok_or(ArithmeticError::Overflow)?; + let amount = fee.try_into().map_err(|_| Error::::TryIntoIntError)?; + Ok(Amount::new(amount, wrapped_currency)) + } + /// Get all vaults that: /// - are below the premium redeem threshold, and /// - have a non-zero amount of redeemable tokens, and thus /// - are not banned /// - /// Maybe returns a tuple of (VaultId, RedeemableTokens) - /// The redeemable tokens are the currently vault.issued_tokens - the vault.to_be_redeemed_tokens - pub fn get_premium_redeem_vaults() -> Result, Amount)>, DispatchError> { + /// Return a tuple of (VaultId, RedeemTokens to get `max_premium` from vault) + pub fn get_premium_redeem_vaults( + redeem_transaction_size: u32, + ) -> Result, Amount)>, DispatchError> { + let premium_reward_rate = ext::fee::premium_redeem_reward_rate::(); + let mut suitable_vaults = Vaults::::iter() .filter_map(|(vault_id, _vault)| { + // The calculation to calculate redeem tokens to get `max_premium` is referenced from + // redeem pallet `request_redeem` method. + // BurnTokensForReachingPremiumThreshold = BurnWrap + InclusionFee + // RedeemTokens = BurnTokensForReachingPremiumThreshold / (1 - RedeemFee) let max_premium_in_collateral = Self::get_vault_max_premium_redeem(&vault_id).ok()?; - let premium_redeemable_tokens = - max_premium_in_collateral.convert_to(vault_id.wrapped_currency()).ok()?; + let redeem_amount_wrapped_in_collateral = + max_premium_in_collateral.checked_div(&premium_reward_rate).ok()?; + let burn_wrap = redeem_amount_wrapped_in_collateral + .convert_to(vault_id.wrapped_currency()) + .ok()?; + let inclusion_fee = + Self::calculate_inclusion_fee(vault_id.wrapped_currency(), redeem_transaction_size).ok()?; + + let vault_to_burn_tokens = burn_wrap.checked_add(&inclusion_fee).ok()?; + + let redeem_fee = ext::fee::get_redeem_fee_value::(); + let amount_wrapped = UnsignedFixedPoint::::one().saturating_sub(redeem_fee); + + let request_redeem_tokens_for_max_premium = vault_to_burn_tokens.checked_div(&amount_wrapped).ok()?; if Self::ensure_not_banned(&vault_id).is_ok() - && !premium_redeemable_tokens.is_zero() + && !request_redeem_tokens_for_max_premium.is_zero() && Self::is_vault_below_premium_threshold(&vault_id).unwrap_or(false) { - Some((vault_id, premium_redeemable_tokens)) + Some((vault_id, request_redeem_tokens_for_max_premium)) } else { None } @@ -1927,7 +1943,8 @@ impl Pallet { collateral_in_wrapped.ratio(&issued_tokens) } - fn is_vault_below_threshold( + #[cfg_attr(feature = "integration-tests", visibility::make(pub))] + fn is_vault_below_certain_threshold( vault_id: &DefaultVaultId, threshold: UnsignedFixedPoint, ) -> Result { @@ -1975,7 +1992,7 @@ impl Pallet { collateral.convert_to(wrapped_currency)?.checked_div(&threshold) } - pub fn vault_to_be_backed_tokens(vault_id: &DefaultVaultId) -> Result, DispatchError> { + fn vault_to_be_backed_tokens(vault_id: &DefaultVaultId) -> Result, DispatchError> { let vault = Self::get_active_rich_vault_from_id(vault_id)?; vault.to_be_backed_tokens() } diff --git a/crates/vault-registry/src/tests.rs b/crates/vault-registry/src/tests.rs index bb1defa353..fc53a25329 100644 --- a/crates/vault-registry/src/tests.rs +++ b/crates/vault-registry/src/tests.rs @@ -1015,13 +1015,15 @@ mod get_vaults_below_premium_collaterlization_tests { #[test] fn get_vaults_below_premium_collateralization_fails() { run_test(|| { + ext::oracle::get_price::.mock_safe(move |_| MockResult::Return(Ok(1.into()))); + // set back to default threshold set_default_thresholds(); add_vault(vault_id(4), 50, 100); assert_err!( - VaultRegistry::get_premium_redeem_vaults(), + VaultRegistry::get_premium_redeem_vaults(400_u32), TestError::NoVaultUnderThePremiumRedeemThreshold ); }) @@ -1030,6 +1032,8 @@ mod get_vaults_below_premium_collaterlization_tests { #[test] fn get_vaults_below_premium_collateralization_succeeds() { run_test(|| { + ext::oracle::get_price::.mock_safe(move |_| MockResult::Return(Ok(1.into()))); + let id1 = vault_id(3); let issue_tokens1: u128 = 50; let collateral1 = 49; @@ -1045,8 +1049,8 @@ mod get_vaults_below_premium_collaterlization_tests { set_default_thresholds(); assert_eq!( - VaultRegistry::get_premium_redeem_vaults(), - Ok(vec![(id2, wrapped(52)), (id1, wrapped(51))]) + VaultRegistry::get_premium_redeem_vaults(400_u32), + Ok(vec![(id2, wrapped(452)), (id1, wrapped(451))]) ); }) } @@ -1054,6 +1058,8 @@ mod get_vaults_below_premium_collaterlization_tests { #[test] fn get_vaults_below_premium_collateralization_filters_banned_and_sufficiently_collateralized_vaults() { run_test(|| { + ext::oracle::get_price::.mock_safe(move |_| MockResult::Return(Ok(1.into()))); + // returned let id1 = vault_id(3); let issue_tokens1: u128 = 50; @@ -1071,13 +1077,16 @@ mod get_vaults_below_premium_collaterlization_tests { // set back to default threshold so that vaults fall under premium redeem set_default_thresholds(); - // not returned + // not returned, since default threshold applied so vault is now not under premium threshold let id3 = vault_id(4); let issue_tokens2: u128 = 50; let collateral2 = 150; add_vault(id3.clone(), issue_tokens2, collateral2); - assert_eq!(VaultRegistry::get_premium_redeem_vaults(), Ok(vec!((id1, wrapped(50))))); + assert_eq!( + VaultRegistry::get_premium_redeem_vaults(400_u32), + Ok(vec!((id1, wrapped(450)))) + ); }) } } diff --git a/parachain/runtime/interlay/src/lib.rs b/parachain/runtime/interlay/src/lib.rs index 04a38c600e..90daff86dd 100644 --- a/parachain/runtime/interlay/src/lib.rs +++ b/parachain/runtime/interlay/src/lib.rs @@ -1812,11 +1812,6 @@ impl_runtime_apis! { Ok(BalanceWrapper{amount:result.amount()}) } - fn get_premium_redeem_vaults() -> Result)>, DispatchError> { - let result = VaultRegistry::get_premium_redeem_vaults()?; - Ok(result.iter().map(|v| (v.0.clone(), BalanceWrapper{amount:v.1.amount()})).collect()) - } - fn get_vaults_with_issuable_tokens() -> Result)>, DispatchError> { let result = VaultRegistry::get_vaults_with_issuable_tokens()?; Ok(result.into_iter().map(|v| (v.0, BalanceWrapper{amount:v.1.amount()})).collect()) @@ -1943,6 +1938,8 @@ impl_runtime_apis! { impl redeem_rpc_runtime_api::RedeemApi< Block, + VaultId, + Balance, AccountId, H256, RedeemRequest @@ -1954,6 +1951,11 @@ impl_runtime_apis! { fn get_vault_redeem_requests(account_id: AccountId) -> Vec { Redeem::get_redeem_requests_for_vault(account_id) } + + fn get_premium_redeem_vaults() -> Result)>, DispatchError> { + let result = Redeem::get_premium_redeem_vaults()?; + Ok(result.iter().map(|v| (v.0.clone(), BalanceWrapper{amount:v.1.amount()})).collect()) + } } impl replace_rpc_runtime_api::ReplaceApi< diff --git a/parachain/runtime/kintsugi/src/lib.rs b/parachain/runtime/kintsugi/src/lib.rs index 5c92592bbe..e689706719 100644 --- a/parachain/runtime/kintsugi/src/lib.rs +++ b/parachain/runtime/kintsugi/src/lib.rs @@ -1683,11 +1683,6 @@ impl_runtime_apis! { Ok(BalanceWrapper{amount:result.amount()}) } - fn get_premium_redeem_vaults() -> Result)>, DispatchError> { - let result = VaultRegistry::get_premium_redeem_vaults()?; - Ok(result.iter().map(|v| (v.0.clone(), BalanceWrapper{amount:v.1.amount()})).collect()) - } - fn get_vaults_with_issuable_tokens() -> Result)>, DispatchError> { let result = VaultRegistry::get_vaults_with_issuable_tokens()?; Ok(result.into_iter().map(|v| (v.0, BalanceWrapper{amount:v.1.amount()})).collect()) @@ -1814,6 +1809,8 @@ impl_runtime_apis! { impl redeem_rpc_runtime_api::RedeemApi< Block, + VaultId, + Balance, AccountId, H256, RedeemRequest @@ -1825,6 +1822,12 @@ impl_runtime_apis! { fn get_vault_redeem_requests(account_id: AccountId) -> Vec { Redeem::get_redeem_requests_for_vault(account_id) } + + + fn get_premium_redeem_vaults() -> Result)>, DispatchError> { + let result = Redeem::get_premium_redeem_vaults()?; + Ok(result.iter().map(|v| (v.0.clone(), BalanceWrapper{amount:v.1.amount()})).collect()) + } } impl replace_rpc_runtime_api::ReplaceApi< diff --git a/parachain/runtime/runtime-tests/src/parachain/redeem.rs b/parachain/runtime/runtime-tests/src/parachain/redeem.rs index 533746af51..4e9a361f19 100644 --- a/parachain/runtime/runtime-tests/src/parachain/redeem.rs +++ b/parachain/runtime/runtime-tests/src/parachain/redeem.rs @@ -114,7 +114,7 @@ fn consume_to_be_replaced(vault: &mut CoreVaultData, amount_btc: Amount mod premium_redeem_tests { use super::{assert_eq, *}; - fn setup_vault_below_secure_threshold(vault_id: VaultId) { + fn setup_vault_below_premium_threshold(vault_id: VaultId) { // with 2000 collateral and exchange rate at 2, the vault is at: // - secure threshold (200%) when it has 2000/2/2 = 500 tokens // - premium threshold (160%) when it has 2000/2/1.6 = 625 tokens @@ -141,7 +141,7 @@ mod premium_redeem_tests { #[test] fn integration_test_premium_redeem_with_reward_for_only_part_of_the_request() { test_setup_for_premium_redeem(|vault_id| { - setup_vault_below_secure_threshold(vault_id.clone()); + setup_vault_below_premium_threshold(vault_id.clone()); assert!(!VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap()); assert!(VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap()); @@ -187,10 +187,71 @@ mod premium_redeem_tests { }); } + #[test] + fn integration_test_redeem_max_premium_redeemable_token() { + test_setup_for_premium_redeem(|vault_id| { + setup_vault_below_premium_threshold(vault_id.clone()); + + let global_secure = VaultRegistryPallet::get_global_secure_threshold(&vault_id.currencies).unwrap(); // 200% + + // secure > premium > liquidation threshold + // at start vault should be below premium threshold, while above global secure & secure threshold + assert!(!VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap()); + assert!(VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap()); + assert!(!VaultRegistryPallet::is_vault_below_certain_threshold(&vault_id, global_secure).unwrap()); + + // Change vault secure threshold, + // now secure > global secure > premium > liquidation threshold + let vault_custom_secure_threshold = UnsignedFixedPoint::checked_from_rational(300, 100); + assert_ok!( + RuntimeCall::VaultRegistry(VaultRegistryCall::set_custom_secure_threshold { + currency_pair: vault_id.currencies.clone(), + custom_threshold: vault_custom_secure_threshold, + }) + .dispatch(origin_of(vault_id.account_id.clone())) + ); + + // vault should be below premium & secure threshold, while above global secure threshold + assert!(VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap()); + assert!(VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap()); + assert!(!VaultRegistryPallet::is_vault_below_certain_threshold(&vault_id, global_secure).unwrap()); + + let max_premium_for_vault = VaultRegistryPallet::get_vault_max_premium_redeem(&vault_id).unwrap(); + // get premium redeem vaults + let premium_redeem_vaults = RedeemPallet::get_premium_redeem_vaults() + .unwrap() + .get(0) + .unwrap() + .clone(); + + // request redeem tokens given by RPC + let redeem_id_1 = setup_redeem(premium_redeem_vaults.1, USER, &vault_id); + + let redeem_1 = RedeemPallet::get_open_redeem_request_from_id(&redeem_id_1).unwrap(); + // recv premium should be equal to max premium + assert_eq!(redeem_1.premium, max_premium_for_vault.amount()); + assert!(!redeem_1.premium.is_zero()); + + // max premium for vault should be zero + let max_premium_for_vault = VaultRegistryPallet::get_vault_max_premium_redeem(&vault_id).unwrap(); + assert!(max_premium_for_vault.amount().is_zero()); + + // redeeming the max premium amount put backs vault above premium threshold + // vault should be below secure threshold, while above global secure & premium threshold + assert!(VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap()); + assert!(!VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap()); + assert!(!VaultRegistryPallet::is_vault_below_certain_threshold(&vault_id, global_secure).unwrap()); + + let redeem_id_2 = setup_redeem(vault_id.wrapped(800_00), USER, &vault_id); + let redeem_2 = RedeemPallet::get_open_redeem_request_from_id(&redeem_id_2).unwrap(); + // no premium is given out for new redeems + assert!(redeem_2.premium.is_zero()); + }); + } #[test] fn integration_test_premium_redeem_with_reward_for_full_request() { test_setup_for_premium_redeem(|vault_id| { - setup_vault_below_secure_threshold(vault_id.clone()); + setup_vault_below_premium_threshold(vault_id.clone()); assert!(!VaultRegistryPallet::is_vault_below_secure_threshold(&vault_id).unwrap()); assert!(VaultRegistryPallet::is_vault_below_premium_threshold(&vault_id).unwrap()); diff --git a/parachain/src/service.rs b/parachain/src/service.rs index 13eb51c1ca..03da257eec 100644 --- a/parachain/src/service.rs +++ b/parachain/src/service.rs @@ -109,6 +109,8 @@ pub trait RuntimeApiCollection: issue::IssueRequest, > + redeem_rpc_runtime_api::RedeemApi< Block, + VaultId, + Balance, AccountId, H256, redeem::RedeemRequest, @@ -157,6 +159,8 @@ where issue::IssueRequest, > + redeem_rpc_runtime_api::RedeemApi< Block, + VaultId, + Balance, AccountId, H256, redeem::RedeemRequest, diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 764a7f69e8..d86b3037da 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -87,6 +87,8 @@ where issue_rpc::IssueRuntimeApi>, C::Api: redeem_rpc::RedeemRuntimeApi< Block, + VaultId, + Balance, AccountId, H256, RedeemRequest,