1extern crate alloc;
2
3use alloc::collections::{BTreeMap, BTreeSet};
4use alloc::vec;
5use alloc::vec::Vec;
6
7use miden_core::{Felt, Word};
8use miden_protocol::account::component::{AccountComponentCode, AccountComponentMetadata};
9use miden_protocol::account::{
10 Account,
11 AccountComponent,
12 AccountId,
13 AccountProcedureRoot,
14 RoleSymbol,
15 StorageMapKey,
16 StorageSlot,
17 StorageSlotName,
18};
19use miden_protocol::crypto::hash::poseidon2::Poseidon2;
20use miden_protocol::crypto::rand::FeltRng;
21use miden_protocol::errors::NoteError;
22use miden_protocol::note::{Note, NoteScriptRoot};
23use miden_standards::account::access::{PausableStorage, RoleConfig};
24use miden_standards::account::auth::AuthNetworkAccount;
25use miden_standards::note::{
26 ConstantFeePolicyConfigNote,
27 NetworkAccountTarget,
28 NetworkAccountTargetError,
29 NoteExecutionHint,
30 PauseConfig,
31 PauseConfigNote,
32 RbacConfigNote,
33};
34use miden_standards::procedure_root;
35use miden_utils_sync::LazyLock;
36use thiserror::Error;
37
38use super::agglayer_bridge_component_package;
39use crate::utils::Keccak256Output;
40
41pub type RemovedGerHashChain = Keccak256Output;
43pub use miden_standards::interop::eth::{
44 EthAddress,
45 EthAmount,
46 EthAmountError,
47 EthEmbeddedAccountId,
48};
49
50pub use crate::{
51 B2AggNote,
52 ClaimNote,
53 ClaimNoteStorage,
54 ConfigAggBridgeNote,
55 DeregisterAggFaucetNote,
56 ExitRoot,
57 GlobalIndex,
58 GlobalIndexError,
59 LeafData,
60 MetadataHash,
61 ProofData,
62 RemoveGerNote,
63 SmtNode,
64 UpdateGerNote,
65};
66
67include!(concat!(env!("OUT_DIR"), "/agglayer_constants.rs"));
71
72static GER_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
79 StorageSlotName::new("agglayer::bridge::ger_map")
80 .expect("GER map storage slot name should be valid")
81});
82static REMOVED_GER_HASH_CHAIN_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
83 StorageSlotName::new("agglayer::bridge::removed_ger_hash_chain_lo")
84 .expect("removed GER hash chain lo storage slot name should be valid")
85});
86static REMOVED_GER_HASH_CHAIN_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
87 StorageSlotName::new("agglayer::bridge::removed_ger_hash_chain_hi")
88 .expect("removed GER hash chain hi storage slot name should be valid")
89});
90static FAUCET_REGISTRY_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
91 StorageSlotName::new("agglayer::bridge::faucet_registry_map")
92 .expect("faucet registry map storage slot name should be valid")
93});
94static TOKEN_REGISTRY_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
95 StorageSlotName::new("agglayer::bridge::token_registry_map")
96 .expect("token registry map storage slot name should be valid")
97});
98static FAUCET_METADATA_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
99 StorageSlotName::new("agglayer::bridge::faucet_metadata_map")
100 .expect("faucet metadata map storage slot name should be valid")
101});
102static NETWORK_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
103 StorageSlotName::new("agglayer::bridge::network_id")
104 .expect("network ID storage slot name should be valid")
105});
106
107static CLAIM_NULLIFIERS_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
111 StorageSlotName::new("agglayer::bridge::claim_nullifiers")
112 .expect("claim nullifiers storage slot name should be valid")
113});
114static CGI_CHAIN_HASH_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
115 StorageSlotName::new("agglayer::bridge::cgi_chain_hash_lo")
116 .expect("CGI chain hash_lo storage slot name should be valid")
117});
118static CGI_CHAIN_HASH_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
119 StorageSlotName::new("agglayer::bridge::cgi_chain_hash_hi")
120 .expect("CGI chain hash_hi storage slot name should be valid")
121});
122
123static LET_FRONTIER_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
127 StorageSlotName::new("agglayer::bridge::let_frontier")
128 .expect("LET frontier storage slot name should be valid")
129});
130static LET_ROOT_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
131 StorageSlotName::new("agglayer::bridge::let_root_lo")
132 .expect("LET root_lo storage slot name should be valid")
133});
134static LET_ROOT_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
135 StorageSlotName::new("agglayer::bridge::let_root_hi")
136 .expect("LET root_hi storage slot name should be valid")
137});
138static LET_NUM_LEAVES_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
139 StorageSlotName::new("agglayer::bridge::let_num_leaves")
140 .expect("LET num_leaves storage slot name should be valid")
141});
142
143static FAUCET_MANAGER_ROLE: LazyLock<RoleSymbol> = LazyLock::new(|| {
147 RoleSymbol::new("FAUCET_MNGR").expect("FAUCET_MNGR role symbol should be valid")
148});
149static GER_INJECTOR_ROLE: LazyLock<RoleSymbol> = LazyLock::new(|| {
150 RoleSymbol::new("GER_INJECTOR").expect("GER_INJECTOR role symbol should be valid")
151});
152static GER_REMOVER_ROLE: LazyLock<RoleSymbol> = LazyLock::new(|| {
153 RoleSymbol::new("GER_REMOVER").expect("GER_REMOVER role symbol should be valid")
154});
155
156static BRIDGE_COMPONENT_CODE: LazyLock<AccountComponentCode> =
159 LazyLock::new(|| AccountComponentCode::from(agglayer_bridge_component_package()));
160
161procedure_root!(
162 REGISTER_FAUCET_ROOT,
163 AggLayerBridge::COMPONENT_NAMESPACE,
164 "register_faucet",
165 AggLayerBridge::code()
166);
167procedure_root!(
168 STORE_FAUCET_METADATA_HASH_ROOT,
169 AggLayerBridge::COMPONENT_NAMESPACE,
170 "store_faucet_metadata_hash",
171 AggLayerBridge::code()
172);
173procedure_root!(
174 UPDATE_GER_ROOT,
175 AggLayerBridge::COMPONENT_NAMESPACE,
176 "update_ger",
177 AggLayerBridge::code()
178);
179procedure_root!(
180 REMOVE_GER_ROOT,
181 AggLayerBridge::COMPONENT_NAMESPACE,
182 "remove_ger",
183 AggLayerBridge::code()
184);
185procedure_root!(
186 DEREGISTER_FAUCET_ROOT,
187 AggLayerBridge::COMPONENT_NAMESPACE,
188 "deregister_faucet",
189 AggLayerBridge::code()
190);
191
192#[derive(Debug, Clone, PartialEq, Eq)]
203pub struct BridgeRoles {
204 roles: Vec<RoleConfig>,
205}
206
207impl BridgeRoles {
208 pub fn new(
217 faucet_managers: BTreeSet<AccountId>,
218 ger_injectors: BTreeSet<AccountId>,
219 ger_removers: BTreeSet<AccountId>,
220 ) -> Result<Self, AgglayerBridgeError> {
221 let mut roles = Vec::new();
222 for (role, members) in [
223 (AggLayerBridge::faucet_manager_role(), &faucet_managers),
224 (AggLayerBridge::ger_injector_role(), &ger_injectors),
225 (AggLayerBridge::ger_remover_role(), &ger_removers),
226 ] {
227 if members.is_empty() {
228 return Err(AgglayerBridgeError::EmptyBridgeRole(role));
229 }
230 roles.push(RoleConfig::new(role).with_members(members.iter().copied()));
231 }
232
233 Ok(Self { roles })
234 }
235}
236
237impl IntoIterator for BridgeRoles {
238 type Item = RoleConfig;
239 type IntoIter = alloc::vec::IntoIter<RoleConfig>;
240
241 fn into_iter(self) -> Self::IntoIter {
242 self.roles.into_iter()
243 }
244}
245
246#[derive(Debug, Clone, Copy)]
302pub struct AggLayerBridge {
303 network_id: u32,
304}
305
306impl AggLayerBridge {
307 const COMPONENT_NAMESPACE: &'static str = "agglayer::components::bridge";
314
315 pub fn new(network_id: u32) -> Self {
323 Self { network_id }
324 }
325
326 pub fn code() -> &'static AccountComponentCode {
331 &BRIDGE_COMPONENT_CODE
332 }
333
334 pub fn faucet_manager_role() -> RoleSymbol {
337 FAUCET_MANAGER_ROLE.clone()
338 }
339
340 pub fn ger_injector_role() -> RoleSymbol {
342 GER_INJECTOR_ROLE.clone()
343 }
344
345 pub fn ger_remover_role() -> RoleSymbol {
347 GER_REMOVER_ROLE.clone()
348 }
349
350 pub fn register_faucet_root() -> AccountProcedureRoot {
352 *REGISTER_FAUCET_ROOT
353 }
354
355 pub fn store_faucet_metadata_hash_root() -> AccountProcedureRoot {
357 *STORE_FAUCET_METADATA_HASH_ROOT
358 }
359
360 pub fn update_ger_root() -> AccountProcedureRoot {
362 *UPDATE_GER_ROOT
363 }
364
365 pub fn remove_ger_root() -> AccountProcedureRoot {
367 *REMOVE_GER_ROOT
368 }
369
370 pub fn deregister_faucet_root() -> AccountProcedureRoot {
372 *DEREGISTER_FAUCET_ROOT
373 }
374
375 pub fn procedure_roles() -> BTreeMap<AccountProcedureRoot, RoleSymbol> {
379 BTreeMap::from([
380 (Self::register_faucet_root(), Self::faucet_manager_role()),
381 (Self::store_faucet_metadata_hash_root(), Self::faucet_manager_role()),
382 (Self::deregister_faucet_root(), Self::faucet_manager_role()),
383 (Self::update_ger_root(), Self::ger_injector_role()),
384 (Self::remove_ger_root(), Self::ger_remover_role()),
385 ])
386 }
387
388 pub fn ger_map_slot_name() -> &'static StorageSlotName {
395 &GER_MAP_SLOT_NAME
396 }
397
398 pub fn removed_ger_hash_chain_lo_slot_name() -> &'static StorageSlotName {
400 &REMOVED_GER_HASH_CHAIN_LO_SLOT_NAME
401 }
402
403 pub fn removed_ger_hash_chain_hi_slot_name() -> &'static StorageSlotName {
405 &REMOVED_GER_HASH_CHAIN_HI_SLOT_NAME
406 }
407
408 pub fn faucet_registry_map_slot_name() -> &'static StorageSlotName {
410 &FAUCET_REGISTRY_MAP_SLOT_NAME
411 }
412
413 pub fn token_registry_map_slot_name() -> &'static StorageSlotName {
415 &TOKEN_REGISTRY_MAP_SLOT_NAME
416 }
417
418 pub fn faucet_metadata_map_slot_name() -> &'static StorageSlotName {
423 &FAUCET_METADATA_MAP_SLOT_NAME
424 }
425
426 pub fn network_id_slot_name() -> &'static StorageSlotName {
431 &NETWORK_ID_SLOT_NAME
432 }
433
434 pub fn claim_nullifiers_slot_name() -> &'static StorageSlotName {
438 &CLAIM_NULLIFIERS_SLOT_NAME
439 }
440
441 pub fn cgi_chain_hash_lo_slot_name() -> &'static StorageSlotName {
443 &CGI_CHAIN_HASH_LO_SLOT_NAME
444 }
445
446 pub fn cgi_chain_hash_hi_slot_name() -> &'static StorageSlotName {
448 &CGI_CHAIN_HASH_HI_SLOT_NAME
449 }
450
451 pub fn let_frontier_slot_name() -> &'static StorageSlotName {
455 &LET_FRONTIER_SLOT_NAME
456 }
457
458 pub fn let_root_lo_slot_name() -> &'static StorageSlotName {
460 &LET_ROOT_LO_SLOT_NAME
461 }
462
463 pub fn let_root_hi_slot_name() -> &'static StorageSlotName {
465 &LET_ROOT_HI_SLOT_NAME
466 }
467
468 pub fn let_num_leaves_slot_name() -> &'static StorageSlotName {
470 &LET_NUM_LEAVES_SLOT_NAME
471 }
472
473 pub fn allowed_notes() -> BTreeSet<NoteScriptRoot> {
481 let mut notes = BTreeSet::from([
482 ClaimNote::script_root(),
483 B2AggNote::script_root(),
484 ConfigAggBridgeNote::script_root(),
485 DeregisterAggFaucetNote::script_root(),
486 UpdateGerNote::script_root(),
487 RemoveGerNote::script_root(),
488 PauseConfigNote::script_root(),
489 RbacConfigNote::script_root(),
490 ConstantFeePolicyConfigNote::script_root(),
491 ]);
492 notes.extend(AuthNetworkAccount::default_allowed_note_scripts());
493 notes
494 }
495
496 pub fn pause_note<R: FeltRng>(
509 config: PauseConfig,
510 sender: AccountId,
511 bridge_id: AccountId,
512 rng: &mut R,
513 ) -> Result<Note, AgglayerBridgeError> {
514 let attachment = NetworkAccountTarget::new(bridge_id, NoteExecutionHint::Always)
515 .map_err(AgglayerBridgeError::NonPublicPauseNoteTarget)?;
516
517 PauseConfigNote::builder()
518 .sender(sender)
519 .target(bridge_id)
520 .config(config)
521 .attachment(attachment)
522 .generate_serial_number(rng)
523 .build()
524 .map(Into::into)
525 .map_err(AgglayerBridgeError::PauseNoteCreationFailed)
526 }
527
528 const REGISTERED_GER_MAP_VALUE: Word =
532 Word::new([Felt::ONE, Felt::ZERO, Felt::ZERO, Felt::ZERO]);
533
534 pub fn is_ger_registered(
542 ger: ExitRoot,
543 bridge_account: &Account,
544 ) -> Result<bool, AgglayerBridgeError> {
545 Self::assert_bridge_account(bridge_account)?;
547
548 let ger_lower: Word = ger.to_elements()[0..4].try_into().unwrap();
550 let ger_upper: Word = ger.to_elements()[4..8].try_into().unwrap();
551 let ger_hash = Poseidon2::merge(&[ger_lower, ger_upper]);
552
553 let stored_value = bridge_account
556 .storage()
557 .get_map_item(AggLayerBridge::ger_map_slot_name(), StorageMapKey::from_raw(ger_hash))
558 .expect("provided account should have AggLayer Bridge specific storage slots");
559
560 if stored_value == Self::REGISTERED_GER_MAP_VALUE {
561 Ok(true)
562 } else {
563 Ok(false)
564 }
565 }
566
567 pub fn read_local_exit_root(account: &Account) -> Result<Vec<Felt>, AgglayerBridgeError> {
581 Self::assert_bridge_account(account)?;
583
584 let root_lo_slot = AggLayerBridge::let_root_lo_slot_name();
585 let root_hi_slot = AggLayerBridge::let_root_hi_slot_name();
586
587 let root_lo = account
588 .storage()
589 .get_item(root_lo_slot)
590 .expect("should be able to read LET root lo");
591 let root_hi = account
592 .storage()
593 .get_item(root_hi_slot)
594 .expect("should be able to read LET root hi");
595
596 let mut root = Vec::with_capacity(8);
597 root.extend(root_lo.to_vec());
598 root.extend(root_hi.to_vec());
599
600 Ok(root)
601 }
602
603 pub fn network_id(account: &Account) -> Result<u32, AgglayerBridgeError> {
610 Self::assert_bridge_account(account)?;
612
613 let value = account
614 .storage()
615 .get_item(AggLayerBridge::network_id_slot_name())
616 .expect("should be able to read the network ID");
617 let network_id = u32::try_from(value.to_vec()[0].as_canonical_u64())
618 .map_err(|_| AgglayerBridgeError::InvalidNetworkId)?;
619
620 Ok(network_id)
621 }
622
623 pub fn read_let_num_leaves(account: &Account) -> u64 {
625 let num_leaves_slot = AggLayerBridge::let_num_leaves_slot_name();
626 let value = account
627 .storage()
628 .get_item(num_leaves_slot)
629 .expect("should be able to read LET num leaves");
630 value.to_vec()[0].as_canonical_u64()
631 }
632
633 pub fn cgi_chain_hash(
640 bridge_account: &Account,
641 ) -> Result<crate::claim_note::CgiChainHash, AgglayerBridgeError> {
642 Self::assert_bridge_account(bridge_account)?;
644
645 let cgi_chain_hash_lo = bridge_account
646 .storage()
647 .get_item(AggLayerBridge::cgi_chain_hash_lo_slot_name())
648 .expect("failed to get CGI hash chain lo slot");
649 let cgi_chain_hash_hi = bridge_account
650 .storage()
651 .get_item(AggLayerBridge::cgi_chain_hash_hi_slot_name())
652 .expect("failed to get CGI hash chain hi slot");
653
654 Ok(crate::claim_note::CgiChainHash::new(Self::chain_hash_bytes(
655 cgi_chain_hash_lo,
656 cgi_chain_hash_hi,
657 )))
658 }
659
660 pub fn removed_ger_hash_chain(
670 bridge_account: &Account,
671 ) -> Result<RemovedGerHashChain, AgglayerBridgeError> {
672 Self::assert_bridge_account(bridge_account)?;
674
675 let chain_lo = bridge_account
676 .storage()
677 .get_item(AggLayerBridge::removed_ger_hash_chain_lo_slot_name())
678 .expect("failed to get removed GER hash chain lo slot");
679 let chain_hi = bridge_account
680 .storage()
681 .get_item(AggLayerBridge::removed_ger_hash_chain_hi_slot_name())
682 .expect("failed to get removed GER hash chain hi slot");
683
684 Ok(RemovedGerHashChain::new(Self::chain_hash_bytes(chain_lo, chain_hi)))
685 }
686
687 fn chain_hash_bytes(lo: Word, hi: Word) -> [u8; 32] {
692 lo.iter()
693 .chain(hi.iter())
694 .flat_map(|felt| {
695 (u32::try_from(felt.as_canonical_u64()).expect("Felt value does not fit into u32"))
696 .to_le_bytes()
697 })
698 .collect::<Vec<u8>>()
699 .try_into()
700 .expect("keccak hash should consist of exactly 32 bytes")
701 }
702
703 fn assert_bridge_account(account: &Account) -> Result<(), AgglayerBridgeError> {
712 Self::assert_storage_slots(account)?;
714
715 Self::assert_code_commitment(account)?;
717
718 Ok(())
719 }
720
721 fn assert_storage_slots(account: &Account) -> Result<(), AgglayerBridgeError> {
728 let account_storage_slot_names: Vec<&StorageSlotName> = account
730 .storage()
731 .slots()
732 .iter()
733 .map(|storage_slot| storage_slot.name())
734 .collect::<Vec<&StorageSlotName>>();
735
736 let are_slots_present = Self::slot_names()
738 .iter()
739 .all(|slot_name| account_storage_slot_names.contains(slot_name));
740 if !are_slots_present {
741 return Err(AgglayerBridgeError::StorageSlotsMismatch);
742 }
743
744 Ok(())
745 }
746
747 fn assert_code_commitment(account: &Account) -> Result<(), AgglayerBridgeError> {
756 if BRIDGE_CODE_COMMITMENT != account.code().commitment() {
757 return Err(AgglayerBridgeError::CodeCommitmentMismatch);
758 }
759
760 Ok(())
761 }
762
763 fn slot_names() -> Vec<&'static StorageSlotName> {
770 vec![
771 &*GER_MAP_SLOT_NAME,
772 &*LET_FRONTIER_SLOT_NAME,
773 &*LET_ROOT_LO_SLOT_NAME,
774 &*LET_ROOT_HI_SLOT_NAME,
775 &*LET_NUM_LEAVES_SLOT_NAME,
776 &*FAUCET_REGISTRY_MAP_SLOT_NAME,
777 &*TOKEN_REGISTRY_MAP_SLOT_NAME,
778 &*FAUCET_METADATA_MAP_SLOT_NAME,
779 &*REMOVED_GER_HASH_CHAIN_LO_SLOT_NAME,
780 &*REMOVED_GER_HASH_CHAIN_HI_SLOT_NAME,
781 &*CGI_CHAIN_HASH_LO_SLOT_NAME,
782 &*CGI_CHAIN_HASH_HI_SLOT_NAME,
783 &*CLAIM_NULLIFIERS_SLOT_NAME,
784 &*NETWORK_ID_SLOT_NAME,
785 PausableStorage::is_paused_slot(),
786 ]
787 }
788}
789
790impl From<AggLayerBridge> for AccountComponent {
791 fn from(bridge: AggLayerBridge) -> Self {
792 let bridge_storage_slots = vec![
793 StorageSlot::with_empty_map(GER_MAP_SLOT_NAME.clone()),
794 StorageSlot::with_empty_map(LET_FRONTIER_SLOT_NAME.clone()),
795 StorageSlot::with_value(LET_ROOT_LO_SLOT_NAME.clone(), Word::empty()),
796 StorageSlot::with_value(LET_ROOT_HI_SLOT_NAME.clone(), Word::empty()),
797 StorageSlot::with_value(LET_NUM_LEAVES_SLOT_NAME.clone(), Word::empty()),
798 StorageSlot::with_empty_map(FAUCET_REGISTRY_MAP_SLOT_NAME.clone()),
799 StorageSlot::with_empty_map(TOKEN_REGISTRY_MAP_SLOT_NAME.clone()),
800 StorageSlot::with_empty_map(FAUCET_METADATA_MAP_SLOT_NAME.clone()),
801 StorageSlot::with_value(REMOVED_GER_HASH_CHAIN_LO_SLOT_NAME.clone(), Word::empty()),
802 StorageSlot::with_value(REMOVED_GER_HASH_CHAIN_HI_SLOT_NAME.clone(), Word::empty()),
803 StorageSlot::with_value(CGI_CHAIN_HASH_LO_SLOT_NAME.clone(), Word::empty()),
804 StorageSlot::with_value(CGI_CHAIN_HASH_HI_SLOT_NAME.clone(), Word::empty()),
805 StorageSlot::with_empty_map(CLAIM_NULLIFIERS_SLOT_NAME.clone()),
806 StorageSlot::with_value(
807 NETWORK_ID_SLOT_NAME.clone(),
808 Word::new([Felt::from(bridge.network_id), Felt::ZERO, Felt::ZERO, Felt::ZERO]),
809 ),
810 ];
811 bridge_component(bridge_storage_slots)
812 }
813}
814
815#[derive(Debug, Error)]
820pub enum AgglayerBridgeError {
821 #[error(
822 "provided account does not have storage slots required for the AggLayer Bridge account"
823 )]
824 StorageSlotsMismatch,
825 #[error(
826 "the code commitment of the provided account does not match the code commitment of the AggLayer Bridge account"
827 )]
828 CodeCommitmentMismatch,
829 #[error("bridge role {0} must have at least one initial holder")]
830 EmptyBridgeRole(RoleSymbol),
831 #[error("the network ID stored in the bridge account does not fit into a u32")]
832 InvalidNetworkId,
833 #[error("bridge account must be public to be named by a network account target")]
834 NonPublicPauseNoteTarget(#[source] NetworkAccountTargetError),
835 #[error("failed to create a PAUSE_CONFIG note for the bridge account")]
836 PauseNoteCreationFailed(#[source] NoteError),
837}
838
839fn bridge_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
844 let package = agglayer_bridge_component_package();
845 let metadata = AccountComponentMetadata::new("agglayer::bridge")
846 .with_description("Bridge component for AggLayer");
847
848 AccountComponent::new(package, storage_slots, metadata)
849 .expect("bridge component should satisfy the requirements of a valid account component")
850}