Skip to content

Commit

Permalink
Macros: Return optional extrinsic instead of unwrapping (#709)
Browse files Browse the repository at this point in the history
* remove unwraps from macros and add Option return value instead

fix examples

fix examples

* fix macro and add unit tests

* fix tests
  • Loading branch information
haerdib committed Feb 20, 2024
1 parent 4425731 commit 763164c
Show file tree
Hide file tree
Showing 22 changed files with 268 additions and 132 deletions.
172 changes: 129 additions & 43 deletions compose-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,28 @@ pub use log;

mod rpc;

/// Generates the extrinsic's call field for a given module and call passed as &str
/// Generates the extrinsic's call field for a given module and call passed as &str, if found in the metadata.
/// Otherwise None is returned.
/// # Arguments
///
/// * 'node_metadata' - This crate's parsed node metadata as field of the API.
/// * 'pallet' - Pallet name as &str for which the call is composed.
/// * 'pallet_name' - Pallet name as &str for which the call is composed.
/// * 'call_name' - Call name as &str
/// * 'args' - Optional sequence of arguments of the call. They are not checked against the metadata.
/// As of now the user needs to check himself that the correct arguments are supplied.
#[macro_export]
macro_rules! compose_call {
($node_metadata: expr, $pallet: expr, $call_name: expr $(, $args: expr) *) => {
($node_metadata: expr, $pallet_name: expr, $call_name: expr $(, $args: expr) *) => {
{
let pallet_metadata = $node_metadata.pallet_by_name($pallet).unwrap().to_owned();
$crate::compose_call_for_pallet_metadata!(pallet_metadata, $call_name $(, ($args)) *)
}
let maybe_pallet = $node_metadata.pallet_by_name($pallet_name);
let maybe_call = match maybe_pallet {
Some(pallet) => {
$crate::compose_call_for_pallet_metadata!(pallet, $call_name $(, ($args)) *)
},
None => None,
};
maybe_call
}

};
}

Expand All @@ -54,13 +61,19 @@ macro_rules! compose_call {
macro_rules! compose_call_for_pallet_metadata {
($pallet_metadata: expr, $call_name: expr $(, $args: expr) *) => {
{
let call_index = $pallet_metadata.call_variant_by_name($call_name).unwrap().index;
([$pallet_metadata.index(), call_index as u8] $(, ($args)) *)

let maybe_call_variant = $pallet_metadata.call_variant_by_name($call_name);
match maybe_call_variant {
Some(call_variant) => Some(([$pallet_metadata.index(), call_variant.index as u8] $(, ($args)) *)),
None => None,
}

}

};
}

/// Generates an Unchecked extrinsic for a given call
/// Generates an UncheckedExtrinsic for a given call.
/// # Arguments
///
/// * 'signer' - AccountKey that is used to sign the extrinsic.
Expand All @@ -85,75 +98,87 @@ macro_rules! compose_extrinsic_offline {
}};
}

/// Generates an Unchecked extrinsic for a given module and call passed as a &str.
/// Generates an UncheckedExtrinsic for the given pallet and call, if they are found within the metadata.
/// Otherwise None is returned.
/// # Arguments
///
/// * 'api' - This instance of API. If the *signer* field is not set, an unsigned extrinsic will be generated.
/// * 'nonce' - signer's account nonce: Index
/// * 'module' - Module name as &str for which the call is composed.
/// * 'call' - Call name as &str
/// * 'pallet_name' - Pallet name as &str for which the call is composed.
/// * 'call_name' - Call name as &str
/// * 'args' - Optional sequence of arguments of the call. They are not checked against the metadata.
/// As of now the user needs to check himself that the correct arguments are supplied.
#[macro_export]
macro_rules! compose_extrinsic_with_nonce {
($api: expr,
$nonce: expr,
$module: expr,
$call: expr
$pallet_name: expr,
$call_name: expr
$(, $args: expr) *) => {
{
use $crate::log::debug;
use $crate::primitives::UncheckedExtrinsicV4;

debug!("Composing generic extrinsic for module {:?} and call {:?}", $module, $call);
debug!("Composing generic extrinsic for module {:?} and call {:?}", $pallet_name, $call_name);

let metadata = $api.metadata();
let call = $crate::compose_call!(metadata, $module, $call $(, ($args)) *);
if let Some(signer) = $api.signer() {
$crate::compose_extrinsic_offline!(
signer,
call.clone(),
$api.extrinsic_params($nonce)
)
} else {
UncheckedExtrinsicV4::new_unsigned(call.clone())
}
let maybe_call = $crate::compose_call!(metadata, $pallet_name, $call_name $(, ($args)) *);

let maybe_extrinsic = match maybe_call {
Some(call) => {
let extrinsic = if let Some(signer) = $api.signer() {
$crate::compose_extrinsic_offline!(
signer,
call.clone(),
$api.extrinsic_params($nonce)
)
} else {
UncheckedExtrinsicV4::new_unsigned(call.clone())
};
Some(extrinsic)
},
None => None,
};
maybe_extrinsic


}
};
}

