Skip to content

Commit

Permalink
Integrate account sponsorship in pallet-meta-tx
Browse files Browse the repository at this point in the history
Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
  • Loading branch information
georgepisaltu committed May 17, 2024
1 parent 54938ec commit 9f464bd
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 3 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.

1 change: 1 addition & 0 deletions substrate/frame/account-sponsorship/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ impl pallet_meta_tx::Config for Runtime {
type PublicKey = <Signature as Verify>::Signer;
type Context = ();
type Extension = MetaTxExtension;
type ExistenceProvider = AccountSponsorship;
}

#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
Expand Down
1 change: 1 addition & 0 deletions substrate/frame/meta-tx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ frame-benchmarking = { path = "../benchmarking", default-features = false, optio

[dev-dependencies]
pallet-balances = { path = "../balances", features = ["std"] }
pallet-account-sponsorship = { path = "../account-sponsorship", features = ["std"] }
sp-io = { path = "../../primitives/io", features = ["std"] }
keyring = { package = "sp-keyring", path = "../../primitives/keyring" }
pallet-transaction-payment = { path = "../../frame/transaction-payment" }
Expand Down
73 changes: 72 additions & 1 deletion substrate/frame/meta-tx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ use frame_support::{
};
use frame_system::pallet_prelude::*;
use sp_runtime::traits::{
Dispatchable, IdentifyAccount, TransactionExtension, TransactionExtensionBase, Verify,
AccountExistenceProvider, Dispatchable, IdentifyAccount, TransactionExtension,
TransactionExtensionBase, Verify,
};
use sp_std::prelude::*;

Expand Down Expand Up @@ -150,6 +151,8 @@ pub mod pallet {
/// [frame_system::CheckTxVersion], [frame_system::CheckGenesis],
/// [frame_system::CheckMortality], [frame_system::CheckNonce], etc.
type Extension: TransactionExtension<<Self as Config>::RuntimeCall, Self::Context>;
/// Type to provide for new, nonexistent accounts.
type ExistenceProvider: AccountExistenceProvider<Self::AccountId>;
}

#[pallet::error]
Expand Down Expand Up @@ -240,6 +243,74 @@ pub mod pallet {

res
}

/// Dispatch a given meta transaction.
///
/// - `origin`: Can be any kind of origin.
/// - `meta_tx`: Meta Transaction with a target call to be dispatched.
#[pallet::call_index(1)]
#[pallet::weight({
let dispatch_info = meta_tx.call.get_dispatch_info();
// TODO: plus T::WeightInfo::dispatch() which must include the weight of T::Extension
(
dispatch_info.weight,
dispatch_info.class,
)
})]
pub fn dispatch_creating(
origin: OriginFor<T>,
meta_tx: MetaTxFor<T>,
) -> DispatchResultWithPostInfo {
let sponsor = ensure_signed(origin)?;
let meta_tx_size = meta_tx.encoded_size();

let (signer, signature) = match meta_tx.proof {
Proof::Signed(signer, signature) => (signer, signature),
};

let signed_payload = SignedPayloadFor::<T>::new(*meta_tx.call, meta_tx.extension)
.map_err(|_| Error::<T>::Invalid)?;

if !signed_payload.using_encoded(|payload| signature.verify(payload, &signer)) {
return Err(Error::<T>::BadProof.into());
}

if !<frame_system::Pallet<T>>::account_exists(&signer) {
T::ExistenceProvider::provide(&sponsor, &signer)?;
}

let origin = T::RuntimeOrigin::signed(signer);
let (call, extension, _) = signed_payload.deconstruct();
let info = call.get_dispatch_info();
let mut ctx = T::Context::default();

let (_, val, origin) = T::Extension::validate(
&extension,
origin,
&call,
&info,
meta_tx_size,
&mut ctx,
extension.implicit().map_err(|_| Error::<T>::Invalid)?,
&call,
)
.map_err(Error::<T>::from)?;

let pre =
T::Extension::prepare(extension, val, &origin, &call, &info, meta_tx_size, &ctx)
.map_err(Error::<T>::from)?;

let res = call.dispatch(origin);
let post_info = res.unwrap_or_else(|err| err.post_info);
let pd_res = res.map(|_| ()).map_err(|e| e.error);

T::Extension::post_dispatch(pre, &info, &post_info, meta_tx_size, &pd_res, &ctx)
.map_err(Error::<T>::from)?;

Self::deposit_event(Event::Dispatched { result: res });

res
}
}

