avalanche_types/key/secp256k1/txs/
transfer.rs

1use std::{
2    cmp::Ordering,
3    io::{self, Error, ErrorKind},
4};
5
6use crate::{codec, key};
7use serde::{Deserialize, Serialize};
8
9/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOutput>
10/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableOut>
11/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferOutput>
12/// ref. <https://docs.avax.network/apis/avalanchego/apis/p-chain#platformgettx>
13#[derive(Debug, Serialize, Deserialize, Eq, Clone, Default)]
14pub struct Output {
15    pub amount: u64,
16
17    /// The custom de/serializer embeds "output_owners" at the same level as "amount" as in avalanchego.
18    #[serde(flatten)]
19    pub output_owners: key::secp256k1::txs::OutputOwners,
20}
21
22impl Output {
23    pub fn new(amount: u64, output_owners: key::secp256k1::txs::OutputOwners) -> Self {
24        Self {
25            amount,
26            output_owners,
27        }
28    }
29
30    pub fn type_name() -> String {
31        "secp256k1fx.TransferOutput".to_string()
32    }
33
34    pub fn type_id() -> u32 {
35        *(codec::X_TYPES.get(&Self::type_name()).unwrap()) as u32
36    }
37}
38
39/// RUST_LOG=debug cargo test --package avalanche-types --lib -- key::secp256k1::txs::transfer::test_transfer_output_custom_de_serializer --exact --show-output
40#[test]
41fn test_transfer_output_custom_de_serializer() {
42    use crate::ids::short;
43
44    let d = Output {
45        amount: 1234,
46        output_owners: key::secp256k1::txs::OutputOwners {
47            locktime: 1,
48            threshold: 2,
49            addresses: vec![short::Id::empty()],
50        },
51    };
52
53    let yaml_encoded = serde_yaml::to_string(&d).unwrap();
54    println!("yaml_encoded:\n{}", yaml_encoded);
55    let yaml_decoded = serde_yaml::from_str(&yaml_encoded).unwrap();
56    assert_eq!(d, yaml_decoded);
57
58    let json_encoded = serde_json::to_string(&d).unwrap();
59    println!("json_encoded:\n{}", json_encoded);
60    let json_decoded = serde_json::from_str(&json_encoded).unwrap();
61    assert_eq!(d, json_decoded);
62}
63
64impl Ord for Output {
65    fn cmp(&self, other: &Output) -> Ordering {
66        self.amount
67            .cmp(&(other.amount)) // returns when "amount"s are not Equal
68            .then_with(
69                || self.output_owners.cmp(&(other.output_owners)), // if "amount"s are Equal, compare "output_owners"
70            )
71    }
72}
73
74impl PartialOrd for Output {
75    fn partial_cmp(&self, other: &Output) -> Option<Ordering> {
76        Some(self.cmp(other))
77    }
78}
79
80impl PartialEq for Output {
81    fn eq(&self, other: &Output) -> bool {
82        self.cmp(other) == Ordering::Equal
83    }
84}
85
86/// RUST_LOG=debug cargo test --package avalanche-types --lib -- key::secp256k1::txs::transfer::test_sort_transfer_outputs --exact --show-output
87#[test]
88fn test_sort_transfer_outputs() {
89    use crate::ids::short;
90
91    let mut outputs: Vec<Output> = Vec::new();
92    for i in (0..10).rev() {
93        outputs.push(Output {
94            amount: i as u64,
95            output_owners: key::secp256k1::txs::OutputOwners {
96                locktime: (i + 1) as u64,
97                threshold: (i + 1) as u32,
98                addresses: vec![
99                    short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
100                    short::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5]),
101                ],
102            },
103        });
104        outputs.push(Output {
105            amount: i as u64,
106            output_owners: key::secp256k1::txs::OutputOwners {
107                locktime: (i + 1) as u64,
108                threshold: (i + 1) as u32,
109                addresses: vec![
110                    short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
111                    short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
112                ],
113            },
114        });
115        outputs.push(Output {
116            amount: i as u64,
117            output_owners: key::secp256k1::txs::OutputOwners {
118                locktime: (i + 1) as u64,
119                threshold: (i + 1) as u32,
120                addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
121            },
122        });
123        outputs.push(Output {
124            amount: i as u64,
125            output_owners: key::secp256k1::txs::OutputOwners {
126                locktime: (i + 1) as u64,
127                threshold: i as u32,
128                addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
129            },
130        });
131        outputs.push(Output {
132            amount: i as u64,
133            output_owners: key::secp256k1::txs::OutputOwners {
134                locktime: i as u64,
135                threshold: i as u32,
136                addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
137            },
138        });
139    }
140    assert!(!cmp_manager::is_sorted_and_unique(&outputs));
141    outputs.sort();
142
143    let mut sorted_outputs: Vec<Output> = Vec::new();
144    for i in 0..10 {
145        sorted_outputs.push(Output {
146            amount: i as u64,
147            output_owners: key::secp256k1::txs::OutputOwners {
148                locktime: i as u64,
149                threshold: i as u32,
150                addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
151            },
152        });
153        sorted_outputs.push(Output {
154            amount: i as u64,
155            output_owners: key::secp256k1::txs::OutputOwners {
156                locktime: (i + 1) as u64,
157                threshold: i as u32,
158                addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
159            },
160        });
161        sorted_outputs.push(Output {
162            amount: i as u64,
163            output_owners: key::secp256k1::txs::OutputOwners {
164                locktime: (i + 1) as u64,
165                threshold: (i + 1) as u32,
166                addresses: vec![short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5])],
167            },
168        });
169        sorted_outputs.push(Output {
170            amount: i as u64,
171            output_owners: key::secp256k1::txs::OutputOwners {
172                locktime: (i + 1) as u64,
173                threshold: (i + 1) as u32,
174                addresses: vec![
175                    short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
176                    short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
177                ],
178            },
179        });
180        sorted_outputs.push(Output {
181            amount: i as u64,
182            output_owners: key::secp256k1::txs::OutputOwners {
183                locktime: (i + 1) as u64,
184                threshold: (i + 1) as u32,
185                addresses: vec![
186                    short::Id::from_slice(&[i as u8, 1, 2, 3, 4, 5]),
187                    short::Id::from_slice(&[i as u8, 2, 2, 3, 4, 5]),
188                ],
189            },
190        });
191    }
192    assert!(cmp_manager::is_sorted_and_unique(&sorted_outputs));
193    assert_eq!(outputs, sorted_outputs);
194}
195
196/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableInput>
197/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/components/avax#TransferableIn>
198/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#TransferInput>
199/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/vms/secp256k1fx#Input>
200#[derive(Debug, Serialize, Deserialize, Eq, Clone, Default)]
201pub struct Input {
202    pub amount: u64,
203    #[serde(rename = "signatureIndices")]
204    pub sig_indices: Vec<u32>,
205}
206
207impl Input {
208    pub fn new(amount: u64, sig_indices: Vec<u32>) -> Self {
209        Self {
210            amount,
211            sig_indices,
212        }
213    }
214
215    pub fn type_name() -> String {
216        "secp256k1fx.TransferInput".to_string()
217    }
218
219    pub fn type_id() -> u32 {
220        *(codec::X_TYPES.get(&Self::type_name()).unwrap()) as u32
221    }
222
223    pub fn verify(&self) -> io::Result<()> {
224        if self.amount == 0 {
225            return Err(Error::new(
226                ErrorKind::InvalidInput,
227                "input has no value", // ref. "errNoValueInput"
228            ));
229        }
230        if !cmp_manager::is_sorted_and_unique(&self.sig_indices) {
231            return Err(Error::new(
232                ErrorKind::InvalidInput,
233                "signatures not sorted and unique", // ref. "errNotSortedUnique"
234            ));
235        }
236        Ok(())
237    }
238
239    /// ref. "vms/secp256k1fx.Input.Cost"
240    pub fn sig_costs(&self) -> u64 {
241        let sigs = self.sig_indices.len();
242        (sigs as u64) * 1000
243    }
244}
245
246impl Ord for Input {
247    fn cmp(&self, other: &Input) -> Ordering {
248        self.amount
249            .cmp(&(other.amount)) // returns when "amount"s are not Equal
250            .then_with(
251                || {
252                    key::secp256k1::txs::SigIndices::new(&self.sig_indices)
253                        .cmp(&key::secp256k1::txs::SigIndices::new(&other.sig_indices))
254                }, // if "amount"s are Equal, compare "sig_indices"
255            )
256    }
257}
258
259impl PartialOrd for Input {
260    fn partial_cmp(&self, other: &Input) -> Option<Ordering> {
261        Some(self.cmp(other))
262    }
263}
264
265impl PartialEq for Input {
266    fn eq(&self, other: &Input) -> bool {
267        self.cmp(other) == Ordering::Equal
268    }
269}
270
271/// RUST_LOG=debug cargo test --package avalanche-types --lib -- key::secp256k1::txs::transfer::test_sort_transfer_inputs --exact --show-output
272#[test]
273fn test_sort_transfer_inputs() {
274    let mut inputs: Vec<Input> = Vec::new();
275    for i in (0..10).rev() {
276        inputs.push(Input {
277            amount: 5,
278            sig_indices: vec![i as u32, 2, 2, 3, 4, 5, 6, 7, 8, 9],
279        });
280        inputs.push(Input {
281            amount: 5,
282            sig_indices: vec![i as u32, 1, 2, 3, 4, 5, 6, 7, 8, 9],
283        });
284        inputs.push(Input {
285            amount: 50,
286            sig_indices: vec![i as u32, 1, 2, 3, 4, 5],
287        });
288        inputs.push(Input {
289            amount: 5,
290            sig_indices: vec![i as u32, 1, 2, 3, 4, 5],
291        });
292        inputs.push(Input {
293            amount: 1,
294            sig_indices: vec![(i + 100) as u32, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
295        });
296    }
297    assert!(!cmp_manager::is_sorted_and_unique(&inputs));
298    inputs.sort();
299
300    let mut sorted_inputs: Vec<Input> = Vec::new();
301    for i in 0..10 {
302        sorted_inputs.push(Input {
303            amount: 1,
304            sig_indices: vec![(i + 100) as u32, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
305        });
306    }
307    for i in 0..10 {
308        sorted_inputs.push(Input {
309            amount: 5,
310            sig_indices: vec![i as u32, 1, 2, 3, 4, 5],
311        });
312    }
313    for i in 0..10 {
314        sorted_inputs.push(Input {
315            amount: 5,
316            sig_indices: vec![i as u32, 1, 2, 3, 4, 5, 6, 7, 8, 9],
317        });
318        sorted_inputs.push(Input {
319            amount: 5,
320            sig_indices: vec![i as u32, 2, 2, 3, 4, 5, 6, 7, 8, 9],
321        });
322    }
323    for i in 0..10 {
324        sorted_inputs.push(Input {
325            amount: 50,
326            sig_indices: vec![i as u32, 1, 2, 3, 4, 5],
327        });
328    }
329    assert!(cmp_manager::is_sorted_and_unique(&sorted_inputs));
330    assert_eq!(inputs, sorted_inputs);
331}