Skip to main content

dpp/asset_lock/reduced_asset_lock_value/
mod.rs

1use crate::asset_lock::reduced_asset_lock_value::v0::AssetLockValueV0;
2use crate::fee::Credits;
3use crate::ProtocolError;
4use bincode::{Decode, Encode};
5use derive_more::From;
6use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
7use platform_value::Bytes32;
8use platform_version::version::PlatformVersion;
9
10mod v0;
11
12pub use v0::{AssetLockValueGettersV0, AssetLockValueSettersV0};
13
14#[derive(
15    Debug,
16    Clone,
17    Encode,
18    Decode,
19    PlatformSerialize,
20    PlatformDeserialize,
21    From,
22    PartialEq,
23    serde::Serialize,
24    serde::Deserialize,
25)]
26#[platform_serialize(unversioned)]
27#[serde(tag = "$formatVersion")]
28pub enum AssetLockValue {
29    #[serde(rename = "0")]
30    V0(AssetLockValueV0),
31}
32
33#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
34impl crate::serialization::JsonConvertible for AssetLockValue {}
35
36#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
37impl crate::serialization::ValueConvertible for AssetLockValue {}
38
39impl AssetLockValue {
40    pub fn new(
41        initial_credit_value: Credits,
42        tx_out_script: Vec<u8>,
43        remaining_credit_value: Credits,
44        used_tags: Vec<Bytes32>,
45        platform_version: &PlatformVersion,
46    ) -> Result<Self, ProtocolError> {
47        match platform_version
48            .dpp
49            .asset_lock_versions
50            .reduced_asset_lock_value
51            .default_current_version
52        {
53            0 => Ok(AssetLockValue::V0(AssetLockValueV0 {
54                initial_credit_value,
55                tx_out_script,
56                remaining_credit_value,
57                used_tags,
58            })),
59            version => Err(ProtocolError::UnknownVersionMismatch {
60                method: "ReducedAssetLockValue::new".to_string(),
61                known_versions: vec![0],
62                received: version,
63            }),
64        }
65    }
66}
67
68impl AssetLockValueGettersV0 for AssetLockValue {
69    fn initial_credit_value(&self) -> Credits {
70        match self {
71            AssetLockValue::V0(v0) => v0.initial_credit_value,
72        }
73    }
74
75    fn tx_out_script(&self) -> &Vec<u8> {
76        match self {
77            AssetLockValue::V0(v0) => &v0.tx_out_script,
78        }
79    }
80
81    fn tx_out_script_owned(self) -> Vec<u8> {
82        match self {
83            AssetLockValue::V0(v0) => v0.tx_out_script,
84        }
85    }
86
87    fn remaining_credit_value(&self) -> Credits {
88        match self {
89            AssetLockValue::V0(v0) => v0.remaining_credit_value,
90        }
91    }
92
93    fn used_tags_ref(&self) -> &Vec<Bytes32> {
94        match self {
95            AssetLockValue::V0(v0) => &v0.used_tags,
96        }
97    }
98}
99
100impl AssetLockValueSettersV0 for AssetLockValue {
101    fn set_initial_credit_value(&mut self, value: Credits) {
102        match self {
103            AssetLockValue::V0(v0) => v0.initial_credit_value = value,
104        }
105    }
106
107    fn set_tx_out_script(&mut self, value: Vec<u8>) {
108        match self {
109            AssetLockValue::V0(v0) => v0.tx_out_script = value,
110        }
111    }
112
113    fn set_remaining_credit_value(&mut self, value: Credits) {
114        match self {
115            AssetLockValue::V0(v0) => v0.remaining_credit_value = value,
116        }
117    }
118
119    fn set_used_tags(&mut self, tags: Vec<Bytes32>) {
120        match self {
121            AssetLockValue::V0(v0) => v0.used_tags = tags,
122        }
123    }
124
125    fn add_used_tag(&mut self, tag: Bytes32) {
126        match self {
127            AssetLockValue::V0(v0) => v0.used_tags.push(tag),
128        }
129    }
130}
131
132#[cfg(all(
133    test,
134    feature = "json-conversion",
135    feature = "value-conversion",
136    feature = "serde-conversion"
137))]
138mod json_convertible_tests {
139    use super::*;
140    use platform_value::platform_value;
141    use platform_version::version::PlatformVersion;
142    use serde_json::json;
143
144    fn fixture() -> AssetLockValue {
145        AssetLockValue::new(
146            1_000_000,
147            vec![0xaa, 0xbb, 0xcc, 0xdd],
148            500_000,
149            vec![Bytes32::new([0x42; 32])],
150            PlatformVersion::latest(),
151        )
152        .expect("fixture")
153    }
154
155    #[test]
156    fn json_round_trip_with_full_wire_shape() {
157        use crate::serialization::JsonConvertible;
158        let original = fixture();
159        let json = original.to_json().expect("to_json");
160        // `AssetLockValue` uses the standard `tag = "$formatVersion"`
161        // convention. `Bytes32` is base64 in JSON HR, and `tx_out_script`
162        // (`Vec<u8>`) is base64 too: `#[json_safe_fields]` annotates it with
163        // `serde_bytes_var` (raw bytes in binary, base64 string in JSON).
164        assert_eq!(
165            json,
166            json!({
167                "$formatVersion": "0",
168                "initial_credit_value": 1_000_000,
169                "tx_out_script": "qrvM3Q==",
170                "remaining_credit_value": 500_000,
171                "used_tags": ["QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkI="],
172            })
173        );
174        let recovered = AssetLockValue::from_json(json).expect("from_json");
175        assert_eq!(original, recovered);
176    }
177
178    #[test]
179    fn value_round_trip_with_full_wire_shape() {
180        use crate::serialization::ValueConvertible;
181        use platform_value::Value;
182        let original = fixture();
183        let value = original.to_object().expect("to_object");
184        // `#[json_safe_fields]` annotates `tx_out_script` (`Vec<u8>`) with
185        // `serde_bytes_var`, so it encodes as `Value::Bytes` (raw bytes, not an
186        // array of `U8`). `used_tags` is `Array(Vec<Value::Bytes32>)`.
187        // `initial_credit_value` / `remaining_credit_value` are `Credits` (u64);
188        // in non-human-readable `Value` they stay `Value::U64`.
189        assert_eq!(
190            value,
191            platform_value!({
192                "$formatVersion": "0",
193                "initial_credit_value": 1_000_000u64,
194                "tx_out_script": Value::Bytes(vec![0xaa, 0xbb, 0xcc, 0xdd]),
195                "remaining_credit_value": 500_000u64,
196                "used_tags": [Value::Bytes32([0x42; 32])],
197            })
198        );
199        let recovered = AssetLockValue::from_object(value).expect("from_object");
200        assert_eq!(original, recovered);
201    }
202
203    #[test]
204    fn json_large_credits_serialize_as_strings_for_js_safety() {
205        use crate::serialization::JsonConvertible;
206        // `initial_credit_value` exceeds JS `Number.MAX_SAFE_INTEGER` (2^53 - 1),
207        // so `#[json_safe_fields]` must emit it as a string in human-readable JSON
208        // to avoid silent precision loss when the value crosses into JavaScript.
209        // Without the attribute this serializes as a bare number and the
210        // assertion below fails.
211        let original = AssetLockValue::new(
212            9_007_199_254_740_993, // 2^53 + 1, above MAX_SAFE_INTEGER
213            vec![0xaa, 0xbb, 0xcc, 0xdd],
214            500_000,
215            vec![Bytes32::new([0x42; 32])],
216            PlatformVersion::latest(),
217        )
218        .expect("fixture");
219        let json = original.to_json().expect("to_json");
220        assert_eq!(json["initial_credit_value"], json!("9007199254740993"));
221        // Values within the safe range stay numbers.
222        assert_eq!(json["remaining_credit_value"], json!(500_000));
223        // And the string form round-trips back to the exact u64.
224        let recovered = AssetLockValue::from_json(json).expect("from_json");
225        assert_eq!(original, recovered);
226    }
227}