/// Implements [`From<TransactionValidityError>`] for [`Error`] by mapping the relevant error
Expand Down
13 changes: 12 additions & 1 deletion substrate/frame/meta-tx/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use frame_support::{
construct_runtime, derive_impl,
weights::{FixedFee, NoFee},
};
use sp_core::ConstU8;
use sp_core::{ConstU64, ConstU8};
use sp_runtime::{traits::IdentityLookup, MultiSignature};

pub type Balance = u64;
Expand Down Expand Up @@ -62,6 +62,7 @@ impl Config for Runtime {
type PublicKey = <Signature as Verify>::Signer;
type Context = ();
type Extension = MetaTxExtension;
type ExistenceProvider = AccountSponsorship;
}

#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
Expand Down Expand Up @@ -90,12 +91,22 @@ impl pallet_transaction_payment::Config for Runtime {
type FeeMultiplierUpdate = ();
}

impl pallet_account_sponsorship::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type RuntimeHoldReason = RuntimeHoldReason;
type BaseDeposit = ConstU64<5>;
type BeneficiaryDeposit = ConstU64<1>;
type GracePeriod = ConstU64<10>;
}

construct_runtime!(
pub enum Runtime {
System: frame_system,
Balances: pallet_balances,
MetaTx: pallet_meta_tx,
TxPayment: pallet_transaction_payment,
AccountSponsorship: pallet_account_sponsorship,
}
);

Expand Down
147 changes: 146 additions & 1 deletion substrate/frame/meta-tx/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// limitations under the License.

use crate::*;
use frame_support::traits::tokens::fungible::Inspect;
use frame_support::traits::tokens::fungible::{Inspect, InspectHold};
use keyring::AccountKeyring;
use mock::*;
use sp_io::hashing::blake2_256;
Expand Down Expand Up @@ -141,3 +141,148 @@ fn sign_and_execute_meta_tx() {
assert_eq!(bob_balance - tx_fee, Balances::free_balance(bob_account));
});
}

#[test]
fn nonexistent_account_meta_tx() {
new_test_ext().execute_with(|| {
// meta tx signer
let alice_keyring = AccountKeyring::Alice;
// meta tx relayer
let bob_keyring = AccountKeyring::Bob;

let alice_account = AccountId::from(alice_keyring.public());
let bob_account = AccountId::from(bob_keyring.public());

let ed = Balances::minimum_balance();
let tx_fee: Balance = (2 * TX_FEE).into(); // base tx fee + weight fee
let bob_balance = ed * 100;

{
// setup initial balance only for bob
Balances::force_set_balance(
RuntimeOrigin::root(),
bob_account.clone().into(),
bob_balance,
)
.unwrap();
}

// Alice builds a meta transaction.

let remark_call =
RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] });
let meta_tx_ext: MetaTxExtension = (
frame_system::CheckNonZeroSender::<Runtime>::new(),
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckMortality::<Runtime>::from(sp_runtime::generic::Era::immortal()),
frame_system::CheckNonce::<Runtime>::from(
frame_system::Pallet::<Runtime>::account(&alice_account).nonce,
),
);

let meta_tx_sig = MultiSignature::Sr25519(
(remark_call.clone(), meta_tx_ext.clone(), meta_tx_ext.implicit().unwrap())
.using_encoded(|e| alice_keyring.sign(&blake2_256(e))),
);

