miden_standards/note/
upgrade.rs1use alloc::vec::Vec;
2
3use miden_protocol::Word;
4use miden_protocol::account::{AccountCode, AccountCodeUpgrade, AccountId};
5use miden_protocol::assembly::Path;
6use miden_protocol::crypto::rand::FeltRng;
7use miden_protocol::errors::NoteError;
8use miden_protocol::note::{
9 Note,
10 NoteAssets,
11 NoteAttachment,
12 NoteAttachments,
13 NoteRecipient,
14 NoteScript,
15 NoteScriptRoot,
16 NoteStorage,
17 NoteTag,
18 NoteType,
19 PartialNoteMetadata,
20};
21use miden_protocol::utils::sync::LazyLock;
22
23use crate::StandardsLib;
24use crate::note::costs::{NoteConsumptionCost, UPGRADE_CONSUMPTION_CYCLES};
25use crate::note::{AccountCodeUpgradeAttachment, NetworkAccountTarget};
26
27const UPGRADE_SCRIPT_PATH: &str = "::miden::standards::notes::upgrade::main";
32
33static UPGRADE_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
35 let standards_lib = StandardsLib::default();
36 let path = Path::new(UPGRADE_SCRIPT_PATH);
37 NoteScript::from_package_reference(standards_lib.as_ref(), path)
38 .expect("Standards library contains UPGRADE note script procedure")
39});
40
41#[derive(Debug, Clone)]
61pub struct UpgradeNote {
62 sender: AccountId,
63 target: AccountId,
64 new_code_commitment: Word,
65 serial_number: Word,
66 attachments: NoteAttachments,
67}
68
69#[bon::bon]
70impl UpgradeNote {
71 #[builder]
84 pub fn new(
85 #[builder(field)] mut attachments: Vec<NoteAttachment>,
86 sender: AccountId,
87 target: AccountId,
88 code: AccountCode,
89 serial_number: Word,
90 ) -> Result<Self, NoteError> {
91 let code_upgrade = AccountCodeUpgrade::new(code);
92 let new_code_commitment = code_upgrade.commitment();
93 AccountCodeUpgradeAttachment::ensure_presence(&mut attachments, code_upgrade)?;
94
95 NetworkAccountTarget::ensure_presence(&mut attachments, target).map_err(|err| {
97 NoteError::other_with_source(
98 "failed to bind the upgrade note to its target account",
99 err,
100 )
101 })?;
102
103 let attachments = NoteAttachments::new(attachments)?;
104
105 Ok(Self {
106 sender,
107 target,
108 new_code_commitment,
109 serial_number,
110 attachments,
111 })
112 }
113}
114
115impl UpgradeNote {
116 pub const NUM_STORAGE_ITEMS: usize = Word::NUM_ELEMENTS;
123
124 pub fn script() -> NoteScript {
129 UPGRADE_SCRIPT.clone()
130 }
131
132 pub fn script_root() -> NoteScriptRoot {
134 UPGRADE_SCRIPT.root()
135 }
136
137 pub fn sender(&self) -> AccountId {
139 self.sender
140 }
141
142 pub fn target(&self) -> AccountId {
144 self.target
145 }
146
147 pub fn new_code_commitment(&self) -> Word {
149 self.new_code_commitment
150 }
151
152 pub fn serial_number(&self) -> Word {
154 self.serial_number
155 }
156
157 pub fn attachments(&self) -> &NoteAttachments {
159 &self.attachments
160 }
161}
162
163impl<S: upgrade_note_builder::State> UpgradeNoteBuilder<S> {
167 pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
169 self.attachments.push(attachment.into());
170 self
171 }
172
173 pub fn attachments(
175 mut self,
176 attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
177 ) -> Self {
178 self.attachments.extend(attachments.into_iter().map(Into::into));
179 self
180 }
181}
182
183impl<S: upgrade_note_builder::State> UpgradeNoteBuilder<S>
184where
185 S::SerialNumber: upgrade_note_builder::IsUnset,
186{
187 pub fn generate_serial_number(
189 self,
190 rng: &mut impl FeltRng,
191 ) -> UpgradeNoteBuilder<upgrade_note_builder::SetSerialNumber<S>> {
192 self.serial_number(rng.draw_word())
193 }
194}
195
196impl From<UpgradeNote> for Note {
200 fn from(note: UpgradeNote) -> Self {
201 let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
204 .with_tag(NoteTag::with_account_target(note.target));
205 let storage = NoteStorage::new(note.new_code_commitment.as_elements().to_vec())
206 .expect("number of storage items should not exceed max storage items");
207 let recipient = NoteRecipient::new(note.serial_number, UpgradeNote::script(), storage);
208
209 Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
210 }
211}
212
213impl NoteConsumptionCost for UpgradeNote {
217 fn consumption_cycles() -> u32 {
218 UPGRADE_CONSUMPTION_CYCLES
219 }
220}
221
222#[cfg(test)]
226mod tests {
227 use assert_matches::assert_matches;
228 use miden_protocol::account::AccountType;
229 use miden_protocol::crypto::rand::RandomCoin;
230
231 use super::*;
232 use crate::testing::account_component::{IncrNonceAuthComponent, MockProceduresComponent};
233
234 fn account_id(seed: u8) -> AccountId {
235 AccountId::builder()
236 .account_type(AccountType::Public)
237 .build_with_seed([seed; 32])
238 }
239
240 fn build_upgrade_note(target: AccountId, code: AccountCode) -> Result<UpgradeNote, NoteError> {
241 UpgradeNote::builder()
242 .sender(account_id(2))
243 .target(target)
244 .code(code)
245 .serial_number(Word::empty())
246 .build()
247 }
248
249 fn code_with_procedures(num_procedures: usize) -> anyhow::Result<AccountCode> {
251 Ok(AccountCode::from_components(&[
252 IncrNonceAuthComponent.into(),
253 MockProceduresComponent::new(num_procedures).into(),
254 ])?)
255 }
256
257 #[test]
260 fn builder_builds_upgrade_note() -> anyhow::Result<()> {
261 let mut rng = RandomCoin::new(Word::empty());
262 let target = account_id(1);
263 let sender = account_id(2);
264 let code = AccountCode::mock();
265
266 let note = UpgradeNote::builder()
267 .sender(sender)
268 .target(target)
269 .code(code.clone())
270 .generate_serial_number(&mut rng)
271 .build()?;
272
273 assert_eq!(note.sender(), sender);
274 assert_eq!(note.target(), target);
275 assert_eq!(note.new_code_commitment(), code.commitment());
276
277 let note = Note::from(note);
278 assert_eq!(note.metadata().note_type(), NoteType::Public);
279 assert_eq!(note.metadata().tag(), NoteTag::with_account_target(target));
280 assert_eq!(note.assets().num_assets(), 0);
281 assert_eq!(note.storage().items(), code.commitment().as_elements());
282 assert_eq!(note.storage().items().len(), UpgradeNote::NUM_STORAGE_ITEMS);
283
284 Ok(())
285 }
286
287 #[rstest::rstest]
290 #[case::single_chunk(1)]
291 #[case::two_chunks(200)]
292 fn note_carries_target_and_code_attachments(
293 #[case] num_procedures: usize,
294 ) -> anyhow::Result<()> {
295 let target = account_id(1);
296 let code = code_with_procedures(num_procedures)?;
297 let note = Note::from(build_upgrade_note(target, code.clone())?);
298
299 let network_target = NetworkAccountTarget::try_from(note.attachments())?;
300 assert_eq!(network_target.target_id(), target);
301
302 let code_upgrade = AccountCodeUpgradeAttachment::try_from_attachments(note.attachments())?;
303 assert_eq!(code_upgrade.code_upgrade().code(), &code);
304
305 Ok(())
306 }
307
308 #[test]
310 fn too_large_code_is_rejected() -> anyhow::Result<()> {
311 let code = code_with_procedures(AccountCode::MAX_NUM_PROCEDURES - 1)?;
312 let result = build_upgrade_note(account_id(1), code);
313
314 assert_matches!(result, Err(NoteError::NoteAttachmentsTooManyWords(_)));
315
316 Ok(())
317 }
318}