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

📝 Proxy Bonding documentation #435

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
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
108 changes: 101 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ macros-tests = { path = "macros/tests" }
# External pallets (with default disabled)
orml-oracle = { version = "0.13.0", default-features = false }
orml-traits = { version = "0.13.0", default-features = false }
jwt-compact = { git = "https://github.com/lrazovic/jwt-compact", default-features = false }
jwt-compact-frame = { version = "0.9.0-beta.0", default-features = false }

# Internal support (with default disabled)
shared-configuration = { path = "runtimes/shared-configuration", default-features = false }
Expand Down
3 changes: 2 additions & 1 deletion pallets/proxy-bonding/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
[package]
name = "pallet-proxy-bonding"
description = "A FRAME pallet that facilitates token bonding operations with fee management capabilities."
authors.workspace = true
documentation.workspace = true
edition.workspace = true
homepage.workspace = true
license-file.workspace = true
readme.workspace = true
readme = "README.md"
repository.workspace = true
version.workspace = true

Expand Down
125 changes: 125 additions & 0 deletions pallets/proxy-bonding/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<!-- cargo-rdme start -->

# Proxy Bonding Pallet

A FRAME pallet that facilitates token bonding operations with fee management capabilities. This pallet allows users to bond tokens from a configurable account (we call Treasury) while paying fees in various assets.
This pallet is intended to be used as an alternative to a direct bonding mechanism. In this way, the user does not need to own or hold the tokens, but can still participate in various activities by paying a fee.

## Overview

The Bonding Pallet provides functionality to:
- Bond treasury tokens on behalf of users
- Pay a bonding fee in different assets (e.g., DOT)
- Set the bond release to either immediate refund or time-locked release

## Features

### Token Bonding
- Bond tokens from a treasury account into sub-accounts
- Support for existential deposit management
- Hold-based bonding mechanism using runtime-defined hold reasons

### Fee Management
- Accept fees in configurable assets (e.g., DOT)
- Calculate fees based on bond amount and current token prices
- Support both fee refunds and fee transfers to recipients
- Percentage-based fee calculation in USD terms

### Release Mechanisms
Two types of release mechanisms are supported:
- Immediate refund: Bonds can be immediately returned to treasury, and fees await refunding to users.
- Time-locked release: Bonds are locked until a specific block number, and fees can be sent to the configured fee recipient.
## Extrinsics
- `transfer_bonds_back_to_treasury`: Transfer bonded tokens back to the treasury when release conditions are met.
- `transfer_fees_to_recipient`: Transfer collected fees to the designated fee recipient.

## Public Functions
- `calculate_fee`: Calculate the fee amount in the specified fee asset based on the bond amount.
- `get_bonding_account`: Get the sub-account used for bonding based on a u32.
- `bond_on_behalf_of`: Bond tokens from the treasury into a sub-account on behalf of a user.
- `set_release_type`: Set the release type for a given derivation path and hold reason.
- `refund_fee`: Refund the fee to the specified account (only if the release is set to `Refunded`).


### Example Configuration (Similar on how it's configured on the Polimec Runtime)

```rust
parameter_types! {
// Fee is defined as 1.5% of the USD Amount. Since fee is applied to the PLMC amount, and that is always 5 times
// less than the usd_amount (multiplier of 5), we multiply the 1.5 by 5 to get 7.5%
pub FeePercentage: Perbill = Perbill::from_rational(75u32, 1000u32);
pub FeeRecipient: AccountId = AccountId::from(hex_literal::hex!("3ea952b5fa77f4c67698e79fe2d023a764a41aae409a83991b7a7bdd9b74ab56"));
pub RootId: PalletId = PalletId(*b"treasury");
}

impl pallet_proxy_bonding::Config for Runtime {
type BondingToken = Balances; // The Balances pallet is used for the bonding token
type BondingTokenDecimals = ConstU8<10>; // The PLMC token has 10 decimals
type BondingTokenId = ConstU32<X>; // TODO: Replace with a proper number and explanation.
type FeePercentage = FeePercentage; // The fee kept by the treasury
type FeeRecipient = FeeRecipient; // THe account that receives the fee
type FeeToken = ForeignAssets; // The Asset pallet is used for the fee token
type Id = PalletId; // The ID type used for the ... account
type PriceProvider = OraclePriceProvider<AssetId, Price, Oracle>; // The Oracle pallet is used for the price provider
type RootId = TreasuryId; // The treasury account ID
type Treasury = TreasuryAccount; // The treasury account
type UsdDecimals = ConstU8<X>; // TODO: Replace with a proper number and explanation.
type RuntimeEvent = RuntimeEvent;
type RuntimeHoldReason = RuntimeHoldReason;
}
```
## Example integration