let meta_tx = MetaTxFor::<Runtime>::new_signed(
alice_account.clone(),
meta_tx_sig,
remark_call.clone(),
meta_tx_ext.clone(),
);

// Encode and share with the world.
let meta_tx_encoded = meta_tx.encode();

// Bob acts as meta transaction relayer and as the sponsor for Alice's account existence.

let meta_tx = MetaTxFor::<Runtime>::decode(&mut &meta_tx_encoded[..]).unwrap();
// Use meta dispatch which also creates Alice's account.
let call = RuntimeCall::MetaTx(Call::dispatch_creating { meta_tx: meta_tx.clone() });
let tx_ext: Extension = (
frame_system::CheckNonZeroSender::<Runtime>::new(),
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckMortality::<Runtime>::from(sp_runtime::generic::Era::immortal()),
frame_system::CheckNonce::<Runtime>::from(
frame_system::Pallet::<Runtime>::account(&bob_account).nonce,
),
frame_system::CheckWeight::<Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(0),
);

let tx_sig = MultiSignature::Sr25519(
(call.clone(), tx_ext.clone(), tx_ext.implicit().unwrap())
.using_encoded(|e| bob_keyring.sign(&blake2_256(e))),
);

let uxt = UncheckedExtrinsic::new_signed(call, bob_account.clone(), tx_sig, tx_ext);

// Alice's account doesn't exist yet.
assert!(!System::account_exists(&alice_account));

// Check Extrinsic validity and apply it.

let uxt_info = uxt.get_dispatch_info();
let uxt_len = uxt.using_encoded(|e| e.len());

let xt = <UncheckedExtrinsic as Checkable<IdentityLookup<AccountId>>>::check(
uxt,
&Default::default(),
)
.unwrap();

let res = xt.apply::<Runtime>(&uxt_info, uxt_len).unwrap();

// Asserting the results.

assert!(res.is_ok());

System::assert_has_event(RuntimeEvent::MetaTx(crate::Event::Dispatched { result: res }));

System::assert_has_event(RuntimeEvent::System(frame_system::Event::Remarked {
sender: alice_account.clone(),
hash: <Runtime as frame_system::Config>::Hashing::hash(&[1]),
}));

// Alice's account has been created and ran the transaction, and Bob paid the transaction
// fee.
assert!(System::account_exists(&alice_account));
// Nonce is stored and updated.
assert_eq!(System::account_nonce(&alice_account), 1);
assert_eq!(0, Balances::free_balance(&alice_account));
let provider_deposit =
<<Runtime as pallet_account_sponsorship::Config>::BaseDeposit as sp_core::Get<u64>>::get() +
<<Runtime as pallet_account_sponsorship::Config>::BeneficiaryDeposit as sp_core::Get<u64>>::get() +
pallet_account_sponsorship::AccountDeposit::<Runtime>::get();
assert_eq!(bob_balance - tx_fee - provider_deposit, Balances::free_balance(&bob_account));
assert_eq!(provider_deposit, Balances::total_balance_on_hold(&bob_account));

// Alice has just been sponsored, the sponsorship cannot be withdrawn yet.
assert_eq!(
AccountSponsorship::withdraw_sponsorship(
Some(bob_account.clone()).into(),
alice_account.clone()
)
.unwrap_err(),
pallet_account_sponsorship::Error::<Runtime>::EarlyWithdrawal.into()
);

// Let the grace period pass.
let grace_period =
<<Runtime as pallet_account_sponsorship::Config>::GracePeriod as sp_core::Get<u64>>::get();
System::set_block_number(System::block_number() + grace_period);

// Bob can now withdraw his sponsorship and release the deposit.
frame_support::assert_ok!(AccountSponsorship::withdraw_sponsorship(
Some(bob_account.clone()).into(),
alice_account.clone()
));
assert!(!System::account_exists(&alice_account));
assert_eq!(Balances::total_balance_on_hold(&bob_account), 0);
});
}

0 comments on commit 9f464bd

Please sign in to comment.