/// Generates an Unchecked extrinsic for a given module and call passed as a &str.
/// Fetches the nonce from the given `api` instance
/// Generates an UncheckedExtrinsic for the given pallet and call, if they are found within the metadata.
/// Otherwise None is returned.
/// Fetches the nonce from the given `api` instance. If this fails, zero is taken as default nonce.
/// See also compose_extrinsic_with_nonce
#[macro_export]
#[cfg(feature = "sync-api")]
macro_rules! compose_extrinsic {
($api: expr,
$module: expr,
$call: expr
$pallet_name: expr,
$call_name: expr
$(, $args: expr) *) => {
{
let nonce = $api.get_nonce().unwrap();
let extrinsic = $crate::compose_extrinsic_with_nonce!($api, nonce, $module, $call $(, ($args)) *);
extrinsic
let nonce = $api.get_nonce().unwrap_or_default();
let maybe_extrinisc = $crate::compose_extrinsic_with_nonce!($api, nonce, $pallet_name, $call_name $(, ($args)) *);
maybe_extrinisc
}
};
}

/// Generates an Unchecked extrinsic for a given module and call passed as a &str.
/// Fetches the nonce from the given `api` instance
/// Generates an UncheckedExtrinsic for the given pallet and call, if they are found within the metadata.
/// Otherwise None is returned.
/// Fetches the nonce from the given `api` instance. If this fails, zero is taken as default nonce.
/// See also compose_extrinsic_with_nonce
#[macro_export]
#[cfg(not(feature = "sync-api"))]
macro_rules! compose_extrinsic {
($api: expr,
$module: expr,
$call: expr
$pallet_name: expr,
$call_name: expr
$(, $args: expr) *) => {
{
let nonce = $api.get_nonce().await.unwrap();
let extrinsic = $crate::compose_extrinsic_with_nonce!($api, nonce, $module, $call $(, ($args)) *);
extrinsic
let nonce = $api.get_nonce().await.unwrap_or_default();
let maybe_extrinisc = $crate::compose_extrinsic_with_nonce!($api, nonce, $pallet_name, $call_name $(, ($args)) *);
maybe_extrinisc
}
};
}
Expand Down Expand Up @@ -181,14 +206,75 @@ mod tests {
&pallet_metadata,
"transfer_allow_death",
extra_parameter
);
)
.unwrap();
assert_eq!(expected_call_one, call_one);
let expected_call_two = ([4, 8], extra_parameter);
let call_two = compose_call_for_pallet_metadata!(
&pallet_metadata,
"force_set_balance",
extra_parameter
);
)
.unwrap();
assert_eq!(expected_call_two, call_two);
}

#[test]
fn macro_compose_call_for_pallet_metadata_returns_none_for_unknown_function() {
let encoded_metadata = fs::read("../ksm_metadata_v14.bin").unwrap();
let runtime_metadata_prefixed =
RuntimeMetadataPrefixed::decode(&mut encoded_metadata.as_slice()).unwrap();
let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();

let pallet_metadata = metadata.pallet_by_name("Balances").unwrap();
let non_existent_function = "obladi";

let option = compose_call_for_pallet_metadata!(&pallet_metadata, non_existent_function);
assert!(option.is_none());
}

