1#![allow(clippy::field_reassign_with_default)]
3
4use alloc::{format, string::String, vec::Vec};
5use core::{
6 array::TryFromSliceError,
7 convert::TryFrom,
8 fmt::{self, Debug, Display, Formatter},
9};
10
11#[cfg(feature = "datasize")]
12use datasize::DataSize;
13use rand::{
14 distributions::{Distribution, Standard},
15 Rng,
16};
17#[cfg(feature = "json-schema")]
18use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
19use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
20
21use crate::{
22 account::AccountHash,
23 bytesrepr::{self, FromBytes, ToBytes},
24 checksummed_hex, CLType, CLTyped, URef, U512,
25};
26
27pub const DEPLOY_HASH_LENGTH: usize = 32;
29pub const TRANSFER_ADDR_LENGTH: usize = 32;
31pub(super) const TRANSFER_ADDR_FORMATTED_STRING_PREFIX: &str = "transfer-";
32
33#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
36#[cfg_attr(feature = "datasize", derive(DataSize))]
37pub struct DeployHash([u8; DEPLOY_HASH_LENGTH]);
38
39impl DeployHash {
40 pub const fn new(value: [u8; DEPLOY_HASH_LENGTH]) -> DeployHash {
42 DeployHash(value)
43 }
44
45 pub fn value(&self) -> [u8; DEPLOY_HASH_LENGTH] {
47 self.0
48 }
49
50 pub fn as_bytes(&self) -> &[u8] {
52 &self.0
53 }
54}
55
56#[cfg(feature = "json-schema")]
57impl JsonSchema for DeployHash {
58 fn schema_name() -> String {
59 String::from("DeployHash")
60 }
61
62 fn json_schema(gen: &mut SchemaGenerator) -> Schema {
63 let schema = gen.subschema_for::<String>();
64 let mut schema_object = schema.into_object();
65 schema_object.metadata().description = Some("Hex-encoded deploy hash.".to_string());
66 schema_object.into()
67 }
68}
69
70impl ToBytes for DeployHash {
71 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
72 self.0.to_bytes()
73 }
74
75 fn serialized_length(&self) -> usize {
76 self.0.serialized_length()
77 }
78
79 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
80 self.0.write_bytes(writer)?;
81 Ok(())
82 }
83}
84
85impl FromBytes for DeployHash {
86 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
87 <[u8; DEPLOY_HASH_LENGTH]>::from_bytes(bytes)
88 .map(|(inner, remainder)| (DeployHash(inner), remainder))
89 }
90}
91
92impl Serialize for DeployHash {
93 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
94 if serializer.is_human_readable() {
95 base16::encode_lower(&self.0).serialize(serializer)
96 } else {
97 self.0.serialize(serializer)
98 }
99 }
100}
101
102impl<'de> Deserialize<'de> for DeployHash {
103 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
104 let bytes = if deserializer.is_human_readable() {
105 let hex_string = String::deserialize(deserializer)?;
106 let vec_bytes =
107 checksummed_hex::decode(hex_string.as_bytes()).map_err(SerdeError::custom)?;
108 <[u8; DEPLOY_HASH_LENGTH]>::try_from(vec_bytes.as_ref()).map_err(SerdeError::custom)?
109 } else {
110 <[u8; DEPLOY_HASH_LENGTH]>::deserialize(deserializer)?
111 };
112 Ok(DeployHash(bytes))
113 }
114}
115
116impl Debug for DeployHash {
117 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
118 write!(formatter, "DeployHash({})", base16::encode_lower(&self.0))
119 }
120}
121
122impl Distribution<DeployHash> for Standard {
123 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DeployHash {
124 DeployHash::new(rng.gen())
125 }
126}
127
128#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize, Default)]
130#[cfg_attr(feature = "datasize", derive(DataSize))]
131#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
132#[serde(deny_unknown_fields)]
133pub struct Transfer {
134 pub deploy_hash: DeployHash,
136 pub from: AccountHash,
138 pub to: Option<AccountHash>,
140 pub source: URef,
142 pub target: URef,
144 pub amount: U512,
146 pub gas: U512,
148 pub id: Option<u64>,
150}
151
152impl Transfer {
153 #[allow(clippy::too_many_arguments)]
155 pub fn new(
156 deploy_hash: DeployHash,
157 from: AccountHash,
158 to: Option<AccountHash>,
159 source: URef,
160 target: URef,
161 amount: U512,
162 gas: U512,
163 id: Option<u64>,
164 ) -> Self {
165 Transfer {
166 deploy_hash,
167 from,
168 to,
169 source,
170 target,
171 amount,
172 gas,
173 id,
174 }
175 }
176}
177
178impl FromBytes for Transfer {
179 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
180 let (deploy_hash, rem) = FromBytes::from_bytes(bytes)?;
181 let (from, rem) = AccountHash::from_bytes(rem)?;
182 let (to, rem) = <Option<AccountHash>>::from_bytes(rem)?;
183 let (source, rem) = URef::from_bytes(rem)?;
184 let (target, rem) = URef::from_bytes(rem)?;
185 let (amount, rem) = U512::from_bytes(rem)?;
186 let (gas, rem) = U512::from_bytes(rem)?;
187 let (id, rem) = <Option<u64>>::from_bytes(rem)?;
188 Ok((
189 Transfer {
190 deploy_hash,
191 from,
192 to,
193 source,
194 target,
195 amount,
196 gas,
197 id,
198 },
199 rem,
200 ))
201 }
202}
203
204impl ToBytes for Transfer {
205 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
206 let mut result = bytesrepr::allocate_buffer(self)?;
207 self.deploy_hash.write_bytes(&mut result)?;
208 self.from.write_bytes(&mut result)?;
209 self.to.write_bytes(&mut result)?;
210 self.source.write_bytes(&mut result)?;
211 self.target.write_bytes(&mut result)?;
212 self.amount.write_bytes(&mut result)?;
213 self.gas.write_bytes(&mut result)?;
214 self.id.write_bytes(&mut result)?;
215 Ok(result)
216 }
217
218 fn serialized_length(&self) -> usize {
219 self.deploy_hash.serialized_length()
220 + self.from.serialized_length()
221 + self.to.serialized_length()
222 + self.source.serialized_length()
223 + self.target.serialized_length()
224 + self.amount.serialized_length()
225 + self.gas.serialized_length()
226 + self.id.serialized_length()
227 }
228
229 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
230 self.deploy_hash.write_bytes(writer)?;
231 self.from.write_bytes(writer)?;
232 self.to.write_bytes(writer)?;
233 self.source.write_bytes(writer)?;
234 self.target.write_bytes(writer)?;
235 self.amount.write_bytes(writer)?;
236 self.gas.write_bytes(writer)?;
237 self.id.write_bytes(writer)?;
238 Ok(())
239 }
240}
241
242#[derive(Debug)]
244#[non_exhaustive]
245pub enum FromStrError {
246 InvalidPrefix,
248 Hex(base16::DecodeError),
250 Length(TryFromSliceError),
252}
253
254impl From<base16::DecodeError> for FromStrError {
255 fn from(error: base16::DecodeError) -> Self {
256 FromStrError::Hex(error)
257 }
258}
259
260impl From<TryFromSliceError> for FromStrError {
261 fn from(error: TryFromSliceError) -> Self {
262 FromStrError::Length(error)
263 }
264}
265
266impl Display for FromStrError {
267 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
268 match self {
269 FromStrError::InvalidPrefix => write!(f, "prefix is not 'transfer-'"),
270 FromStrError::Hex(error) => {
271 write!(f, "failed to decode address portion from hex: {}", error)
272 }
273 FromStrError::Length(error) => write!(f, "address portion is wrong length: {}", error),
274 }
275 }
276}
277
278#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
281#[cfg_attr(feature = "datasize", derive(DataSize))]
282pub struct TransferAddr([u8; TRANSFER_ADDR_LENGTH]);
283
284impl TransferAddr {
285 pub const fn new(value: [u8; TRANSFER_ADDR_LENGTH]) -> TransferAddr {
287 TransferAddr(value)
288 }
289
290 pub fn value(&self) -> [u8; TRANSFER_ADDR_LENGTH] {
292 self.0
293 }
294
295 pub fn as_bytes(&self) -> &[u8] {
297 &self.0
298 }
299
300 pub fn to_formatted_string(self) -> String {
302 format!(
303 "{}{}",
304 TRANSFER_ADDR_FORMATTED_STRING_PREFIX,
305 base16::encode_lower(&self.0),
306 )
307 }
308
309 pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
311 let remainder = input
312 .strip_prefix(TRANSFER_ADDR_FORMATTED_STRING_PREFIX)
313 .ok_or(FromStrError::InvalidPrefix)?;
314 let bytes =
315 <[u8; TRANSFER_ADDR_LENGTH]>::try_from(checksummed_hex::decode(remainder)?.as_ref())?;
316 Ok(TransferAddr(bytes))
317 }
318}
319
320#[cfg(feature = "json-schema")]
321impl JsonSchema for TransferAddr {
322 fn schema_name() -> String {
323 String::from("TransferAddr")
324 }
325
326 fn json_schema(gen: &mut SchemaGenerator) -> Schema {
327 let schema = gen.subschema_for::<String>();
328 let mut schema_object = schema.into_object();
329 schema_object.metadata().description = Some("Hex-encoded transfer address.".to_string());
330 schema_object.into()
331 }
332}
333
334impl Serialize for TransferAddr {
335 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
336 if serializer.is_human_readable() {
337 self.to_formatted_string().serialize(serializer)
338 } else {
339 self.0.serialize(serializer)
340 }
341 }
342}
343
344impl<'de> Deserialize<'de> for TransferAddr {
345 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
346 if deserializer.is_human_readable() {
347 let formatted_string = String::deserialize(deserializer)?;
348 TransferAddr::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
349 } else {
350 let bytes = <[u8; TRANSFER_ADDR_LENGTH]>::deserialize(deserializer)?;
351 Ok(TransferAddr(bytes))
352 }
353 }
354}
355
356impl Display for TransferAddr {
357 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
358 write!(f, "{}", base16::encode_lower(&self.0))
359 }
360}
361
362impl Debug for TransferAddr {
363 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
364 write!(f, "TransferAddr({})", base16::encode_lower(&self.0))
365 }
366}
367
368impl CLTyped for TransferAddr {
369 fn cl_type() -> CLType {
370 CLType::ByteArray(TRANSFER_ADDR_LENGTH as u32)
371 }
372}
373
374impl ToBytes for TransferAddr {
375 #[inline(always)]
376 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
377 self.0.to_bytes()
378 }
379
380 #[inline(always)]
381 fn serialized_length(&self) -> usize {
382 self.0.serialized_length()
383 }
384
385 #[inline(always)]
386 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
387 self.0.write_bytes(writer)?;
388 Ok(())
389 }
390}
391
392impl FromBytes for TransferAddr {
393 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
394 let (bytes, remainder) = FromBytes::from_bytes(bytes)?;
395 Ok((TransferAddr::new(bytes), remainder))
396 }
397}
398
399impl AsRef<[u8]> for TransferAddr {
400 fn as_ref(&self) -> &[u8] {
401 self.0.as_ref()
402 }
403}
404
405impl Distribution<TransferAddr> for Standard {
406 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TransferAddr {
407 TransferAddr::new(rng.gen())
408 }
409}
410
411#[cfg(any(feature = "testing", feature = "gens", test))]
413pub mod gens {
414 use proptest::prelude::{prop::option, Arbitrary, Strategy};
415
416 use crate::{
417 deploy_info::gens::{account_hash_arb, deploy_hash_arb},
418 gens::{u512_arb, uref_arb},
419 Transfer,
420 };
421
422 pub fn transfer_arb() -> impl Strategy<Value = Transfer> {
424 (
425 deploy_hash_arb(),
426 account_hash_arb(),
427 option::of(account_hash_arb()),
428 uref_arb(),
429 uref_arb(),
430 u512_arb(),
431 u512_arb(),
432 option::of(<u64>::arbitrary()),
433 )
434 .prop_map(|(deploy_hash, from, to, source, target, amount, gas, id)| {
435 Transfer {
436 deploy_hash,
437 from,
438 to,
439 source,
440 target,
441 amount,
442 gas,
443 id,
444 }
445 })
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use proptest::prelude::*;
452
453 use crate::bytesrepr;
454
455 use super::*;
456
457 proptest! {
458 #[test]
459 fn test_serialization_roundtrip(transfer in gens::transfer_arb()) {
460 bytesrepr::test_serialization_roundtrip(&transfer)
461 }
462 }
463
464 #[test]
465 fn transfer_addr_from_str() {
466 let transfer_address = TransferAddr([4; 32]);
467 let encoded = transfer_address.to_formatted_string();
468 let decoded = TransferAddr::from_formatted_str(&encoded).unwrap();
469 assert_eq!(transfer_address, decoded);
470
471 let invalid_prefix =
472 "transfe-0000000000000000000000000000000000000000000000000000000000000000";
473 assert!(TransferAddr::from_formatted_str(invalid_prefix).is_err());
474
475 let invalid_prefix =
476 "transfer0000000000000000000000000000000000000000000000000000000000000000";
477 assert!(TransferAddr::from_formatted_str(invalid_prefix).is_err());
478
479 let short_addr = "transfer-00000000000000000000000000000000000000000000000000000000000000";
480 assert!(TransferAddr::from_formatted_str(short_addr).is_err());
481
482 let long_addr =
483 "transfer-000000000000000000000000000000000000000000000000000000000000000000";
484 assert!(TransferAddr::from_formatted_str(long_addr).is_err());
485
486 let invalid_hex =
487 "transfer-000000000000000000000000000000000000000000000000000000000000000g";
488 assert!(TransferAddr::from_formatted_str(invalid_hex).is_err());
489 }
490
491 #[test]
492 fn transfer_addr_serde_roundtrip() {
493 let transfer_address = TransferAddr([255; 32]);
494 let serialized = bincode::serialize(&transfer_address).unwrap();
495 let decoded = bincode::deserialize(&serialized).unwrap();
496 assert_eq!(transfer_address, decoded);
497 }
498
499 #[test]
500 fn transfer_addr_json_roundtrip() {
501 let transfer_address = TransferAddr([255; 32]);
502 let json_string = serde_json::to_string_pretty(&transfer_address).unwrap();
503 let decoded = serde_json::from_str(&json_string).unwrap();
504 assert_eq!(transfer_address, decoded);
505 }
506}