From 1c6385466c922dbc0212c3bbde30a4c9b3dbe635 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas Date: Thu, 27 Oct 2022 10:56:09 +0100 Subject: [PATCH] feat(fee): Support fee management on acurast pallet --- pallets/acurast/Cargo.toml | 94 +++++++++++++++++++-------------- pallets/acurast/src/lib.rs | 60 +++------------------ pallets/acurast/src/mock.rs | 28 +++++++--- pallets/acurast/src/payments.rs | 49 +++++++++-------- pallets/acurast/src/tests.rs | 16 +++++- pallets/acurast/src/traits.rs | 64 ++++++++++++++++++++++ pallets/proxy/Cargo.toml | 1 - pallets/proxy/src/mock.rs | 12 +++++ pallets/proxy/src/tests.rs | 6 ++- 9 files changed, 203 insertions(+), 127 deletions(-) create mode 100644 pallets/acurast/src/traits.rs diff --git a/pallets/acurast/Cargo.toml b/pallets/acurast/Cargo.toml index 32483773..1a3c534d 100644 --- a/pallets/acurast/Cargo.toml +++ b/pallets/acurast/Cargo.toml @@ -12,8 +12,12 @@ edition = "2021" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false } -scale-info = { version = "2.0.0", features = ["derive"], default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +], default-features = false } +scale-info = { version = "2.0.0", features = [ + "derive", +], default-features = false } # Substrate frame-benchmarking = { git = "https://github.com/paritytech/substrate", default-features = false, optional = true, branch = "polkadot-v0.9.29" } @@ -28,17 +32,24 @@ pallet-assets = { git = "https://github.com/paritytech/substrate", default-featu parachains-common = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.29", default-features = false } # Polkadot -xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29"} -xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29"} -xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29"} +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.29" } # Attestation asn1 = { version = "0.11.0", default-features = false, features = ["derive"] } -p256 = { git = "https://github.com/Acurast/elliptic-curves", default-features = false, branch = "p256/v0.10.1", features = ["ecdsa", "sha256"] } -p384 = { package = "p384_vendored", path = "p384", default-features = false, features = ["ecdsa", "arithmetic", "expose-field"] } +p256 = { git = "https://github.com/Acurast/elliptic-curves", default-features = false, branch = "p256/v0.10.1", features = [ + "ecdsa", + "sha256", +] } +p384 = { package = "p384_vendored", path = "p384", default-features = false, features = [ + "ecdsa", + "arithmetic", + "expose-field", +] } sha2 = { version = "0.10", default-features = false } num-bigint = { version = "0.4.3", default-features = false } -ecdsa-vendored = { package = "ecdsa_vendored", path = "p384/ecdsa", default-features = false} +ecdsa-vendored = { package = "ecdsa_vendored", path = "p384/ecdsa", default-features = false } # benchmarks hex-literal = { version = "0.3", optional = true } @@ -57,46 +68,47 @@ sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkad pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29" } parachain-info = { git = "https://github.com/paritytech/cumulus", default-features = false, branch = "polkadot-v0.9.29" } +# Acurast acurast-p256-crypto = { path = "../../p256-crypto" } [features] default = ["std"] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "hex-literal", - "sp-runtime", - "parachain-info", - "pallet-balances", - "sp-core" + "frame-benchmarking/runtime-benchmarks", + "hex-literal", + "sp-runtime", + "parachain-info", + "pallet-balances", + "sp-core", ] std = [ - "acurast-p256-crypto/std", - "asn1/std", - "base64/std", - "codec/std", - "ecdsa-vendored/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", - "num-bigint/std", - "p256/std", - "p384/std", - "pallet-assets/std", - "pallet-balances/std", - "pallet-timestamp/std", - "parachain-info/std", - "parachains-common/std", - "scale-info/std", - "sha2/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", - "sp-version/std", - "xcm-builder/std", - "xcm-executor/std", - "xcm/std", + "acurast-p256-crypto/std", + "asn1/std", + "base64/std", + "codec/std", + "ecdsa-vendored/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "num-bigint/std", + "p256/std", + "p384/std", + "pallet-assets/std", + "pallet-balances/std", + "pallet-timestamp/std", + "parachain-info/std", + "parachains-common/std", + "scale-info/std", + "sha2/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-version/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", ] -try-runtime = [ "frame-support/try-runtime" ] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/acurast/src/lib.rs b/pallets/acurast/src/lib.rs index ee669286..eeee4cef 100644 --- a/pallets/acurast/src/lib.rs +++ b/pallets/acurast/src/lib.rs @@ -12,12 +12,14 @@ mod attestation; pub mod payments; mod types; mod utils; +mod traits; pub mod weights; pub mod xcm_adapters; pub use pallet::*; pub use payments::*; pub use types::*; +pub use traits::*; #[frame_support::pallet] pub mod pallet { @@ -31,59 +33,7 @@ pub mod pallet { use crate::payments::*; use crate::types::*; use crate::utils::*; - - /// This trait provides the interface for a fulfillment router. - pub trait FulfillmentRouter { - fn received_fulfillment( - origin: OriginFor, - from: T::AccountId, - fulfillment: Fulfillment, - registration: JobRegistration, - requester: ::Target, - ) -> DispatchResultWithPostInfo; - } - - pub trait RevocationListUpdateBarrier { - fn can_update_revocation_list( - origin: &T::AccountId, - updates: &Vec, - ) -> bool; - } - - impl RevocationListUpdateBarrier for () { - fn can_update_revocation_list( - _origin: &T::AccountId, - _updates: &Vec, - ) -> bool { - false - } - } - - pub trait JobAssignmentUpdateBarrier { - fn can_update_assigned_jobs( - origin: &T::AccountId, - updates: &Vec>, - ) -> bool; - } - - impl JobAssignmentUpdateBarrier for () { - fn can_update_assigned_jobs( - _origin: &T::AccountId, - _updates: &Vec>, - ) -> bool { - false - } - } - - pub trait WeightInfo { - fn register() -> Weight; - fn deregister() -> Weight; - fn update_allowed_sources() -> Weight; - fn update_job_assignments() -> Weight; - fn fulfill() -> Weight; - fn submit_attestation() -> Weight; - fn update_certificate_revocation_list() -> Weight; - } + use crate::traits::*; #[pallet::config] pub trait Config: @@ -106,7 +56,9 @@ pub mod pallet { type RevocationListUpdateBarrier: RevocationListUpdateBarrier; /// Barrier for update_job_assignments extrinsic call. type JobAssignmentUpdateBarrier: JobAssignmentUpdateBarrier; - + // Fee Logic + type FeeManager: FeeManager; + // Weight Logic type WeightInfo: WeightInfo; } diff --git a/pallets/acurast/src/mock.rs b/pallets/acurast/src/mock.rs index 08ec5fd2..87c32461 100644 --- a/pallets/acurast/src/mock.rs +++ b/pallets/acurast/src/mock.rs @@ -4,12 +4,12 @@ use frame_support::{pallet_prelude::GenesisBuild, PalletId}; use hex_literal::hex; use sp_io; use sp_runtime::traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, ConstU128, ConstU32}; -use sp_runtime::{generic, parameter_types, AccountId32}; +use sp_runtime::{generic, parameter_types, AccountId32, DispatchError, Percent}; use xcm::prelude::*; use crate::{ payments, AttestationChain, Fulfillment, JobAssignmentUpdate, JobAssignmentUpdateBarrier, - JobRegistration, LockAndPayAsset, RevocationListUpdateBarrier, Script, SerialNumber, + JobRegistration, LockAndPayAsset, RevocationListUpdateBarrier, Script, SerialNumber, FeeManager, }; type AccountId = AccountId32; @@ -67,18 +67,29 @@ impl LockAndPayAsset for TestTransactor { fn lock_asset( _asset: MultiAsset, _owner: <::Lookup as sp_runtime::traits::StaticLookup>::Source, - ) -> Result<(), ()> { + ) -> Result<(), DispatchError> { Ok(()) } fn pay_asset( _asset: MultiAsset, _target: <::Lookup as sp_runtime::traits::StaticLookup>::Source, - ) -> Result<(), ()> { + ) -> Result<(), DispatchError> { Ok(()) } } +pub struct FeeManagerImpl; +impl FeeManager for FeeManagerImpl { + fn get_fee_percentage() -> Percent { + Percent::from_percent(30) + } + + fn pallet_id() -> PalletId { + PalletId(*b"acurfees") + } +} + pub struct ExtBuilder; impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { @@ -100,6 +111,7 @@ impl ExtBuilder { balances: vec![ (alice_account_id(), INITIAL_BALANCE), (pallet_assets_account(), INITIAL_BALANCE), + (pallet_fees_account(), INITIAL_BALANCE), (bob_account_id(), INITIAL_BALANCE), (processor_account_id(), INITIAL_BALANCE), ], @@ -154,7 +166,7 @@ frame_support::construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Assets: pallet_assets::{Pallet, Config, Event, Storage}, ParachainInfo: parachain_info::{Pallet, Storage, Config}, - Acurast: crate::{Pallet, Call, Storage, Event}, + Acurast: crate::{Pallet, Call, Storage, Event} } ); @@ -167,7 +179,6 @@ parameter_types! { pub AllowedRevocationListUpdate: Vec = vec![alice_account_id(), ::PalletId::get().into_account_truncating()]; pub AllowedJobAssignmentUpdate: Vec = vec![bob_account_id()]; pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; - pub const TestPalletId: PalletId = PalletId(*b"testpid1"); } parameter_types! { pub const MaxReserves: u32 = 50; @@ -253,6 +264,7 @@ impl crate::Config for Test { type PalletId = AcurastPalletId; type RevocationListUpdateBarrier = Barrier; type JobAssignmentUpdateBarrier = JobBarrier; + type FeeManager = FeeManagerImpl; type WeightInfo = crate::weights::WeightInfo; } @@ -392,6 +404,10 @@ pub fn pallet_assets_account() -> ::AccountId { ::PalletId::get().into_account_truncating() } +pub fn pallet_fees_account() -> ::AccountId { + ::FeeManager::pallet_id().into_account_truncating() +} + pub fn alice_account_id() -> AccountId { [0; 32].into() } diff --git a/pallets/acurast/src/payments.rs b/pallets/acurast/src/payments.rs index d31a847f..b0d81244 100644 --- a/pallets/acurast/src/payments.rs +++ b/pallets/acurast/src/payments.rs @@ -4,12 +4,14 @@ use frame_support::{ dispatch::RawOrigin, sp_runtime::traits::{AccountIdConversion, Get, StaticLookup}, }; +use sp_runtime::DispatchError; use xcm::latest::prelude::*; +use crate::traits::FeeManager; pub trait LockAndPayAsset { - fn lock_asset(asset: MultiAsset, owner: ::Source) -> Result<(), ()>; + fn lock_asset(asset: MultiAsset, owner: ::Source) -> Result<(), DispatchError>; - fn pay_asset(asset: MultiAsset, target: ::Source) -> Result<(), ()>; + fn pay_asset(asset: MultiAsset, target: ::Source) -> Result<(), DispatchError>; } pub struct StatemintAssetTransactor; @@ -18,52 +20,55 @@ where T::AssetId: TryFrom, T::Balance: TryFrom, { - fn lock_asset(asset: MultiAsset, owner: ::Source) -> Result<(), ()> { + fn lock_asset(asset: MultiAsset, owner: ::Source) -> Result<(), DispatchError> { let pallet_account: T::AccountId = T::PalletId::get().into_account_truncating(); let raw_origin = RawOrigin::::Signed(pallet_account.clone()); let pallet_origin: T::Origin = raw_origin.into(); - let (id, amount) = get_statemint_asset(&asset).map_err(|_| ())?; + let (id, amount) = get_statemint_asset(&asset).or(Err(DispatchError::Other("Asset not found.")))?; let (id, amount): (T::AssetId, T::Balance) = match (id.try_into(), amount.try_into()) { (Ok(id), Ok(amount)) => (id, amount), - _ => return Err(()), + (Err(_err), _) => return Err(DispatchError::Other("Invalid asset id.")), + (_, Err(_err)) => return Err(DispatchError::Other("Invalid asset balance.")) }; // transfer funds from caller to pallet account for holding until fulfill is called // this is a privileged operation, hence the force_transfer call. // we could do an approve_transfer first, but this would require the assets pallet being // public which we can't do at the moment due to our statemint assets 1 to 1 integration - let extrinsic_call = pallet_assets::Pallet::::force_transfer( + pallet_assets::Pallet::::force_transfer( pallet_origin, id, owner, T::Lookup::unlookup(pallet_account), amount, - ); - - match extrinsic_call { - Ok(_) => Ok(()), - Err(_e) => Err(()), - } + ) } - fn pay_asset(asset: MultiAsset, target: ::Source) -> Result<(), ()> { + fn pay_asset(asset: MultiAsset, target: ::Source) -> Result<(), DispatchError> { let pallet_account: T::AccountId = T::PalletId::get().into_account_truncating(); - let raw_origin = RawOrigin::::Signed(pallet_account); + let raw_origin = RawOrigin::::Signed(pallet_account.clone()); let pallet_origin: T::Origin = raw_origin.into(); - let (id, amount) = get_statemint_asset(&asset).map_err(|_| ())?; + let (id, amount) = get_statemint_asset(&asset).or(Err(DispatchError::Other("Asset not found.")))?; let (id, amount): (T::AssetId, T::Balance) = match (id.try_into(), amount.try_into()) { (Ok(id), Ok(amount)) => (id, amount), - _ => return Err(()), + (Err(_err), _) => return Err(DispatchError::Other("Invalid asset id.")), + (_, Err(_err)) => return Err(DispatchError::Other("Invalid asset balance.")) }; - let extrinsic_call = - pallet_assets::Pallet::::transfer(pallet_origin, id, target, amount); + // Extract fee from the processor reward + let fee_percentage = T::FeeManager::get_fee_percentage(); // TODO: fee will be indexed by version in the future + let fee = fee_percentage.mul_floor(amount); + + // Subtract the fee from the reward + let reward_after_fee = amount - fee; + + // Transfer fees to Acurast fees manager account + let fee_pallet_account: T::AccountId = T::FeeManager::pallet_id().into_account_truncating(); + pallet_assets::Pallet::::transfer(pallet_origin.clone(), id, T::Lookup::unlookup(fee_pallet_account), fee)?; - match extrinsic_call { - Ok(_) => Ok(()), - Err(_) => Err(()), - } + // Transfer reward to the processor + pallet_assets::Pallet::::transfer(pallet_origin, id, target, reward_after_fee) } } diff --git a/pallets/acurast/src/tests.rs b/pallets/acurast/src/tests.rs index db644b49..3e48c950 100644 --- a/pallets/acurast/src/tests.rs +++ b/pallets/acurast/src/tests.rs @@ -420,11 +420,17 @@ fn test_fulfill() { alice_account_id(), updates )), + Event::Assets(pallet_assets::Event::Transferred { + asset_id: 22, + from: pallet_assets_account(), + to: pallet_fees_account(), + amount: 1_500_000_u128, + }), Event::Assets(pallet_assets::Event::Transferred { asset_id: 22, from: pallet_assets_account(), to: processor_account_id(), - amount: INITIAL_BALANCE / 2 as u128, + amount: 3_500_000_u128, }), Event::Acurast(crate::Event::ReceivedFulfillment( processor_account_id(), @@ -566,11 +572,17 @@ fn test_submit_attestation_register_fulfill() { bob_account_id() )), Event::Acurast(crate::Event::JobAssignmentUpdate(bob_account_id(), updates)), + Event::Assets(pallet_assets::Event::Transferred { + asset_id: 22, + from: pallet_assets_account(), + to: pallet_fees_account(), + amount: 1_500_000_u128, + }), Event::Assets(pallet_assets::Event::Transferred { asset_id: 22, from: pallet_assets_account(), to: processor_account_id(), - amount: INITIAL_BALANCE / 2 as u128, + amount: 3_500_000_u128, }), Event::Acurast(crate::Event::ReceivedFulfillment( processor_account_id(), diff --git a/pallets/acurast/src/traits.rs b/pallets/acurast/src/traits.rs new file mode 100644 index 00000000..c90cb8eb --- /dev/null +++ b/pallets/acurast/src/traits.rs @@ -0,0 +1,64 @@ +use crate::{Config, Fulfillment, JobRegistration, CertificateRevocationListUpdate, JobAssignmentUpdate}; +use frame_support::{pallet_prelude::DispatchResultWithPostInfo, weights::Weight, PalletId}; +use frame_system::pallet_prelude::OriginFor; +use sp_runtime::{traits::StaticLookup, Percent}; +use sp_std::prelude::*; + +/// This trait provides the interface for a fulfillment router. +pub trait FulfillmentRouter { + fn received_fulfillment( + origin: OriginFor, + from: T::AccountId, + fulfillment: Fulfillment, + registration: JobRegistration, + requester: ::Target, + ) -> DispatchResultWithPostInfo; +} + +pub trait RevocationListUpdateBarrier { + fn can_update_revocation_list( + origin: &T::AccountId, + updates: &Vec, + ) -> bool; +} + +impl RevocationListUpdateBarrier for () { + fn can_update_revocation_list( + _origin: &T::AccountId, + _updates: &Vec, + ) -> bool { + false + } +} + +pub trait JobAssignmentUpdateBarrier { + fn can_update_assigned_jobs( + origin: &T::AccountId, + updates: &Vec>, + ) -> bool; +} + +impl JobAssignmentUpdateBarrier for () { + fn can_update_assigned_jobs( + _origin: &T::AccountId, + _updates: &Vec>, + ) -> bool { + false + } +} + +pub trait WeightInfo { + fn register() -> Weight; + fn deregister() -> Weight; + fn update_allowed_sources() -> Weight; + fn update_job_assignments() -> Weight; + fn fulfill() -> Weight; + fn submit_attestation() -> Weight; + fn update_certificate_revocation_list() -> Weight; +} + +// This trait provives methods for managing the fees. +pub trait FeeManager { + fn get_fee_percentage() -> Percent; + fn pallet_id() -> PalletId; +} diff --git a/pallets/proxy/Cargo.toml b/pallets/proxy/Cargo.toml index 27c5c4c1..d46fb409 100644 --- a/pallets/proxy/Cargo.toml +++ b/pallets/proxy/Cargo.toml @@ -56,7 +56,6 @@ parachain-info = { git = "https://github.com/paritytech/cumulus", default-featur hex-literal = "0.3.4" - [features] default = ["std"] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] diff --git a/pallets/proxy/src/mock.rs b/pallets/proxy/src/mock.rs index e6b09177..693b1834 100644 --- a/pallets/proxy/src/mock.rs +++ b/pallets/proxy/src/mock.rs @@ -206,6 +206,17 @@ pub mod acurast_runtime { type WeightInfo = (); } + pub struct FeeManagerImpl; + impl pallet_acurast::FeeManager for FeeManagerImpl { + fn get_fee_percentage() -> sp_runtime::Percent { + sp_runtime::Percent::from_percent(30) + } + + fn pallet_id() -> PalletId { + PalletId(*b"acurfees") + } + } + impl pallet_acurast::Config for Runtime { type Event = Event; type RegistrationExtra = (); @@ -215,6 +226,7 @@ pub mod acurast_runtime { type PalletId = AcurastPalletId; type RevocationListUpdateBarrier = (); type JobAssignmentUpdateBarrier = JobBarrier; + type FeeManager = FeeManagerImpl; type WeightInfo = pallet_acurast::weights::WeightInfo; } diff --git a/pallets/proxy/src/tests.rs b/pallets/proxy/src/tests.rs index d70f02c2..81e9279c 100644 --- a/pallets/proxy/src/tests.rs +++ b/pallets/proxy/src/tests.rs @@ -21,7 +21,7 @@ use acurast_runtime::AccountId as AcurastAccountId; use acurast_runtime::Runtime as AcurastRuntime; use frame_support::{pallet_prelude::GenesisBuild, sp_runtime::traits::AccountIdConversion}; use hex_literal::hex; -use pallet_acurast::{JobAssignmentUpdate, JobRegistration}; +use pallet_acurast::{JobAssignmentUpdate, JobRegistration, FeeManager}; use polkadot_parachain::primitives::Id as ParaId; use xcm::latest::{MultiAsset, MultiLocation}; use xcm::prelude::{Concrete, Fungible, GeneralIndex, PalletInstance, Parachain, X3}; @@ -82,6 +82,7 @@ pub fn acurast_ext(para_id: u32) -> sp_io::TestExternalities { balances: vec![ (alice_account_id(), INITIAL_BALANCE), (pallet_assets_account(), INITIAL_BALANCE), + (pallet_fees_account(), INITIAL_BALANCE), (bob_account_id(), INITIAL_BALANCE), (processor_account_id(), INITIAL_BALANCE), ], @@ -161,6 +162,9 @@ pub fn processor_account_id() -> AcurastAccountId { pub fn pallet_assets_account() -> ::AccountId { ::PalletId::get().into_account_truncating() } +pub fn pallet_fees_account() -> ::AccountId { + ::FeeManager::pallet_id().into_account_truncating() +} pub fn alice_account_id() -> AcurastAccountId { [0; 32].into() }