#![no_std]
#![warn(missing_docs)]
#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")]
#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")]
pub use gear_ss58::Ss58Address;
pub use nonzero_u256::NonZeroU256;
pub use primitive_types::{H160, H256, U256};
mod macros;
mod nonzero_u256;
mod utils;
use core::{
fmt,
str::{self, FromStr},
};
use derive_more::{AsMut, AsRef, Display, From, Into};
use gear_ss58::RawSs58Address;
#[cfg(feature = "codec")]
use scale_info::{
scale::{self, Decode, Encode, MaxEncodedLen},
TypeInfo,
};
#[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
pub enum ConversionError {
#[display(fmt = "Slice should be 32 length")]
InvalidSliceLength,
#[display(fmt = "Invalid hex string")]
InvalidHexString,
#[display(fmt = "Invalid SS58 address")]
InvalidSs58Address,
#[display(fmt = "SS58 encoding failed")]
Ss58Encode,
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, From, Into)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
pub struct MessageHandle(u32);
#[derive(Clone, Copy, Default, Hash, Ord, PartialEq, PartialOrd, Eq, From, Into, AsRef, AsMut)]
#[as_ref(forward)]
#[as_mut(forward)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
pub struct ActorId([u8; 32]);
macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 try_from_slice debug, ActorId);
impl ActorId {
pub fn to_ss58check(&self) -> Result<Ss58Address, ConversionError> {
RawSs58Address::from(self.0)
.to_ss58check()
.map_err(|_| ConversionError::Ss58Encode)
}
pub fn to_ss58check_with_version(&self, version: u16) -> Result<Ss58Address, ConversionError> {
RawSs58Address::from(self.0)
.to_ss58check_with_prefix(version)
.map_err(|_| ConversionError::Ss58Encode)
}
pub fn to_address_lossy(&self) -> H160 {
let mut h160 = H160::zero();
h160.0.copy_from_slice(&self.into_bytes()[12..]);
h160
}
}
impl From<H160> for ActorId {
fn from(h160: H160) -> Self {
let mut actor_id = Self::zero();
actor_id.0[12..].copy_from_slice(h160.as_ref());
actor_id
}
}
impl fmt::Display for ActorId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let byte_array = utils::ByteArray(&self.0);
let is_alternate = f.alternate();
if is_alternate {
f.write_str(concat!(stringify!(ActorId), "("))?;
}
let sign_plus = f.sign_plus();
let width = f.width();
if sign_plus && width.is_some() {
return Err(fmt::Error);
}
let version = if sign_plus {
Some(gear_ss58::VARA_SS58_PREFIX)
} else if let Some(version) = width {
Some(version.try_into().map_err(|_| fmt::Error)?)
} else {
None
};
if let Some(version) = version {
let address = self
.to_ss58check_with_version(version)
.map_err(|_| fmt::Error)?;
let address_str = address.as_str();
let len = address.as_str().len();
let median = (len + 1) / 2;
let mut e1 = median;
let mut s2 = median;
if let Some(precision) = f.precision() {
if precision < median {
e1 = precision;
s2 = len - precision;
}
}
let p1 = &address_str[..e1];
let p2 = &address_str[s2..];
let sep = e1.ne(&s2).then_some("..").unwrap_or_default();
write!(f, "{p1}{sep}{p2}")?;
} else {
byte_array.fmt(f)?;
}
if is_alternate {
f.write_str(")")?;
}
Ok(())
}
}
impl FromStr for ActorId {
type Err = ConversionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let actod_id = if let Some(s) = s.strip_prefix("0x") {
if s.len() != 64 {
return Err(ConversionError::InvalidHexString);
}
let mut actor_id = Self::zero();
hex::decode_to_slice(s, &mut actor_id.0)
.map_err(|_| ConversionError::InvalidHexString)?;
actor_id
} else {
let raw_address = RawSs58Address::from_ss58check(s)
.map_err(|_| ConversionError::InvalidSs58Address)?
.into();
Self::new(raw_address)
};
Ok(actod_id)
}
}
#[cfg(feature = "serde")]
impl Serialize for ActorId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let address = self
.to_ss58check_with_version(gear_ss58::VARA_SS58_PREFIX)
.map_err(serde::ser::Error::custom)?;
serializer.serialize_str(address.as_str())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for ActorId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ActorIdVisitor;
impl<'de> de::Visitor<'de> for ActorIdVisitor {
type Value = ActorId;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string in SS58 format")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let raw_address = RawSs58Address::from_ss58check(value)
.map_err(|_| de::Error::invalid_value(de::Unexpected::Str(value), &self))?
.into();
Ok(Self::Value::new(raw_address))
}
}
deserializer.deserialize_identifier(ActorIdVisitor)
}
}
#[derive(Clone, Copy, Default, Hash, Ord, PartialEq, PartialOrd, Eq, From, Into, AsRef, AsMut)]
#[as_ref(forward)]
#[as_mut(forward)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
pub struct MessageId([u8; 32]);
macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 from_str display debug serde, MessageId);
#[derive(Clone, Copy, Default, Hash, Ord, PartialEq, PartialOrd, Eq, From, Into, AsRef, AsMut)]
#[as_ref(forward)]
#[as_mut(forward)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
pub struct CodeId([u8; 32]);
macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 from_str try_from_slice display debug serde, CodeId);
#[derive(Clone, Copy, Default, Hash, Ord, PartialEq, PartialOrd, Eq, From, Into, AsRef, AsMut)]
#[as_ref(forward)]
#[as_mut(forward)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, Decode, MaxEncodedLen), codec(crate = scale))]
pub struct ReservationId([u8; 32]);
macros::impl_primitive!(new zero into_bytes from_u64 from_h256 into_h256 from_str display debug serde, ReservationId);
#[cfg(test)]
mod tests {
extern crate alloc;
use crate::{ActorId, H160};
use alloc::format;
use core::str::FromStr;
fn actor_id() -> ActorId {
ActorId::from_str("0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e")
.unwrap()
}
#[test]
#[should_panic]
fn duplicate_version_in_actor_id_fmt_test() {
let id = actor_id();
let _ = format!("{id:+42}");
}
#[test]
fn formatting_test() {
let id = actor_id();
assert_eq!(
format!("{id:?}"),
"0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
);
assert_eq!(format!("{id:.0?}"), "0x..");
assert_eq!(format!("{id:.1?}"), "0x6a..3e");
assert_eq!(format!("{id:.2?}"), "0x6a51..ed3e");
assert_eq!(format!("{id:.4?}"), "0x6a519a19..f765ed3e");
assert_eq!(
format!("{id:.15?}"),
"0x6a519a19ffdfd8f45c310b44aecf15..0c713bf841a8cb695b0ea5f765ed3e"
);
assert_eq!(
format!("{id:.30?}"),
"0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e"
);
assert_eq!(
format!("{id:+}"),
"kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
);
assert_eq!(
format!("{id:42}"),
"5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD"
);
assert_eq!(format!("{id:+.0}"), "..");
assert_eq!(format!("{id:+.1}"), "k..m");
assert_eq!(format!("{id:+.2}"), "kG..Sm");
assert_eq!(format!("{id:+.4}"), "kGhw..6PSm");
assert_eq!(format!("{id:+.15}"), "kGhwPiWGsCZkaUN..APCSDJM2uJv6PSm");
assert_eq!(
format!("{id:+.25}"),
"kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm"
);
assert_eq!(
format!("{id:#}"),
"ActorId(0x6a519a19ffdfd8f45c310b44aecf156b080c713bf841a8cb695b0ea5f765ed3e)"
);
assert_eq!(format!("{id:#.2}"), "ActorId(0x6a51..ed3e)");
assert_eq!(format!("{id:+#.2}"), "ActorId(kG..Sm)");
assert_eq!(
format!("{id:+#}"),
"ActorId(kGhwPiWGsCZkaUNqotftspabNLRTcNoMe5APCSDJM2uJv6PSm)"
);
assert_eq!(
format!("{id:#42}"),
"ActorId(5EU7B2s4m2XrgSbUyt8U92fDpSi2EtW3Z3kKwUW4drZ1KAZD)"
);
}
#[test]
fn actor_id_from_slice_error_implementation() {
let bytes = "foobar";
let result: Result<ActorId, _> = bytes.as_bytes().try_into();
assert!(result.is_err());
}
#[test]
fn actor_id_ethereum_address() {
let address: H160 = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
.parse()
.unwrap();
assert_eq!(
format!("{address:?}"),
"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
);
let actor_id: ActorId = address.into();
assert_eq!(
format!("{actor_id}"),
"0x00000000000000000000000095222290dd7278aa3ddd389cc1e1d165cc4bafe5"
);
let address = actor_id.to_address_lossy();
assert_eq!(
format!("{address:?}"),
"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
);
}
}