miden_standards/tx_script/expiration_script.rs
1use core::num::NonZeroU16;
2
3use miden_protocol::transaction::{TransactionScript, TransactionScriptRoot};
4use miden_protocol::utils::sync::LazyLock;
5use miden_protocol::{Felt, Word};
6
7use crate::tx_script::transaction_script;
8
9// CONSTANTS
10// ================================================================================================
11
12/// Path to the expiration transaction script procedure in the standards library, assembled from
13/// `asm/standards/tx_scripts/expiration.masm`.
14const EXPIRATION_TX_SCRIPT_PATH: &str = "::miden::standards::tx_scripts::expiration::main";
15
16// EXPIRATION TRANSACTION SCRIPT
17// ================================================================================================
18
19static EXPIRATION_TX_SCRIPT: LazyLock<TransactionScript> =
20 LazyLock::new(|| transaction_script(EXPIRATION_TX_SCRIPT_PATH));
21
22/// The canonical transaction script that sets the transaction's expiration delta to the value
23/// supplied in the first element of `TX_SCRIPT_ARGS`.
24///
25/// This is the standard tx script a network account allowlists so that the network transaction
26/// builder can bound how long a submitted transaction stays valid. Because the delta is an
27/// input rather than hardcoded, the single [`ExpirationTransactionScript::script_root`] covers
28/// every delta. It is safe to allowlist on an open network account even though an arbitrary
29/// submitter controls the argument: the delta only bounds the inclusion window of the submitter's
30/// own transaction - it cannot touch the account's nonce, state, or assets - and the kernel
31/// hard-caps it at `0xFFFF` blocks. So the only thing the submitter decides is how soon their own
32/// transaction must be included before it expires, within that fixed bound.
33///
34/// The type pairs the script (via [`From<ExpirationTransactionScript>`]) with the matching
35/// `TX_SCRIPT_ARGS` ([`ExpirationTransactionScript::tx_script_args`]), so callers do not assemble
36/// the argument word by hand:
37///
38/// ```ignore
39/// let script = ExpirationTransactionScript::new(delta);
40/// let tx_args = TransactionArgs::new(AdviceMap::default())
41/// .with_tx_script_and_args(script.into(), script.tx_script_args());
42/// ```
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct ExpirationTransactionScript {
45 delta: NonZeroU16,
46}
47
48impl ExpirationTransactionScript {
49 /// Creates an expiration script that sets the transaction's expiration block delta to `delta`.
50 ///
51 /// `delta` is a [`NonZeroU16`] because the kernel's `update_expiration_block_delta` only
52 /// accepts a delta in `1..=0xFFFF` and otherwise fails the transaction with
53 /// `ERR_TX_INVALID_EXPIRATION_DELTA`. Encoding that bound in the type keeps this constructor
54 /// infallible and guarantees the delta produced by [`Self::tx_script_args`] is always in
55 /// range.
56 pub fn new(delta: NonZeroU16) -> Self {
57 Self { delta }
58 }
59
60 /// The `TX_SCRIPT_ARGS` word the script reads its delta from: `[delta, 0, 0, 0]`.
61 ///
62 /// Since `delta` is a [`NonZeroU16`], this word always carries an in-range delta, so the
63 /// script never triggers the kernel's range check. A caller that bypasses this type and
64 /// hand-crafts an out-of-range first element makes the kernel reject the transaction with
65 /// `ERR_TX_INVALID_EXPIRATION_DELTA`; it does not panic the host.
66 pub fn tx_script_args(&self) -> Word {
67 Word::from([Felt::from(self.delta.get()), Felt::ZERO, Felt::ZERO, Felt::ZERO])
68 }
69
70 /// The [`TransactionScriptRoot`] of the canonical script, allowlisted on a network account by
71 /// default via `AuthNetworkAccount::new`.
72 pub fn script_root() -> TransactionScriptRoot {
73 EXPIRATION_TX_SCRIPT.root()
74 }
75}
76
77impl From<ExpirationTransactionScript> for TransactionScript {
78 fn from(_script: ExpirationTransactionScript) -> Self {
79 EXPIRATION_TX_SCRIPT.clone()
80 }
81}