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

Feat/vbnc convert #1413

Merged
merged 8 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
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
23 changes: 23 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 @@ -33,6 +33,7 @@ members = [
"pallets/leverage-staking",
"pallets/channel-commission",
"pallets/clouds-convert",
"pallets/vbnc-convert",

"runtime/bifrost-kusama",
"runtime/bifrost-polkadot/src/evm/evm-utility/macro",
Expand Down Expand Up @@ -85,6 +86,7 @@ bifrost-stable-pool-rpc-runtime-api = { path = "pallets/stable-pool/rpc/runti
bifrost-system-maker = { path = "pallets/system-maker", default-features = false }
bifrost-system-staking = { path = "pallets/system-staking", default-features = false }
bifrost-token-issuer = { path = "pallets/token-issuer", default-features = false }
bifrost-vbnc-convert = { path = "pallets/vbnc-convert", default-features = false }
bifrost-ve-minting = { path = "pallets/ve-minting", default-features = false }
bifrost-ve-minting-rpc-runtime-api = { path = "pallets/ve-minting/rpc/runtime-api", default-features = false }
bifrost-vesting = { path = "pallets/vesting", default-features = false }
Expand Down
54 changes: 54 additions & 0 deletions pallets/vbnc-convert/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[package]
name = "bifrost-vbnc-convert"
version = "0.8.0"
authors = ["Liebi Technologies <[email protected]>"]
edition = "2021"

[dependencies]
parity-scale-codec = { workspace = true, features = ["derive"] }
scale-info = { workspace = true, features = ["derive"] }
sp-std = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-runtime = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
bifrost-primitives = { workspace = true }
orml-traits = { workspace = true }
sp-core = { workspace = true }
pallet-balances = { workspace = true }
xcm-builder = { workspace = true }

[dev-dependencies]
orml-tokens = { workspace = true }
bifrost-currencies = { workspace = true }
sp-io = { workspace = true }
bifrost-asset-registry = { workspace = true }

[features]
default = ["std"]
std = [
"parity-scale-codec/std",
"scale-info/std",
"sp-std/std",
"sp-runtime/std",
"sp-core/std",
"bifrost-primitives/std",
"frame-support/std",
"frame-system/std",
"frame-benchmarking/std",
"orml-traits/std",
"orml-tokens/std",
"bifrost-currencies/std",
"sp-io/std",
"bifrost-asset-registry/std",
"pallet-balances/std",
]

runtime-benchmarks = [
"frame-benchmarking",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime"]
68 changes: 68 additions & 0 deletions pallets/vbnc-convert/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// This file is part of Bifrost.

// Copyright (C) Liebi Technologies PTE. LTD.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::*;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
use sp_runtime::traits::{UniqueSaturatedFrom, Zero};

#[benchmarks(where T: Config)]
mod benchmarks {
use super::*;

#[benchmark]
fn convert_to_vbnc_p() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let currency = VBNC;
let value = BalanceOf::<T>::unique_saturated_from(100_000_000_000_000_000_000u128);
let vbnc_pool_account = Pallet::<T>::vbnc_p_pool_account();

// Ensure the pool has enough balance
T::MultiCurrency::deposit(VBNC_P, &vbnc_pool_account, value)?;

// Make sure the user has enough balance in the provided currency
T::MultiCurrency::deposit(currency, &caller, value)?;

#[extrinsic_call]
Pallet::<T>::convert_to_vbnc_p(RawOrigin::Signed(caller.clone()), currency, value);

assert!(T::MultiCurrency::free_balance(VBNC_P, &caller) > Zero::zero());

Ok(())
}

#[benchmark]
fn charge_vbnc_p() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let amount = BalanceOf::<T>::unique_saturated_from(100_000_000_000_000_000_000u128);
let vbnc_pool_account = Pallet::<T>::vbnc_p_pool_account();

// Ensure the caller has enough vBNC-P balance
T::MultiCurrency::deposit(VBNC_P, &caller, amount)?;

#[extrinsic_call]
Pallet::<T>::charge_vbnc_p(RawOrigin::Signed(caller.clone()), amount);

assert_eq!(T::MultiCurrency::free_balance(VBNC_P, &caller), Zero::zero());
assert_eq!(T::MultiCurrency::free_balance(VBNC_P, &vbnc_pool_account), amount);

Ok(())
}

impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext_benchmark(), crate::mock::Runtime);
}
211 changes: 211 additions & 0 deletions pallets/vbnc-convert/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// This file is part of Bifrost.

// Copyright (C) Liebi Technologies PTE. LTD.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;

use frame_support::{ensure, pallet_prelude::*, sp_runtime::traits::AccountIdConversion, PalletId};
use frame_system::pallet_prelude::*;
use orml_traits::MultiCurrency;

use bifrost_primitives::{
currency::{VBNC, VBNC_P},
CurrencyId,
};
pub use pallet::*;
pub use weights::WeightInfo;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
mod mock;
mod tests;
pub mod weights;

type BalanceOf<T> = <<T as Config>::MultiCurrency as MultiCurrency<
<T as frame_system::Config>::AccountId,
>>::Balance;
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub(crate) type CurrencyIdOf<T> = <<T as Config>::MultiCurrency as MultiCurrency<
<T as frame_system::Config>::AccountId,
>>::CurrencyId;

