Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macros: Return optional extrinsic instead of unwrapping #709

Merged
merged 7 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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