From b92d205d0f1ef0af1417f9e7f7d15cc8c7dd52cb Mon Sep 17 00:00:00 2001 From: Ivan Kalinin Date: Wed, 18 Oct 2023 16:00:14 +0200 Subject: [PATCH] Add `WithoutName` wrapper for abi --- src/abi/mod.rs | 88 +++++++++++++++++++++- src/abi/traits.rs | 169 ++++++++++++++++++++++++++++++++++++++++++- src/abi/ty.rs | 89 +++++++++++++++++++++++ src/abi/value/mod.rs | 57 ++++++++++++++- 4 files changed, 400 insertions(+), 3 deletions(-) diff --git a/src/abi/mod.rs b/src/abi/mod.rs index b583f60b..a2e419fa 100644 --- a/src/abi/mod.rs +++ b/src/abi/mod.rs @@ -1,5 +1,6 @@ //! Common ABI implementation. +use std::hash::{BuildHasher, Hash}; use std::str::FromStr; pub use self::contract::{ @@ -8,7 +9,7 @@ pub use self::contract::{ }; pub use self::signature::{extend_signature_with_id, sign_with_signature_id}; pub use self::traits::{ - FromAbi, FromPlainAbi, IntoAbi, IntoPlainAbi, WithAbiType, WithPlainAbiType, + FromAbi, FromPlainAbi, IgnoreName, IntoAbi, IntoPlainAbi, WithAbiType, WithPlainAbiType, }; pub use self::ty::{AbiHeaderType, AbiType, NamedAbiType, PlainAbiType}; pub use self::value::{AbiHeader, AbiValue, NamedAbiValue, PlainAbiValue}; @@ -75,3 +76,88 @@ impl std::fmt::Display for AbiVersion { write!(f, "{}.{}", self.major, self.minor) } } + +/// A wrapper around [`AbiType`], [`NamedAbiType`], [`AbiValue`] and [`NamedAbiValue`] +/// that implements hash/comparison traits without name. +#[repr(transparent)] +pub struct WithoutName(pub T); + +impl WithoutName { + /// Wraps a reference of the inner type. + pub fn wrap(value: &T) -> &Self { + // SAFETY: HashWithoutName is #[repr(transparent)] + unsafe { &*(value as *const T as *const Self) } + } + + /// Wraps a slice of the inner type. + pub fn wrap_slice(value: &[T]) -> &[Self] { + // SAFETY: HashWithoutName is #[repr(transparent)] + unsafe { &*(value as *const [T] as *const [Self]) } + } +} + +impl std::fmt::Debug for WithoutName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("WithoutName").field(&self.0).finish() + } +} + +impl Clone for WithoutName { + #[inline] + fn clone(&self) -> Self { + WithoutName(self.0.clone()) + } +} + +impl Eq for WithoutName where WithoutName: PartialEq {} + +impl PartialOrd for WithoutName +where + WithoutName: Ord, +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for WithoutName> +where + WithoutName: PartialEq, +{ + fn eq(&self, WithoutName(other): &Self) -> bool { + WithoutName::wrap_slice(self.0.as_slice()) == WithoutName::wrap_slice(other.as_slice()) + } +} + +impl PartialEq for WithoutName> +where + K: PartialEq, + WithoutName: PartialEq, +{ + fn eq(&self, WithoutName(other): &Self) -> bool { + self.0.len() == other.len() + && self.0.iter().zip(other).all(|((ak, av), (bk, bv))| { + (ak, WithoutName::wrap(av)) == (bk, WithoutName::wrap(bv)) + }) + } +} + +impl PartialEq for WithoutName> +where + K: Eq + Hash, + WithoutName: PartialEq, + S: BuildHasher, +{ + fn eq(&self, WithoutName(other): &Self) -> bool { + if self.0.len() != other.len() { + return false; + } + + self.0.iter().all(|(key, value)| { + other + .get(key) + .map_or(false, |v| WithoutName::wrap(value) == WithoutName::wrap(v)) + }) + } +} diff --git a/src/abi/traits.rs b/src/abi/traits.rs index d5f0f874..da81d35e 100644 --- a/src/abi/traits.rs +++ b/src/abi/traits.rs @@ -9,12 +9,154 @@ use bytes::Bytes; use num_bigint::{BigInt, BigUint}; use num_traits::ToPrimitive; -use super::{AbiType, AbiValue, NamedAbiType, NamedAbiValue, PlainAbiType, PlainAbiValue}; +use super::{ + AbiType, AbiValue, NamedAbiType, NamedAbiValue, PlainAbiType, PlainAbiValue, WithoutName, +}; use crate::cell::{Cell, HashBytes}; use crate::num::*; use crate::models::message::{IntAddr, StdAddr, VarAddr}; +/// ABI entity wrapper. +pub trait IgnoreName { + /// Wrapped ABI entity. + type Unnamed<'a> + where + Self: 'a; + + /// Wraps an ABI entity into [`WithoutName`]. + fn ignore_name(&self) -> Self::Unnamed<'_>; +} + +impl IgnoreName for &'_ T { + type Unnamed<'a> = T::Unnamed<'a> where Self: 'a; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + T::ignore_name(self) + } +} + +impl IgnoreName for Vec +where + [T]: IgnoreName, +{ + type Unnamed<'a> = <[T] as IgnoreName>::Unnamed<'a> where Self: 'a; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + <[T] as IgnoreName>::ignore_name(self.as_slice()) + } +} + +impl IgnoreName for Box { + type Unnamed<'a> = T::Unnamed<'a> where Self: 'a; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + T::ignore_name(self.as_ref()) + } +} + +impl IgnoreName for Arc { + type Unnamed<'a> = T::Unnamed<'a> where Self: 'a; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + T::ignore_name(self.as_ref()) + } +} + +impl IgnoreName for Rc { + type Unnamed<'a> = T::Unnamed<'a> where Self: 'a; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + T::ignore_name(self.as_ref()) + } +} + +impl IgnoreName for Option { + type Unnamed<'a> = Option> where Self: 'a; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + self.as_ref().map(|t| T::ignore_name(t)) + } +} + +impl IgnoreName for AbiType { + type Unnamed<'a> = &'a WithoutName; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + WithoutName::wrap(self) + } +} + +impl IgnoreName for [AbiType] { + type Unnamed<'a> = &'a [WithoutName]; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + WithoutName::wrap_slice(self) + } +} + +impl IgnoreName for NamedAbiType { + type Unnamed<'a> = &'a WithoutName; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + WithoutName::wrap(self) + } +} + +impl IgnoreName for [NamedAbiType] { + type Unnamed<'a> = &'a [WithoutName]; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + WithoutName::wrap_slice(self) + } +} + +impl IgnoreName for AbiValue { + type Unnamed<'a> = &'a WithoutName; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + WithoutName::wrap(self) + } +} + +impl IgnoreName for [AbiValue] { + type Unnamed<'a> = &'a [WithoutName]; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + WithoutName::wrap_slice(self) + } +} + +impl IgnoreName for NamedAbiValue { + type Unnamed<'a> = &'a WithoutName; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + WithoutName::wrap(self) + } +} + +impl IgnoreName for [NamedAbiValue] { + type Unnamed<'a> = &'a [WithoutName]; + + #[inline] + fn ignore_name(&self) -> Self::Unnamed<'_> { + WithoutName::wrap_slice(self) + } +} + /// A type with a known ABI type. pub trait WithAbiType { /// Returns a corresponding ABI type. @@ -991,6 +1133,8 @@ impl FromAbi for Rc { #[cfg(test)] mod tests { + use ahash::HashSet; + use crate::prelude::CellFamily; use super::*; @@ -1011,4 +1155,27 @@ mod tests { assert_eq!(abi.into_abi(), target_abi); } + + #[test] + fn entities_without_name() { + let only_signatures = HashSet::from_iter( + [ + u32::abi_type().named("u32"), + bool::abi_type().named("bool"), + <(u32, bool)>::abi_type().named("(u32,bool)"), + ] + .map(WithoutName), + ); + + assert!(only_signatures.contains(u32::abi_type().named("qwe").ignore_name())); + assert!(only_signatures.contains(u32::abi_type().ignore_name())); + + assert!(only_signatures.contains(bool::abi_type().named("asd").ignore_name())); + assert!(only_signatures.contains(bool::abi_type().ignore_name())); + + assert!(only_signatures.contains(<(u32, bool)>::abi_type().named("zxc").ignore_name())); + assert!(only_signatures.contains(<(u32, bool)>::abi_type().ignore_name())); + + assert!(!only_signatures.contains(u64::abi_type().ignore_name())); + } } diff --git a/src/abi/ty.rs b/src/abi/ty.rs index a879c05b..8ddf113b 100644 --- a/src/abi/ty.rs +++ b/src/abi/ty.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::hash::Hash; use std::num::NonZeroU8; use std::str::FromStr; use std::sync::Arc; @@ -6,6 +7,7 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; use super::error::{ParseAbiTypeError, ParseNamedAbiTypeError}; +use crate::abi::WithoutName; use crate::cell::{CellTreeStats, MAX_BIT_LEN, MAX_REF_COUNT}; use crate::models::{IntAddr, StdAddr}; use crate::num::Tokens; @@ -157,6 +159,25 @@ impl From<(usize, AbiType)> for NamedAbiType { } } +impl PartialEq for WithoutName { + #[inline] + fn eq(&self, other: &Self) -> bool { + WithoutName::wrap(&self.0.ty).eq(WithoutName::wrap(&other.0.ty)) + } +} + +impl Hash for WithoutName { + fn hash(&self, state: &mut H) { + WithoutName::wrap(&self.0.ty).hash(state); + } +} + +impl std::borrow::Borrow> for WithoutName { + fn borrow(&self) -> &WithoutName { + WithoutName::wrap(&self.0.ty) + } +} + /// Contract header value type. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum AbiHeaderType { @@ -608,6 +629,74 @@ impl std::fmt::Display for DisplayAbiTypeSimple<'_> { } } +impl PartialEq for WithoutName { + fn eq(&self, other: &Self) -> bool { + match (&self.0, &other.0) { + (AbiType::Uint(a), AbiType::Uint(b)) => a.eq(b), + (AbiType::Int(a), AbiType::Int(b)) => a.eq(b), + (AbiType::VarUint(a), AbiType::VarUint(b)) => a.eq(b), + (AbiType::VarInt(a), AbiType::VarInt(b)) => a.eq(b), + (AbiType::Bool, AbiType::Bool) => true, + (AbiType::Cell, AbiType::Cell) => true, + (AbiType::Address, AbiType::Address) => true, + (AbiType::Bytes, AbiType::Bytes) => true, + (AbiType::FixedBytes(a), AbiType::FixedBytes(b)) => a.eq(b), + (AbiType::String, AbiType::String) => true, + (AbiType::Token, AbiType::Token) => true, + (AbiType::Tuple(a), AbiType::Tuple(b)) => { + WithoutName::wrap_slice(a.as_ref()).eq(WithoutName::wrap_slice(b.as_ref())) + } + (AbiType::Array(a), AbiType::Array(b)) => { + WithoutName::wrap(a.as_ref()).eq(WithoutName::wrap(b.as_ref())) + } + (AbiType::FixedArray(a, an), AbiType::FixedArray(b, bn)) => { + WithoutName::wrap(a.as_ref()).eq(WithoutName::wrap(b.as_ref())) && an.eq(bn) + } + (AbiType::Map(ak, av), AbiType::Map(bk, bv)) => { + ak.eq(bk) && WithoutName::wrap(av.as_ref()).eq(WithoutName::wrap(bv.as_ref())) + } + (AbiType::Optional(a), AbiType::Optional(b)) => { + WithoutName::wrap(a.as_ref()).eq(WithoutName::wrap(b.as_ref())) + } + (AbiType::Ref(a), AbiType::Ref(b)) => { + WithoutName::wrap(a.as_ref()).eq(WithoutName::wrap(b.as_ref())) + } + _ => false, + } + } +} + +impl Hash for WithoutName { + fn hash(&self, state: &mut H) { + core::mem::discriminant(&self.0).hash(state); + match &self.0 { + AbiType::Uint(x) => x.hash(state), + AbiType::Int(x) => x.hash(state), + AbiType::VarUint(x) => x.hash(state), + AbiType::VarInt(x) => x.hash(state), + AbiType::Bool => {} + AbiType::Cell => {} + AbiType::Address => {} + AbiType::Bytes => {} + AbiType::FixedBytes(x) => x.hash(state), + AbiType::String => {} + AbiType::Token => {} + AbiType::Tuple(x) => WithoutName::wrap_slice(x.as_ref()).hash(state), + AbiType::Array(x) => WithoutName::wrap(x.as_ref()).hash(state), + AbiType::FixedArray(x, n) => { + WithoutName::wrap(x.as_ref()).hash(state); + n.hash(state); + } + AbiType::Map(k, v) => { + k.hash(state); + WithoutName::wrap(v.as_ref()).hash(state); + } + AbiType::Optional(x) => WithoutName::wrap(x.as_ref()).hash(state), + AbiType::Ref(x) => WithoutName::wrap(x.as_ref()).hash(state), + } + } +} + /// ABI type which has a fixed bits representation /// and therefore can be used as a map key. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] diff --git a/src/abi/value/mod.rs b/src/abi/value/mod.rs index 786a3905..bbcfdc40 100644 --- a/src/abi/value/mod.rs +++ b/src/abi/value/mod.rs @@ -6,7 +6,7 @@ use anyhow::Result; use bytes::Bytes; use num_bigint::{BigInt, BigUint}; -use super::{ty::*, IntoAbi, IntoPlainAbi, WithAbiType, WithPlainAbiType}; +use super::{ty::*, IntoAbi, IntoPlainAbi, WithAbiType, WithPlainAbiType, WithoutName}; use crate::abi::error::AbiError; use crate::cell::{Cell, CellFamily}; use crate::models::IntAddr; @@ -105,6 +105,19 @@ impl NamedAbiType { } } +impl PartialEq for WithoutName { + #[inline] + fn eq(&self, other: &Self) -> bool { + WithoutName::wrap(&self.0.value).eq(WithoutName::wrap(&other.0.value)) + } +} + +impl std::borrow::Borrow> for WithoutName { + fn borrow(&self) -> &WithoutName { + WithoutName::wrap(&self.0.value) + } +} + /// ABI encoded value. #[derive(Debug, Clone, Eq, PartialEq)] pub enum AbiValue { @@ -425,6 +438,48 @@ impl AbiType { } } +impl PartialEq for WithoutName { + fn eq(&self, other: &Self) -> bool { + match (&self.0, &other.0) { + (AbiValue::Uint(an, a), AbiValue::Uint(bn, b)) => an.eq(bn) && a.eq(b), + (AbiValue::Int(an, a), AbiValue::Int(bn, b)) => an.eq(bn) && a.eq(b), + (AbiValue::VarUint(an, a), AbiValue::VarUint(bn, b)) => an.eq(bn) && a.eq(b), + (AbiValue::VarInt(an, a), AbiValue::VarInt(bn, b)) => an.eq(bn) && a.eq(b), + (AbiValue::Bool(a), AbiValue::Bool(b)) => a.eq(b), + (AbiValue::Cell(a), AbiValue::Cell(b)) => a.eq(b), + (AbiValue::Address(a), AbiValue::Address(b)) => a.eq(b), + (AbiValue::Bytes(a), AbiValue::Bytes(b)) => a.eq(b), + (AbiValue::FixedBytes(a), AbiValue::FixedBytes(b)) => a.eq(b), + (AbiValue::String(a), AbiValue::String(b)) => a.eq(b), + (AbiValue::Token(a), AbiValue::Token(b)) => a.eq(b), + (AbiValue::Tuple(a), AbiValue::Tuple(b)) => { + WithoutName::wrap_slice(a.as_slice()).eq(WithoutName::wrap_slice(b.as_slice())) + } + (AbiValue::Array(at, av), AbiValue::Array(bt, bv)) + | (AbiValue::FixedArray(at, av), AbiValue::FixedArray(bt, bv)) => { + WithoutName::wrap(at.as_ref()).eq(WithoutName::wrap(bt.as_ref())) + && WithoutName::wrap_slice(av.as_slice()) + .eq(WithoutName::wrap_slice(bv.as_slice())) + } + (AbiValue::Map(akt, avt, a), AbiValue::Map(bkt, bvt, b)) => { + akt.eq(bkt) + && WithoutName::wrap(avt.as_ref()).eq(WithoutName::wrap(bvt.as_ref())) + && WithoutName::wrap(a).eq(WithoutName::wrap(b)) + } + (AbiValue::Optional(at, a), AbiValue::Optional(bt, b)) => { + WithoutName::wrap(at.as_ref()).eq(WithoutName::wrap(bt.as_ref())) + && a.as_deref() + .map(WithoutName::wrap) + .eq(&b.as_deref().map(WithoutName::wrap)) + } + (AbiValue::Ref(a), AbiValue::Ref(b)) => { + WithoutName::wrap(a.as_ref()).eq(WithoutName::wrap(b.as_ref())) + } + _unused => false, + } + } +} + /// ABI value which has a fixed bits representation /// and therefore can be used as a map key. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]