#[frame_support::pallet]
pub mod pallet {
use super::*;

#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Currecny operation handler
type MultiCurrency: MultiCurrency<AccountIdOf<Self>, CurrencyId = CurrencyId>;
/// VBNC-convert Pallet Id
type VBNCConvertPalletId: Get<PalletId>;
/// Weight information for extrinsics in this module.
type WeightInfo: WeightInfo;
}
#[pallet::error]
/// Error types for the VBNC convert pallet.
pub enum Error<T> {
/// The account does not have enough balance to complete the operation.
NotEnoughBalance,
/// The resulting balance is less than the existential deposit, which would lead to the
/// account being reaped.
LessThanExistentialDeposit,
/// The specified currency is not supported for conversion to VBNC-P.
CurrencyNotSupport,
}

#[pallet::event]
/// Event types emitted by the VBNC convert pallet.
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
/// Emitted when VBNC-P has been successfully converted and transferred to the user.
VBNCPConverted {
/// The account that received the converted VBNC-P.
to: AccountIdOf<T>,
/// The amount of VBNC-P converted.
value: BalanceOf<T>,
},
/// Emitted when VBNC-P has been successfully charged from a user account.
VbncPCharged {
/// The account from which VBNC-P was charged.
who: AccountIdOf<T>,
/// The amount of VBNC-P charged.
value: BalanceOf<T>,
},
}

#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>);

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Converts the specified `value` of a supported currency to VBNC-P.
///
/// # Parameters
/// - `origin`: The origin of the transaction, which must be a signed account.
/// - `currency`: The currency to be converted into VBNC-P.
/// - `value`: The amount of the specified currency to be converted.
///
/// # Errors
/// - `Error::<T>::CurrencyNotSupport`: If the provided currency is not supported for
/// conversion.
/// - `Error::<T>::NotEnoughBalance`: If the user does not have sufficient balance of the
/// specified currency.
/// - `Error::<T>::LessThanExistentialDeposit`: If the converted amount of VBNC-P is less
/// than the minimum required balance.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::convert_to_vbnc_p())]
pub fn convert_to_vbnc_p(
origin: OriginFor<T>,
currency: CurrencyIdOf<T>,
value: BalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::ensure_currency(&currency)?;

// check the user balance of currency
T::MultiCurrency::ensure_can_withdraw(currency, &who, value)
.map_err(|_| Error::<T>::NotEnoughBalance)?;

// the VBNC and VBNC-P exchange ratio is one to one
let vbnc_p_amount = value;
Self::ensure_pool_balance_enough(VBNC_P, vbnc_p_amount)?;

let existential_deposit = T::MultiCurrency::minimum_balance(VBNC_P);
ensure!(vbnc_p_amount >= existential_deposit, Error::<T>::LessThanExistentialDeposit);

// transfer vBNC-p from pool to user
let vbnc_pool_account = Self::vbnc_p_pool_account();
T::MultiCurrency::transfer(VBNC_P, &vbnc_pool_account, &who, vbnc_p_amount)?;

// burn currency
T::MultiCurrency::withdraw(currency, &who, value)?;

// deposit event
Self::deposit_event(Event::VBNCPConverted { to: who, value: vbnc_p_amount });

Ok(())
}

/// Charges the specified `amount` of VBNC-P from the user's account.
///
/// # Parameters
/// - `origin`: The origin of the transaction, which must be a signed account.
/// - `amount`: The amount of VBNC-P to charge from the user's account.
///
/// # Errors
/// - `Error::<T>::NotEnoughBalance`: If the user does not have sufficient VBNC-P to charge.
/// - `Error::<T>::LessThanExistentialDeposit`: If the amount to be charged is less than the
/// existential deposit.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::charge_vbnc_p())]
pub fn charge_vbnc_p(origin: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
let who = ensure_signed(origin)?;

// check the user balance of currency
T::MultiCurrency::ensure_can_withdraw(VBNC_P, &who, amount)
.map_err(|_| Error::<T>::NotEnoughBalance)?;

let vbnc_pool_account = Self::vbnc_p_pool_account();
T::MultiCurrency::transfer(VBNC_P, &who, &vbnc_pool_account, amount)?;

// deposit event
Self::deposit_event(Event::VbncPCharged { who, value: amount });

Ok(())
}
}

impl<T: Config> Pallet<T> {
/// Returns the account ID of VBNCConvertPalletId.
/// This account is used to hold VBNC-P and perform conversions.
pub fn vbnc_p_pool_account() -> AccountIdOf<T> {
T::VBNCConvertPalletId::get().into_account_truncating()
}

/// Ensures that the provided currency is supported for conversion.
/// Currently, only VBNC is supported.
fn ensure_currency(currency: &CurrencyIdOf<T>) -> Result<(), DispatchError> {
// Ensure that the currency is VBNC, otherwise return an error.
ensure!(*currency == VBNC, Error::<T>::CurrencyNotSupport);
Ok(())
}

/// Ensures that the VBNC-P pool has enough balance to complete the transaction.
/// If the pool has insufficient balance, an error is returned.
fn ensure_pool_balance_enough(
currency: CurrencyIdOf<T>,
value: BalanceOf<T>,
) -> Result<(), DispatchError> {
let pool_account = Self::vbnc_p_pool_account();

// Check if the pool account can withdraw the specified amount of currency.
T::MultiCurrency::ensure_can_withdraw(currency, &pool_account, value)
.map_err(|_| Error::<T>::NotEnoughBalance)?;

Ok(())
}
}
}
Loading