Skip to content

Commit

Permalink
MESH-2101/Add as_derivative extrinsic in the utility pallet (#1560)
Browse files Browse the repository at this point in the history
* Add as_derivative extrinsic in the utility pallet

* Fix draft for as derivative

* Add run_with_temporary_did_and_payer; Change dispatch_as to use run_with_temporary_did_and_payer; Fix derivative_account_id impl; Add unit tests; Run benchmarks

* Run format

* Remove unused import

* Add with_call_metadata

* Add extra parameter for entropy

---------

Co-authored-by: Adam Dossa <[email protected]>
  • Loading branch information
HenriqueNogara and adamdossa authored Nov 23, 2023
1 parent fca761d commit 5da6564
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 62 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

63 changes: 42 additions & 21 deletions pallets/runtime/tests/src/utility_test.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
use super::storage::example::Call as ExampleCall;
use super::{
assert_event_doesnt_exist, assert_event_exists, assert_last_event,
committee_test::set_members,
pips_test::{assert_balance, assert_state, committee_proposal, community_proposal},
storage::{
add_secondary_key, get_secondary_keys, next_block, register_keyring_account_with_balance,
EventTest, Identity, Portfolio, RuntimeCall, RuntimeOrigin, System, TestBaseCallFilter,
TestStorage, User, Utility,
},
ExtBuilder,
};
use codec::Encode;
use frame_support::dispatch::{
extract_actual_weight, DispatchError, DispatchErrorWithPostInfo, Dispatchable, GetDispatchInfo,
Pays, PostDispatchInfo, Weight,
};
use frame_support::error::BadOrigin;
use frame_support::traits::Contains;
use frame_support::{
assert_err_ignore_postinfo, assert_noop, assert_ok, assert_storage_noop,
dispatch::{
extract_actual_weight, DispatchError, DispatchErrorWithPostInfo, Dispatchable,
GetDispatchInfo, Pays, PostDispatchInfo, Weight,
},
error::BadOrigin,
storage,
traits::Contains,
assert_err_ignore_postinfo, assert_noop, assert_ok, assert_storage_noop, storage, StorageMap,
};
use frame_system::{Call as SystemCall, EventRecord};
use pallet_timestamp::Call as TimestampCall;

use pallet_asset::Tickers;
use pallet_balances::Call as BalancesCall;
use pallet_pips::{ProposalState, SnapshotResult};
use pallet_portfolio::Call as PortfolioCall;
Expand All @@ -33,11 +21,21 @@ use pallet_utility::{
use polymesh_common_utilities::traits::transaction_payment::CddAndFeeDetails;
use polymesh_primitives::{
AccountId, Balance, PalletPermissions, Permissions, PortfolioName, PortfolioNumber,
SubsetRestriction,
SubsetRestriction, Ticker,
};
use sp_core::sr25519::Signature;
use sp_keyring::AccountKeyring;

use super::committee_test::set_members;
use super::pips_test::{assert_balance, assert_state, committee_proposal, community_proposal};
use super::storage::example::Call as ExampleCall;
use super::storage::{
add_secondary_key, get_secondary_keys, next_block, register_keyring_account_with_balance,
EventTest, Identity, Portfolio, RuntimeCall, RuntimeOrigin, System, TestBaseCallFilter,
TestStorage, User, Utility,
};
use super::{assert_event_doesnt_exist, assert_event_exists, assert_last_event, ExtBuilder};

type Error = utility::Error<TestStorage>;

type Balances = pallet_balances::Module<TestStorage>;
Expand Down Expand Up @@ -1017,3 +1015,26 @@ fn sub_with_weight_works() {
);
})
}

#[test]
fn as_derivative() {
new_test_ext().execute_with(|| {
let ticker: Ticker = Ticker::from_slice_truncated(b"TICKER".as_ref());
let alice = User::new(AccountKeyring::Alice).balance(1_000_000);
let derivative_alice_account = Utility::derivative_account_id(alice.acc(), 1).unwrap();
Identity::unsafe_join_identity(
alice.did,
Permissions::default(),
derivative_alice_account.clone(),
);
let derivative_alice_id = Identity::get_identity(&derivative_alice_account).unwrap();
Balances::transfer(alice.origin(), derivative_alice_account.into(), 1_000_000).unwrap();

let call = RuntimeCall::Asset(pallet_asset::Call::register_ticker { ticker });
assert_ok!(Utility::as_derivative(alice.origin(), 1, Box::new(call)));
assert_eq!(
Tickers::<TestStorage>::get(ticker).unwrap().owner,
derivative_alice_id
);
});
}
1 change: 1 addition & 0 deletions pallets/utility/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition = "2021"
pallet-balances = { path = "../balances", default-features = false }
pallet-permissions = { path = "../permissions", default-features = false }
polymesh-common-utilities = { path = "../common", default-features = false }
pallet-identity = { path = "../identity", default-features = false }
polymesh-primitives = { path = "../../primitives", default-features = false }

