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