1use crate::{Bytes32, BytesImpl};
2use chik_sha2::Sha256;
3use chik_streamable_macro::streamable;
4use klvm_traits::{
5 destructure_list, klvm_list, match_list, FromKlvm, FromKlvmError, KlvmDecoder, KlvmEncoder,
6 ToKlvm, ToKlvmError,
7};
8
9#[cfg(feature = "py-bindings")]
10use pyo3::exceptions::PyNotImplementedError;
11#[cfg(feature = "py-bindings")]
12use pyo3::prelude::*;
13#[cfg(feature = "py-bindings")]
14use pyo3::types::PyType;
15
16#[streamable]
17#[derive(Copy)]
18pub struct Coin {
19 parent_coin_info: Bytes32,
20 puzzle_hash: Bytes32,
21 amount: u64,
22}
23
24impl Coin {
25 pub fn coin_id(&self) -> Bytes32 {
26 let mut hasher = Sha256::new();
27 hasher.update(self.parent_coin_info);
28 hasher.update(self.puzzle_hash);
29
30 let amount_bytes = self.amount.to_be_bytes();
31 if self.amount >= 0x8000_0000_0000_0000_u64 {
32 hasher.update([0_u8]);
33 hasher.update(amount_bytes);
34 } else {
35 let start = match self.amount {
36 n if n >= 0x0080_0000_0000_0000_u64 => 0,
37 n if n >= 0x8000_0000_0000_u64 => 1,
38 n if n >= 0x0080_0000_0000_u64 => 2,
39 n if n >= 0x8000_0000_u64 => 3,
40 n if n >= 0x0080_0000_u64 => 4,
41 n if n >= 0x8000_u64 => 5,
42 n if n >= 0x80_u64 => 6,
43 n if n > 0 => 7,
44 _ => 8,
45 };
46 hasher.update(&amount_bytes[start..]);
47 }
48
49 let coin_id: [u8; 32] = hasher.finalize().as_slice().try_into().unwrap();
50 Bytes32::new(coin_id)
51 }
52}
53
54#[cfg(feature = "py-bindings")]
55#[pymethods]
56impl Coin {
57 fn name(&self) -> Bytes32 {
58 self.coin_id()
59 }
60}
61
62#[cfg(feature = "py-bindings")]
63#[pymethods]
64impl Coin {
65 #[classmethod]
66 #[pyo3(name = "from_parent")]
67 pub fn from_parent(_cls: &Bound<'_, PyType>, _coin: Self) -> PyResult<PyObject> {
68 Err(PyNotImplementedError::new_err(
69 "Coin does not support from_parent().",
70 ))
71 }
72}
73
74impl<N, E: KlvmEncoder<Node = N>> ToKlvm<E> for Coin {
75 fn to_klvm(&self, encoder: &mut E) -> Result<N, ToKlvmError> {
76 klvm_list!(self.parent_coin_info, self.puzzle_hash, self.amount).to_klvm(encoder)
77 }
78}
79
80impl<N, D: KlvmDecoder<Node = N>> FromKlvm<D> for Coin {
81 fn from_klvm(decoder: &D, node: N) -> Result<Self, FromKlvmError> {
82 let destructure_list!(parent_coin_info, puzzle_hash, amount) =
83 <match_list!(BytesImpl<32>, BytesImpl<32>, u64)>::from_klvm(decoder, node)?;
84 Ok(Coin {
85 parent_coin_info,
86 puzzle_hash,
87 amount,
88 })
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use klvmr::{
96 serde::{node_from_bytes, node_to_bytes},
97 Allocator,
98 };
99 use rstest::rstest;
100
101 #[rstest]
102 #[case(0, &[])]
103 #[case(1, &[1])]
104 #[case(0xff, &[0, 0xff])]
105 #[case(0xffff, &[0, 0xff, 0xff])]
106 #[case(0x00ff_ffff, &[0, 0xff, 0xff, 0xff])]
107 #[case(0xffff_ffff, &[0, 0xff, 0xff, 0xff, 0xff])]
108 #[case(0x00ff_ffff_ffff, &[0, 0xff, 0xff, 0xff, 0xff, 0xff])]
109 #[case(0xffff_ffff_ffff_ffff, &[0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])]
110 #[case(0x7f, &[0x7f])]
111 #[case(0x7fff, &[0x7f, 0xff])]
112 #[case(0x007f_ffff, &[0x7f, 0xff, 0xff])]
113 #[case(0x7fff_ffff, &[0x7f, 0xff, 0xff, 0xff])]
114 #[case(0x007f_ffff_ffff, &[0x7f, 0xff, 0xff, 0xff, 0xff])]
115 #[case(0x7fff_ffff_ffff_ffff, &[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])]
116 #[case(0x80, &[0, 0x80])]
117 #[case(0x8000, &[0, 0x80, 0x00])]
118 #[case(0x0080_0000, &[0, 0x80, 0x00, 0x00])]
119 #[case(0x8000_0000, &[0, 0x80, 0x00, 0x00, 0x00])]
120 #[case(0x0080_0000_0000, &[0, 0x80, 0x00, 0x00, 0x00, 0x00])]
121 #[case(0x8000_0000_0000_0000, &[0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])]
122 fn coin_id(#[case] amount: u64, #[case] bytes: &[u8]) {
123 let parent_coin = b"---foo--- ";
124 let puzzle_hash = b"---bar--- ";
125
126 let c = Coin::new(parent_coin.into(), puzzle_hash.into(), amount);
127 let mut sha256 = Sha256::new();
128 sha256.update(parent_coin);
129 sha256.update(puzzle_hash);
130 sha256.update(bytes);
131 assert_eq!(c.coin_id().to_bytes(), sha256.finalize().as_ref());
132 }
133
134 #[test]
135 fn coin_roundtrip() {
136 let a = &mut Allocator::new();
137 let expected = "ffa09e144397decd2b831551f9710c17ae776d9c5a3ae5283c5f9747263fd1255381ffa0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ff0180";
138 let expected_bytes = hex::decode(expected).unwrap();
139
140 let ptr = node_from_bytes(a, &expected_bytes).unwrap();
141 let coin = Coin::from_klvm(a, ptr).unwrap();
142
143 let round_trip = coin.to_klvm(a).unwrap();
144 assert_eq!(expected, hex::encode(node_to_bytes(a, round_trip).unwrap()));
145 }
146}