#[test]
fn macro_compose_call_returns_none_for_unknown_function() {
let encoded_metadata = fs::read("../ksm_metadata_v14.bin").unwrap();
let runtime_metadata_prefixed =
RuntimeMetadataPrefixed::decode(&mut encoded_metadata.as_slice()).unwrap();
let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();

let pallet_name = "Balances";
let non_existent_function = "obladi";

let option = compose_call!(&metadata, pallet_name, non_existent_function);
assert!(option.is_none());
}

#[test]
fn macro_compose_call_returns_none_for_unknown_pallet() {
let encoded_metadata = fs::read("../ksm_metadata_v14.bin").unwrap();
let runtime_metadata_prefixed =
RuntimeMetadataPrefixed::decode(&mut encoded_metadata.as_slice()).unwrap();
let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();

let pallet_name = "Balance";
let non_existent_function = "force_set_balance";

let option = compose_call!(&metadata, pallet_name, non_existent_function);
assert!(option.is_none());
}

#[test]
fn macro_compose_call_works_for_valid_input() {
let encoded_metadata = fs::read("../ksm_metadata_v14.bin").unwrap();
let runtime_metadata_prefixed =
RuntimeMetadataPrefixed::decode(&mut encoded_metadata.as_slice()).unwrap();
let metadata = Metadata::try_from(runtime_metadata_prefixed).unwrap();

let pallet_name = "Balances";
let non_existent_function = "force_set_balance";
let extra_parameter = 10000;

let expected_call = ([4, 8], extra_parameter);
let call =
compose_call!(&metadata, pallet_name, non_existent_function, extra_parameter).unwrap();
assert_eq!(call, expected_call);
}
}
12 changes: 8 additions & 4 deletions examples/async/examples/check_extrinsic_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ async fn main() {
// So lets create an extrinsic that will not succeed:
// Alice tries so transfer all her balance, but that will not work, because
// she will not have enough balance left to pay the fees.
let bad_transfer_extrinsic =
api.balance_transfer_allow_death(bob.clone().into(), balance_of_alice).await;
let bad_transfer_extrinsic = api
.balance_transfer_allow_death(bob.clone().into(), balance_of_alice)
.await
.unwrap();
println!("[+] Composed extrinsic: {bad_transfer_extrinsic:?}\n",);

// Send and watch extrinsic until InBlock.
Expand Down Expand Up @@ -87,8 +89,10 @@ async fn main() {

// Next, we send an extrinsic that should succeed:
let balance_to_transfer = 1000;
let good_transfer_extrinsic =
api.balance_transfer_allow_death(bob.clone().into(), balance_to_transfer).await;
let good_transfer_extrinsic = api
.balance_transfer_allow_death(bob.clone().into(), balance_to_transfer)
.await
.unwrap();
// Send and watch extrinsic until InBlock.
let result = api
.submit_and_watch_extrinsic_until(good_transfer_extrinsic, XtStatus::InBlock)
Expand Down
3 changes: 2 additions & 1 deletion examples/async/examples/compose_extrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ async fn main() {
"transfer_allow_death",
recipients_extrinsic_address,
Compact(4u32)
);
)
.unwrap();
compose_extrinsic_offline!(extrinsic_signer, call, extrinsic_params)
};

