miden_lib/note/
well_known_note.rs1use miden_objects::{
2 Digest, Felt,
3 account::AccountId,
4 assembly::{ProcedureName, QualifiedProcedureName},
5 block::BlockNumber,
6 note::{Note, NoteScript},
7 utils::{Deserializable, sync::LazyLock},
8 vm::Program,
9};
10
11use crate::account::{
12 components::basic_wallet_library,
13 interface::{AccountComponentInterface, AccountInterface, NoteAccountCompatibility},
14};
15
16static P2ID_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
21 let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/P2ID.masb"));
22 let program = Program::read_from_bytes(bytes).expect("Shipped P2ID script is well-formed");
23 NoteScript::new(program)
24});
25
26static P2IDE_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
28 let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/P2IDE.masb"));
29 let program = Program::read_from_bytes(bytes).expect("Shipped P2IDE script is well-formed");
30 NoteScript::new(program)
31});
32
33static SWAP_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
35 let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/SWAP.masb"));
36 let program = Program::read_from_bytes(bytes).expect("Shipped SWAP script is well-formed");
37 NoteScript::new(program)
38});
39
40fn p2id() -> NoteScript {
42 P2ID_SCRIPT.clone()
43}
44
45fn p2id_root() -> Digest {
47 P2ID_SCRIPT.root()
48}
49
50fn p2ide() -> NoteScript {
52 P2IDE_SCRIPT.clone()
53}
54
55fn p2ide_root() -> Digest {
57 P2IDE_SCRIPT.root()
58}
59
60fn swap() -> NoteScript {
62 SWAP_SCRIPT.clone()
63}
64
65fn swap_root() -> Digest {
67 SWAP_SCRIPT.root()
68}
69
70pub enum WellKnownNote {
75 P2ID,
76 P2IDE,
77 SWAP,
78}
79
80impl WellKnownNote {
81 const P2ID_NUM_INPUTS: usize = 2;
86
87 const P2IDE_NUM_INPUTS: usize = 4;
89
90 const SWAP_NUM_INPUTS: usize = 10;
92
93 pub fn from_note(note: &Note) -> Option<Self> {
99 let note_script_root = note.script().root();
100
101 if note_script_root == p2id_root() {
102 return Some(Self::P2ID);
103 }
104 if note_script_root == p2ide_root() {
105 return Some(Self::P2IDE);
106 }
107 if note_script_root == swap_root() {
108 return Some(Self::SWAP);
109 }
110
111 None
112 }
113
114 pub fn num_expected_inputs(&self) -> usize {
119 match self {
120 Self::P2ID => Self::P2ID_NUM_INPUTS,
121 Self::P2IDE => Self::P2IDE_NUM_INPUTS,
122 Self::SWAP => Self::SWAP_NUM_INPUTS,
123 }
124 }
125
126 pub fn script(&self) -> NoteScript {
128 match self {
129 Self::P2ID => p2id(),
130 Self::P2IDE => p2ide(),
131 Self::SWAP => swap(),
132 }
133 }
134
135 pub fn script_root(&self) -> Digest {
137 match self {
138 Self::P2ID => p2id_root(),
139 Self::P2IDE => p2ide_root(),
140 Self::SWAP => swap_root(),
141 }
142 }
143
144 pub fn is_compatible_with(&self, account_interface: &AccountInterface) -> bool {
147 if account_interface.components().contains(&AccountComponentInterface::BasicWallet) {
148 return true;
149 }
150
151 let interface_proc_digests = account_interface.get_procedure_digests();
152 match self {
153 Self::P2ID | &Self::P2IDE => {
154 let receive_asset_proc_name = QualifiedProcedureName::new(
158 Default::default(),
159 ProcedureName::new("receive_asset").unwrap(),
160 );
161 let node_id = basic_wallet_library().get_export_node_id(&receive_asset_proc_name);
162 let receive_asset_digest = basic_wallet_library().mast_forest()[node_id].digest();
163
164 interface_proc_digests.contains(&receive_asset_digest)
165 },
166 Self::SWAP => {
167 basic_wallet_library()
171 .mast_forest()
172 .procedure_digests()
173 .all(|proc_digest| interface_proc_digests.contains(&proc_digest))
174 },
175 }
176 }
177
178 pub fn check_note_inputs(
193 &self,
194 note: &Note,
195 target_account_id: AccountId,
196 block_ref: BlockNumber,
197 ) -> NoteAccountCompatibility {
198 match self {
199 WellKnownNote::P2ID => {
200 let note_inputs = note.inputs().values();
201 if note_inputs.len() != self.num_expected_inputs() {
202 return NoteAccountCompatibility::No;
203 }
204
205 let Some(input_account_id) = try_read_account_id_from_inputs(note_inputs) else {
207 return NoteAccountCompatibility::No;
208 };
209
210 if input_account_id == target_account_id {
212 NoteAccountCompatibility::Yes
213 } else {
214 NoteAccountCompatibility::No
215 }
216 },
217 WellKnownNote::P2IDE => {
218 let note_inputs = note.inputs().values();
219
220 if note_inputs.len() != self.num_expected_inputs() {
222 return NoteAccountCompatibility::No;
223 }
224
225 let Ok(timelock_height) = note_inputs[3].try_into() else {
227 return NoteAccountCompatibility::No;
228 };
229 if block_ref.as_u32() < timelock_height {
230 return NoteAccountCompatibility::No; }
232
233 let Some(input_account_id) = try_read_account_id_from_inputs(note_inputs) else {
235 return NoteAccountCompatibility::No;
236 };
237 let sender_account_id = note.metadata().sender();
238 let is_target = input_account_id == target_account_id;
239 let is_sender = target_account_id == sender_account_id;
240
241 if is_target {
242 NoteAccountCompatibility::Yes
244 } else if is_sender {
245 let Ok(reclaim_height) = note_inputs[2].try_into() else {
247 return NoteAccountCompatibility::No;
248 };
249 return if block_ref.as_u32() >= reclaim_height {
250 NoteAccountCompatibility::Yes
251 } else {
252 NoteAccountCompatibility::No
253 };
254 } else {
255 NoteAccountCompatibility::No
257 }
258 },
259
260 WellKnownNote::SWAP => {
261 if note.inputs().values().len() != self.num_expected_inputs() {
262 return NoteAccountCompatibility::No;
263 }
264
265 NoteAccountCompatibility::Maybe
266 },
267 }
268 }
269}
270
271fn try_read_account_id_from_inputs(note_inputs: &[Felt]) -> Option<AccountId> {
278 let account_id_felts: [Felt; 2] = note_inputs[0..2].try_into().expect(
279 "Should be able to convert the first two note inputs to an array of two Felt elements",
280 );
281
282 AccountId::try_from([account_id_felts[1], account_id_felts[0]]).ok()
283}