use std::collections::HashMap;
use algonaut_abi::{
abi_interactions::{AbiArgType, AbiMethod, ReferenceArgType, TransactionArgType},
abi_type::{AbiType, AbiValue},
make_tuple_type,
};
use algonaut_core::{Address, AppId, AssetId};
use algonaut_transaction::{
Transaction, TransactionType,
transaction::{ApplicationCallTransaction, to_tx_type_enum},
};
use crate::Error;
use super::{AbiArgValue, MAX_ATOMIC_GROUP_SIZE, MethodCall, TransactionWithSigner};
const MAX_ABI_ARG_TYPE_LEN: usize = 15;
const FOREIGN_OBJ_ABI_UINT_SIZE: usize = 8;
pub(super) fn process_method_call(
call: MethodCall,
txs: &mut Vec<TransactionWithSigner>,
method_map: &mut HashMap<usize, AbiMethod>,
) -> Result<(), Error> {
let MethodCall {
app_id,
mut method,
method_args,
fee,
sender,
first_valid,
last_valid,
genesis_hash,
genesis_id,
on_complete,
approval_program,
clear_program,
global_schema,
local_schema,
extra_pages,
note,
lease,
rekey_to,
signer,
boxes,
} = call;
if method_args.len() != method.args.len() {
return Err(Error::AbiArgumentCountMismatch {
expected: method.args.len(),
actual: method_args.len(),
});
}
if txs.len() + method.get_tx_count() > MAX_ATOMIC_GROUP_SIZE {
return Err(Error::ComposerGroupFull {
max: MAX_ATOMIC_GROUP_SIZE,
});
}
let mut foreign = ForeignArrays::default();
let mut args = EncodedArgs::default();
let mut tx_args: Vec<TransactionWithSigner> = Vec::new();
for (arg_spec, arg_value) in method.args.iter_mut().zip(method_args) {
match arg_spec.type_()? {
AbiArgType::Tx(expected_type) => {
let tx_with_signer = match arg_value {
AbiArgValue::TxWithSigner(tx_with_signer) => *tx_with_signer,
AbiArgValue::AbiValue(_) => return Err(Error::ExpectedTransactionArgument),
};
validate_transaction(&tx_with_signer.transaction, expected_type)?;
tx_args.push(tx_with_signer);
}
AbiArgType::Ref(ref_type) => {
let index = foreign.add_ref(&ref_type, &arg_value, sender, app_id)?;
args.push_ref_index(index)?;
}
AbiArgType::AbiObj(abi_type) => match arg_value {
AbiArgValue::AbiValue(value) => args.push(abi_type, value),
AbiArgValue::TxWithSigner(_) => {
return Err(Error::InvalidAbiArgument {
expected: "ABI value",
actual: "transaction".to_owned(),
});
}
},
}
}
args.wrap_overflow()?;
let app_arguments = args.encode(method.get_selector()?)?;
let ForeignArrays {
accounts,
assets,
apps,
} = foreign;
let app_call = TransactionType::ApplicationCallTransaction(ApplicationCallTransaction {
sender,
app_id: Some(app_id),
on_complete,
accounts: Some(accounts),
approval_program,
app_arguments: Some(app_arguments),
clear_state_program: clear_program,
foreign_apps: Some(apps),
foreign_assets: Some(assets),
global_state_schema: global_schema,
local_state_schema: local_schema,
extra_pages,
boxes: (!boxes.is_empty()).then_some(boxes),
});
let transaction = Transaction {
fee,
first_valid,
genesis_hash,
last_valid,
txn_type: app_call,
genesis_id: Some(genesis_id),
group: None,
lease,
note: (!note.is_empty()).then_some(note),
rekey_to,
};
txs.append(&mut tx_args);
txs.push(TransactionWithSigner {
transaction,
signer: Some(signer),
});
method_map.insert(txs.len() - 1, method);
Ok(())
}
pub(super) fn validate_transaction(
transaction: &Transaction,
expected_type: TransactionArgType,
) -> Result<(), Error> {
if transaction.group.is_some() {
return Err(Error::TransactionAlreadyGrouped);
}
let actual_type = to_tx_type_enum(&transaction.txn_type);
if expected_type != TransactionArgType::Any
&& expected_type != TransactionArgType::One(actual_type.clone())
{
return Err(Error::TransactionTypeMismatch {
expected: format!("{expected_type:?}"),
actual: format!("{actual_type:?}"),
});
}
Ok(())
}
#[derive(Default)]
struct ForeignArrays {
accounts: Vec<Address>,
assets: Vec<AssetId>,
apps: Vec<AppId>,
}
impl ForeignArrays {
fn add_ref(
&mut self,
arg_type: &ReferenceArgType,
arg_value: &AbiArgValue,
sender: Address,
app_id: AppId,
) -> Result<usize, Error> {
match arg_type {
ReferenceArgType::Account => {
let address = Address::try_from(arg_value)?;
Ok(populate_foreign_array(
address,
&mut self.accounts,
Some(sender),
))
}
ReferenceArgType::Asset => {
let asset_id = u64::try_from(arg_value)?;
Ok(populate_foreign_array(
AssetId(asset_id),
&mut self.assets,
None,
))
}
ReferenceArgType::Application => {
let referenced_app = u64::try_from(arg_value)?;
Ok(populate_foreign_array(
AppId(referenced_app),
&mut self.apps,
Some(app_id),
))
}
}
}
}
#[derive(Default)]
struct EncodedArgs {
types: Vec<AbiType>,
values: Vec<AbiValue>,
}
impl EncodedArgs {
fn push(&mut self, ty: AbiType, value: AbiValue) {
self.types.push(ty);
self.values.push(value);
}
fn push_ref_index(&mut self, index: usize) -> Result<(), Error> {
self.types.push(AbiType::uint(FOREIGN_OBJ_ABI_UINT_SIZE)?);
self.values.push(AbiValue::Int(index.into()));
Ok(())
}
fn wrap_overflow(&mut self) -> Result<(), Error> {
if self.values.len() <= MAX_ABI_ARG_TYPE_LEN {
return Ok(());
}
let split_at = MAX_ABI_ARG_TYPE_LEN - 1;
let wrapped_types = self.types.split_off(split_at);
let wrapped_values = self.values.split_off(split_at);
let tuple_type = make_tuple_type(&wrapped_types)?;
self.types.push(tuple_type);
self.values.push(AbiValue::Array(wrapped_values));
Ok(())
}
fn encode(self, selector: [u8; 4]) -> Result<Vec<Vec<u8>>, Error> {
let mut encoded = Vec::with_capacity(self.values.len() + 1);
encoded.push(selector.to_vec());
for (ty, value) in self.types.iter().zip(self.values) {
encoded.push(ty.encode(value)?);
}
Ok(encoded)
}
}
fn populate_foreign_array<T: Eq>(
obj_to_add: T,
obj_array: &mut Vec<T>,
zeroth_obj: Option<T>,
) -> usize {
if let Some(o) = &zeroth_obj
&& &obj_to_add == o
{
return 0;
}
let start_from: usize = zeroth_obj.map(|_| 1).unwrap_or(0);
let search_in_vec_index = obj_array.iter().position(|o| o == &obj_to_add);
if let Some(index) = search_in_vec_index {
start_from + index
} else {
obj_array.push(obj_to_add);
obj_array.len() - 1 + start_from
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrap_overflow_packs_args_past_15_into_a_trailing_tuple() {
let mut args = EncodedArgs::default();
for i in 0..16u8 {
args.push(AbiType::Byte, AbiValue::Byte(i));
}
args.wrap_overflow().unwrap();
assert_eq!(args.types.len(), 15, "must be 14 direct args + 1 tuple");
assert_eq!(args.values.len(), 15);
for (i, value) in args.values.iter().take(14).enumerate() {
assert_eq!(*value, AbiValue::Byte(i as u8));
}
match (&args.types[14], &args.values[14]) {
(AbiType::Tuple { len, child_types }, AbiValue::Array(items)) => {
assert_eq!(*len, 2);
assert_eq!(child_types.len(), 2);
assert_eq!(items, &vec![AbiValue::Byte(14), AbiValue::Byte(15)]);
}
other => panic!("expected a trailing 2-tuple, got {other:?}"),
}
}
#[test]
fn wrap_overflow_leaves_15_or_fewer_args_untouched() {
let mut args = EncodedArgs::default();
for i in 0..15u8 {
args.push(AbiType::Byte, AbiValue::Byte(i));
}
args.wrap_overflow().unwrap();
assert_eq!(args.values.len(), 15);
assert!(
args.values.iter().all(|v| matches!(v, AbiValue::Byte(_))),
"no tuple should be introduced at the boundary"
);
}
}