--- a/Cargo.lock +++ b/Cargo.lock @@ -1096,6 +1096,26 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] +name = "const_format" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2353,6 +2373,7 @@ version = "0.1.3" dependencies = [ "concat-idents", + "const_format", "ethereum", "evm-coder-procedural", "evm-core 0.35.0 (git+https://github.com/uniquenetwork/evm?branch=unique-polkadot-v0.9.30)", @@ -2362,6 +2383,7 @@ "impl-trait-for-tuples", "pallet-evm 6.0.0-dev (git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit)", "primitive-types", + "sha3-const", "similar-asserts", "sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.30)", ] @@ -11085,6 +11107,12 @@ ] [[package]] +name = "sha3-const" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9625d558d174dbdc711248b479271c0fdca39e9d3d67f6e890b3d4251fbf8e" + +[[package]] name = "sharded-slab" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" --- a/crates/evm-coder/Cargo.toml +++ b/crates/evm-coder/Cargo.toml @@ -5,6 +5,10 @@ edition = "2021" [dependencies] +const_format = { version = "0.2.26", default-features = false } +sha3-const = { version = "0.1.0", default-features = false } +# Ethereum uses keccak (=sha3) for selectors +# sha3 = "0.10.1" # evm-coder reexports those proc-macro evm-coder-procedural = { path = "./procedural" } # Evm uses primitive-types for H160, H256 and others --- a/crates/evm-coder/procedural/src/solidity_interface.rs +++ b/crates/evm-coder/procedural/src/solidity_interface.rs @@ -20,6 +20,7 @@ // about Procedural Macros in Rust book: // https://doc.rust-lang.org/reference/procedural-macros.html +use proc_macro2::{TokenStream, Group}; use quote::{quote, ToTokens}; use inflector::cases; use std::fmt::Write; @@ -328,6 +329,7 @@ } } +#[derive(Debug)] enum AbiType { // type Plain(Ident), @@ -473,6 +475,7 @@ } } +#[derive(Debug)] struct MethodArg { name: Ident, camel_name: String, @@ -722,13 +725,25 @@ } } + fn expand_selector(&self) -> proc_macro2::TokenStream { + let custom_signature = self.expand_custom_signature(); + quote! { + { + let a = ::evm_coder::sha3_const::Keccak256::new() + .update(#custom_signature.as_bytes()) + .finalize(); + [a[0], a[1], a[2], a[3]] + } + } + } + fn expand_const(&self) -> proc_macro2::TokenStream { let screaming_name = &self.screaming_name; - let selector = u32::to_be_bytes(self.selector); let selector_str = &self.selector_str; + let selector = &self.expand_selector(); quote! { #[doc = #selector_str] - const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*]; + const #screaming_name: ::evm_coder::types::bytes4 = #selector; } } @@ -831,6 +846,59 @@ } } + fn expand_custom_signature(&self) -> proc_macro2::TokenStream { + let mut first_comma = true; + let mut custom_signature = TokenStream::new(); + let mut template = self.camel_name.clone() + "("; + self.args + .iter() + .filter_map(|a| { + if a.is_special() { + return None; + }; + + match a.ty { + AbiType::Plain(ref ident) => Some(ident), + _ => None, + } + }) + .for_each(|ident| { + if !first_comma { + custom_signature.extend(quote!(,)); + template.push(','); + } else { + first_comma = false; + }; + template.push_str("{}"); + let ident_str = ident.to_string(); + match ident_str.as_str() { + "address" | "uint8" | "uint16" | "uint32" | "uint64" | "uint128" + | "uint256" | "bytes4" | "topic" | "string" | "bytes" | "void" | "caller" + | "bool" | "" => { + custom_signature.extend(quote!(#ident_str)); + } + _ => { + custom_signature.extend(quote! { + #ident::SIGNATURE_STRING + }); + } + } + }); + + template.push(')'); + let mut template = quote!(#template); + template.extend(quote!(,)); + template.extend(custom_signature); + let custom_signature_group = Group::new(proc_macro2::Delimiter::Parenthesis, template); + let mut custom_signature = quote! { + ::evm_coder::const_format::formatcp! + }; + custom_signature.extend(custom_signature_group.to_token_stream()); + + // println!("!!!!! {}", custom_signature); + custom_signature + } + fn expand_solidity_function(&self) -> proc_macro2::TokenStream { let camel_name = &self.camel_name; let mutability = match self.mutability { @@ -847,15 +915,17 @@ .map(MethodArg::expand_solidity_argument); let docs = &self.docs; let selector_str = &self.selector_str; - let selector = self.selector; + let selector = &self.expand_selector(); let hide = self.hide; + let custom_signature = self.expand_custom_signature(); let is_payable = self.has_value_args; quote! { SolidityFunction { docs: &[#(#docs),*], selector_str: #selector_str, - selector: #selector, hide: #hide, + selector: u32::from_be_bytes(#selector), + custom_signature: #custom_signature, name: #camel_name, mutability: #mutability, is_payable: #is_payable, --- a/crates/evm-coder/src/lib.rs +++ b/crates/evm-coder/src/lib.rs @@ -90,6 +90,8 @@ pub use evm_coder_procedural::solidity; /// See [`solidity_interface`] pub use evm_coder_procedural::weight; +pub use const_format; +pub use sha3_const; /// Derives [`ToLog`] for enum /// @@ -227,6 +229,14 @@ } } + impl SignatureString for EthCrossAccount { + const SIGNATURE_STRING: &'static str = "(address,uint256)"; + } + + pub trait SignatureString { + const SIGNATURE_STRING: &'static str; + } + /// Convert `CrossAccountId` to `uint256`. pub fn convert_cross_account_to_uint256( from: &T::CrossAccountId, @@ -348,6 +358,18 @@ assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb); } + // #[test] + // fn function_selector_generation_1() { + // assert_eq!( + // fn_selector!(transferFromCrossAccountToCrossAccount( + // EthCrossAccount, + // EthCrossAccount, + // uint256 + // )), + // 2543295963 + // ); + // } + #[test] fn event_topic_generation() { assert_eq!( --- a/crates/evm-coder/src/solidity.rs +++ b/crates/evm-coder/src/solidity.rs @@ -35,6 +35,12 @@ use crate::types::*; #[derive(Default)] +pub struct FunctionSelectorMaker { + pub name: string, + pub args: Vec string>, +} + +#[derive(Default)] pub struct TypeCollector { /// Code => id /// id ordering is required to perform topo-sort on the resulting data @@ -482,11 +488,25 @@ View, Mutable, } + +// fn fn_selector_str(input: &str) -> u32 { +// use sha3::Digest; +// let mut hasher = sha3::Keccak256::new(); +// hasher.update(input.as_bytes()); +// let result = hasher.finalize(); + +// let mut selector_bytes = [0; 4]; +// selector_bytes.copy_from_slice(&result[0..4]); + +// u32::from_be_bytes(selector_bytes) +// } + pub struct SolidityFunction { pub docs: &'static [&'static str], pub selector_str: &'static str, pub selector: u32, pub hide: bool, + pub custom_signature: &'static str, pub name: &'static str, pub args: A, pub result: R, @@ -512,7 +532,7 @@ writeln!( writer, "\t{hide_comment}/// or in textual repr: {}", - self.selector_str + self.custom_signature )?; write!(writer, "\t{hide_comment}function {}(", self.name)?; self.args.solidity_name(writer, tc)?; --- a/pallets/evm-contract-helpers/Cargo.toml +++ b/pallets/evm-contract-helpers/Cargo.toml @@ -27,7 +27,7 @@ evm-coder = { default-features = false, path = '../../crates/evm-coder' } pallet-common = { default-features = false, path = '../../pallets/common' } pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' } -pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' } +pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' } up-data-structs = { default-features = false, path = '../../primitives/data-structs', features = [ 'serde1', ] } --- a/pallets/evm-contract-helpers/src/eth.rs +++ b/pallets/evm-contract-helpers/src/eth.rs @@ -16,6 +16,8 @@ //! Implementation of magic contract +extern crate alloc; +use alloc::{format, string::ToString}; use core::marker::PhantomData; use evm_coder::{ abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog, --- a/pallets/fungible/src/erc.rs +++ b/pallets/fungible/src/erc.rs @@ -16,6 +16,8 @@ //! ERC-20 standart support implementation. +extern crate alloc; +use alloc::{format, string::ToString}; use core::char::{REPLACEMENT_CHARACTER, decode_utf16}; use core::convert::TryInto; use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight}; --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -20,6 +20,7 @@ //! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods. extern crate alloc; +use alloc::{format, string::ToString}; use core::{ char::{REPLACEMENT_CHARACTER, decode_utf16}, convert::TryInto, --- a/pallets/refungible/Cargo.toml +++ b/pallets/refungible/Cargo.toml @@ -26,7 +26,9 @@ struct-versioning = { path = "../../crates/struct-versioning" } up-data-structs = { default-features = false, path = '../../primitives/data-structs' } ethereum = { version = "0.12.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive",] } +scale-info = { version = "2.0.1", default-features = false, features = [ + "derive", +] } derivative = { version = "2.2.0", features = ["use_core"] } [features] --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -21,6 +21,7 @@ extern crate alloc; +use alloc::{format, string::ToString}; use core::{ char::{REPLACEMENT_CHARACTER, decode_utf16}, convert::TryInto,