miden_objects/account/builder/
mod.rs1use alloc::{boxed::Box, vec::Vec};
2
3use vm_core::FieldElement;
4use vm_processor::Digest;
5
6use crate::{
7 AccountError, Felt, Word,
8 account::{
9 Account, AccountCode, AccountComponent, AccountId, AccountIdAnchor, AccountIdV0,
10 AccountIdVersion, AccountStorage, AccountStorageMode, AccountType,
11 },
12 asset::AssetVault,
13};
14
15#[derive(Debug, Clone)]
43pub struct AccountBuilder {
44 #[cfg(any(feature = "testing", test))]
45 assets: Vec<crate::asset::Asset>,
46 components: Vec<AccountComponent>,
47 account_type: AccountType,
48 storage_mode: AccountStorageMode,
49 id_anchor: Option<AccountIdAnchor>,
50 init_seed: [u8; 32],
51 id_version: AccountIdVersion,
52}
53
54impl AccountBuilder {
55 pub fn new(init_seed: [u8; 32]) -> Self {
60 Self {
61 #[cfg(any(feature = "testing", test))]
62 assets: vec![],
63 components: vec![],
64 id_anchor: None,
65 init_seed,
66 account_type: AccountType::RegularAccountUpdatableCode,
67 storage_mode: AccountStorageMode::Private,
68 id_version: AccountIdVersion::Version0,
69 }
70 }
71
72 pub fn anchor(mut self, anchor: AccountIdAnchor) -> Self {
74 self.id_anchor = Some(anchor);
75 self
76 }
77
78 pub fn version(mut self, version: AccountIdVersion) -> Self {
80 self.id_version = version;
81 self
82 }
83
84 pub fn account_type(mut self, account_type: AccountType) -> Self {
86 self.account_type = account_type;
87 self
88 }
89
90 pub fn storage_mode(mut self, storage_mode: AccountStorageMode) -> Self {
92 self.storage_mode = storage_mode;
93 self
94 }
95
96 pub fn with_component(mut self, account_component: impl Into<AccountComponent>) -> Self {
101 self.components.push(account_component.into());
102 self
103 }
104
105 fn build_inner(&self) -> Result<(AssetVault, AccountCode, AccountStorage), AccountError> {
107 #[cfg(any(feature = "testing", test))]
108 let vault = AssetVault::new(&self.assets).map_err(|err| {
109 AccountError::BuildError(format!("asset vault failed to build: {err}"), None)
110 })?;
111
112 #[cfg(all(not(feature = "testing"), not(test)))]
113 let vault = AssetVault::default();
114
115 let (code, storage) =
116 Account::initialize_from_components(self.account_type, &self.components).map_err(
117 |err| {
118 AccountError::BuildError(
119 "account components failed to build".into(),
120 Some(Box::new(err)),
121 )
122 },
123 )?;
124
125 Ok((vault, code, storage))
126 }
127
128 fn grind_account_id(
130 &self,
131 init_seed: [u8; 32],
132 version: AccountIdVersion,
133 code_commitment: Digest,
134 storage_commitment: Digest,
135 block_commitment: Digest,
136 ) -> Result<Word, AccountError> {
137 let seed = AccountIdV0::compute_account_seed(
138 init_seed,
139 self.account_type,
140 self.storage_mode,
141 version,
142 code_commitment,
143 storage_commitment,
144 block_commitment,
145 )
146 .map_err(|err| {
147 AccountError::BuildError("account seed generation failed".into(), Some(Box::new(err)))
148 })?;
149
150 Ok(seed)
151 }
152
153 pub fn build(self) -> Result<(Account, Word), AccountError> {
168 let (vault, code, storage) = self.build_inner()?;
169
170 let id_anchor = self
171 .id_anchor
172 .ok_or_else(|| AccountError::BuildError("anchor must be set".into(), None))?;
173
174 #[cfg(any(feature = "testing", test))]
175 if !vault.is_empty() {
176 return Err(AccountError::BuildError(
177 "account asset vault must be empty on new accounts".into(),
178 None,
179 ));
180 }
181
182 let seed = self.grind_account_id(
183 self.init_seed,
184 self.id_version,
185 code.commitment(),
186 storage.commitment(),
187 id_anchor.block_commitment(),
188 )?;
189
190 let account_id = AccountId::new(
191 seed,
192 id_anchor,
193 AccountIdVersion::Version0,
194 code.commitment(),
195 storage.commitment(),
196 )
197 .expect("get_account_seed should provide a suitable seed");
198
199 debug_assert_eq!(account_id.account_type(), self.account_type);
200 debug_assert_eq!(account_id.storage_mode(), self.storage_mode);
201
202 let account = Account::from_parts(account_id, vault, storage, code, Felt::ZERO);
203
204 Ok((account, seed))
205 }
206}
207
208#[cfg(any(feature = "testing", test))]
209impl AccountBuilder {
210 pub fn with_assets<I: IntoIterator<Item = crate::asset::Asset>>(mut self, assets: I) -> Self {
215 self.assets.extend(assets);
216 self
217 }
218
219 pub fn build_existing(self) -> Result<Account, AccountError> {
225 let (vault, code, storage) = self.build_inner()?;
226
227 let account_id = {
228 let bytes = <[u8; 15]>::try_from(&self.init_seed[0..15])
229 .expect("we should have sliced exactly 15 bytes off");
230 AccountId::dummy(
231 bytes,
232 AccountIdVersion::Version0,
233 self.account_type,
234 self.storage_mode,
235 )
236 };
237
238 Ok(Account::from_parts(account_id, vault, storage, code, Felt::ONE))
239 }
240}
241
242#[cfg(test)]
246mod tests {
247 use std::sync::LazyLock;
248
249 use assembly::{Assembler, Library};
250 use assert_matches::assert_matches;
251 use vm_core::FieldElement;
252
253 use super::*;
254 use crate::{account::StorageSlot, block::BlockNumber};
255
256 const CUSTOM_CODE1: &str = "
257 export.foo
258 push.2.2 add eq.4
259 end
260 ";
261 const CUSTOM_CODE2: &str = "
262 export.bar
263 push.4.4 add eq.8
264 end
265 ";
266
267 static CUSTOM_LIBRARY1: LazyLock<Library> = LazyLock::new(|| {
268 Assembler::default()
269 .assemble_library([CUSTOM_CODE1])
270 .expect("code should be valid")
271 });
272 static CUSTOM_LIBRARY2: LazyLock<Library> = LazyLock::new(|| {
273 Assembler::default()
274 .assemble_library([CUSTOM_CODE2])
275 .expect("code should be valid")
276 });
277
278 struct CustomComponent1 {
279 slot0: u64,
280 }
281 impl From<CustomComponent1> for AccountComponent {
282 fn from(custom: CustomComponent1) -> Self {
283 let mut value = Word::default();
284 value[0] = Felt::new(custom.slot0);
285
286 AccountComponent::new(CUSTOM_LIBRARY1.clone(), vec![StorageSlot::Value(value)])
287 .expect("component should be valid")
288 .with_supports_all_types()
289 }
290 }
291
292 struct CustomComponent2 {
293 slot0: u64,
294 slot1: u64,
295 }
296 impl From<CustomComponent2> for AccountComponent {
297 fn from(custom: CustomComponent2) -> Self {
298 let mut value0 = Word::default();
299 value0[3] = Felt::new(custom.slot0);
300 let mut value1 = Word::default();
301 value1[3] = Felt::new(custom.slot1);
302
303 AccountComponent::new(
304 CUSTOM_LIBRARY2.clone(),
305 vec![StorageSlot::Value(value0), StorageSlot::Value(value1)],
306 )
307 .expect("component should be valid")
308 .with_supports_all_types()
309 }
310 }
311
312 #[test]
313 fn account_builder() {
314 let storage_slot0 = 25;
315 let storage_slot1 = 12;
316 let storage_slot2 = 42;
317
318 let anchor_block_commitment = Digest::new([Felt::new(42); 4]);
319 let anchor_block_number = 1 << 16;
320 let id_anchor =
321 AccountIdAnchor::new(BlockNumber::from(anchor_block_number), anchor_block_commitment)
322 .unwrap();
323
324 let (account, seed) = Account::builder([5; 32])
325 .anchor(id_anchor)
326 .with_component(CustomComponent1 { slot0: storage_slot0 })
327 .with_component(CustomComponent2 {
328 slot0: storage_slot1,
329 slot1: storage_slot2,
330 })
331 .build()
332 .unwrap();
333
334 assert_eq!(account.nonce(), Felt::ZERO);
336
337 let computed_id = AccountId::new(
338 seed,
339 id_anchor,
340 AccountIdVersion::Version0,
341 account.code.commitment(),
342 account.storage.commitment(),
343 )
344 .unwrap();
345 assert_eq!(account.id(), computed_id);
346
347 assert_eq!(account.code.procedure_roots().count(), 2);
349
350 let foo_root = CUSTOM_LIBRARY1.mast_forest()
351 [CUSTOM_LIBRARY1.get_export_node_id(CUSTOM_LIBRARY1.exports().next().unwrap())]
352 .digest();
353 let bar_root = CUSTOM_LIBRARY2.mast_forest()
354 [CUSTOM_LIBRARY2.get_export_node_id(CUSTOM_LIBRARY2.exports().next().unwrap())]
355 .digest();
356
357 let foo_procedure_info = &account
358 .code()
359 .procedures()
360 .iter()
361 .find(|info| info.mast_root() == &foo_root)
362 .unwrap();
363 assert_eq!(foo_procedure_info.storage_offset(), 0);
364 assert_eq!(foo_procedure_info.storage_size(), 1);
365
366 let bar_procedure_info = &account
367 .code()
368 .procedures()
369 .iter()
370 .find(|info| info.mast_root() == &bar_root)
371 .unwrap();
372 assert_eq!(bar_procedure_info.storage_offset(), 1);
373 assert_eq!(bar_procedure_info.storage_size(), 2);
374
375 assert_eq!(
376 account.storage().get_item(0).unwrap(),
377 [Felt::new(storage_slot0), Felt::new(0), Felt::new(0), Felt::new(0)].into()
378 );
379 assert_eq!(
380 account.storage().get_item(1).unwrap(),
381 [Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(storage_slot1)].into()
382 );
383 assert_eq!(
384 account.storage().get_item(2).unwrap(),
385 [Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(storage_slot2)].into()
386 );
387 }
388
389 #[test]
390 fn account_builder_non_empty_vault_on_new_account() {
391 let storage_slot0 = 25;
392
393 let anchor = AccountIdAnchor::new_unchecked(5, Digest::default());
394 let build_error = Account::builder([0xff; 32])
395 .anchor(anchor)
396 .with_component(CustomComponent1 { slot0: storage_slot0 })
397 .with_assets(AssetVault::mock().assets())
398 .build()
399 .unwrap_err();
400
401 assert_matches!(build_error, AccountError::BuildError(msg, _) if msg == "account asset vault must be empty on new accounts")
402 }
403
404 }