Expand Down
5 changes: 3 additions & 2 deletions examples/async/examples/contract_instantiate_with_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ async fn main() {

let xt = api
.contract_instantiate_with_code(1_000_000_000_000_000, 500_000, wasm, vec![1u8], vec![1u8])
.await;
.await
.unwrap();

println!("[+] Creating a contract instance with extrinsic:\n\n{:?}\n", xt);
let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::InBlock).await.unwrap();
Expand All @@ -83,7 +84,7 @@ async fn main() {
let contract = contract_instantiated_events[0].contract.clone();
println!("[+] Event was received. Contract deployed at: {contract:?}\n");

let xt = api.contract_call(contract.into(), 500_000, 500_000, vec![0u8]).await;
let xt = api.contract_call(contract.into(), 500_000, 500_000, vec![0u8]).await.unwrap();

println!("[+] Calling the contract with extrinsic Extrinsic:\n{:?}\n\n", xt);
let report = api.submit_and_watch_extrinsic_until(xt, XtStatus::Finalized).await.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion examples/async/examples/get_account_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async fn main() {
};

let xt: UncheckedExtrinsicV4<_, _, _, _> =
compose_extrinsic!(&api, "Identity", "set_identity", Box::new(info.clone()));
compose_extrinsic!(&api, "Identity", "set_identity", Box::new(info.clone())).unwrap();
println!("[+] Composed Extrinsic:\n {:?}\n", xt);

// Send and watch extrinsic until InBlock.
Expand Down
3 changes: 2 additions & 1 deletion examples/async/examples/query_runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ async fn main() {

// Query the fee of an extrinsic.
let bob = AccountKeyring::Bob.to_account_id();
let balance_extrinsic = api.balance_transfer_allow_death(bob.clone().into(), 1000).await;
let balance_extrinsic =
api.balance_transfer_allow_death(bob.clone().into(), 1000).await.unwrap();
let extrinsic_fee_details = runtime_api
.query_fee_details(balance_extrinsic.clone(), 1000, None)
.await
Expand Down
4 changes: 2 additions & 2 deletions examples/async/examples/runtime_update_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ pub async fn send_code_update_extrinsic(
let new_wasm: &[u8] = include_bytes!("kitchensink_runtime.compact.compressed.wasm");

// this call can only be called by sudo
let call = compose_call!(api.metadata(), "System", "set_code", new_wasm.to_vec());
let call = compose_call!(api.metadata(), "System", "set_code", new_wasm.to_vec()).unwrap();
let weight: Weight = 0.into();
let xt = compose_extrinsic!(&api, "Sudo", "sudo_unchecked_weight", call, weight);
let xt = compose_extrinsic!(&api, "Sudo", "sudo_unchecked_weight", call, weight).unwrap();

println!("Sending extrinsic to trigger runtime update");
let block_hash = api
Expand Down
10 changes: 6 additions & 4 deletions examples/async/examples/staking_batch_payout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ async fn main() {
// error is returned from the node, the extrinsic has been created correctly.
// Sidenote: We could theoretically force a new era with sudo, but this takes at least 10 minutes ( = 1 epoch) in the
// kitchensink rutime. We don't want to wait that long.
let payout_staker_xt = api.payout_stakers(0, validator_stash).await;
let payout_staker_xt = api.payout_stakers(0, validator_stash).await.unwrap();
let result = api.submit_and_watch_extrinsic_until(payout_staker_xt, XtStatus::InBlock).await;
assert!(result.is_err());
assert!(format!("{result:?}").contains("InvalidEraToReward"));
Expand Down Expand Up @@ -123,16 +123,18 @@ async fn main() {

if exposure.total.to_be_bytes() > 0_u128.to_be_bytes() && is_grace_period_satisfied
{
let payout_extrinsic =
api.payout_stakers(payout_era_index, validator_account.clone()).await;
let payout_extrinsic = api
.payout_stakers(payout_era_index, validator_account.clone())
.await
.unwrap();
payout_calls.push(payout_extrinsic.function);
}
i += 1;
last_reward_received_at_era += 1;
}
num_of_claimed_payouts += payout_calls.len();
num_of_unclaimed_payouts -= tx_limit_in_current_batch;
let batch_xt = api.batch(payout_calls).await;
let batch_xt = api.batch(payout_calls).await.unwrap();

let report =
api.submit_and_watch_extrinsic_until(batch_xt, XtStatus::InBlock).await.unwrap();
Expand Down
Loading

0 comments on commit 763164c

Please sign in to comment.