use std::{
fmt,
time::{Duration, SystemTime},
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{
doc_scalar,
identifiers::{ApplicationId, Destination, GenericApplicationId},
};
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug)]
pub struct Amount(u128);
#[derive(Serialize, Deserialize)]
#[serde(rename = "Amount")]
struct AmountString(String);
#[derive(Serialize, Deserialize)]
#[serde(rename = "Amount")]
struct AmountU128(u128);
impl Serialize for Amount {
fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
AmountString(self.to_string()).serialize(serializer)
} else {
AmountU128(self.0).serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for Amount {
fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let AmountString(s) = AmountString::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
} else {
Ok(Amount(AmountU128::deserialize(deserializer)?.0))
}
}
}
#[derive(
Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Serialize, Deserialize,
)]
#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
pub struct BlockHeight(pub u64);
#[derive(
Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Serialize, Deserialize,
)]
pub enum Round {
#[default]
Fast,
MultiLeader(u32),
SingleLeader(u32),
}
#[derive(
Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Serialize, Deserialize,
)]
pub struct Timestamp(u64);
impl Timestamp {
pub fn now() -> Timestamp {
Timestamp(
SystemTime::UNIX_EPOCH
.elapsed()
.expect("system time should be after Unix epoch")
.as_micros()
.try_into()
.unwrap_or(u64::MAX),
)
}
pub fn micros(&self) -> u64 {
self.0
}
pub fn saturating_diff_micros(&self, other: Timestamp) -> u64 {
self.0.saturating_sub(other.0)
}
pub fn duration_since(&self, other: Timestamp) -> Duration {
Duration::from_micros(self.saturating_diff_micros(other))
}
pub fn saturating_add(&self, duration: Duration) -> Timestamp {
let micros = u64::try_from(duration.as_micros()).unwrap_or(u64::MAX);
Timestamp(self.0.saturating_add(micros))
}
pub fn saturating_add_micros(&self, micros: u64) -> Timestamp {
Timestamp(self.0.saturating_add(micros))
}
pub fn saturating_sub_micros(&self, micros: u64) -> Timestamp {
Timestamp(self.0.saturating_sub(micros))
}
}
impl From<u64> for Timestamp {
fn from(t: u64) -> Timestamp {
Timestamp(t)
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(date_time) = chrono::DateTime::from_timestamp(
(self.0 / 1_000_000) as i64,
((self.0 % 1_000_000) * 1_000) as u32,
) {
return date_time.naive_utc().fmt(f);
}
self.0.fmt(f)
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Resources {
pub fuel: u64,
pub read_operations: u32,
pub write_operations: u32,
pub bytes_to_read: u32,
pub bytes_to_write: u32,
pub messages: u32,
pub message_size: u32,
pub storage_size_delta: u32,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(with_testing, derive(Eq, PartialEq))]
pub struct SendMessageRequest<Message> {
pub destination: Destination,
pub authenticated: bool,
pub is_tracked: bool,
pub grant: Resources,
pub message: Message,
}
impl<Message> SendMessageRequest<Message>
where
Message: Serialize,
{
pub fn into_raw(self) -> SendMessageRequest<Vec<u8>> {
let message = bcs::to_bytes(&self.message).expect("Failed to serialize message");
SendMessageRequest {
destination: self.destination,
authenticated: self.authenticated,
is_tracked: self.is_tracked,
grant: self.grant,
message,
}
}
}
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum ArithmeticError {
#[error("Number overflow")]
Overflow,
#[error("Number underflow")]
Underflow,
}
macro_rules! impl_wrapped_number {
($name:ident, $wrapped:ident) => {
impl $name {
pub const ZERO: Self = Self(0);
pub const MAX: Self = Self($wrapped::MAX);
pub fn try_add(self, other: Self) -> Result<Self, ArithmeticError> {
let val = self
.0
.checked_add(other.0)
.ok_or(ArithmeticError::Overflow)?;
Ok(Self(val))
}
pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
Ok(Self(val))
}
pub fn saturating_add(self, other: Self) -> Self {
let val = self.0.saturating_add(other.0);
Self(val)
}
pub fn try_sub(self, other: Self) -> Result<Self, ArithmeticError> {
let val = self
.0
.checked_sub(other.0)
.ok_or(ArithmeticError::Underflow)?;
Ok(Self(val))
}
pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
Ok(Self(val))
}
pub fn saturating_sub(self, other: Self) -> Self {
let val = self.0.saturating_sub(other.0);
Self(val)
}
pub fn try_add_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
self.0 = self
.0
.checked_add(other.0)
.ok_or(ArithmeticError::Overflow)?;
Ok(())
}
pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
Ok(())
}
pub fn saturating_add_assign(&mut self, other: Self) {
self.0 = self.0.saturating_add(other.0);
}
pub fn try_sub_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
self.0 = self
.0
.checked_sub(other.0)
.ok_or(ArithmeticError::Underflow)?;
Ok(())
}
pub fn saturating_mul(&self, other: $wrapped) -> Self {
Self(self.0.saturating_mul(other))
}
pub fn try_mul(self, other: $wrapped) -> Result<Self, ArithmeticError> {
let val = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
Ok(Self(val))
}
pub fn try_mul_assign(&mut self, other: $wrapped) -> Result<(), ArithmeticError> {
self.0 = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
Ok(())
}
}
impl From<$name> for $wrapped {
fn from(value: $name) -> Self {
value.0
}
}
#[cfg(with_testing)]
impl From<$wrapped> for $name {
fn from(value: $wrapped) -> Self {
Self(value)
}
}
#[cfg(with_testing)]
impl std::ops::Add for $name {
type Output = Self;
fn add(self, other: Self) -> Self {
Self(self.0 + other.0)
}
}
#[cfg(with_testing)]
impl std::ops::Sub for $name {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self(self.0 - other.0)
}
}
#[cfg(with_testing)]
impl std::ops::Mul<$wrapped> for $name {
type Output = Self;
fn mul(self, other: $wrapped) -> Self {
Self(self.0 * other)
}
}
};
}
impl TryFrom<BlockHeight> for usize {
type Error = ArithmeticError;
fn try_from(height: BlockHeight) -> Result<usize, ArithmeticError> {
usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)
}
}
#[cfg(not(with_testing))]
impl From<u64> for BlockHeight {
fn from(value: u64) -> Self {
Self(value)
}
}
impl_wrapped_number!(Amount, u128);
impl_wrapped_number!(BlockHeight, u64);
impl fmt::Display for Amount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let places = Amount::DECIMAL_PLACES as usize;
let min_digits = places + 1;
let decimals = format!("{:0min_digits$}", self.0);
let integer_part = &decimals[..(decimals.len() - places)];
let fractional_part = decimals[(decimals.len() - places)..].trim_end_matches('0');
let precision = f.precision().unwrap_or(0).max(fractional_part.len());
let sign = if f.sign_plus() && self.0 > 0 { "+" } else { "" };
let pad_width = f.width().map_or(0, |w| {
w.saturating_sub(precision)
.saturating_sub(sign.len() + integer_part.len() + 1)
});
let left_pad = match f.align() {
None | Some(fmt::Alignment::Right) => pad_width,
Some(fmt::Alignment::Center) => pad_width / 2,
Some(fmt::Alignment::Left) => 0,
};
for _ in 0..left_pad {
write!(f, "{}", f.fill())?;
}
write!(f, "{sign}{integer_part}.{fractional_part:0<precision$}")?;
for _ in left_pad..pad_width {
write!(f, "{}", f.fill())?;
}
Ok(())
}
}
#[derive(Error, Debug)]
#[allow(missing_docs)]
pub enum ParseAmountError {
#[error("cannot parse amount")]
Parse,
#[error("cannot represent amount: number too high")]
TooHigh,
#[error("cannot represent amount: too many decimal places after the point")]
TooManyDigits,
}
impl std::str::FromStr for Amount {
type Err = ParseAmountError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
let mut result: u128 = 0;
let mut decimals: Option<u8> = None;
let mut chars = src.trim().chars().peekable();
if chars.peek() == Some(&'+') {
chars.next();
}
for char in chars {
match char {
'_' => {}
'.' if decimals.is_some() => return Err(ParseAmountError::Parse),
'.' => decimals = Some(Amount::DECIMAL_PLACES),
char => {
let digit = u128::from(char.to_digit(10).ok_or(ParseAmountError::Parse)?);
if let Some(d) = &mut decimals {
*d = d.checked_sub(1).ok_or(ParseAmountError::TooManyDigits)?;
}
result = result
.checked_mul(10)
.and_then(|r| r.checked_add(digit))
.ok_or(ParseAmountError::TooHigh)?;
}
}
}
result = result
.checked_mul(10u128.pow(decimals.unwrap_or(Amount::DECIMAL_PLACES) as u32))
.ok_or(ParseAmountError::TooHigh)?;
Ok(Amount(result))
}
}
impl fmt::Display for BlockHeight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::str::FromStr for BlockHeight {
type Err = std::num::ParseIntError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
Ok(Self(u64::from_str(src)?))
}
}
impl fmt::Display for Round {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Round::Fast => write!(f, "fast round"),
Round::MultiLeader(r) => write!(f, "multi-leader round {}", r),
Round::SingleLeader(r) => write!(f, "single-leader round {}", r),
}
}
}
impl Round {
pub fn is_multi_leader(&self) -> bool {
matches!(self, Round::MultiLeader(_))
}
pub fn is_fast(&self) -> bool {
matches!(self, Round::Fast)
}
pub fn number(&self) -> u32 {
match self {
Round::Fast => 0,
Round::MultiLeader(r) | Round::SingleLeader(r) => *r,
}
}
pub fn type_name(&self) -> &'static str {
match self {
Round::Fast => "fast",
Round::MultiLeader(_) => "multi",
Round::SingleLeader(_) => "single",
}
}
}
impl<'a> std::iter::Sum<&'a Amount> for Amount {
fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
iter.fold(Self::ZERO, |a, b| a.saturating_add(*b))
}
}
impl Amount {
pub const DECIMAL_PLACES: u8 = 18;
pub const ONE: Amount = Amount(10u128.pow(Amount::DECIMAL_PLACES as u32));
pub fn from_tokens(tokens: u128) -> Amount {
Self::ONE.saturating_mul(tokens)
}
pub fn from_millis(millitokens: u128) -> Amount {
Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 3)).saturating_mul(millitokens)
}
pub fn from_micros(microtokens: u128) -> Amount {
Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 6)).saturating_mul(microtokens)
}
pub fn from_nanos(nanotokens: u128) -> Amount {
Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 9)).saturating_mul(nanotokens)
}
pub fn from_attos(attotokens: u128) -> Amount {
Amount(attotokens)
}
pub fn upper_half(self) -> u64 {
(self.0 >> 64) as u64
}
pub fn lower_half(self) -> u64 {
self.0 as u64
}
pub fn saturating_div(self, other: Amount) -> u128 {
self.0.checked_div(other.0).unwrap_or(u128::MAX)
}
}
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct ApplicationPermissions {
pub execute_operations: Option<Vec<ApplicationId>>,
pub close_chain: Vec<ApplicationId>,
}
impl ApplicationPermissions {
pub fn new_single(app_id: ApplicationId) -> Self {
Self {
execute_operations: Some(vec![app_id]),
close_chain: vec![app_id],
}
}
pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
match (app_id, &self.execute_operations) {
(_, None) => true,
(GenericApplicationId::System, Some(_)) => false,
(GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
}
}
pub fn can_close_chain(&self, app_id: &ApplicationId) -> bool {
self.close_chain.contains(app_id)
}
}
doc_scalar!(Amount, "A non-negative amount of tokens.");
doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
doc_scalar!(
Timestamp,
"A timestamp, in microseconds since the Unix epoch"
);
doc_scalar!(
Round,
"A number to identify successive attempts to decide a value in a consensus protocol."
);
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::Amount;
#[test]
fn display_amount() {
assert_eq!("1.", Amount::ONE.to_string());
assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
assert_eq!(
Amount(10_000_000_000_000_000_000),
Amount::from_str("10").unwrap()
);
assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string(),);
assert_eq!(
"1001.3",
(Amount::from_str("1.1")
.unwrap()
.saturating_add(Amount::from_str("1_000.2").unwrap()))
.to_string()
);
assert_eq!(
" 1.00000000000000000000",
format!("{:25.20}", Amount::ONE)
);
assert_eq!(
"~+12.34~~",
format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
);
}
}