# Substrate
Expand Down
19 changes: 12 additions & 7 deletions pallets/utility/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,16 @@

#![cfg(feature = "runtime-benchmarks")]

use super::*;
use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller};
use frame_system::RawOrigin;

// POLYMESH:
use polymesh_common_utilities::{
benchs::{AccountIdOf, User, UserBuilder},
traits::TestUtilsFn,
};
use sp_core::sr25519::Signature;
use sp_runtime::MultiSignature;

use polymesh_common_utilities::benchs::{user, AccountIdOf, User, UserBuilder};
use polymesh_common_utilities::traits::TestUtilsFn;

use super::*;

// POLYMESH:
const MAX_CALLS: u32 = 30;

Expand Down Expand Up @@ -191,4 +189,11 @@ benchmarks! {
verify {
// NB see comment at `batch` verify section.
}

// POLYMESH:
as_derivative {
let index = 1;
let alice = user::<T>("Alice", 0);
let call = Box::new(frame_system::Call::remark { remark: vec![] }.into());
}: _(alice.origin, index, call)
}
194 changes: 162 additions & 32 deletions pallets/utility/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,29 +69,28 @@
mod benchmarking;

use codec::{Decode, Encode};
use frame_support::{
dispatch::{extract_actual_weight, GetDispatchInfo, PostDispatchInfo},
traits::{IsSubType, OriginTrait, UnfilteredDispatchable},
};
use frame_support::dispatch::{extract_actual_weight, GetDispatchInfo, PostDispatchInfo};
use frame_support::dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight};
use frame_support::ensure;
use frame_support::storage::{with_transaction, TransactionOutcome};
use frame_support::traits::GetCallMetadata;
use frame_support::traits::{IsSubType, OriginTrait, UnfilteredDispatchable};
use frame_system::{ensure_root, ensure_signed, Pallet as System, RawOrigin};
use scale_info::TypeInfo;
use sp_core::Get;
use sp_io::hashing::blake2_256;
use sp_runtime::traits::TrailingZeroInput;
use sp_runtime::traits::{BadOrigin, Dispatchable};
use sp_runtime::{traits::Verify, DispatchError, RuntimeDebug};
use sp_std::prelude::*;

// POLYMESH:
use frame_support::storage::{with_transaction, TransactionOutcome};
use frame_support::{
dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight},
ensure,
traits::GetCallMetadata,
};
use frame_system::{ensure_root, ensure_signed, Pallet as System, RawOrigin};
use pallet_permissions::with_call_metadata;
use polymesh_common_utilities::{
balances::{CheckCdd, Config as BalancesConfig},
identity::{AuthorizationNonce, Config as IdentityConfig},
};
use polymesh_common_utilities::balances::{CheckCdd, Config as BalancesConfig};
use polymesh_common_utilities::identity::{AuthorizationNonce, Config as IdentityConfig};
use polymesh_common_utilities::Context;
use polymesh_primitives::IdentityId;
use scale_info::TypeInfo;
use sp_runtime::{traits::Verify, DispatchError, RuntimeDebug};

type Identity<T> = pallet_identity::Module<T>;

pub trait WeightInfo {
fn batch(c: u32) -> Weight;
Expand All @@ -105,6 +104,7 @@ pub trait WeightInfo {
fn batch_old(c: u32) -> Weight;
fn batch_atomic(c: u32) -> Weight;
fn batch_optimistic(c: u32) -> Weight;
fn as_derivative() -> Weight;
}

// POLYMESH:
Expand Down Expand Up @@ -262,6 +262,8 @@ pub mod pallet {
/// If the provided nonce > current nonce, the call(s) before the current failed to execute
/// POLYMESH error
InvalidNonce,
/// Decoding derivative account Id failed.
UnableToDeriveAccountId,
}

/// Nonce for `relay_tx`.
Expand Down Expand Up @@ -520,20 +522,7 @@ pub mod pallet {
as_origin: Box<T::PalletsOrigin>,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
Self::ensure_root(origin)?;

let info = call.get_dispatch_info();
let res = call.dispatch_bypass_filter((*as_origin).into());
// Get the actual weight of this call.
let weight = extract_actual_weight(&res, &info);

Self::deposit_event(Event::<T>::DispatchedAs {
result: res.map(|_| ()).map_err(|e| e.error),
});

// POLYMESH: return the actual weight of the call.
let base_weight = <T as Config>::WeightInfo::dispatch_as();
Ok(Some(base_weight.saturating_add(weight)).into())
Self::base_dispatch_as(origin, as_origin, call)
}

/// Send a batch of dispatch calls.
Expand Down Expand Up @@ -799,6 +788,30 @@ pub mod pallet {
Self::deposit_event(Self::run_batch(origin, is_root, calls, false));
Ok(())
}

/// Send a call through an indexed pseudonym of the sender.
///
/// Filter from origin are passed along. The call will be dispatched with an origin which
/// use the same filter as the origin of this call.
///
/// The dispatch origin for this call must be _Signed_.
#[pallet::call_index(9)]
#[pallet::weight({
let dispatch_info = call.get_dispatch_info();
(
<T as Config>::WeightInfo::as_derivative()
.saturating_add(T::DbWeight::get().reads_writes(1, 1))
.saturating_add(dispatch_info.weight),
dispatch_info.class,
)
})]
pub fn as_derivative(
origin: OriginFor<T>,
index: u16,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
Self::base_as_derivative(origin, index, call)
}
}
}

