use crate::step_defs::{
integration::world::World,
util::{read_teal, wait_for_pending_transaction},
};
use algonaut::{
atomic_transaction_composer::{
transaction_signer::TransactionSigner, AbiArgValue, AbiMethodReturnValue,
AbiReturnDecodeError, AddMethodCallParams, AtomicTransactionComposer,
AtomicTransactionComposerStatus, TransactionWithSigner,
},
error::ServiceError,
};
use algonaut_abi::{
abi_interactions::{AbiArgType, AbiMethod, AbiReturn, AbiReturnType, ReferenceArgType},
abi_type::{AbiType, AbiValue},
};
use algonaut_core::{to_app_address, Address, MicroAlgos};
use algonaut_model::algod::v2::PendingTransaction;
use algonaut_transaction::{
builder::TxnFee,
transaction::{ApplicationCallOnComplete, StateSchema},
Pay, TxnBuilder,
};
use cucumber::{codegen::Regex, given, then, when};
use data_encoding::BASE64;
use num_traits::ToPrimitive;
use sha2::Digest;
use std::convert::TryInto;
use std::error::Error;
#[given(regex = r#"^I make a transaction signer for the ([^"]*) account\.$"#)]
#[when(regex = r#"^I make a transaction signer for the ([^"]*) account\.$"#)]
async fn i_make_a_transaction_signer_for_the_account(w: &mut World, account_str: String) {
let signer = TransactionSigner::BasicAccount(match account_str.as_ref() {
"transient" => w.transient_account.clone().unwrap(),
_ => panic!("Not handled account string: {}", account_str),
});
w.tx_signer = Some(signer);
}
#[given(expr = "a new AtomicTransactionComposer")]
async fn a_new_atomic_transaction_composer(w: &mut World) {
w.tx_composer = Some(AtomicTransactionComposer::default());
w.tx_composer_methods = Some(vec![]);
}
#[when(
regex = r#"^I build a payment transaction with sender "([^"]*)", receiver "([^"]*)", amount (\d+), close remainder to "([^"]*)"$"#
)]
#[given(
regex = r#"^I build a payment transaction with sender "([^"]*)", receiver "([^"]*)", amount (\d+), close remainder to "([^"]*)"$"#
)]
async fn i_build_a_payment_transaction_with_sender_receiver_amount_close_remainder_to(
w: &mut World,
sender_str: String,
receiver_str: String,
amount: u64,
close_to: String,
) {
let transient_account = w.transient_account.clone().unwrap();
let tx_params = w.tx_params.as_ref().unwrap();
let close_to = if close_to == "" {
None
} else {
Some(close_to.parse::
().unwrap())
};
let sender = if sender_str == "transient" {
transient_account.address()
} else {
panic!("sender_str not supported: {}", sender_str);
};
let receiver = if receiver_str == "transient" {
transient_account.address()
} else {
panic!("receiver_str not supported: {}", receiver_str);
};
let mut payment = Pay::new(sender, receiver, MicroAlgos(amount));
if let Some(close_to) = close_to {
payment = payment.close_remainder_to(close_to);
}
let tx = TxnBuilder::with(tx_params, payment.build())
.build()
.unwrap();
w.tx = Some(tx);
}
#[when(expr = "I create a transaction with signer with the current transaction.")]
#[given(expr = "I create a transaction with signer with the current transaction.")]
async fn i_create_a_transaction_with_signer_with_the_current_transaction(w: &mut World) {
let tx = w.tx.clone().unwrap();
let signer = w.tx_signer.clone().unwrap();
w.tx_with_signer = Some(TransactionWithSigner { tx, signer });
}
#[when(expr = "I add the current transaction with signer to the composer.")]
async fn i_add_the_current_transaction_with_signer_tothecomposer(w: &mut World) {
let tx_with_signer = w.tx_with_signer.clone().unwrap();
let tx_composer = w.tx_composer.as_mut().unwrap();
tx_composer.add_transaction(tx_with_signer).unwrap();
}
#[then(expr = "I gather signatures with the composer.")]
async fn i_gather_signatures_with_the_composer(w: &mut World) {
let tx_composer = w.tx_composer.as_mut().unwrap();
w.signed_txs = Some(tx_composer.gather_signatures().unwrap());
}
#[then(regex = r#"^The composer should have a status of "([^"]*)"\.$"#)]
async fn the_composer_should_have_a_status_of(w: &mut World, status_str: String) {
let tx_composer = w.tx_composer.as_mut().unwrap();
let status = match status_str.as_ref() {
"BUILDING" => AtomicTransactionComposerStatus::Building,
"BUILT" => AtomicTransactionComposerStatus::Built,
"SIGNED" => AtomicTransactionComposerStatus::Signed,
"SUBMITTED" => AtomicTransactionComposerStatus::Submitted,
"COMMITTED" => AtomicTransactionComposerStatus::Committed,
_ => panic!("Not handled status string: {}", status_str),
};
if status != tx_composer.status() {
panic!("status doesn't match");
}
}
#[then(expr = "I clone the composer.")]
async fn i_clone_the_composer(w: &mut World) {
let tx_composer = w.tx_composer.as_mut().unwrap();
w.tx_composer = Some(tx_composer.clone_composer());
}
#[when(regex = r#"I create the Method object from method signature "([^"]*)"$"#)]
#[given(regex = r#"I create the Method object from method signature "([^"]*)"$"#)]
async fn create_method_object_from_signature(w: &mut World, method_sig: String) {
let abi_method = AbiMethod::from_signature(&method_sig).unwrap();
w.abi_method = Some(abi_method);
}
#[given(expr = "I create a new method arguments array.")]
#[when(expr = "I create a new method arguments array.")]
async fn i_create_a_new_method_arguments_array(w: &mut World) {
let abi_method = w.abi_method.as_ref().unwrap();
let mut arg_types = vec![];
for mut arg_type in abi_method.args.clone() {
match arg_type.type_().expect("no type") {
AbiArgType::Tx(_) => continue,
AbiArgType::Ref(rf) => match rf {
ReferenceArgType::Account => arg_types.push(AbiType::address()),
_ => arg_types.push(AbiType::uint(64).expect("couldn't create int type")),
},
AbiArgType::AbiObj(obj) => {
arg_types.push(obj);
}
}
}
w.abi_method_arg_types = Some(arg_types);
// w.abi_method_args = Some(arg_types);
w.abi_method_arg_values = Some(vec![]);
}
#[given(regex = r#"I append the encoded arguments "([^"]*)" to the method arguments array.$"#)]
#[when(regex = r#"I append the encoded arguments "([^"]*)" to the method arguments array.$"#)]
async fn i_append_the_encoded_arguments_to_the_method_arguments_array(
w: &mut World,
comma_separated_b64_args: String,
) -> Result<(), Box> {
let application_ids: &[u64] = w.app_ids.as_ref();
let method_args = w.abi_method_arg_values.as_mut().expect("no method args");
let abi_method_arg_types = w
.abi_method_arg_types
.as_ref()
.expect("No method arg types");
if comma_separated_b64_args.is_empty() {
return Ok(());
}
let b64_args = comma_separated_b64_args.split(',');
for (arg_index, b64_arg) in b64_args.into_iter().enumerate() {
if b64_arg.contains(':') {
let parts: Vec<&str> = b64_arg.split(':').collect();
if parts.len() != 2 || parts[0] != "ctxAppIdx" {
panic!("Cannot process argument: {}", b64_arg);
}
let parsed_index = parts[1].parse::().unwrap();
if parsed_index >= application_ids.len() {
panic!(
"Application index out of bounds: {}, number of app IDs is {}",
parsed_index,
application_ids.len()
);
}
let arg = AbiValue::Int(application_ids[parsed_index].into());
method_args.push(AbiArgValue::AbiValue(arg));
} else {
let base64_decoded_arg = BASE64.decode(b64_arg.as_bytes()).unwrap();
let decoded = abi_method_arg_types[arg_index].decode(&base64_decoded_arg)?;
method_args.push(AbiArgValue::AbiValue(decoded));
}
}
Ok(())
}
#[when(
regex = r#"^I add a method call with the ([^"]*) account, the current application, suggested params, on complete "([^"]*)", current transaction signer, current method arguments.$"#
)]
#[given(
regex = r#"^I add a method call with the ([^"]*) account, the current application, suggested params, on complete "([^"]*)", current transaction signer, current method arguments.$"#
)]
async fn i_add_a_method_call(w: &mut World, account_type: String, on_complete: String) {
add_method_call(
w,
account_type,
on_complete,
None,
None,
None,
None,
None,
None,
None,
false,
)
.await;
}
#[when(
regex = r#"^I add a method call with the ([^"]*) account, the current application, suggested params, on complete "([^"]*)", current transaction signer, current method arguments, approval-program "([^"]*)", clear-program "([^"]*)"\.$"#
)]
async fn i_add_a_method_call_for_update(
w: &mut World,
account_type: String,
on_complete: String,
approval_program: String,
clear_program: String,
) {
add_method_call(
w,
account_type,
on_complete,
Some(approval_program),
Some(clear_program),
None,
None,
None,
None,
None,
false,
)
.await;
}
#[when(
regex = r#"^I add a method call with the ([^"]*) account, the current application, suggested params, on complete "([^"]*)", current transaction signer, current method arguments, approval-program "([^"]*)", clear-program "([^"]*)", global-bytes (\d+), global-ints (\d+), local-bytes (\d+), local-ints (\d+), extra-pages (\d+)\.$"#
)]
async fn i_add_a_method_call_for_create(
w: &mut World,
account_type: String,
on_complete: String,
approval_program: String,
clear_program: String,
global_bytes: u64,
global_ints: u64,
local_bytes: u64,
local_ints: u64,
extra_pages: u32,
) {
add_method_call(
w,
account_type,
on_complete,
Some(approval_program),
Some(clear_program),
Some(global_bytes),
Some(global_ints),
Some(local_bytes),
Some(local_ints),
Some(extra_pages),
false,
)
.await;
}
#[given(
regex = r#"^I add a nonced method call with the ([^"]*) account, the current application, suggested params, on complete "([^"]*)", current transaction signer, current method arguments\.$"#
)]
async fn i_add_method_call_with_nonce(w: &mut World, account_type: String, on_complete: String) {
add_method_call(
w,
account_type,
on_complete,
None,
None,
None,
None,
None,
None,
None,
true,
)
.await;
}
async fn add_method_call(
w: &mut World,
account_type: String,
on_complete: String,
approval_program: Option,
clear_program: Option,
global_bytes: Option,
global_ints: Option,
local_bytes: Option,
local_ints: Option,
extra_pages: Option,
use_nonce: bool,
) {
let algod = w.algod.as_ref().unwrap();
let transient_account = w.transient_account.clone().unwrap();
let abi_method = w.abi_method.as_ref().unwrap();
let abi_method_args = w.abi_method_arg_values.as_mut().unwrap();
let application_id = w.app_id.clone().unwrap();
let tx_params = w.tx_params.clone().unwrap();
let tx_signer = w.tx_signer.clone().unwrap();
let tx_composer = w.tx_composer.as_mut().unwrap();
let tx_composer_methods = w.tx_composer_methods.as_mut().unwrap();
let extra_pages = extra_pages.unwrap_or(0);
let global_schema = match (global_ints, global_bytes) {
(Some(ints), Some(bytes)) => Some(StateSchema {
number_ints: ints,
number_byteslices: bytes,
}),
_ => None,
};
let local_schema = match (local_ints, local_bytes) {
(Some(ints), Some(bytes)) => Some(StateSchema {
number_ints: ints,
number_byteslices: bytes,
}),
_ => None,
};
let on_complete = match on_complete.as_ref() {
"crate" | "noop" | "call" => ApplicationCallOnComplete::NoOp,
"update" => ApplicationCallOnComplete::UpdateApplication,
"optin" => ApplicationCallOnComplete::OptIn,
"clear" => ApplicationCallOnComplete::ClearState,
"closeout" => ApplicationCallOnComplete::CloseOut,
"delete" => ApplicationCallOnComplete::DeleteApplication,
_ => panic!("invalid onComplete value"),
};
let use_account = match account_type.as_ref() {
"transient" => transient_account,
_ => panic!("Not handled account string: {}", account_type),
};
let approval = match approval_program {
Some(p) => Some(read_teal(algod, &p).await),
None => None,
};
let clear = match clear_program {
Some(p) => Some(read_teal(algod, &p).await),
None => None,
};
// populate args from methodArgs
if abi_method_args.len() != abi_method.args.len() {
panic!(
"Provided argument count is incorrect. Expected {}, got {}",
abi_method_args.len(),
abi_method.args.len()
);
};
let note_opt = if use_nonce {
Some(
w.note
.clone()
.expect("note should be set if using use_nonce"),
)
} else {
None
};
let mut params = AddMethodCallParams {
app_id: application_id,
method: abi_method.to_owned(),
method_args: abi_method_args.to_owned(),
fee: TxnFee::Estimated {
fee_per_byte: tx_params.fee_per_byte,
min_fee: tx_params.min_fee,
},
sender: use_account.address(),
suggested_params: tx_params,
on_complete,
approval_program: approval,
clear_program: clear,
global_schema,
local_schema,
extra_pages,
note: note_opt,
lease: None,
rekey_to: None,
signer: tx_signer,
};
tx_composer_methods.push(abi_method.to_owned());
tx_composer.add_method_call(&mut params).unwrap();
}
#[given(regex = r#"I add the nonce "([^"]*)"$"#)]
fn i_add_the_nonce(w: &mut World, nonce: String) {
w.note = Some(
format!("I should be unique thanks to this nonce: {nonce}")
.as_bytes()
.to_vec(),
);
}
#[given(expr = "I append the current transaction with signer to the method arguments array.")]
#[when(expr = "I append the current transaction with signer to the method arguments array.")]
fn i_append_the_current_transaction_with_signer_to_the_method_arguments_array(w: &mut World) {
let method_args = w.abi_method_arg_values.as_mut().expect("no method args");
let tx_with_signer = w.tx_with_signer.clone().expect("no tx signer");
method_args.push(AbiArgValue::TxWithSigner(tx_with_signer));
}
#[when(
regex = r#"^I build the transaction group with the composer. If there is an error it is "([^"]*)".$"#
)]
#[then(
regex = r#"^I build the transaction group with the composer. If there is an error it is "([^"]*)".$"#
)]
#[given(
regex = r#"^I build the transaction group with the composer. If there is an error it is "([^"]*)".$"#
)]
fn i_build_the_transaction_group_with_the_composer(w: &mut World, error_type: String) {
let tx_composer = w.tx_composer.as_mut().unwrap();
let build_res = tx_composer.build_group();
match error_type.as_ref() {
"" => {
// no error expected
build_res.unwrap();
}
"zero group size error" => {
let message = match build_res {
Ok(_) => None,
Err(e) => match e {
ServiceError::Msg(m) => Some(m),
_ => None,
},
};
match message.as_deref() {
Some("attempting to build group with zero transactions") => {}
_ => panic!("expected error, but got: {:?}", message),
}
}
_ => panic!("Unknown error type: {}", error_type),
}
}
#[then(expr = "I execute the current transaction group with the composer.")]
async fn i_execute_the_current_transaction_group_with_the_composer(w: &mut World) {
let algod = w.algod.as_ref().unwrap();
let tx_composer = w.tx_composer.as_mut().unwrap();
let res = tx_composer.execute(algod).await;
let res = res.expect("Failed executing");
w.tx_composer_res = Some(res)
}
#[then(regex = r#"^The app should have returned "([^"]*)"\.$"#)]
async fn the_app_should_have_returned(w: &mut World, comma_separated_b64_results: String) {
let tx_composer_res = w.tx_composer_res.as_ref().unwrap();
let tx_composer_methods = w.tx_composer_methods.as_ref().unwrap();
let b64_expected_results: Vec<&str> = comma_separated_b64_results.split(',').collect();
if b64_expected_results.len() != tx_composer_res.method_results.len() {
panic!(
"length of expected results doesn't match actual: {:?} != {}",
b64_expected_results,
tx_composer_res.method_results.len()
);
}
if tx_composer_methods.len() != tx_composer_res.method_results.len() {
panic!(
"length of composer's methods doesn't match results: {:?} != {}",
tx_composer_methods,
tx_composer_res.method_results.len()
);
}
for (i, b64_expected_result) in b64_expected_results.into_iter().enumerate() {
let expected_res_bytes = BASE64
.decode(b64_expected_result.as_bytes())
.expect("couldn't decode b64");
match &tx_composer_res.method_results[i].return_value {
Ok(AbiMethodReturnValue::Some(value)) => {
let mut method = tx_composer_methods[i].clone();
match method.returns.type_().expect("error retrieving type") {
AbiReturnType::Some(type_) => {
let expected_value = type_
.decode(&expected_res_bytes)
.expect("the expected value doesn't match the actual result");
assert_eq!(&expected_value, value);
}
AbiReturnType::Void => panic!("unexpected void return type"),
}
}
Ok(AbiMethodReturnValue::Void) => {
if !expected_res_bytes.is_empty() {
panic!("Expected result should be empty")
}
}
Err(AbiReturnDecodeError(e)) => panic!("decode error: {:?}", e),
}
}
}
#[then(regex = r#"^The app should have returned ABI types "([^"]*)"\.$"#)]
async fn the_app_should_have_returned_abi_types(w: &mut World, expected_type_strings_str: String) {
let tx_composer_res = w.tx_composer_res.as_ref().unwrap();
let expected_type_strings: Vec<&str> = expected_type_strings_str.split(':').collect();
if expected_type_strings.len() != tx_composer_res.method_results.len() {
panic!(
"length of expected results doesn't match actual: {} != {}",
expected_type_strings.len(),
tx_composer_res.method_results.len()
);
}
for (i, expected_type_string) in expected_type_strings.into_iter().enumerate() {
let actual_res = &tx_composer_res.method_results[i];
match &actual_res.return_value {
Ok(AbiMethodReturnValue::Some(value)) => {
let expected_type = expected_type_string.parse::().unwrap();
let encoded = expected_type
.encode(value.clone())
.expect("couldn't encode value");
let decoded = expected_type
.decode(&encoded)
.expect("couldn't decode value");
assert_eq!(
&decoded, value,
"The round trip result does not match the original result"
)
}
Ok(AbiMethodReturnValue::Void) => {
if !AbiReturn::is_void_str(expected_type_string) {
panic!("Not a void return type: {:?}", actual_res.return_value);
}
}
Err(e) => {
panic!("Decode error: {:?}", e)
}
}
}
}
// The 1th atomic result for randomInt(1337) proves correct
// #[then(regex = r#"^The (\d+)th atomic result for randomInt\((\d+)\) proves correct$"#)]
#[then(regex = r#"^The (\d+)th atomic result for randomInt\((\d+)\) proves correct$"#)]
async fn check_random_int_result(w: &mut World, result_index: usize, input: u64) {
let tx_composers = w.tx_composer_res.as_ref().expect("No tx composer res");
let tx_composer_res = &tx_composers.method_results[result_index];
let value = match &tx_composer_res.return_value {
Ok(AbiMethodReturnValue::Some(value)) => value,
_ => panic!("No decoded res"),
};
let (rand_int, witness) = match value {
AbiValue::Array(array) => match &array[0] {
AbiValue::Int(i) => match &array[1] {
AbiValue::Array(nested_array) => (
i.to_u64().expect("couldn't convert bigint to int"),
nested_array.clone(),
),
_ => panic!("nested abi value isn't an array"),
},
_ => panic!("nested abi value isn't an int"),
},
_ => panic!("abi value isn't an array"),
};
let mut witness_bytes = vec![];
for (_, value) in witness.into_iter().enumerate() {
witness_bytes.push(match value {
AbiValue::Byte(b) => b,
_ => panic!("abi value isn't a byte"),
});
}
let x = sha2::Sha512_256::digest(&witness_bytes);
let int = u64::from_be_bytes(x[..8].try_into().expect("couldn't get slice from hash"));
let quotient = int % input as u64;
if quotient != rand_int {
panic!(
"Unexpected result: quotient is {} and randInt is {}",
quotient, rand_int
);
}
}
#[then(regex = r#"^The (\d+)th atomic result for randElement\("([^"]*)"\) proves correct$"#)]
async fn check_random_element_result(w: &mut World, result_index: usize, input: String) {
let tx_composers = w.tx_composer_res.as_ref().expect("No tx composer res");
let tx_composer_res = &tx_composers.method_results[result_index];
let value = match &tx_composer_res.return_value {
Ok(value) => match value {
AbiMethodReturnValue::Some(value) => value,
AbiMethodReturnValue::Void => panic!("No decoded res"),
},
_ => panic!("No decoded res"),
};
let (rand_el, witness) = match value {
AbiValue::Array(array) => match &array[0] {
AbiValue::Byte(b) => match &array[1] {
AbiValue::Array(nested_array) => (b.clone(), nested_array.clone()),
_ => panic!("nested abi value isn't an array"),
},
_ => panic!("nested abi value isn't an byte"),
},
_ => panic!("abi value isn't an array"),
};
let mut witness_bytes = vec![];
for (_, value) in witness.into_iter().enumerate() {
witness_bytes.push(match value {
AbiValue::Byte(b) => b,
_ => panic!(),
});
}
let x = sha2::Sha512_256::digest(&witness_bytes);
let int = usize::from_be_bytes(x[..8].try_into().expect("couldn't get slice from hash"));
let quotient = int % input.len();
if input.as_bytes()[quotient] != rand_el {
panic!(
"Unexpected result: quotient is {} and randInt is {}",
quotient, rand_el
);
}
}
#[then(
regex = r#"^I dig into the paths "([^"]*)" of the resulting atomic transaction tree I see group ids and they are all the same$"#
)]
async fn check_inner_txn_group_ids(w: &mut World, colon_separated_paths_string: String) {
let tx_composer_res = w.tx_composer_res.as_ref().expect("No tx composer res");
let mut paths: Vec> = vec![];
let comma_separated_path_strings = colon_separated_paths_string.split(':');
for comma_separated_path_string in comma_separated_path_strings {
let path_of_strings = comma_separated_path_string.split(',');
let mut path = vec![];
for string_component in path_of_strings {
let int_component = string_component.parse().unwrap();
path.push(int_component)
}
paths.push(path)
}
let mut tx_infos_to_check = vec![];
for path in paths {
let mut current: PendingTransaction = tx_composer_res.method_results[0].tx_info.clone();
for path_index in 1..path.len() {
let inner_txn_index = path[path_index];
if path_index == 0 {
current = tx_composer_res.method_results[inner_txn_index]
.tx_info
.clone();
} else {
current = current.inner_txs[inner_txn_index].clone();
}
}
tx_infos_to_check.push(current);
}
// TODO https://github.com/manuelmauro/algonaut/issues/156
// let mut group;
// for (i, txInfo) in txInfosToCheck.into_iter().enumerate() {
// if i == 0 {
// group = txInfo.txn.group
// }
// if group != txInfo.txn.group {
// panic!("Group hashes differ: {} != {}", group, txInfo.txn.group);
// }
// }
}
#[then(
regex = r#"^I can dig the (\d+)th atomic result with path "([^"]*)" and see the value "([^"]*)"$"#
)]
async fn check_atomic_result_against_value(
_w: &mut World,
_result_index: u64,
_path: String,
_expected_value: String,
) {
// TODO https://github.com/manuelmauro/algonaut/issues/156
}
#[given(regex = r#"^an application id (\d+)$"#)]
async fn an_application_id(w: &mut World, app_id: u64) {
w.app_id = Some(app_id);
}
#[then(regex = r#"^The (\d+)th atomic result for "([^"]*)" satisfies the regex "([^"]*)"$"#)]
async fn check_spin_result(w: &mut World, result_index: usize, method: String, r: String) {
let tx_composer_res = w.tx_composer_res.as_ref().expect("No tx composer res");
if method != "spin()" {
panic!("Incorrect method name, expected 'spin()', got '{}'", method);
}
let result = &tx_composer_res.method_results[result_index];
let decoded_result = match &result.return_value {
Ok(AbiMethodReturnValue::Some(value)) => match value {
AbiValue::Array(array) => array,
_ => panic!("return value isn't an array"),
},
_ => panic!("unexpected return value: {:?}", result.return_value),
};
let spin = match &decoded_result[0] {
AbiValue::Array(array) => array,
_ => panic!("first spin element isn't an array"),
};
let mut spin_bytes = vec![];
for value in spin {
spin_bytes.push(match value {
AbiValue::Byte(b) => *b,
_ => panic!("non-byte in spin array"),
});
}
let regex: Regex = Regex::new(&r).expect(&format!("couldn't create regex for: {}", r));
let str = String::from_utf8(spin_bytes).expect("couldn't convert bytes to string");
let matched = regex.is_match(&str);
if !matched {
panic!("Result did not match regex. spin str: {}", str);
}
}
#[given(regex = r#"^I fund the current application's address with (\d+) microalgos\.$"#)]
async fn i_fund_the_current_applications_address(w: &mut World, micro_algos: u64) {
let algod = w.algod.as_ref().expect("no algod");
let app_id = w.app_id.expect("no app id");
let accounts = w.accounts.as_ref().expect("no accounts");
let kmd = w.kmd.as_ref().expect("no kmd");
let kmd_handle = w.handle.as_ref().expect("no kmd handle");
let kmd_pw = w.password.as_ref().expect("no kmd pw");
let first_account = accounts[0];
let app_address = to_app_address(app_id);
let tx_params = algod
.suggested_transaction_params()
.await
.expect("couldn't get params");
let tx = TxnBuilder::with(
&tx_params,
Pay::new(first_account, app_address, MicroAlgos(micro_algos)).build(),
)
.build()
.unwrap();
let signed_tx = kmd
.sign_transaction(kmd_handle, kmd_pw, &tx)
.await
.expect("couldn't sign tx");
let res = algod
.broadcast_raw_transaction(&signed_tx.signed_transaction)
.await
.expect("couldn't send tx");
let _ = wait_for_pending_transaction(algod, &res.tx_id);
}
#[given(regex = r#"^I reset the array of application IDs to remember\.$"#)]
async fn i_reset_the_array_of_application_ids_to_remember(w: &mut World) {
w.app_ids = vec![];
}