iota_types/timelock/
timelock.rs

1// Copyright (c) 2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use move_core_types::{
5    ident_str,
6    identifier::IdentStr,
7    language_storage::{StructTag, TypeTag},
8};
9use serde::{Deserialize, Serialize};
10
11use crate::{
12    IOTA_FRAMEWORK_ADDRESS,
13    balance::Balance,
14    base_types::ObjectID,
15    error::IotaError,
16    gas_coin::GasCoin,
17    id::UID,
18    object::{Data, Object},
19};
20
21pub const TIMELOCK_MODULE_NAME: &IdentStr = ident_str!("timelock");
22pub const TIMELOCK_STRUCT_NAME: &IdentStr = ident_str!("TimeLock");
23
24/// All basic outputs whose IDs start with this prefix represent vested rewards
25/// that were created during the stardust upgrade on IOTA mainnet.
26pub const VESTED_REWARD_ID_PREFIX: &str =
27    "0xb191c4bc825ac6983789e50545d5ef07a1d293a98ad974fc9498cb18";
28
29/// Rust version of the Move stardust::TimeLock type.
30#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
31pub struct TimeLock<T> {
32    id: UID,
33    /// The locked object.
34    locked: T,
35    /// This is the epoch time stamp of when the lock expires.
36    expiration_timestamp_ms: u64,
37    /// Timelock related label.
38    label: Option<String>,
39}
40
41impl<T> TimeLock<T> {
42    /// Constructor.
43    pub fn new(id: UID, locked: T, expiration_timestamp_ms: u64, label: Option<String>) -> Self {
44        Self {
45            id,
46            locked,
47            expiration_timestamp_ms,
48            label,
49        }
50    }
51
52    /// Get the TimeLock's `type`.
53    pub fn type_(type_param: TypeTag) -> StructTag {
54        StructTag {
55            address: IOTA_FRAMEWORK_ADDRESS,
56            module: TIMELOCK_MODULE_NAME.to_owned(),
57            name: TIMELOCK_STRUCT_NAME.to_owned(),
58            type_params: vec![type_param],
59        }
60    }
61
62    /// Get the TimeLock's `id`.
63    pub fn id(&self) -> &ObjectID {
64        self.id.object_id()
65    }
66
67    /// Get the TimeLock's `locked` object.
68    pub fn locked(&self) -> &T {
69        &self.locked
70    }
71
72    /// Get the TimeLock's `expiration_timestamp_ms`.
73    pub fn expiration_timestamp_ms(&self) -> u64 {
74        self.expiration_timestamp_ms
75    }
76
77    /// Get the TimeLock's `label``.
78    pub fn label(&self) -> &Option<String> {
79        &self.label
80    }
81}
82
83impl<'de, T> TimeLock<T>
84where
85    T: Serialize + Deserialize<'de>,
86{
87    /// Create a `TimeLock` from BCS bytes.
88    pub fn from_bcs_bytes(content: &'de [u8]) -> Result<Self, IotaError> {
89        bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
90            error: format!("Unable to deserialize TimeLock object: {err:?}"),
91        })
92    }
93
94    /// Serialize a `TimeLock` as a `Vec<u8>` of BCS.
95    pub fn to_bcs_bytes(&self) -> Vec<u8> {
96        bcs::to_bytes(&self).unwrap()
97    }
98}
99
100/// Is this other StructTag representing a TimeLock?
101pub fn is_timelock(other: &StructTag) -> bool {
102    other.address == IOTA_FRAMEWORK_ADDRESS
103        && other.module.as_ident_str() == TIMELOCK_MODULE_NAME
104        && other.name.as_ident_str() == TIMELOCK_STRUCT_NAME
105}
106
107/// Is this other StructTag representing a `TimeLock<Balance<T>>`?
108pub fn is_timelocked_balance(other: &StructTag) -> bool {
109    if !is_timelock(other) {
110        return false;
111    }
112
113    if other.type_params.len() != 1 {
114        return false;
115    }
116
117    match &other.type_params[0] {
118        TypeTag::Struct(tag) => Balance::is_balance(tag),
119        _ => false,
120    }
121}
122
123/// Is this other StructTag representing a `TimeLock<Balance<IOTA>>`?
124pub fn is_timelocked_gas_balance(other: &StructTag) -> bool {
125    if !is_timelock(other) {
126        return false;
127    }
128
129    if other.type_params.len() != 1 {
130        return false;
131    }
132
133    match &other.type_params[0] {
134        TypeTag::Struct(tag) => GasCoin::is_gas_balance(tag),
135        _ => false,
136    }
137}
138
139impl<'de, T> TryFrom<&'de Object> for TimeLock<T>
140where
141    T: Serialize + Deserialize<'de>,
142{
143    type Error = IotaError;
144
145    fn try_from(object: &'de Object) -> Result<Self, Self::Error> {
146        match &object.data {
147            Data::Move(o) => {
148                if o.type_().is_timelock() {
149                    return TimeLock::from_bcs_bytes(o.contents());
150                }
151            }
152            Data::Package(_) => {}
153        }
154
155        Err(IotaError::Type {
156            error: format!("Object type is not a TimeLock: {object:?}"),
157        })
158    }
159}