chik_protocol/
coin.rs

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}