use alloc::collections::BTreeSet;
use crate::account::AccountId;
use crate::account::code::AccountCode;
use crate::account::code::procedure::AccountProcedureRoot;
use crate::errors::AccountCodeInterfaceError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccountCodeInterface {
account_id: AccountId,
procedures: BTreeSet<AccountProcedureRoot>,
}
impl AccountCodeInterface {
pub fn new(
account_id: AccountId,
procedures: BTreeSet<AccountProcedureRoot>,
) -> Result<Self, AccountCodeInterfaceError> {
let count = procedures.len();
if count < AccountCode::MIN_NUM_PROCEDURES {
return Err(AccountCodeInterfaceError::TooFewProcedures { actual: count });
}
if count > AccountCode::MAX_NUM_PROCEDURES {
return Err(AccountCodeInterfaceError::TooManyProcedures { actual: count });
}
Ok(Self { account_id, procedures })
}
pub fn id(&self) -> AccountId {
self.account_id
}
pub fn procedures(&self) -> &BTreeSet<AccountProcedureRoot> {
&self.procedures
}
pub fn contains(&self, procedures: impl IntoIterator<Item = AccountProcedureRoot>) -> bool {
procedures
.into_iter()
.all(|procedure_root| self.procedures.contains(&procedure_root))
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use rstest::rstest;
use super::*;
use crate::Word;
use crate::account::{AccountBuilder, PartialAccount};
use crate::testing::add_component::AddComponent;
use crate::testing::noop_auth_component::NoopAuthComponent;
fn procedure(value: u32) -> AccountProcedureRoot {
AccountProcedureRoot::from_raw(Word::from([value, value, value, value]))
}
fn build_code_interface(values: &[u32]) -> AccountCodeInterface {
let procedures: BTreeSet<AccountProcedureRoot> =
values.iter().copied().map(procedure).collect();
AccountCodeInterface::new(dummy_account_id(), procedures).unwrap()
}
fn dummy_account_id() -> AccountId {
AccountId::try_from(
crate::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
)
.unwrap()
}
#[rstest]
#[case::empty(vec![])]
#[case::subset(vec![1, 2])]
#[case::exact(vec![1, 2, 3])]
fn contains_returns_true(#[case] query: Vec<u32>) {
let interface = build_code_interface(&[1, 2, 3]);
let query: Vec<AccountProcedureRoot> = query.into_iter().map(procedure).collect();
assert!(interface.contains(query));
}
#[rstest]
#[case::missing_one(vec![1, 4])]
#[case::all_missing(vec![10, 11])]
fn contains_returns_false(#[case] query: Vec<u32>) {
let interface = build_code_interface(&[1, 2, 3]);
let query: Vec<AccountProcedureRoot> = query.into_iter().map(procedure).collect();
assert!(!interface.contains(query));
}
#[test]
fn new_rejects_too_few_procedures() {
let procedures: BTreeSet<AccountProcedureRoot> = BTreeSet::new();
let err = AccountCodeInterface::new(dummy_account_id(), procedures).unwrap_err();
assert_matches::assert_matches!(
err,
AccountCodeInterfaceError::TooFewProcedures { actual: 0 }
);
let one_procedure: BTreeSet<AccountProcedureRoot> = [procedure(1)].into_iter().collect();
let err = AccountCodeInterface::new(dummy_account_id(), one_procedure).unwrap_err();
assert_matches::assert_matches!(
err,
AccountCodeInterfaceError::TooFewProcedures { actual: 1 }
);
}
#[test]
fn new_rejects_too_many_procedures() {
let procedures: BTreeSet<AccountProcedureRoot> =
(0..=AccountCode::MAX_NUM_PROCEDURES as u32).map(procedure).collect();
let expected = AccountCode::MAX_NUM_PROCEDURES + 1;
let err = AccountCodeInterface::new(dummy_account_id(), procedures).unwrap_err();
assert_matches::assert_matches!(
err,
AccountCodeInterfaceError::TooManyProcedures { actual } if actual == expected
);
}
#[test]
fn new_accepts_min_and_max_procedures() {
let min: BTreeSet<AccountProcedureRoot> =
(0..AccountCode::MIN_NUM_PROCEDURES as u32).map(procedure).collect();
AccountCodeInterface::new(dummy_account_id(), min).unwrap();
let max: BTreeSet<AccountProcedureRoot> =
(0..AccountCode::MAX_NUM_PROCEDURES as u32).map(procedure).collect();
AccountCodeInterface::new(dummy_account_id(), max).unwrap();
}
#[test]
fn account_code_interface_matches_code_procedures() -> anyhow::Result<()> {
let account = AccountBuilder::new([7; 32])
.with_auth_component(NoopAuthComponent)
.with_component(AddComponent)
.build()?;
let code_interface = account.code_interface();
let expected: BTreeSet<AccountProcedureRoot> =
account.code().procedures().iter().copied().collect();
assert_eq!(code_interface.id(), account.id());
assert_eq!(code_interface.procedures(), &expected);
Ok(())
}
#[test]
fn partial_account_code_interface_matches_code_procedures() -> anyhow::Result<()> {
let account = AccountBuilder::new([8; 32])
.with_auth_component(NoopAuthComponent)
.with_component(AddComponent)
.build()?;
let partial = PartialAccount::from(&account);
let code_interface = partial.code_interface();
let expected: BTreeSet<AccountProcedureRoot> =
partial.code().procedures().iter().copied().collect();
assert_eq!(code_interface.id(), partial.id());
assert_eq!(code_interface.procedures(), &expected);
Ok(())
}
}