miden_protocol/account/interface/
code_interface.rs1use alloc::collections::BTreeSet;
2
3use crate::account::AccountId;
4use crate::account::code::AccountCode;
5use crate::account::code::procedure::AccountProcedureRoot;
6use crate::errors::AccountCodeInterfaceError;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct AccountCodeInterface {
15 account_id: AccountId,
16 procedures: BTreeSet<AccountProcedureRoot>,
17}
18
19impl AccountCodeInterface {
20 pub fn new(
28 account_id: AccountId,
29 procedures: BTreeSet<AccountProcedureRoot>,
30 ) -> Result<Self, AccountCodeInterfaceError> {
31 let count = procedures.len();
32 if count < AccountCode::MIN_NUM_PROCEDURES {
33 return Err(AccountCodeInterfaceError::TooFewProcedures { actual: count });
34 }
35 if count > AccountCode::MAX_NUM_PROCEDURES {
36 return Err(AccountCodeInterfaceError::TooManyProcedures { actual: count });
37 }
38 Ok(Self { account_id, procedures })
39 }
40
41 pub fn id(&self) -> AccountId {
43 self.account_id
44 }
45
46 pub fn procedures(&self) -> &BTreeSet<AccountProcedureRoot> {
48 &self.procedures
49 }
50
51 pub fn contains(&self, procedures: impl IntoIterator<Item = AccountProcedureRoot>) -> bool {
54 procedures
55 .into_iter()
56 .all(|procedure_root| self.procedures.contains(&procedure_root))
57 }
58}
59
60#[cfg(test)]
64mod tests {
65 use alloc::vec;
66 use alloc::vec::Vec;
67
68 use rstest::rstest;
69
70 use super::*;
71 use crate::Word;
72 use crate::account::{AccountBuilder, PartialAccount};
73 use crate::testing::add_component::AddComponent;
74 use crate::testing::noop_auth_component::NoopAuthComponent;
75
76 fn procedure(value: u32) -> AccountProcedureRoot {
77 AccountProcedureRoot::from_raw(Word::from([value, value, value, value]))
78 }
79
80 fn build_code_interface(values: &[u32]) -> AccountCodeInterface {
81 let procedures: BTreeSet<AccountProcedureRoot> =
82 values.iter().copied().map(procedure).collect();
83 AccountCodeInterface::new(dummy_account_id(), procedures).unwrap()
84 }
85
86 fn dummy_account_id() -> AccountId {
87 AccountId::try_from(
88 crate::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
89 )
90 .unwrap()
91 }
92
93 #[rstest]
94 #[case::empty(vec![])]
95 #[case::subset(vec![1, 2])]
96 #[case::exact(vec![1, 2, 3])]
97 fn contains_returns_true(#[case] query: Vec<u32>) {
98 let interface = build_code_interface(&[1, 2, 3]);
99 let query: Vec<AccountProcedureRoot> = query.into_iter().map(procedure).collect();
100 assert!(interface.contains(query));
101 }
102
103 #[rstest]
104 #[case::missing_one(vec![1, 4])]
105 #[case::all_missing(vec![10, 11])]
106 fn contains_returns_false(#[case] query: Vec<u32>) {
107 let interface = build_code_interface(&[1, 2, 3]);
108 let query: Vec<AccountProcedureRoot> = query.into_iter().map(procedure).collect();
109 assert!(!interface.contains(query));
110 }
111
112 #[test]
113 fn new_rejects_too_few_procedures() {
114 let procedures: BTreeSet<AccountProcedureRoot> = BTreeSet::new();
115 let err = AccountCodeInterface::new(dummy_account_id(), procedures).unwrap_err();
116 assert_matches::assert_matches!(
117 err,
118 AccountCodeInterfaceError::TooFewProcedures { actual: 0 }
119 );
120
121 let one_procedure: BTreeSet<AccountProcedureRoot> = [procedure(1)].into_iter().collect();
122 let err = AccountCodeInterface::new(dummy_account_id(), one_procedure).unwrap_err();
123 assert_matches::assert_matches!(
124 err,
125 AccountCodeInterfaceError::TooFewProcedures { actual: 1 }
126 );
127 }
128
129 #[test]
130 fn new_rejects_too_many_procedures() {
131 let procedures: BTreeSet<AccountProcedureRoot> =
132 (0..=AccountCode::MAX_NUM_PROCEDURES as u32).map(procedure).collect();
133 let expected = AccountCode::MAX_NUM_PROCEDURES + 1;
134 let err = AccountCodeInterface::new(dummy_account_id(), procedures).unwrap_err();
135 assert_matches::assert_matches!(
136 err,
137 AccountCodeInterfaceError::TooManyProcedures { actual } if actual == expected
138 );
139 }
140
141 #[test]
142 fn new_accepts_min_and_max_procedures() {
143 let min: BTreeSet<AccountProcedureRoot> =
144 (0..AccountCode::MIN_NUM_PROCEDURES as u32).map(procedure).collect();
145 AccountCodeInterface::new(dummy_account_id(), min).unwrap();
146
147 let max: BTreeSet<AccountProcedureRoot> =
148 (0..AccountCode::MAX_NUM_PROCEDURES as u32).map(procedure).collect();
149 AccountCodeInterface::new(dummy_account_id(), max).unwrap();
150 }
151
152 #[test]
153 fn account_code_interface_matches_code_procedures() -> anyhow::Result<()> {
154 let account = AccountBuilder::new([7; 32])
155 .with_auth_component(NoopAuthComponent)
156 .with_component(AddComponent)
157 .build()?;
158
159 let code_interface = account.code_interface();
160 let expected: BTreeSet<AccountProcedureRoot> =
161 account.code().procedures().iter().copied().collect();
162
163 assert_eq!(code_interface.id(), account.id());
164 assert_eq!(code_interface.procedures(), &expected);
165
166 Ok(())
167 }
168
169 #[test]
170 fn partial_account_code_interface_matches_code_procedures() -> anyhow::Result<()> {
171 let account = AccountBuilder::new([8; 32])
172 .with_auth_component(NoopAuthComponent)
173 .with_component(AddComponent)
174 .build()?;
175 let partial = PartialAccount::from(&account);
176
177 let code_interface = partial.code_interface();
178 let expected: BTreeSet<AccountProcedureRoot> =
179 partial.code().procedures().iter().copied().collect();
180
181 assert_eq!(code_interface.id(), partial.id());
182 assert_eq!(code_interface.procedures(), &expected);
183
184 Ok(())
185 }
186}