use crate::{
internal_type::BorrowedInternalType,
utils::{mk_eparam, mk_param, validate_identifier},
InternalType,
};
use alloc::{
borrow::{Cow, ToOwned},
string::String,
vec::Vec,
};
use core::{fmt, str::FromStr};
use parser::{Error, ParameterSpecifier, TypeSpecifier};
use serde::{de::Unexpected, Deserialize, Deserializer, Serialize, Serializer};
/// JSON specification of a parameter.
///
/// Parameters are the inputs and outputs of [Function]s, and the fields of
/// [Error]s.
///
/// [Function]: crate::Function
/// [Error]: crate::Error
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Param {
/// The canonical Solidity type of the parameter, using the word "tuple" to
/// represent complex types. E.g. `uint256` or `bytes[2]` or `tuple` or
/// `tuple[2]`.
///
/// Generally, this is a valid [`TypeSpecifier`], but in very rare
/// circumstances, such as when a function in a library contains an enum
/// in its parameters or return types, this will be `Contract.EnumName`
/// instead of the actual type (`uint8`).
/// Visible for macros, functions inside the crate, and doc tests. It is not recommended to
/// instantiate directly. Use Param::new instead.
#[doc(hidden)]
pub ty: String,
/// The name of the parameter. This field always contains either the empty
/// string, or a valid Solidity identifier.
/// Visible for macros, functions inside the crate, and doc tests. It is not recommended to
/// instantiate directly. Use Param::new instead.
#[doc(hidden)]
pub name: String,
/// If the paramaeter is a compound type (a struct or tuple), a list of the
/// parameter's components, in order. Empty otherwise
pub components: Vec,
/// The internal type of the parameter. This type represents the type that
/// the author of the Solidity contract specified. E.g. for a contract, this
/// will be `contract MyContract` while the `type` field will be `address`.
pub internal_type: Option,
}
impl fmt::Display for Param {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(it) = &self.internal_type { it.fmt(f) } else { f.write_str(&self.ty) }?;
f.write_str(" ")?;
f.write_str(&self.name)
}
}
impl<'de> Deserialize<'de> for Param {
fn deserialize>(deserializer: D) -> Result {
BorrowedParam::deserialize(deserializer).and_then(|inner| {
if inner.indexed.is_none() {
inner.validate_fields()?;
Ok(Self {
name: inner.name.to_owned(),
ty: inner.ty.to_owned(),
internal_type: inner.internal_type.map(Into::into),
components: inner.components.into_owned(),
})
} else {
Err(serde::de::Error::custom("indexed is not supported in params"))
}
})
}
}
impl Serialize for Param {
#[inline]
fn serialize(&self, serializer: S) -> Result {
self.as_inner().serialize(serializer)
}
}
impl FromStr for Param {
type Err = parser::Error;
#[inline]
fn from_str(s: &str) -> Result {
Self::parse(s)
}
}
impl Param {
/// Parse a parameter from a Solidity parameter string.
///
/// # Examples
///
/// ```
/// # use linera_alloy_json_abi::Param;
/// assert_eq!(
/// Param::parse("uint256[] foo"),
/// Ok(Param {
/// name: "foo".into(),
/// ty: "uint256[]".into(),
/// components: vec![],
/// internal_type: None,
/// })
/// );
/// ```
pub fn parse(input: &str) -> parser::Result {
ParameterSpecifier::parse(input).map(|p| mk_param(p.name, p.ty))
}
/// Validate and create new instance of Param.
pub fn new(
name: &str,
ty: &str,
components: Vec,
internal_type: Option,
) -> parser::Result {
Self::validate_fields(name, ty, !components.is_empty())?;
Ok(Self { ty: ty.into(), name: name.into(), components, internal_type })
}
/// The internal type of the parameter.
#[inline]
pub const fn internal_type(&self) -> Option<&InternalType> {
self.internal_type.as_ref()
}
/// True if the parameter is a UDT (user-defined type).
///
/// A UDT will have
/// - an internal type that does not match its canonical type
/// - no space in its internal type (as it does not have a keyword body)
///
/// Any `Other` specifier will definitely be a UDT if it contains a
/// contract.
#[inline]
pub fn is_udt(&self) -> bool {
match self.internal_type().and_then(|it| it.as_other()) {
Some((contract, ty)) => contract.is_some() || (self.is_simple_type() && ty != self.ty),
_ => false,
}
}
/// True if the parameter is a struct.
#[inline]
pub const fn is_struct(&self) -> bool {
match self.internal_type() {
Some(ty) => ty.is_struct(),
None => false,
}
}
/// True if the parameter is an enum.
#[inline]
pub const fn is_enum(&self) -> bool {
match self.internal_type() {
Some(ty) => ty.is_enum(),
None => false,
}
}
/// True if the parameter is a contract.
#[inline]
pub const fn is_contract(&self) -> bool {
match self.internal_type() {
Some(ty) => ty.is_contract(),
None => false,
}
}
/// The UDT specifier is a [`TypeSpecifier`] containing the UDT name and any
/// array sizes. It is computed from the `internal_type`. If this param is
/// not a UDT, this function will return `None`.
#[inline]
pub fn udt_specifier(&self) -> Option> {
// UDTs are more annoying to check for, so we reuse logic here.
if !self.is_udt() {
return None;
}
self.internal_type().and_then(|ty| ty.other_specifier())
}
/// The struct specifier is a [`TypeSpecifier`] containing the struct name
/// and any array sizes. It is computed from the `internal_type` If this
/// param is not a struct, this function will return `None`.
#[inline]
pub fn struct_specifier(&self) -> Option> {
self.internal_type().and_then(|ty| ty.struct_specifier())
}
/// The enum specifier is a [`TypeSpecifier`] containing the enum name and
/// any array sizes. It is computed from the `internal_type`. If this param
/// is not a enum, this function will return `None`.
#[inline]
pub fn enum_specifier(&self) -> Option> {
self.internal_type().and_then(|ty| ty.enum_specifier())
}
/// The struct specifier is a [`TypeSpecifier`] containing the contract name
/// and any array sizes. It is computed from the `internal_type` If this
/// param is not a struct, this function will return `None`.
#[inline]
pub fn contract_specifier(&self) -> Option> {
self.internal_type().and_then(|ty| ty.contract_specifier())
}
/// True if the type is simple
#[inline]
pub fn is_simple_type(&self) -> bool {
self.components.is_empty()
}
/// True if the type is complex (tuple or struct)
#[inline]
pub fn is_complex_type(&self) -> bool {
!self.components.is_empty()
}
/// Formats the canonical type of this parameter into the given string.
///
/// This is used to encode the preimage of a function or error selector.
#[inline]
pub fn selector_type_raw(&self, s: &mut String) {
if self.components.is_empty() {
s.push_str(&self.ty);
} else {
crate::utils::signature_raw(&self.components, s);
// checked during deserialization, but might be invalid from a user
if let Some(suffix) = self.ty.strip_prefix("tuple") {
s.push_str(suffix);
}
}
}
/// Formats the canonical type of this parameter into the given string including then names of
/// the params.
#[inline]
pub fn full_selector_type_raw(&self, s: &mut String) {
if self.components.is_empty() {
s.push_str(&self.ty);
} else {
s.push_str("tuple");
crate::utils::full_signature_raw(&self.components, s);
// checked during deserialization, but might be invalid from a user
if let Some(suffix) = self.ty.strip_prefix("tuple") {
s.push_str(suffix);
}
}
}
/// Returns the canonical type of this parameter.
///
/// This is used to encode the preimage of a function or error selector.
#[inline]
pub fn selector_type(&self) -> Cow<'_, str> {
if self.components.is_empty() {
Cow::Borrowed(&self.ty)
} else {
let mut s = String::with_capacity(self.components.len() * 32);
self.selector_type_raw(&mut s);
Cow::Owned(s)
}
}
#[inline]
fn borrowed_internal_type(&self) -> Option> {
self.internal_type().as_ref().map(|it| it.as_borrowed())
}
#[inline]
fn as_inner(&self) -> BorrowedParam<'_> {
BorrowedParam {
name: &self.name,
ty: &self.ty,
indexed: None,
internal_type: self.borrowed_internal_type(),
components: Cow::Borrowed(&self.components),
}
}
#[inline]
fn validate_fields(name: &str, ty: &str, has_components: bool) -> parser::Result<()> {
if !name.is_empty() && !parser::is_valid_identifier(name) {
return Err(Error::invalid_identifier_string(name));
}
// any components means type is "tuple" + maybe brackets, so we can skip
// parsing with TypeSpecifier
if !has_components {
parser::TypeSpecifier::parse(ty)?;
} else {
// https://docs.soliditylang.org/en/latest/abi-spec.html#handling-tuple-types
// checking for "tuple" prefix should be enough
if !ty.starts_with("tuple") {
return Err(Error::invalid_type_string(ty));
}
}
Ok(())
}
}
/// A Solidity Event parameter.
///
/// Event parameters are distinct from function parameters in that they have an
/// `indexed` field.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct EventParam {
/// The canonical Solidity type of the parameter, using the word "tuple" to
/// represent complex types. E.g. `uint256` or `bytes[2]` or `tuple` or
/// `tuple[2]`.
///
/// Generally, this is a valid [`TypeSpecifier`], but in very rare
/// circumstances, such as when a function in a library contains an enum
/// in its parameters or return types, this will be `Contract.EnumName`
/// instead of the actual type (`uint8`).
/// Visible for macros, functions inside the crate, and doc tests. It is not recommended to
/// instantiate directly. Use Param::new instead.
#[doc(hidden)]
pub ty: String,
/// The name of the parameter. This field always contains either the empty
/// string, or a valid Solidity identifier.
/// Visible for macros, functions inside the crate, and doc tests. It is not recommended to
/// instantiate directly. Use Param::new instead.
#[doc(hidden)]
pub name: String,
/// Whether the parameter is indexed. Indexed parameters have their
/// value, or the hash of their value, stored in the log topics.
pub indexed: bool,
/// If the paramaeter is a compound type (a struct or tuple), a list of the
/// parameter's components, in order. Empty otherwise. Because the
/// components are not top-level event params, they will not have an
/// `indexed` field.
pub components: Vec,
/// The internal type of the parameter. This type represents the type that
/// the author of the Solidity contract specified. E.g. for a contract, this
/// will be `contract MyContract` while the `type` field will be `address`.
pub internal_type: Option,
}
impl fmt::Display for EventParam {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(it) = &self.internal_type { it.fmt(f) } else { f.write_str(&self.ty) }?;
f.write_str(" ")?;
f.write_str(&self.name)
}
}
impl<'de> Deserialize<'de> for EventParam {
fn deserialize>(deserializer: D) -> Result {
BorrowedParam::deserialize(deserializer).and_then(|inner| {
inner.validate_fields()?;
Ok(Self {
name: inner.name.to_owned(),
ty: inner.ty.to_owned(),
indexed: inner.indexed.unwrap_or(false),
internal_type: inner.internal_type.map(Into::into),
components: inner.components.into_owned(),
})
})
}
}
impl Serialize for EventParam {
#[inline]
fn serialize(&self, serializer: S) -> Result {
self.as_inner().serialize(serializer)
}
}
impl FromStr for EventParam {
type Err = parser::Error;
#[inline]
fn from_str(s: &str) -> Result {
Self::parse(s)
}
}
impl EventParam {
/// Parse an event parameter from a Solidity parameter string.
///
/// # Examples
///
/// ```
/// # use std::panic::catch_unwind;
/// use linera_alloy_json_abi::EventParam;
/// assert_eq!(
/// EventParam::parse("uint256[] indexed foo"),
/// Ok(EventParam {
/// name: "foo".into(),
/// ty: "uint256[]".into(),
/// indexed: true,
/// components: vec![],
/// internal_type: None,
/// })
/// );
/// ```
#[inline]
pub fn parse(input: &str) -> parser::Result {
ParameterSpecifier::parse(input).map(mk_eparam)
}
/// Validate and create new instance of EventParam
pub fn new(
name: &str,
ty: &str,
indexed: bool,
components: Vec,
internal_type: Option,
) -> parser::Result {
Param::validate_fields(name, ty, !components.is_empty())?;
Ok(Self { name: name.into(), ty: ty.into(), indexed, components, internal_type })
}
/// The internal type of the parameter.
#[inline]
pub const fn internal_type(&self) -> Option<&InternalType> {
self.internal_type.as_ref()
}
/// True if the parameter is a UDT (user-defined type).
///
/// A UDT will have
/// - an internal type that does not match its canonical type
/// - no space in its internal type (as it does not have a keyword body)
///
/// Any `Other` specifier will definitely be a UDT if it contains a
/// contract.
#[inline]
pub fn is_udt(&self) -> bool {
match self.internal_type().and_then(|it| it.as_other()) {
Some((contract, ty)) => contract.is_some() || (self.is_simple_type() && ty != self.ty),
_ => false,
}
}
/// True if the parameter is a struct.
#[inline]
pub const fn is_struct(&self) -> bool {
match self.internal_type() {
Some(ty) => ty.is_struct(),
None => false,
}
}
/// True if the parameter is an enum.
#[inline]
pub const fn is_enum(&self) -> bool {
match self.internal_type() {
Some(ty) => ty.is_enum(),
None => false,
}
}
/// True if the parameter is a contract.
#[inline]
pub const fn is_contract(&self) -> bool {
match self.internal_type() {
Some(ty) => ty.is_contract(),
None => false,
}
}
/// The UDT specifier is a [`TypeSpecifier`] containing the UDT name and any
/// array sizes. It is computed from the `internal_type`. If this param is
/// not a UDT, this function will return `None`.
#[inline]
pub fn udt_specifier(&self) -> Option> {
// UDTs are more annoying to check for, so we reuse logic here.
if !self.is_udt() {
return None;
}
self.internal_type().and_then(|ty| ty.other_specifier())
}
/// The struct specifier is a [`TypeSpecifier`] containing the struct name
/// and any array sizes. It is computed from the `internal_type` If this
/// param is not a struct, this function will return `None`.
#[inline]
pub fn struct_specifier(&self) -> Option> {
self.internal_type().and_then(|ty| ty.struct_specifier())
}
/// The enum specifier is a [`TypeSpecifier`] containing the enum name and
/// any array sizes. It is computed from the `internal_type`. If this param
/// is not a enum, this function will return `None`.
#[inline]
pub fn enum_specifier(&self) -> Option> {
self.internal_type().and_then(|ty| ty.enum_specifier())
}
/// The struct specifier is a [`TypeSpecifier`] containing the contract name
/// and any array sizes. It is computed from the `internal_type` If this
/// param is not a struct, this function will return `None`.
#[inline]
pub fn contract_specifier(&self) -> Option> {
self.internal_type().and_then(|ty| ty.contract_specifier())
}
/// True if the type is simple
#[inline]
pub fn is_simple_type(&self) -> bool {
self.components.is_empty()
}
/// True if the type is complex (tuple or struct)
#[inline]
pub fn is_complex_type(&self) -> bool {
!self.components.is_empty()
}
/// Formats the canonical type of this parameter into the given string.
///
/// This is used to encode the preimage of the event selector.
#[inline]
pub fn selector_type_raw(&self, s: &mut String) {
if self.components.is_empty() {
s.push_str(&self.ty);
} else {
crate::utils::signature_raw(&self.components, s);
// checked during deserialization, but might be invalid from a user
if let Some(suffix) = self.ty.strip_prefix("tuple") {
s.push_str(suffix);
}
}
}
/// Formats the canonical type of this parameter into the given string including then names of
/// the params.
#[inline]
pub fn full_selector_type_raw(&self, s: &mut String) {
if self.components.is_empty() {
s.push_str(&self.ty);
} else {
s.push_str("tuple");
crate::utils::full_signature_raw(&self.components, s);
// checked during deserialization, but might be invalid from a user
if let Some(suffix) = self.ty.strip_prefix("tuple") {
s.push_str(suffix);
}
}
}
/// Returns the canonical type of this parameter.
///
/// This is used to encode the preimage of the event selector.
#[inline]
pub fn selector_type(&self) -> Cow<'_, str> {
if self.components.is_empty() {
Cow::Borrowed(&self.ty)
} else {
let mut s = String::with_capacity(self.components.len() * 32);
self.selector_type_raw(&mut s);
Cow::Owned(s)
}
}
#[inline]
fn borrowed_internal_type(&self) -> Option> {
self.internal_type().as_ref().map(|it| it.as_borrowed())
}
#[inline]
fn as_inner(&self) -> BorrowedParam<'_> {
BorrowedParam {
name: &self.name,
ty: &self.ty,
indexed: Some(self.indexed),
internal_type: self.borrowed_internal_type(),
components: Cow::Borrowed(&self.components),
}
}
}
#[derive(Deserialize, Serialize)]
struct BorrowedParam<'a> {
#[serde(default)]
name: &'a str,
#[serde(rename = "type")]
ty: &'a str,
#[serde(default, skip_serializing_if = "Option::is_none")]
indexed: Option,
#[serde(rename = "internalType", default, skip_serializing_if = "Option::is_none")]
internal_type: Option>,
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
components: Cow<'a, [Param]>,
}
impl BorrowedParam<'_> {
#[inline(always)]
fn validate_fields(&self) -> Result<(), E> {
validate_identifier!(self.name);
// any components means type is "tuple" + maybe brackets, so we can skip
// parsing with TypeSpecifier
if self.components.is_empty() {
if parser::TypeSpecifier::parse(self.ty).is_err() {
return Err(E::invalid_value(
Unexpected::Str(self.ty),
&"a valid Solidity type specifier",
));
}
} else {
// https://docs.soliditylang.org/en/latest/abi-spec.html#handling-tuple-types
// checking for "tuple" prefix should be enough
if !self.ty.starts_with("tuple") {
return Err(E::invalid_value(
Unexpected::Str(self.ty),
&"a string prefixed with `tuple`, optionally followed by a sequence of `[]` or `[k]` with integers `k`",
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn param_from_str() {
let param = r#"{
"internalType": "string",
"name": "reason",
"type": "string"
}"#;
let param = serde_json::from_str::(param).unwrap();
assert_eq!(
param,
Param {
name: "reason".into(),
ty: "string".into(),
internal_type: Some(InternalType::Other { contract: None, ty: "string".into() }),
components: vec![],
}
);
}
#[test]
fn param_from_new() {
let param = Param::new("something", "string", vec![], None);
assert_eq!(
param,
Ok(Param {
name: "something".into(),
ty: "string".into(),
components: vec![],
internal_type: None,
})
);
let err_not_a_type = Param::new("something", "not a type", vec![], None);
assert!(err_not_a_type.is_err());
let err_not_tuple = Param::new("something", "string", vec![param.unwrap()], None);
assert!(err_not_tuple.is_err())
}
}