#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
#![doc(html_logo_url = "https://gear-tech.io/logo.png")]
#![doc(html_favicon_url = "https://gear-tech.io/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate alloc;
use bytemuck::{Pod, Zeroable};
pub use gear_ss58::Ss58Address;
pub use nonzero_u256::NonZeroU256;
pub use primitive_types::{H160, H256, U256};
pub mod utils;
mod macros;
mod nonzero_u256;
#[cfg(feature = "ethexe")]
mod sol_types;
use core::{
fmt,
str::{self, FromStr},
};
use derive_more::{AsMut, AsRef, From, Into};
use gear_ss58::RawSs58Address;
#[cfg(feature = "codec")]
use scale_decode::DecodeAsType;
#[cfg(feature = "codec")]
use scale_encode::EncodeAsType;
#[cfg(feature = "codec")]
use scale_info::{
TypeInfo,
scale::{self, Decode, Encode, MaxEncodedLen},
};
#[cfg(all(feature = "serde", not(feature = "ethexe")))]
use serde::de;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum ConversionError {
#[error("Slice should be 32 length")]
InvalidSliceLength,
#[error("Invalid hex string")]
InvalidHexString,
#[error("Invalid SS58 address")]
InvalidSs58Address,
#[error("SS58 encoding failed")]
Ss58Encode,
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, From, Into, Zeroable, Pod)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, MaxEncodedLen), codec(crate = scale))]
pub struct MessageHandle(u32);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Default,
Hash,
Ord,
PartialEq,
PartialOrd,
Eq,
From,
Into,
AsRef,
AsMut,
Zeroable,
Pod,
)]
#[as_ref(forward)]
#[as_mut(forward)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, MaxEncodedLen), codec(crate = scale))]
pub struct ActorId([u8; 32]);
macros::impl_primitive!(new zero into_bytes 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<u64> for ActorId {
fn from(value: u64) -> Self {
let mut id = Self::zero();
id.0[12..20].copy_from_slice(&value.to_le_bytes()[..]);
id
}
}
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 TryInto<H160> for ActorId {
type Error = &'static str;
fn try_into(self) -> Result<H160, Self::Error> {
if !self.0[..12].iter().all(|i| i.eq(&0)) {
Err("ActorId has non-zero prefix")
} else {
let mut h160 = H160::zero();
h160.0.copy_from_slice(&self.into_bytes()[12..]);
Ok(h160)
}
}
}
impl fmt::Display for ActorId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let byte_array = utils::ByteSliceFormatter::Array(&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.div_ceil(2);
let mut e1 = median;
let mut s2 = median;
if let Some(precision) = f.precision()
&& precision < median
{
e1 = precision;
s2 = len - precision;
}
let p1 = &address_str[..e1];
let p2 = &address_str[s2..];
let sep = if e1.ne(&s2) { ".." } else { Default::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(all(feature = "serde", not(feature = "ethexe")))]
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(all(feature = "serde", feature = "ethexe"))]
impl Serialize for ActorId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let id: H160 = self.to_address_lossy();
id.serialize(serializer)
}
}
#[cfg(all(feature = "serde", not(feature = "ethexe")))]
impl<'de> Deserialize<'de> for ActorId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ActorIdVisitor;
impl de::Visitor<'_> 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)
}
}
#[cfg(all(feature = "serde", feature = "ethexe"))]
impl<'de> Deserialize<'de> for ActorId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let str = String::deserialize(deserializer)?;
let hex_str = str.strip_prefix("0x").unwrap_or(&str);
let bytes = hex::decode(hex_str)
.map_err(|e| serde::de::Error::custom(format!("Invalid hex: {e}")))?;
if bytes.len() != 20 {
return Err(serde::de::Error::custom(format!(
"Expected 20 bytes, got {}",
bytes.len()
)));
}
let mut actor_id = [0u8; 32];
actor_id[12..].copy_from_slice(&bytes);
Ok(ActorId(actor_id))
}
}
#[repr(transparent)]
#[derive(
Clone,
Copy,
Default,
Hash,
Ord,
PartialEq,
PartialOrd,
Eq,
From,
Into,
AsRef,
AsMut,
Zeroable,
Pod,
)]
#[as_ref(forward)]
#[as_mut(forward)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, 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);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Default,
Hash,
Ord,
PartialEq,
PartialOrd,
Eq,
From,
Into,
AsRef,
AsMut,
Zeroable,
Pod,
)]
#[as_ref(forward)]
#[as_mut(forward)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, 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);
#[repr(transparent)]
#[derive(
Clone,
Copy,
Default,
Hash,
Ord,
PartialEq,
PartialOrd,
Eq,
From,
Into,
AsRef,
AsMut,
Zeroable,
Pod,
)]
#[as_ref(forward)]
#[as_mut(forward)]
#[cfg_attr(feature = "codec", derive(TypeInfo, Encode, EncodeAsType, Decode, DecodeAsType, 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"
);
}
}