Skip to main content

audit_trails/core/locking/
transactions.rs

1// Copyright 2020-2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Transaction payloads for locking updates.
5
6use async_trait::async_trait;
7use iota_interaction::OptionalSync;
8use iota_interaction::rpc_types::IotaTransactionBlockEffects;
9use iota_sdk_types::{Address, ObjectId, ProgrammableTransaction};
10use product_common::core_client::CoreClientReadOnly;
11use product_common::transaction::transaction_builder::Transaction;
12use tokio::sync::OnceCell;
13
14use super::operations::LockingOps;
15use crate::core::types::{LockingConfig, LockingWindow, TimeLock};
16use crate::error::Error;
17
18/// Transaction that replaces the full locking configuration.
19///
20/// Requires the `UpdateLockingConfig` permission. The new `delete_trail_lock` must not be
21/// [`TimeLock::UntilDestroyed`]; the Move package aborts otherwise. This writes the full
22/// `LockingConfig` object and therefore updates all locking dimensions in one call.
23///
24/// On success a `LockingConfigUpdated` event is emitted.
25#[derive(Debug, Clone)]
26pub struct UpdateLockingConfig {
27    trail_id: ObjectId,
28    owner: Address,
29    config: LockingConfig,
30    selected_capability_id: Option<ObjectId>,
31    cached_ptb: OnceCell<ProgrammableTransaction>,
32}
33
34impl UpdateLockingConfig {
35    /// Creates an `UpdateLockingConfig` transaction builder payload.
36    pub fn new(
37        trail_id: ObjectId,
38        owner: Address,
39        config: LockingConfig,
40        selected_capability_id: Option<ObjectId>,
41    ) -> Self {
42        Self {
43            trail_id,
44            owner,
45            config,
46            selected_capability_id,
47            cached_ptb: OnceCell::new(),
48        }
49    }
50
51    async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
52    where
53        C: CoreClientReadOnly + OptionalSync,
54    {
55        LockingOps::update_locking_config(
56            client,
57            self.trail_id,
58            self.owner,
59            self.config.clone(),
60            self.selected_capability_id,
61        )
62        .await
63    }
64}
65
66#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
67#[cfg_attr(feature = "send-sync", async_trait)]
68impl Transaction for UpdateLockingConfig {
69    type Error = Error;
70    type Output = ();
71
72    async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
73    where
74        C: CoreClientReadOnly + OptionalSync,
75    {
76        self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
77    }
78
79    async fn apply<C>(self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
80    where
81        C: CoreClientReadOnly + OptionalSync,
82    {
83        Ok(())
84    }
85}
86
87/// Transaction that updates the delete-record window.
88///
89/// Requires the `UpdateLockingConfigForDeleteRecord` permission. Updates only the rule that governs how
90/// long after creation, or for how many trailing records, an individual record stays *locked against
91/// deletion*.
92///
93/// On success a `LockingConfigUpdated` event is emitted.
94#[derive(Debug, Clone)]
95pub struct UpdateDeleteRecordWindow {
96    trail_id: ObjectId,
97    owner: Address,
98    window: LockingWindow,
99    selected_capability_id: Option<ObjectId>,
100    cached_ptb: OnceCell<ProgrammableTransaction>,
101}
102
103impl UpdateDeleteRecordWindow {
104    /// Creates an `UpdateDeleteRecordWindow` transaction builder payload.
105    pub fn new(
106        trail_id: ObjectId,
107        owner: Address,
108        window: LockingWindow,
109        selected_capability_id: Option<ObjectId>,
110    ) -> Self {
111        Self {
112            trail_id,
113            owner,
114            window,
115            selected_capability_id,
116            cached_ptb: OnceCell::new(),
117        }
118    }
119
120    async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
121    where
122        C: CoreClientReadOnly + OptionalSync,
123    {
124        LockingOps::update_delete_record_window(
125            client,
126            self.trail_id,
127            self.owner,
128            self.window.clone(),
129            self.selected_capability_id,
130        )
131        .await
132    }
133}
134
135#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
136#[cfg_attr(feature = "send-sync", async_trait)]
137impl Transaction for UpdateDeleteRecordWindow {
138    type Error = Error;
139    type Output = ();
140
141    async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
142    where
143        C: CoreClientReadOnly + OptionalSync,
144    {
145        self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
146    }
147
148    async fn apply<C>(self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
149    where
150        C: CoreClientReadOnly + OptionalSync,
151    {
152        Ok(())
153    }
154}
155
156/// Transaction that updates the delete-trail lock.
157///
158/// Requires the `UpdateLockingConfigForDeleteTrail` permission. The new lock must not be
159/// [`TimeLock::UntilDestroyed`]; the Move package aborts otherwise. This updates only the time lock
160/// guarding deletion of the entire trail object.
161///
162/// On success a `LockingConfigUpdated` event is emitted.
163#[derive(Debug, Clone)]
164pub struct UpdateDeleteTrailLock {
165    trail_id: ObjectId,
166    owner: Address,
167    lock: TimeLock,
168    selected_capability_id: Option<ObjectId>,
169    cached_ptb: OnceCell<ProgrammableTransaction>,
170}
171
172impl UpdateDeleteTrailLock {
173    /// Creates an `UpdateDeleteTrailLock` transaction builder payload.
174    pub fn new(trail_id: ObjectId, owner: Address, lock: TimeLock, selected_capability_id: Option<ObjectId>) -> Self {
175        Self {
176            trail_id,
177            owner,
178            lock,
179            selected_capability_id,
180            cached_ptb: OnceCell::new(),
181        }
182    }
183
184    async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
185    where
186        C: CoreClientReadOnly + OptionalSync,
187    {
188        LockingOps::update_delete_trail_lock(
189            client,
190            self.trail_id,
191            self.owner,
192            self.lock.clone(),
193            self.selected_capability_id,
194        )
195        .await
196    }
197}
198
199#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
200#[cfg_attr(feature = "send-sync", async_trait)]
201impl Transaction for UpdateDeleteTrailLock {
202    type Error = Error;
203    type Output = ();
204
205    async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
206    where
207        C: CoreClientReadOnly + OptionalSync,
208    {
209        self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
210    }
211
212    async fn apply<C>(self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
213    where
214        C: CoreClientReadOnly + OptionalSync,
215    {
216        Ok(())
217    }
218}
219
220/// Transaction that updates the write lock.
221///
222/// Requires the `UpdateLockingConfigForWrite` permission. Updates only the time lock guarding future
223/// record writes; while the lock is active, `add_record` aborts with `ETrailWriteLocked`.
224///
225/// On success a `LockingConfigUpdated` event is emitted.
226#[derive(Debug, Clone)]
227pub struct UpdateWriteLock {
228    trail_id: ObjectId,
229    owner: Address,
230    lock: TimeLock,
231    selected_capability_id: Option<ObjectId>,
232    cached_ptb: OnceCell<ProgrammableTransaction>,
233}
234
235impl UpdateWriteLock {
236    /// Creates an `UpdateWriteLock` transaction builder payload.
237    pub fn new(trail_id: ObjectId, owner: Address, lock: TimeLock, selected_capability_id: Option<ObjectId>) -> Self {
238        Self {
239            trail_id,
240            owner,
241            lock,
242            selected_capability_id,
243            cached_ptb: OnceCell::new(),
244        }
245    }
246
247    async fn make_ptb<C>(&self, client: &C) -> Result<ProgrammableTransaction, Error>
248    where
249        C: CoreClientReadOnly + OptionalSync,
250    {
251        LockingOps::update_write_lock(
252            client,
253            self.trail_id,
254            self.owner,
255            self.lock.clone(),
256            self.selected_capability_id,
257        )
258        .await
259    }
260}
261
262#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
263#[cfg_attr(feature = "send-sync", async_trait)]
264impl Transaction for UpdateWriteLock {
265    type Error = Error;
266    type Output = ();
267
268    async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
269    where
270        C: CoreClientReadOnly + OptionalSync,
271    {
272        self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
273    }
274
275    async fn apply<C>(self, _: &mut IotaTransactionBlockEffects, _: &C) -> Result<Self::Output, Self::Error>
276    where
277        C: CoreClientReadOnly + OptionalSync,
278    {
279        Ok(())
280    }
281}