dpp/asset_lock/reduced_asset_lock_value/
mod.rs1use crate::asset_lock::reduced_asset_lock_value::v0::AssetLockValueV0;
2use crate::fee::Credits;
3#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
4use crate::serialization::JsonConvertible;
5#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
6use crate::serialization::ValueConvertible;
7use crate::ProtocolError;
8use bincode::{Decode, DecodeUntrusted, Encode};
9use derive_more::From;
10use platform_serialization_derive::{
11 PlatformDeserializeTrusted, PlatformDeserializeUntrusted, PlatformSerialize,
12};
13use platform_value::Bytes32;
14use platform_version::version::PlatformVersion;
15
16mod v0;
17
18pub use v0::{AssetLockValueGettersV0, AssetLockValueSettersV0};
19
20#[derive(
21 Debug,
22 Clone,
23 Encode,
24 Decode,
25 PlatformSerialize,
26 PlatformDeserializeTrusted,
27 PlatformDeserializeUntrusted,
28 From,
29 PartialEq,
30 serde::Serialize,
31 serde::Deserialize,
32 DecodeUntrusted,
33)]
34#[platform_serialize(limit = 15000, unversioned)]
40#[serde(tag = "$formatVersion")]
41pub enum AssetLockValue {
42 #[serde(rename = "0")]
43 V0(AssetLockValueV0),
44}
45
46#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
47impl JsonConvertible for AssetLockValue {}
48
49#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
50impl ValueConvertible for AssetLockValue {}
51
52impl AssetLockValue {
53 pub fn new(
54 initial_credit_value: Credits,
55 tx_out_script: Vec<u8>,
56 remaining_credit_value: Credits,
57 used_tags: Vec<Bytes32>,
58 platform_version: &PlatformVersion,
59 ) -> Result<Self, ProtocolError> {
60 match platform_version
61 .dpp
62 .asset_lock_versions
63 .reduced_asset_lock_value
64 .default_current_version
65 {
66 0 => Ok(AssetLockValue::V0(AssetLockValueV0 {
67 initial_credit_value,
68 tx_out_script,
69 remaining_credit_value,
70 used_tags,
71 })),
72 version => Err(ProtocolError::UnknownVersionMismatch {
73 method: "ReducedAssetLockValue::new".to_string(),
74 known_versions: vec![0],
75 received: version,
76 }),
77 }
78 }
79}
80
81impl AssetLockValueGettersV0 for AssetLockValue {
82 fn initial_credit_value(&self) -> Credits {
83 match self {
84 AssetLockValue::V0(v0) => v0.initial_credit_value,
85 }
86 }
87
88 fn tx_out_script(&self) -> &Vec<u8> {
89 match self {
90 AssetLockValue::V0(v0) => &v0.tx_out_script,
91 }
92 }
93
94 fn tx_out_script_owned(self) -> Vec<u8> {
95 match self {
96 AssetLockValue::V0(v0) => v0.tx_out_script,
97 }
98 }
99
100 fn remaining_credit_value(&self) -> Credits {
101 match self {
102 AssetLockValue::V0(v0) => v0.remaining_credit_value,
103 }
104 }
105
106 fn used_tags_ref(&self) -> &Vec<Bytes32> {
107 match self {
108 AssetLockValue::V0(v0) => &v0.used_tags,
109 }
110 }
111}
112
113impl AssetLockValueSettersV0 for AssetLockValue {
114 fn set_initial_credit_value(&mut self, value: Credits) {
115 match self {
116 AssetLockValue::V0(v0) => v0.initial_credit_value = value,
117 }
118 }
119
120 fn set_tx_out_script(&mut self, value: Vec<u8>) {
121 match self {
122 AssetLockValue::V0(v0) => v0.tx_out_script = value,
123 }
124 }
125
126 fn set_remaining_credit_value(&mut self, value: Credits) {
127 match self {
128 AssetLockValue::V0(v0) => v0.remaining_credit_value = value,
129 }
130 }
131
132 fn set_used_tags(&mut self, tags: Vec<Bytes32>) {
133 match self {
134 AssetLockValue::V0(v0) => v0.used_tags = tags,
135 }
136 }
137
138 fn add_used_tag(&mut self, tag: Bytes32) {
139 match self {
140 AssetLockValue::V0(v0) => v0.used_tags.push(tag),
141 }
142 }
143}
144
145#[cfg(all(
146 test,
147 feature = "json-conversion",
148 feature = "value-conversion",
149 feature = "serde-conversion"
150))]
151mod json_convertible_tests {
152 use super::*;
153 use platform_value::platform_value;
154 use platform_version::version::PlatformVersion;
155 use serde_json::json;
156
157 fn fixture() -> AssetLockValue {
158 AssetLockValue::new(
159 1_000_000,
160 vec![0xaa, 0xbb, 0xcc, 0xdd],
161 500_000,
162 vec![Bytes32::new([0x42; 32])],
163 PlatformVersion::latest(),
164 )
165 .expect("fixture")
166 }
167
168 #[test]
169 fn json_round_trip_with_full_wire_shape() {
170 use crate::serialization::JsonConvertible;
171 let original = fixture();
172 let json = original.to_json().expect("to_json");
173 assert_eq!(
178 json,
179 json!({
180 "$formatVersion": "0",
181 "initial_credit_value": 1_000_000,
182 "tx_out_script": "qrvM3Q==",
183 "remaining_credit_value": 500_000,
184 "used_tags": ["QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkI="],
185 })
186 );
187 let recovered = AssetLockValue::from_json(json).expect("from_json");
188 assert_eq!(original, recovered);
189 }
190
191 #[test]
192 fn value_round_trip_with_full_wire_shape() {
193 use crate::serialization::ValueConvertible;
194 use platform_value::Value;
195 let original = fixture();
196 let value = original.to_object().expect("to_object");
197 assert_eq!(
203 value,
204 platform_value!({
205 "$formatVersion": "0",
206 "initial_credit_value": 1_000_000u64,
207 "tx_out_script": Value::Bytes(vec![0xaa, 0xbb, 0xcc, 0xdd]),
208 "remaining_credit_value": 500_000u64,
209 "used_tags": [Value::Bytes32([0x42; 32])],
210 })
211 );
212 let recovered = AssetLockValue::from_object(value).expect("from_object");
213 assert_eq!(original, recovered);
214 }
215
216 #[test]
217 fn json_large_credits_serialize_as_strings_for_js_safety() {
218 use crate::serialization::JsonConvertible;
219 let original = AssetLockValue::new(
225 9_007_199_254_740_993, vec![0xaa, 0xbb, 0xcc, 0xdd],
227 500_000,
228 vec![Bytes32::new([0x42; 32])],
229 PlatformVersion::latest(),
230 )
231 .expect("fixture");
232 let json = original.to_json().expect("to_json");
233 assert_eq!(json["initial_credit_value"], json!("9007199254740993"));
234 assert_eq!(json["remaining_credit_value"], json!(500_000));
236 let recovered = AssetLockValue::from_json(json).expect("from_json");
238 assert_eq!(original, recovered);
239 }
240}
241
242#[cfg(test)]
243mod deserialize_limit_tests {
244 use super::*;
245 use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
246
247 fn payload_with_script_length(fake_len: u64) -> Vec<u8> {
250 let config = bincode::config::standard()
251 .with_big_endian()
252 .with_no_limit();
253 let mut buf = Vec::new();
254 buf.extend_from_slice(&bincode::encode_to_vec(0u32, config).unwrap());
256 buf.extend_from_slice(&bincode::encode_to_vec(1_000u64, config).unwrap());
258 buf.extend_from_slice(&bincode::encode_to_vec(fake_len, config).unwrap());
260 buf
261 }
262
263 #[test]
267 fn rejects_script_length_prefix_beyond_budget_without_allocating() {
268 let payload = payload_with_script_length(8_000_000_000);
269 let err = AssetLockValue::deserialize_from_bytes_untrusted(&payload)
270 .expect_err("oversized length prefix must be rejected");
271 assert!(
272 matches!(err, ProtocolError::MaxEncodedBytesReachedError { .. }),
273 "unexpected error: {err}"
274 );
275 }
276
277 #[test]
281 fn largest_valid_value_round_trips_under_limit() {
282 let platform_version = PlatformVersion::latest();
283 let max_tags = platform_version
284 .drive_abci
285 .validation_and_processing
286 .state_transitions
287 .max_asset_lock_usage_attempts as usize;
288 let original = AssetLockValue::new(
289 u64::MAX,
290 vec![0xffu8; 10_000],
291 u64::MAX,
292 vec![Bytes32::new([0xff; 32]); max_tags],
293 platform_version,
294 )
295 .expect("value");
296 let bytes = original.serialize_to_bytes().expect("serialize");
297 let recovered =
298 AssetLockValue::deserialize_from_bytes_untrusted(&bytes).expect("deserialize");
299 assert_eq!(original, recovered);
300 }
301}