Skip to content

Commit

Permalink
automation-price pallet mvp rework (#420)
Browse files Browse the repository at this point in the history
An un-optimization pallet to enable an oracle price feed, price update,
task scheduling based on price and task triggering based on asset price
compartion with target price

The weight is hardcode, document is still lack off and data structure is
still being finalized.
  • Loading branch information
v9n authored Sep 26, 2023
1 parent b279567 commit b6187fc
Show file tree
Hide file tree
Showing 10 changed files with 2,105 additions and 572 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

22 changes: 21 additions & 1 deletion pallets/automation-price/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ xcm = { git = "https://github.com/paritytech/polkadot", default-features = false
cumulus-pallet-xcm = { git = 'https://github.com/paritytech/cumulus', default-features = false, branch = 'polkadot-v0.9.38' }
cumulus-primitives-core = { git = 'https://github.com/paritytech/cumulus', default-features = false, branch = 'polkadot-v0.9.38' }

## ORML
orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "polkadot-v0.9.38" }
orml-currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "polkadot-v0.9.38" }


# Substrate Dependencies
## Substrate Primitive Dependencies
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }
Expand All @@ -42,19 +47,31 @@ frame-system = { git = "https://github.com/paritytech/substrate", default-featur
## Substrate Pallet Dependencies
pallet-timestamp = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }

## Polkdadot deps
xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.38" }

## Local
pallet-valve = { path = "../valve", default-features = false }
pallet-xcmp-handler = { path = "../xcmp-handler", default-features = false }
primitives = { path = "../../primitives", default-features = false }

[dev-dependencies]
rand = { version = "0.7.3" }
serde = { version = "1.0.144" }
sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }
sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" }

pallet-xcm = { git = 'https://github.com/paritytech/polkadot', default-features = false, branch = "release-v0.9.38" }
xcm-builder = { git = 'https://github.com/paritytech/polkadot', default-features = false, branch = "release-v0.9.38" }
xcm-executor = { git = 'https://github.com/paritytech/polkadot', default-features = false, branch = "release-v0.9.38" }

# Cumulus dependencies
parachain-info = { git = 'https://github.com/paritytech/cumulus', branch = 'polkadot-v0.9.38' }

orml-currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "polkadot-v0.9.38" }
orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "polkadot-v0.9.38" }