The Proxy Bonding Pallet work seamlessly with the Funding Pallet to handle OTM (One-Token-Model) participation modes in project funding. Here's how the integration works:

### Funding Pallet Flow
1. When a user contributes to a project using OTM mode:
- The Funding Pallet calls `bond_on_behalf_of` with:
- Project ID as the derivation path
- User's account
- PLMC bond amount
- Funding asset ID
- Participation hold reason

2. During project settlement phase:
- For successful projects:
- An OTM release type is set with a time-lock based on the multiplier
- Bonds remain locked until the vesting duration completes
- For failed projects:
- Release type is set to `Refunded`
- Allows immediate return of bonds to treasury
- Enables fee refunds to participants

### Key Integration
```rust
// In Funding Pallet
pub fn bond_plmc_with_mode(
who: &T::AccountId,
project_id: ProjectId,
amount: Balance,
mode: ParticipationMode,
asset: AcceptedFundingAsset,
) -> DispatchResult {
match mode {
ParticipationMode::OTM => pallet_proxy_bonding::Pallet::<T>::bond_on_behalf_of(
project_id,
who.clone(),
amount,
asset.id(),
HoldReason::Participation.into(),
),
ParticipationMode::Classic(_) => // ... other handling
}
}
```

### Settlement Process
The settlement process determines the release conditions for bonded tokens:
- Success: Tokens remain locked with a time-based release schedule
- Failure: Tokens are marked for immediate return to treasury with fee refunds

## License

License: GPL-3.0

<!-- cargo-rdme end -->
11 changes: 7 additions & 4 deletions pallets/proxy-bonding/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use sp_runtime::{

impl<T: Config> Pallet<T> {
/// Calculate the USD fee in `fee_asset` for bonding `bond_amount` of the native token.
/// e.g. if the fee is 1%, native token PLMC, fee_asset USDT, bond_amount 1000 PLMC, PLMC price 0.5USD, USDT price 1USD,
/// Then the calculated fee would be 1% * 1000 * 0.5 = 5USD, which is 5 USDT at a price of 1USD.
/// e.g. if the fee = 1%, native token = PLMC, fee_asset = USDT, bond_amount= 1000 PLMC, PLMC price = 0.5USD, USDT = 1USD,
/// Then the calculated fee would be 1% * 1000 * 0.5 = 5USD, which is 5 USDT at a price of 1 USD/USDT.
pub fn calculate_fee(bond_amount: BalanceOf<T>, fee_asset: AssetId) -> Result<BalanceOf<T>, DispatchError> {
let bonding_token_price = <PriceProviderOf<T>>::get_decimals_aware_price(
T::BondingTokenId::get(),
Expand All @@ -38,13 +38,14 @@ impl<T: Config> Pallet<T> {
Ok(fee_in_fee_asset)
}

/// Generate a sub-account for bonding, based on a u32 number.
pub fn get_bonding_account(derivation_path: u32) -> AccountIdOf<T> {
// We need to add 1 since 0u32 means no derivation from root.
T::RootId::get().into_sub_account_truncating(derivation_path.saturating_add(1u32))
}

/// Put some tokens on hold from the treasury into a sub-account, on behalf of a user.
/// User pays a fee for this functionality, which can be later refunded.
/// User pays a fee for this functionality, which can be later refunded or paid out to the configured fee-recipient.
pub fn bond_on_behalf_of(
derivation_path: u32,
account: T::AccountId,
Expand Down Expand Up @@ -84,7 +85,9 @@ impl<T: Config> Pallet<T> {
Ok(())
}

/// Set the block for which we can release the bonds of a sub-account, and transfer it back to the treasury.
/// Configure what kind of release should be done for a given derivation path and hold reason.
/// This can be a refund (i.e. if the reason for bonding is no longer valid), or a release at a certain block
/// (i.e. if now we know for sure the reason is valid and the treasury should get the tokens back after some bonding time).
pub fn set_release_type(
derivation_path: u32,
hold_reason: T::RuntimeHoldReason,
Expand Down
Loading