ibc_app_transfer_types/
amount.rs

1//! Contains the `Amount` type, which represents amounts of tokens transferred.
2use core::ops::Deref;
3use core::str::FromStr;
4
5use derive_more::{Display, From, Into};
6use ibc_core::host::types::error::DecodingError;
7use ibc_core::primitives::prelude::*;
8#[cfg(feature = "serde")]
9use ibc_core::primitives::serializers;
10use primitive_types::U256;
11
12/// A type for representing token transfer amounts.
13#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
16#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Display, From, Into)]
17pub struct Amount(
18    #[cfg_attr(feature = "arbitrary", arbitrary(with = arb_u256))]
19    #[cfg_attr(feature = "schema", schemars(with = "String"))]
20    #[cfg_attr(feature = "serde", serde(serialize_with = "serializers::serialize"))]
21    #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize"))]
22    U256,
23);
24
25#[cfg(feature = "parity-scale-codec")]
26impl parity_scale_codec::WrapperTypeDecode for Amount {
27    type Wrapped = [u64; 4];
28}
29
30#[cfg(feature = "parity-scale-codec")]
31impl parity_scale_codec::WrapperTypeEncode for Amount {}
32
33#[cfg(feature = "borsh")]
34impl borsh::BorshSerialize for Amount {
35    fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
36        // Note: a "word" is 8 bytes (i.e. a u64)
37        let words = self.as_slice();
38        let bytes: Vec<u8> = words.iter().flat_map(|word| word.to_be_bytes()).collect();
39
40        writer.write_all(&bytes)
41    }
42}
43#[cfg(feature = "borsh")]
44impl borsh::BorshDeserialize for Amount {
45    fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
46        const NUM_BYTES_IN_U64: usize = 8;
47        const NUM_WORDS_IN_U256: usize = 4;
48
49        let mut buf = [0; 32];
50        let bytes_read = reader.read(&mut buf)?;
51        if bytes_read != 32 {
52            return Err(borsh::io::Error::new(
53                borsh::io::ErrorKind::InvalidInput,
54                format!("Expected to read 32 bytes, read {bytes_read}"),
55            ));
56        }
57
58        let words: Vec<u64> = buf
59            .chunks_exact(NUM_BYTES_IN_U64)
60            .map(|word| {
61                let word: [u8; NUM_BYTES_IN_U64] = word
62                    .try_into()
63                    .expect("exact chunks of 8 bytes are expected to be 8 bytes");
64                u64::from_be_bytes(word)
65            })
66            .collect();
67
68        let four_words: [u64; NUM_WORDS_IN_U256] = words
69            .try_into()
70            .expect("U256 is always 4 four words, and we confirmed that we read 32 bytes");
71
72        Ok(four_words.into())
73    }
74}
75
76impl Deref for Amount {
77    type Target = [u64; 4];
78
79    fn deref(&self) -> &Self::Target {
80        &self.0 .0
81    }
82}
83
84impl From<[u64; 4]> for Amount {
85    fn from(value: [u64; 4]) -> Self {
86        Self(U256(value))
87    }
88}
89
90impl Amount {
91    pub fn checked_add(self, rhs: Self) -> Option<Self> {
92        self.0.checked_add(rhs.0).map(Self)
93    }
94
95    pub fn checked_sub(self, rhs: Self) -> Option<Self> {
96        self.0.checked_sub(rhs.0).map(Self)
97    }
98}
99
100impl AsRef<U256> for Amount {
101    fn as_ref(&self) -> &U256 {
102        &self.0
103    }
104}
105
106impl FromStr for Amount {
107    type Err = DecodingError;
108
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        let amount = U256::from_dec_str(s).map_err(|e| {
111            DecodingError::invalid_raw_data(format!("amount could not be parsed as a U256: {e}"))
112        })?;
113
114        Ok(Self(amount))
115    }
116}
117
118impl From<u64> for Amount {
119    fn from(v: u64) -> Self {
120        Self(v.into())
121    }
122}
123
124#[cfg(feature = "serde")]
125fn deserialize<'de, D>(deserializer: D) -> Result<U256, D::Error>
126where
127    D: serde::Deserializer<'de>,
128{
129    use serde::Deserialize;
130    U256::from_dec_str(<String>::deserialize(deserializer)?.as_str())
131        .map_err(serde::de::Error::custom)
132}
133
134#[cfg(feature = "arbitrary")]
135fn arb_u256(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<U256> {
136    let parts: [u8; 32] = arbitrary::Arbitrary::arbitrary(u)?;
137    Ok(U256::from_big_endian(&parts))
138}
139
140#[cfg(test)]
141mod tests {
142    use super::Amount;
143
144    #[cfg(feature = "serde")]
145    #[test]
146    fn serde_amount() {
147        let value = Amount::from(42);
148        let string = serde_json::to_string(&value).expect("can serde string");
149        assert_eq!(string, "\"42\"");
150        let binary = serde_json::to_vec(&value).expect("can serde binary");
151        let de: Amount = serde_json::from_slice(binary.as_ref()).expect("can deserialize");
152        assert_eq!(de, value);
153    }
154
155    #[cfg(feature = "borsh")]
156    #[test]
157    fn borsh_amount() {
158        let value = Amount::from(42);
159        let serialized = borsh::to_vec(&value).unwrap();
160
161        // Amount is supposed to be a U256 according to the spec, which is 32 bytes
162        assert_eq!(serialized.len(), 32);
163
164        let value_deserialized = borsh::from_slice::<Amount>(&serialized).unwrap();
165
166        assert_eq!(value, value_deserialized);
167    }
168}