difftreelog
feat support complex types in solidity defs
in: master
5 files changed
crates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth--- a/crates/evm-coder-macros/src/solidity_interface.rs
+++ b/crates/evm-coder-macros/src/solidity_interface.rs
@@ -1,16 +1,16 @@
#![allow(dead_code)]
use quote::quote;
-use darling::FromMeta;
+use darling::{FromMeta, ToTokens};
use inflector::cases;
use std::fmt::Write;
use syn::{
- FnArg, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Meta, NestedMeta, PatType, Path,
- ReturnType, Type, spanned::Spanned,
+ Expr, FnArg, GenericArgument, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta,
+ NestedMeta, PatType, Path, PathArguments, ReturnType, Type, spanned::Spanned,
};
use crate::{
- fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_ident_from_type,
+ fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment,
parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,
snake_ident_to_screaming,
};
@@ -77,14 +77,14 @@
fn expand_generator(&self) -> proc_macro2::TokenStream {
let pascal_call_name = &self.pascal_call_name;
quote! {
- #pascal_call_name::generate_solidity_interface(out_set, is_impl);
+ #pascal_call_name::generate_solidity_interface(tc, is_impl);
}
}
fn expand_event_generator(&self) -> proc_macro2::TokenStream {
let name = &self.name;
quote! {
- #name::generate_solidity_interface(out_set, is_impl);
+ #name::generate_solidity_interface(tc, is_impl);
}
}
}
@@ -121,10 +121,161 @@
rename_selector: Option<String>,
}
+enum AbiType {
+ // type
+ Plain(Ident),
+ // (type1,type2)
+ Tuple(Vec<AbiType>),
+ // type[]
+ Vec(Box<AbiType>),
+ // type[20]
+ Array(Box<AbiType>, usize),
+}
+impl AbiType {
+ fn try_from(value: &Type) -> syn::Result<Self> {
+ let value = Self::try_maybe_special_from(value)?;
+ if value.is_special() {
+ return Err(syn::Error::new(value.span(), "unexpected special type"));
+ }
+ Ok(value)
+ }
+ fn try_maybe_special_from(value: &Type) -> syn::Result<Self> {
+ match value {
+ Type::Array(arr) => {
+ let wrapped = AbiType::try_from(&arr.elem)?;
+ match &arr.len {
+ Expr::Lit(l) => match &l.lit {
+ Lit::Int(i) => {
+ let num = i.base10_parse::<usize>()?;
+ Ok(AbiType::Array(Box::new(wrapped), num as usize))
+ }
+ _ => Err(syn::Error::new(arr.len.span(), "should be int literal")),
+ },
+ _ => Err(syn::Error::new(arr.len.span(), "should be literal")),
+ }
+ }
+ Type::Path(_) => {
+ let path = parse_path(value)?;
+ let segment = parse_path_segment(path)?;
+ if segment.ident == "Vec" {
+ let args = match &segment.arguments {
+ PathArguments::AngleBracketed(e) => e,
+ _ => {
+ return Err(syn::Error::new(
+ segment.arguments.span(),
+ "missing Vec generic",
+ ))
+ }
+ };
+ let args = &args.args;
+ if args.len() != 1 {
+ return Err(syn::Error::new(
+ args.span(),
+ "expected only one generic for vec",
+ ));
+ }
+ let arg = args.first().unwrap();
+
+ let ty = match arg {
+ GenericArgument::Type(ty) => ty,
+ _ => {
+ return Err(syn::Error::new(
+ arg.span(),
+ "expected first generic to be type",
+ ))
+ }
+ };
+
+ let wrapped = AbiType::try_from(ty)?;
+ Ok(Self::Vec(Box::new(wrapped)))
+ } else {
+ if !segment.arguments.is_empty() {
+ return Err(syn::Error::new(
+ segment.arguments.span(),
+ "unexpected generic arguments for non-vec type",
+ ));
+ }
+ Ok(Self::Plain(segment.ident.clone()))
+ }
+ }
+ Type::Tuple(t) => {
+ let mut out = Vec::with_capacity(t.elems.len());
+ for el in t.elems.iter() {
+ out.push(AbiType::try_from(el)?)
+ }
+ Ok(Self::Tuple(out))
+ }
+ _ => Err(syn::Error::new(
+ value.span(),
+ "unexpected type, only arrays, plain types and tuples are supported",
+ )),
+ }
+ }
+ fn is_value(&self) -> bool {
+ match self {
+ Self::Plain(v) if v == "value" => true,
+ _ => false,
+ }
+ }
+ fn is_caller(&self) -> bool {
+ match self {
+ Self::Plain(v) if v == "caller" => true,
+ _ => false,
+ }
+ }
+ fn is_special(&self) -> bool {
+ self.is_caller() || self.is_value()
+ }
+ fn selector_ty_buf(&self, buf: &mut String) -> std::fmt::Result {
+ match self {
+ AbiType::Plain(t) => {
+ write!(buf, "{}", t)
+ }
+ AbiType::Tuple(t) => {
+ write!(buf, "(")?;
+ for (i, t) in t.iter().enumerate() {
+ if i != 0 {
+ write!(buf, ",")?;
+ }
+ t.selector_ty_buf(buf)?;
+ }
+ write!(buf, ")")
+ }
+ AbiType::Vec(v) => {
+ v.selector_ty_buf(buf)?;
+ write!(buf, "[]")
+ }
+ AbiType::Array(v, len) => {
+ v.selector_ty_buf(buf)?;
+ write!(buf, "[{}]", len)
+ }
+ }
+ }
+ fn selector_ty(&self) -> String {
+ let mut out = String::new();
+ self.selector_ty_buf(&mut out).expect("no fmt error");
+ out
+ }
+}
+impl ToTokens for AbiType {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ match self {
+ AbiType::Plain(t) => tokens.extend(quote! {#t}),
+ AbiType::Tuple(t) => {
+ tokens.extend(quote! {(
+ #(#t),*
+ )});
+ }
+ AbiType::Vec(v) => tokens.extend(quote! {Vec<#v>}),
+ AbiType::Array(v, l) => tokens.extend(quote! {[#v; #l]}),
+ }
+ }
+}
+
struct MethodArg {
name: Ident,
camel_name: String,
- ty: Ident,
+ ty: AbiType,
}
impl MethodArg {
fn try_from(value: &PatType) -> syn::Result<Self> {
@@ -132,21 +283,21 @@
Ok(Self {
camel_name: cases::camelcase::to_camel_case(&name.to_string()),
name,
- ty: parse_ident_from_type(&value.ty, false)?.clone(),
+ ty: AbiType::try_maybe_special_from(&value.ty)?,
})
}
fn is_value(&self) -> bool {
- self.ty == "value"
+ self.ty.is_value()
}
fn is_caller(&self) -> bool {
- self.ty == "caller"
+ self.ty.is_caller()
}
fn is_special(&self) -> bool {
- self.is_value() || self.is_caller()
+ self.ty.is_special()
}
- fn selector_ty(&self) -> &Ident {
+ fn selector_ty(&self) -> String {
assert!(!self.is_special());
- &self.ty
+ self.ty.selector_ty()
}
fn expand_call_def(&self) -> proc_macro2::TokenStream {
@@ -544,7 +695,7 @@
)*
)
}
- pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
+ pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {
use evm_coder::solidity::*;
use core::fmt::Write;
let interface = SolidityInterface {
@@ -559,9 +710,9 @@
)*),
};
if is_impl {
- out_set.insert("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());
+ tc.collect("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());
} else {
- out_set.insert("// Common stubs holder\ninterface Dummy {\n}\n".into());
+ tc.collect("// Common stubs holder\ninterface Dummy {\n}\n".into());
}
#(
#solidity_generators
@@ -576,8 +727,8 @@
if #solidity_name.starts_with("Inline") {
out.push_str("// Inline\n");
}
- let _ = interface.format(is_impl, &mut out);
- out_set.insert(out);
+ let _ = interface.format(is_impl, &mut out, tc);
+ tc.collect(out);
}
}
impl ::evm_coder::Call for #call_name {
crates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth--- a/crates/evm-coder-macros/src/to_log.rs
+++ b/crates/evm-coder-macros/src/to_log.rs
@@ -179,7 +179,7 @@
#consts
)*
- pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
+ pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {
use evm_coder::solidity::*;
use core::fmt::Write;
let interface = SolidityInterface {
@@ -191,8 +191,8 @@
};
let mut out = string::new();
out.push_str("// Inline\n");
- let _ = interface.format(is_impl, &mut out);
- out_set.insert(out);
+ let _ = interface.format(is_impl, &mut out, tc);
+ tc.collect(out);
}
}
crates/evm-coder/src/abi.rsdiffbeforeafterboth--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -247,6 +247,51 @@
impl_abi_readable!(bool, bool);
impl_abi_readable!(string, string);
+mod sealed {
+ /// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead
+ pub trait CanBePlacedInVec {}
+}
+
+impl sealed::CanBePlacedInVec for U256 {}
+impl sealed::CanBePlacedInVec for string {}
+impl sealed::CanBePlacedInVec for H160 {}
+
+impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>
+where
+ Self: AbiRead<R>,
+{
+ fn abi_read(&mut self) -> Result<Vec<R>> {
+ todo!()
+ }
+}
+
+macro_rules! impl_tuples {
+ ($($ident:ident)+) => {
+ impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}
+ impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>
+ where
+ $(Self: AbiRead<$ident>),+
+ {
+ fn abi_read(&mut self) -> Result<($($ident,)+)> {
+ Ok((
+ $(<Self as AbiRead<$ident>>::abi_read(self)?,)+
+ ))
+ }
+ }
+ };
+}
+
+impl_tuples! {A}
+impl_tuples! {A B}
+impl_tuples! {A B C}
+impl_tuples! {A B C D}
+impl_tuples! {A B C D E}
+impl_tuples! {A B C D E F}
+impl_tuples! {A B C D E F G}
+impl_tuples! {A B C D E F G H}
+impl_tuples! {A B C D E F G H I}
+impl_tuples! {A B C D E F G H I J}
+
pub trait AbiWrite {
fn abi_write(&self, writer: &mut AbiWriter);
}
crates/evm-coder/src/lib.rsdiffbeforeafterboth--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -67,8 +67,8 @@
#[test]
#[ignore]
fn $name() {
- use sp_std::collections::btree_set::BTreeSet;
- let mut out = BTreeSet::new();
+ use evm_coder::solidity::TypeCollector;
+ let mut out = TypeCollector::new();
$decl::generate_solidity_interface(&mut out, $is_impl);
println!("=== SNIP START ===");
println!("// SPDX-License-Identifier: OTHER");
@@ -76,7 +76,7 @@
println!();
println!("pragma solidity >=0.8.0 <0.9.0;");
println!();
- for b in out {
+ for b in out.finish() {
println!("{}", b);
}
println!("=== SNIP END ===");
crates/evm-coder/src/solidity.rsdiffbeforeafterboth1#[cfg(not(feature = "std"))]2use alloc::{string::String};3use core::{fmt, marker::PhantomData};4use impl_trait_for_tuples::impl_for_tuples;5use crate::types::*;67pub trait SolidityTypeName: 'static {8 fn solidity_name(writer: &mut impl fmt::Write) -> fmt::Result;9 fn solidity_default(writer: &mut impl fmt::Write) -> fmt::Result;10 fn is_void() -> bool {11 false12 }13}1415macro_rules! solidity_type_name {16 ($($ty:ident => $name:literal = $default:literal),* $(,)?) => {17 $(18 impl SolidityTypeName for $ty {19 fn solidity_name(writer: &mut impl core::fmt::Write) -> core::fmt::Result {20 write!(writer, $name)21 }22 fn solidity_default(writer: &mut impl core::fmt::Write) -> core::fmt::Result {23 write!(writer, $default)24 }25 }26 )*27 };28}2930solidity_type_name! {31 uint8 => "uint8" = "0",32 uint32 => "uint32" = "0",33 uint128 => "uint128" = "0",34 uint256 => "uint256" = "0",35 address => "address" = "0x0000000000000000000000000000000000000000",36 string => "string memory" = "\"\"",37 bytes => "bytes memory" = "hex\"\"",38 bool => "bool" = "false",39}40impl SolidityTypeName for void {41 fn solidity_name(_writer: &mut impl fmt::Write) -> fmt::Result {42 Ok(())43 }44 fn solidity_default(_writer: &mut impl fmt::Write) -> fmt::Result {45 Ok(())46 }47 fn is_void() -> bool {48 true49 }50}5152pub trait SolidityArguments {53 fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result;54 fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result;55 fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result;56 fn is_empty(&self) -> bool {57 self.len() == 058 }59 fn len(&self) -> usize;60}6162#[derive(Default)]63pub struct UnnamedArgument<T>(PhantomData<*const T>);6465impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {66 fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {67 if !T::is_void() {68 T::solidity_name(writer)69 } else {70 Ok(())71 }72 }73 fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {74 Ok(())75 }76 fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {77 T::solidity_default(writer)78 }79 fn len(&self) -> usize {80 if T::is_void() {81 082 } else {83 184 }85 }86}8788pub struct NamedArgument<T>(&'static str, PhantomData<*const T>);8990impl<T> NamedArgument<T> {91 pub fn new(name: &'static str) -> Self {92 Self(name, Default::default())93 }94}9596impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {97 fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {98 if !T::is_void() {99 T::solidity_name(writer)?;100 write!(writer, " {}", self.0)101 } else {102 Ok(())103 }104 }105 fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {106 writeln!(writer, "\t\t{};", self.0)107 }108 fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {109 T::solidity_default(writer)110 }111 fn len(&self) -> usize {112 if T::is_void() {113 0114 } else {115 1116 }117 }118}119120pub struct SolidityEventArgument<T>(pub bool, &'static str, PhantomData<*const T>);121122impl<T> SolidityEventArgument<T> {123 pub fn new(indexed: bool, name: &'static str) -> Self {124 Self(indexed, name, Default::default())125 }126}127128impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {129 fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {130 if !T::is_void() {131 T::solidity_name(writer)?;132 if self.0 {133 write!(writer, " indexed")?;134 }135 write!(writer, " {}", self.1)136 } else {137 Ok(())138 }139 }140 fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {141 writeln!(writer, "\t\t{};", self.1)142 }143 fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {144 T::solidity_default(writer)145 }146 fn len(&self) -> usize {147 if T::is_void() {148 0149 } else {150 1151 }152 }153}154155impl SolidityArguments for () {156 fn solidity_name(&self, _writer: &mut impl fmt::Write) -> fmt::Result {157 Ok(())158 }159 fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {160 Ok(())161 }162 fn solidity_default(&self, _writer: &mut impl fmt::Write) -> fmt::Result {163 Ok(())164 }165 fn len(&self) -> usize {166 0167 }168}169170#[impl_for_tuples(1, 5)]171impl SolidityArguments for Tuple {172 for_tuples!( where #( Tuple: SolidityArguments ),* );173174 fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {175 let mut first = true;176 for_tuples!( #(177 if !Tuple.is_empty() {178 if !first {179 write!(writer, ", ")?;180 }181 first = false;182 Tuple.solidity_name(writer)?;183 }184 )* );185 Ok(())186 }187 fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {188 for_tuples!( #(189 Tuple.solidity_get(writer)?;190 )* );191 Ok(())192 }193 fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {194 if self.is_empty() {195 Ok(())196 } else if self.len() == 1 {197 for_tuples!( #(198 Tuple.solidity_default(writer)?;199 )* );200 Ok(())201 } else {202 write!(writer, "(")?;203 let mut first = true;204 for_tuples!( #(205 if !Tuple.is_empty() {206 if !first {207 write!(writer, ", ")?;208 }209 first = false;210 Tuple.solidity_name(writer)?;211 }212 )* );213 write!(writer, ")")?;214 Ok(())215 }216 }217 fn len(&self) -> usize {218 for_tuples!( #( Tuple.len() )+* )219 }220}221222pub trait SolidityFunctions {223 fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result;224}225226pub enum SolidityMutability {227 Pure,228 View,229 Mutable,230}231pub struct SolidityFunction<A, R> {232 pub name: &'static str,233 pub args: A,234 pub result: R,235 pub mutability: SolidityMutability,236}237impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {238 fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {239 write!(writer, "\tfunction {}(", self.name)?;240 self.args.solidity_name(writer)?;241 write!(writer, ")")?;242 if is_impl {243 write!(writer, " public")?;244 } else {245 write!(writer, " external")?;246 }247 match &self.mutability {248 SolidityMutability::Pure => write!(writer, " pure")?,249 SolidityMutability::View => write!(writer, " view")?,250 SolidityMutability::Mutable => {}251 }252 if !self.result.is_empty() {253 write!(writer, " returns (")?;254 self.result.solidity_name(writer)?;255 write!(writer, ")")?;256 }257 if is_impl {258 writeln!(writer, " {{")?;259 writeln!(writer, "\t\trequire(false, stub_error);")?;260 self.args.solidity_get(writer)?;261 match &self.mutability {262 SolidityMutability::Pure => {}263 SolidityMutability::View => writeln!(writer, "\t\tdummy;")?,264 SolidityMutability::Mutable => writeln!(writer, "\t\tdummy = 0;")?,265 }266 if !self.result.is_empty() {267 write!(writer, "\t\treturn ")?;268 self.result.solidity_default(writer)?;269 writeln!(writer, ";")?;270 }271 writeln!(writer, "\t}}")?;272 } else {273 writeln!(writer, ";")?;274 }275 Ok(())276 }277}278279#[impl_for_tuples(0, 12)]280impl SolidityFunctions for Tuple {281 for_tuples!( where #( Tuple: SolidityFunctions ),* );282283 fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {284 let mut first = false;285 for_tuples!( #(286 Tuple.solidity_name(is_impl, writer)?;287 )* );288 Ok(())289 }290}291292pub struct SolidityInterface<F: SolidityFunctions> {293 pub name: &'static str,294 pub is: &'static [&'static str],295 pub functions: F,296}297298impl<F: SolidityFunctions> SolidityInterface<F> {299 pub fn format(&self, is_impl: bool, out: &mut impl fmt::Write) -> fmt::Result {300 if is_impl {301 write!(out, "contract ")?;302 } else {303 write!(out, "interface ")?;304 }305 write!(out, "{}", self.name)?;306 if !self.is.is_empty() {307 write!(out, " is")?;308 for (i, n) in self.is.iter().enumerate() {309 if i != 0 {310 write!(out, ",")?;311 }312 write!(out, " {}", n)?;313 }314 }315 writeln!(out, " {{")?;316 self.functions.solidity_name(is_impl, out)?;317 writeln!(out, "}}")?;318 Ok(())319 }320}321322pub struct SolidityEvent<A> {323 pub name: &'static str,324 pub args: A,325}326327impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {328 fn solidity_name(&self, _is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {329 write!(writer, "\tevent {}(", self.name)?;330 self.args.solidity_name(writer)?;331 writeln!(writer, ");")332 }333}