[features]
default = ["std"]
runtime-benchmarks = ["frame-benchmarking"]
Expand All @@ -66,6 +83,9 @@ std = [
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"orml-currencies/std",
"orml-tokens/std",
"orml-traits/std",
"pallet-timestamp/std",
"pallet-valve/std",
"pallet-xcm/std",
Expand Down
143 changes: 111 additions & 32 deletions pallets/automation-price/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,68 +16,147 @@
// limitations under the License.

/// ! Traits and default implementation for paying execution fees.
use crate::{BalanceOf, Config};
use crate::{AccountOf, Action, ActionOf, Config, Error, MultiBalanceOf};

use orml_traits::MultiCurrency;
use pallet_xcmp_handler::{InstructionSequence, XcmpTransactor};
use sp_runtime::{
traits::{CheckedSub, Zero},
DispatchError,
traits::{CheckedSub, Convert, Saturating, Zero},
DispatchError, DispatchResult, SaturatedConversion,
TokenError::BelowMinimum,
};
use sp_std::marker::PhantomData;
use xcm::latest::prelude::*;
use xcm_builder::TakeRevenue;

use frame_support::traits::{Currency, ExistenceRequirement, OnUnbalanced, WithdrawReasons};

type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;

/// Handle withdrawing, refunding and depositing of transaction fees.
/// Handle execution fee payments in the context of automation actions
pub trait HandleFees<T: Config> {
/// Ensure the fee can be paid.
fn can_pay_fee(who: &T::AccountId, fee: BalanceOf<T>) -> Result<(), DispatchError>;

/// Once the task has been scheduled we need to charge for the execution cost.
fn withdraw_fee(who: &T::AccountId, fee: BalanceOf<T>) -> Result<(), DispatchError>;
fn pay_checked_fees_for<R, F: FnOnce() -> Result<R, DispatchError>>(
owner: &AccountOf<T>,
action: &ActionOf<T>,
executions: u32,
prereq: F,
) -> Result<R, DispatchError>;
}
pub struct FeeHandler<T: Config, TR> {
owner: T::AccountId,
pub schedule_fee_location: MultiLocation,
pub schedule_fee_amount: MultiBalanceOf<T>,
pub execution_fee_amount: MultiBalanceOf<T>,
_phantom_data: PhantomData<TR>,
}

pub struct FeeHandler<OU>(PhantomData<OU>);
impl<T, TR> HandleFees<T> for FeeHandler<T, TR>
where
T: Config,
TR: TakeRevenue,
{
fn pay_checked_fees_for<R, F: FnOnce() -> Result<R, DispatchError>>(
owner: &AccountOf<T>,
action: &ActionOf<T>,
executions: u32,
prereq: F,
) -> Result<R, DispatchError> {
let fee_handler = Self::new(owner, action, executions)?;
fee_handler.can_pay_fee().map_err(|_| Error::<T>::InsufficientBalance)?;
let outcome = prereq()?;
fee_handler.pay_fees()?;
Ok(outcome)
}
}

/// Implements the transaction payment for a pallet implementing the `Currency`
/// trait (eg. the pallet_balances) using an unbalance handler (implementing
/// `OnUnbalanced`).
impl<T, OU> HandleFees<T> for FeeHandler<OU>
impl<T, TR> FeeHandler<T, TR>
where
T: Config,
OU: OnUnbalanced<NegativeImbalanceOf<T>>,
TR: TakeRevenue,
{
// Ensure the fee can be paid.
fn can_pay_fee(who: &T::AccountId, fee: BalanceOf<T>) -> Result<(), DispatchError> {
/// Ensure the fee can be paid.
fn can_pay_fee(&self) -> Result<(), DispatchError> {
let fee = self.schedule_fee_amount.saturating_add(self.execution_fee_amount);

if fee.is_zero() {
return Ok(())
}

let free_balance = T::Currency::free_balance(who);
let new_amount =
free_balance.checked_sub(&fee).ok_or(DispatchError::Token(BelowMinimum))?;
T::Currency::ensure_can_withdraw(who, fee, WithdrawReasons::FEE, new_amount)?;
// Manually check for ExistenceRequirement since MultiCurrency doesn't currently support it
let currency_id = T::CurrencyIdConvert::convert(self.schedule_fee_location)
.ok_or("IncoveribleMultilocation")?;
let currency_id = currency_id.into();
let free_balance = T::MultiCurrency::free_balance(currency_id, &self.owner);

free_balance
.checked_sub(&fee)
.ok_or(DispatchError::Token(BelowMinimum))?
.checked_sub(&T::MultiCurrency::minimum_balance(currency_id))
.ok_or(DispatchError::Token(BelowMinimum))?;
T::MultiCurrency::ensure_can_withdraw(currency_id, &self.owner, fee)?;
Ok(())
}

/// Withdraw the fee.
fn withdraw_fee(who: &T::AccountId, fee: BalanceOf<T>) -> Result<(), DispatchError> {
fn withdraw_fee(&self) -> Result<(), DispatchError> {
let fee = self.schedule_fee_amount.saturating_add(self.execution_fee_amount);

if fee.is_zero() {
return Ok(())
}

let withdraw_reason = WithdrawReasons::FEE;
let currency_id = T::CurrencyIdConvert::convert(self.schedule_fee_location)
.ok_or("IncoveribleMultilocation")?;
let currency_id = currency_id.into();

match T::MultiCurrency::withdraw(currency_id, &self.owner, fee) {
Ok(_) => {
TR::take_revenue(MultiAsset {
id: AssetId::Concrete(self.schedule_fee_location),
fun: Fungibility::Fungible(self.schedule_fee_amount.saturated_into()),
});

if self.execution_fee_amount > MultiBalanceOf::<T>::zero() {
T::XcmpTransactor::pay_xcm_fee(
self.owner.clone(),
self.execution_fee_amount.saturated_into(),
)?;
}

match T::Currency::withdraw(who, fee, withdraw_reason, ExistenceRequirement::KeepAlive) {
Ok(imbalance) => {
OU::on_unbalanceds(Some(imbalance).into_iter());
Ok(())
},
Err(_) => Err(DispatchError::Token(BelowMinimum)),
}
}

/// Builds an instance of the struct
pub fn new(
owner: &AccountOf<T>,
action: &ActionOf<T>,
executions: u32,
) -> Result<Self, DispatchError> {
let schedule_fee_location = action.schedule_fee_location::<T>();

// TODO: FIX THIS BEFORE MERGE
let schedule_fee_amount: u128 = 1_000;
//Pallet::<T>::calculate_schedule_fee_amount(action, executions)?.saturated_into();

let execution_fee_amount = match action.clone() {
Action::XCMP { execution_fee, instruction_sequence, .. }
if instruction_sequence == InstructionSequence::PayThroughSovereignAccount =>
execution_fee.amount.saturating_mul(executions.into()).saturated_into(),
_ => 0u32.saturated_into(),
};

Ok(Self {
owner: owner.clone(),
schedule_fee_location,
schedule_fee_amount: schedule_fee_amount.saturated_into(),
execution_fee_amount,
_phantom_data: Default::default(),
})
}

/// Executes the fee handler
fn pay_fees(self) -> DispatchResult {
// This should never error if can_pay_fee passed.
self.withdraw_fee().map_err(|_| Error::<T>::LiquidityRestrictions)?;
Ok(())
}
}
Loading

0 comments on commit b6187fc

Please sign in to comment.