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