use snarkvm_utilities::{FromBytes, ToBytes};
use serde::{Deserialize, Serialize};
use std::{
fmt,
io::{Read, Result as IoResult, Write},
iter::Sum,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AleoAmount(pub i64);
pub enum Denomination {
BYTE,
GATE,
CREDIT,
}
impl Denomination {
fn precision(self) -> u32 {
match self {
Denomination::BYTE => 0,
Denomination::GATE => 3,
Denomination::CREDIT => 6,
}
}
}
impl fmt::Display for Denomination {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match self {
Denomination::BYTE => "AB",
Denomination::GATE => "AG",
Denomination::CREDIT => "ALEO",
})
}
}
impl AleoAmount {
pub const ONE_BYTE: AleoAmount = AleoAmount(1i64);
pub const ONE_CREDIT: AleoAmount = AleoAmount(1_000_000i64);
pub const ONE_GATE: AleoAmount = AleoAmount(1_000i64);
pub const ZERO: AleoAmount = AleoAmount(0i64);
pub fn from_i64(bytes: i64) -> Self {
Self(bytes)
}
pub fn from_gates(gate_value: i64) -> Self {
Self::from_i64(gate_value * 10_i64.pow(Denomination::GATE.precision()))
}
pub fn from_aleo(aleo_value: i64) -> Self {
Self::from_i64(aleo_value * 10_i64.pow(Denomination::CREDIT.precision()))
}
#[allow(clippy::should_implement_trait)]
pub fn add(self, b: Self) -> Self {
Self::from_i64(self.0 + b.0)
}
#[allow(clippy::should_implement_trait)]
pub fn sub(self, b: AleoAmount) -> Self {
Self::from_i64(self.0 - b.0)
}
pub const fn is_positive(self) -> bool {
self.0.is_positive()
}
pub const fn is_negative(self) -> bool {
self.0.is_negative()
}
pub const fn is_zero(self) -> bool {
self.0 == 0
}
pub const fn as_i64(&self) -> i64 {
self.0
}
}
impl Sum for AleoAmount {
fn sum<I: Iterator<Item = AleoAmount>>(iter: I) -> AleoAmount {
iter.fold(AleoAmount::ZERO, |a, b| a.add(b))
}
}
impl ToBytes for AleoAmount {
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
self.0.write_le(&mut writer)
}
}
impl FromBytes for AleoAmount {
#[inline]
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
let amount: i64 = FromBytes::read_le(&mut reader)?;
Ok(Self(amount))
}
}
impl fmt::Display for AleoAmount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_from_i64(byte_value: i64, expected_amount: AleoAmount) {
let amount = AleoAmount::from_i64(byte_value);
assert_eq!(expected_amount, amount)
}
fn test_from_gate(gate_value: i64, expected_amount: AleoAmount) {
let amount = AleoAmount::from_gates(gate_value);
assert_eq!(expected_amount, amount)
}
fn test_from_aleo(aleo_value: i64, expected_amount: AleoAmount) {
let amount = AleoAmount::from_aleo(aleo_value);
assert_eq!(expected_amount, amount)
}
fn test_addition(a: &i64, b: &i64, result: &i64) {
let a = AleoAmount::from_i64(*a);
let b = AleoAmount::from_i64(*b);
let result = AleoAmount::from_i64(*result);
assert_eq!(result, a.add(b));
}
fn test_subtraction(a: &i64, b: &i64, result: &i64) {
let a = AleoAmount::from_i64(*a);
let b = AleoAmount::from_i64(*b);
let result = AleoAmount::from_i64(*result);
assert_eq!(result, a.sub(b));
}
pub(crate) struct AmountDenominationTestCase {
byte: i64,
gate: i64,
aleo: i64,
}
mod valid_conversions {
use super::*;
const TEST_AMOUNTS: [AmountDenominationTestCase; 5] = [
AmountDenominationTestCase {
byte: 0,
gate: 0,
aleo: 0,
},
AmountDenominationTestCase {
byte: 1_000_000,
gate: 1_000,
aleo: 1,
},
AmountDenominationTestCase {
byte: 1_000_000_000,
gate: 1_000_000,
aleo: 1_000,
},
AmountDenominationTestCase {
byte: 1_234_567_000_000_000,
gate: 1_234_567_000_000,
aleo: 1_234_567_000,
},
AmountDenominationTestCase {
byte: 1_000_000_000_000_000_000,
gate: 1_000_000_000_000_000,
aleo: 1_000_000_000_000,
},
];
#[test]
fn test_byte_conversion() {
TEST_AMOUNTS
.iter()
.for_each(|amounts| test_from_i64(amounts.byte, AleoAmount(amounts.byte)));
}
#[test]
fn test_gate_conversion() {
TEST_AMOUNTS
.iter()
.for_each(|amounts| test_from_gate(amounts.gate, AleoAmount(amounts.byte)));
}
#[test]
fn test_aleo_conversion() {
TEST_AMOUNTS
.iter()
.for_each(|amounts| test_from_aleo(amounts.aleo, AleoAmount(amounts.byte)));
}
}
mod valid_arithmetic {
use super::*;
const TEST_VALUES: [(i64, i64, i64); 7] = [
(0, 0, 0),
(1, 2, 3),
(100000, 0, 100000),
(123456789, 987654321, 1111111110),
(100000000000000, 1000000000000000, 1100000000000000),
(-100000000000000, -1000000000000000, -1100000000000000),
(100000000000000, -100000000000000, 0),
];
#[test]
fn test_valid_addition() {
TEST_VALUES.iter().for_each(|(a, b, c)| test_addition(a, b, c));
}
#[test]
fn test_valid_subtraction() {
TEST_VALUES.iter().for_each(|(a, b, c)| test_subtraction(c, b, a));
}
}
mod test_invalid {
use super::*;
mod test_invalid_conversion {
use super::*;
const INVALID_TEST_AMOUNTS: [AmountDenominationTestCase; 4] = [
AmountDenominationTestCase {
byte: 1,
gate: 1,
aleo: 1,
},
AmountDenominationTestCase {
byte: 10,
gate: 10000,
aleo: 100000000,
},
AmountDenominationTestCase {
byte: 1234567,
gate: 123,
aleo: 1,
},
AmountDenominationTestCase {
byte: 1_000_000_000_000_000_000,
gate: 1_000_000_000_000_000,
aleo: 999_999_999_999,
},
];
#[should_panic]
#[test]
fn test_invalid_gate_conversion() {
INVALID_TEST_AMOUNTS
.iter()
.for_each(|amounts| test_from_gate(amounts.gate, AleoAmount(amounts.byte)));
}
#[should_panic]
#[test]
fn test_invalid_aleo_conversion() {
INVALID_TEST_AMOUNTS
.iter()
.for_each(|amounts| test_from_aleo(amounts.aleo, AleoAmount(amounts.byte)));
}
}
mod invalid_arithmetic {
use super::*;
const TEST_VALUES: [(i64, i64, i64); 5] = [
(0, 0, 1),
(1, 2, 5),
(100000, 1, 100000),
(123456789, 123456789, 123456789),
(-1000, -1000, 2000),
];
#[should_panic]
#[test]
fn test_invalid_addition() {
TEST_VALUES.iter().for_each(|(a, b, c)| test_addition(a, b, c));
}
#[should_panic]
#[test]
fn test_invalid_subtraction() {
TEST_VALUES.iter().for_each(|(a, b, c)| test_subtraction(a, b, c));
}
}
}
}