Expand Down Expand Up @@ -871,4 +884,121 @@ impl<T: Config> Pallet<T> {
}
Ok(is_root)
}

fn base_dispatch_as(
origin: T::RuntimeOrigin,
as_origin: Box<T::PalletsOrigin>,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
Self::ensure_root(origin)?;

let as_origin: T::RuntimeOrigin = (*as_origin).into();

let behalf_account_id = {
match as_origin.clone().into() {
Ok(RawOrigin::Signed(account_id)) => Some(account_id.clone()),
_ => None,
}
};
let behalf_identity = behalf_account_id
.clone()
.and_then(|acc| Identity::<T>::get_identity(&acc));

let dispatch_info = call.get_dispatch_info();
let call_result = Self::run_with_temporary_did_and_payer(
as_origin,
behalf_account_id,
behalf_identity,
call,
true,
);
// Get the actual weight of this call.
let weight = extract_actual_weight(&call_result, &dispatch_info);

Self::deposit_event(Event::<T>::DispatchedAs {
result: call_result.map(|_| ()).map_err(|e| e.error),
});
// POLYMESH: return the actual weight of the call.
let base_weight = <T as Config>::WeightInfo::dispatch_as();
Ok(Some(base_weight.saturating_add(weight)).into())
}

fn base_as_derivative(
origin: T::RuntimeOrigin,
index: u16,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
let origin_account_id = ensure_signed(origin.clone())?;

// Sets the caller to be the derivative of the caller's account
let derivative_account_id = Self::derivative_account_id(origin_account_id, index)?;
let mut origin = origin;
origin.set_caller_from(frame_system::RawOrigin::Signed(
derivative_account_id.clone(),
));
// Get the identity of the derivative account id
let derivative_did = Identity::<T>::get_identity(&derivative_account_id);

let dispatch_info = call.get_dispatch_info();
let call_result = Self::run_with_temporary_did_and_payer(
origin,
Some(derivative_account_id),
derivative_did,
call,
false,
);

// Always take into account the base weight of this call and add the real weight of the dispatch.
let mut weight = <T as Config>::WeightInfo::as_derivative()
.saturating_add(T::DbWeight::get().reads_writes(1, 1));
weight = weight.saturating_add(extract_actual_weight(&call_result, &dispatch_info));

call_result
.map_err(|mut err| {
err.post_info = Some(weight).into();
err
})
.map(|_| Some(weight).into())
}

/// Derive a derivative account ID from the owner account and the index.
pub fn derivative_account_id(
origin_account_id: T::AccountId,
index: u16,
) -> Result<T::AccountId, DispatchError> {
let entropy = (b"modlpy/utilisuba", origin_account_id, index).using_encoded(blake2_256);
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
.map_err(|_| Error::<T>::UnableToDeriveAccountId.into())
}

/// Dispatches `call` Setting CurrentDid and CurrentPayer to `did` and `account_id`.
/// The values are reset once the call is done.
fn run_with_temporary_did_and_payer(
origin: T::RuntimeOrigin,
account_id: Option<T::AccountId>,
did: Option<IdentityId>,
call: Box<<T as Config>::RuntimeCall>,
bypass_filter: bool,
) -> Result<PostDispatchInfo, DispatchErrorWithPostInfo> {
// Hold the original value for payer and identity
let original_payer = Context::current_payer::<Identity<T>>();
let original_did = Context::current_identity::<Identity<T>>();
// Temporarily change identity and payer
Context::set_current_identity::<Identity<T>>(did);
Context::set_current_payer::<Identity<T>>(account_id);
// dispatch the call
let call_result = {
with_call_metadata(call.get_call_metadata(), || {
if bypass_filter {
call.dispatch_bypass_filter(origin)
} else {
call.dispatch(origin)
}
})
};
// Restore the original payer and identity
Context::set_current_payer::<Identity<T>>(original_payer);
Context::set_current_identity::<Identity<T>>(original_did);
call_result
}
}
Loading

0 comments on commit 5da6564

Please sign in to comment.