difftreelog
doc(evm-coder): document public api
in: master
9 files changed
crates/evm-coder/Cargo.tomldiffbeforeafterboth--- a/crates/evm-coder/Cargo.toml
+++ b/crates/evm-coder/Cargo.toml
@@ -5,15 +5,21 @@
edition = "2021"
[dependencies]
-evm-coder-macros = { path = "../evm-coder-macros" }
+# evm-coder reexports those proc-macro
+evm-coder-procedural = { path = "./procedural" }
+# Evm uses primitive-types for H160, H256 and others
primitive-types = { version = "0.11.1", default-features = false }
-hex-literal = "0.3.3"
+# Evm doesn't have reexports for log and others
ethereum = { version = "0.12.0", default-features = false }
+# Error types for execution
evm-core = { default-features = false, git = "https://github.com/uniquenetwork/evm", branch = "unique-polkadot-v0.9.27" }
-impl-trait-for-tuples = "0.2.1"
+# We have tuple-heavy code in solidity.rs
+impl-trait-for-tuples = "0.2.2"
[dev-dependencies]
+# We want to assert some large binary blobs equality in tests
hex = "0.4.3"
+hex-literal = "0.3.4"
[features]
default = ["std"]
crates/evm-coder/README.mddiffbeforeafterboth--- /dev/null
+++ b/crates/evm-coder/README.md
@@ -0,0 +1,15 @@
+# evm-coder
+
+Library for seamless call translation between Rust and Solidity code
+
+By encoding solidity definitions in Rust, this library also provides generation of
+solidity interfaces for ethereum developers
+
+## Overview
+
+Most of this library functionality shouldn't be used directly, but via macros
+
+- [`solidity_interface`]
+- [`ToLog`]
+
+<!-- TODO: make links useable on github, by publishing crate to docs.rs, and linking it from here instead -->
\ No newline at end of file
crates/evm-coder/procedural/Cargo.tomldiffbeforeafterboth--- a/crates/evm-coder/procedural/Cargo.toml
+++ b/crates/evm-coder/procedural/Cargo.toml
@@ -8,9 +8,12 @@
proc-macro = true
[dependencies]
+# Ethereum uses keccak (=sha3) for selectors
sha3 = "0.10.1"
+# Value formatting
+hex = "0.4.3"
+Inflector = "0.11.4"
+# General proc-macro utilities
quote = "1.0"
proc-macro2 = "1.0"
syn = { version = "1.0", features = ["full"] }
-hex = "0.4.3"
-Inflector = "0.11.4"
crates/evm-coder/procedural/src/lib.rsdiffbeforeafterboth--- a/crates/evm-coder/procedural/src/lib.rs
+++ b/crates/evm-coder/procedural/src/lib.rs
@@ -21,8 +21,8 @@
use quote::quote;
use sha3::{Digest, Keccak256};
use syn::{
- DeriveInput, GenericArgument, Ident, ItemImpl, Pat, Path, PathArguments,
- PathSegment, Type, parse_macro_input, spanned::Spanned,
+ DeriveInput, GenericArgument, Ident, ItemImpl, Pat, Path, PathArguments, PathSegment, Type,
+ parse_macro_input, spanned::Spanned,
};
mod solidity_interface;
@@ -199,58 +199,7 @@
Ident::new(&name, ident.span())
}
-/// Derives call enum implementing [`evm_coder::Callable`], [`evm_coder::Weighted`]
-/// and [`evm_coder::Call`] from impl block
-///
-/// ## Macro syntax
-///
-/// `#[solidity_interface(name, is, inline_is, events)]`
-/// - *name*: used in generated code, and for Call enum name
-/// - *is*: used to provide call inheritance, not found methods will be delegated to all contracts
-/// specified in is/inline_is
-/// - *inline_is*: same as is, but selectors for passed contracts will be used by derived ERC165
-/// implementation
-///
-/// `#[weight(value)]`
-/// Can be added to every method of impl block, used for deriving [`evm_coder::Weighted`], which
-/// is used by substrate bridge
-/// - *value*: expression, which evaluates to weight required to call this method.
-/// This expression can use call arguments to calculate non-constant execution time.
-/// This expression should evaluate faster than actual execution does, and may provide worser case
-/// than one is called
-///
-/// `#[solidity_interface(rename_selector)]`
-/// - *rename_selector*: by default, selector name will be generated by transforming method name
-/// from snake_case to camelCase. Use this option, if other naming convention is required.
-/// I.e: method `token_uri` will be automatically renamed to `tokenUri` in selector, but name
-/// required by ERC721 standard is `tokenURI`, thus we need to specify `rename_selector = "tokenURI"`
-/// explicitly
-///
-/// Also, any contract method may have doc comments, which will be automatically added to generated
-/// solidity interface definitions
-///
-/// ## Example
-///
-/// ```ignore
-/// struct SuperContract;
-/// struct InlineContract;
-/// struct Contract;
-///
-/// #[derive(ToLog)]
-/// enum ContractEvents {
-/// Event(#[indexed] uint32),
-/// }
-///
-/// #[solidity_interface(name = MyContract, is(SuperContract), inline_is(InlineContract))]
-/// impl Contract {
-/// /// Multiply two numbers
-/// #[weight(200 + a + b)]
-/// #[solidity_interface(rename_selector = "mul")]
-/// fn mul(&mut self, a: uint32, b: uint32) -> Result<uint32> {
-/// Ok(a.checked_mul(b).ok_or("overflow")?)
-/// }
-/// }
-/// ```
+/// See documentation for this proc-macro reexported in `evm-coder` crate
#[proc_macro_attribute]
pub fn solidity_interface(args: TokenStream, stream: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as solidity_interface::InterfaceInfo);
@@ -282,10 +231,7 @@
stream
}
-/// ## Syntax
-///
-/// `#[indexed]`
-/// Marks this field as indexed, so it will appear in [`ethereum::Log`] topics instead of data
+/// See documentation for this proc-macro reexported in `evm-coder` crate
#[proc_macro_derive(ToLog, attributes(indexed))]
pub fn to_log(value: TokenStream) -> TokenStream {
let input = parse_macro_input!(value as DeriveInput);
crates/evm-coder/src/abi.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! TODO: I misunterstood therminology, abi IS rlp encoded, so18//! this module should be replaced with rlp crate1920#![allow(dead_code)]2122#[cfg(not(feature = "std"))]23use alloc::vec::Vec;24use evm_core::ExitError;25use primitive_types::{H160, U256};2627use crate::{28 execution::{Error, ResultWithPostInfo, WithPostDispatchInfo},29 types::{string, self},30};31use crate::execution::Result;3233const ABI_ALIGNMENT: usize = 32;3435#[derive(Clone)]36pub struct AbiReader<'i> {37 buf: &'i [u8],38 subresult_offset: usize,39 offset: usize,40}41impl<'i> AbiReader<'i> {42 pub fn new(buf: &'i [u8]) -> Self {43 Self {44 buf,45 subresult_offset: 0,46 offset: 0,47 }48 }49 pub fn new_call(buf: &'i [u8]) -> Result<(types::bytes4, Self)> {50 if buf.len() < 4 {51 return Err(Error::Error(ExitError::OutOfOffset));52 }53 let mut method_id = [0; 4];54 method_id.copy_from_slice(&buf[0..4]);5556 Ok((57 method_id,58 Self {59 buf,60 subresult_offset: 4,61 offset: 4,62 },63 ))64 }6566 fn read_pad<const S: usize>(67 buf: &[u8],68 offset: usize,69 pad_start: usize,70 pad_size: usize,71 block_start: usize,72 block_size: usize,73 ) -> Result<[u8; S]> {74 if buf.len() - offset < ABI_ALIGNMENT {75 return Err(Error::Error(ExitError::OutOfOffset));76 }77 let mut block = [0; S];78 // Verify padding is empty79 if !buf[pad_start..pad_size].iter().all(|&v| v == 0) {80 return Err(Error::Error(ExitError::InvalidRange));81 }82 block.copy_from_slice(&buf[block_start..block_size]);83 Ok(block)84 }8586 fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {87 let offset = self.offset;88 self.offset += ABI_ALIGNMENT;89 Self::read_pad(90 self.buf,91 offset,92 offset,93 offset + ABI_ALIGNMENT - S,94 offset + ABI_ALIGNMENT - S,95 offset + ABI_ALIGNMENT,96 )97 }9899 fn read_padright<const S: usize>(&mut self) -> Result<[u8; S]> {100 let offset = self.offset;101 self.offset += ABI_ALIGNMENT;102 Self::read_pad(103 self.buf,104 offset,105 offset + S,106 offset + ABI_ALIGNMENT,107 offset,108 offset + S,109 )110 }111112 pub fn address(&mut self) -> Result<H160> {113 Ok(H160(self.read_padleft()?))114 }115116 pub fn bool(&mut self) -> Result<bool> {117 let data: [u8; 1] = self.read_padleft()?;118 match data[0] {119 0 => Ok(false),120 1 => Ok(true),121 _ => Err(Error::Error(ExitError::InvalidRange)),122 }123 }124125 pub fn bytes4(&mut self) -> Result<[u8; 4]> {126 self.read_padright()127 }128129 pub fn bytes(&mut self) -> Result<Vec<u8>> {130 let mut subresult = self.subresult()?;131 let length = subresult.read_usize()?;132 if subresult.buf.len() < subresult.offset + length {133 return Err(Error::Error(ExitError::OutOfOffset));134 }135 Ok(subresult.buf[subresult.offset..subresult.offset + length].into())136 }137 pub fn string(&mut self) -> Result<string> {138 string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))139 }140141 pub fn uint8(&mut self) -> Result<u8> {142 Ok(self.read_padleft::<1>()?[0])143 }144145 pub fn uint32(&mut self) -> Result<u32> {146 Ok(u32::from_be_bytes(self.read_padleft()?))147 }148149 pub fn uint128(&mut self) -> Result<u128> {150 Ok(u128::from_be_bytes(self.read_padleft()?))151 }152153 pub fn uint256(&mut self) -> Result<U256> {154 let buf: [u8; 32] = self.read_padleft()?;155 Ok(U256::from_big_endian(&buf))156 }157158 pub fn uint64(&mut self) -> Result<u64> {159 Ok(u64::from_be_bytes(self.read_padleft()?))160 }161162 pub fn read_usize(&mut self) -> Result<usize> {163 Ok(usize::from_be_bytes(self.read_padleft()?))164 }165166 fn subresult(&mut self) -> Result<AbiReader<'i>> {167 let offset = self.read_usize()?;168 if offset + self.subresult_offset > self.buf.len() {169 return Err(Error::Error(ExitError::InvalidRange));170 }171 Ok(AbiReader {172 buf: self.buf,173 subresult_offset: offset + self.subresult_offset,174 offset: offset + self.subresult_offset,175 })176 }177178 pub fn is_finished(&self) -> bool {179 self.buf.len() == self.offset180 }181}182183#[derive(Default)]184pub struct AbiWriter {185 static_part: Vec<u8>,186 dynamic_part: Vec<(usize, AbiWriter)>,187 had_call: bool,188}189impl AbiWriter {190 pub fn new() -> Self {191 Self::default()192 }193 pub fn new_call(method_id: u32) -> Self {194 let mut val = Self::new();195 val.static_part.extend(&method_id.to_be_bytes());196 val.had_call = true;197 val198 }199200 fn write_padleft(&mut self, block: &[u8]) {201 assert!(block.len() <= ABI_ALIGNMENT);202 self.static_part203 .extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - block.len()]);204 self.static_part.extend(block);205 }206207 fn write_padright(&mut self, bytes: &[u8]) {208 assert!(bytes.len() <= ABI_ALIGNMENT);209 self.static_part.extend(bytes);210 self.static_part211 .extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - bytes.len()]);212 }213214 pub fn address(&mut self, address: &H160) {215 self.write_padleft(&address.0)216 }217218 pub fn bool(&mut self, value: &bool) {219 self.write_padleft(&[if *value { 1 } else { 0 }])220 }221222 pub fn uint8(&mut self, value: &u8) {223 self.write_padleft(&[*value])224 }225226 pub fn uint32(&mut self, value: &u32) {227 self.write_padleft(&u32::to_be_bytes(*value))228 }229230 pub fn uint128(&mut self, value: &u128) {231 self.write_padleft(&u128::to_be_bytes(*value))232 }233234 pub fn uint256(&mut self, value: &U256) {235 let mut out = [0; 32];236 value.to_big_endian(&mut out);237 self.write_padleft(&out)238 }239240 pub fn write_usize(&mut self, value: &usize) {241 self.write_padleft(&usize::to_be_bytes(*value))242 }243244 pub fn write_subresult(&mut self, result: Self) {245 self.dynamic_part.push((self.static_part.len(), result));246 // Empty block, to be filled later247 self.write_padleft(&[]);248 }249250 pub fn memory(&mut self, value: &[u8]) {251 let mut sub = Self::new();252 sub.write_usize(&value.len());253 for chunk in value.chunks(ABI_ALIGNMENT) {254 sub.write_padright(chunk);255 }256 self.write_subresult(sub);257 }258259 pub fn string(&mut self, value: &str) {260 self.memory(value.as_bytes())261 }262263 pub fn bytes(&mut self, value: &[u8]) {264 self.memory(value)265 }266267 pub fn finish(mut self) -> Vec<u8> {268 for (static_offset, part) in self.dynamic_part {269 let part_offset = self.static_part.len() - self.had_call.then(|| 4).unwrap_or(0);270271 let encoded_dynamic_offset = usize::to_be_bytes(part_offset);272 self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()273 ..static_offset + ABI_ALIGNMENT]274 .copy_from_slice(&encoded_dynamic_offset);275 self.static_part.extend(part.finish())276 }277 self.static_part278 }279}280281pub trait AbiRead<T> {282 fn abi_read(&mut self) -> Result<T>;283}284285macro_rules! impl_abi_readable {286 ($ty:ty, $method:ident) => {287 impl AbiRead<$ty> for AbiReader<'_> {288 fn abi_read(&mut self) -> Result<$ty> {289 self.$method()290 }291 }292 };293}294295impl_abi_readable!(u8, uint8);296impl_abi_readable!(u32, uint32);297impl_abi_readable!(u64, uint64);298impl_abi_readable!(u128, uint128);299impl_abi_readable!(U256, uint256);300impl_abi_readable!([u8; 4], bytes4);301impl_abi_readable!(H160, address);302impl_abi_readable!(Vec<u8>, bytes);303impl_abi_readable!(bool, bool);304impl_abi_readable!(string, string);305306mod sealed {307 /// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead308 pub trait CanBePlacedInVec {}309}310311impl sealed::CanBePlacedInVec for U256 {}312impl sealed::CanBePlacedInVec for string {}313impl sealed::CanBePlacedInVec for H160 {}314315impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>316where317 Self: AbiRead<R>,318{319 fn abi_read(&mut self) -> Result<Vec<R>> {320 let mut sub = self.subresult()?;321 let size = sub.read_usize()?;322 sub.subresult_offset = sub.offset;323 let mut out = Vec::with_capacity(size);324 for _ in 0..size {325 out.push(<Self as AbiRead<R>>::abi_read(&mut sub)?);326 }327 Ok(out)328 }329}330331macro_rules! impl_tuples {332 ($($ident:ident)+) => {333 impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}334 impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>335 where336 $(Self: AbiRead<$ident>),+337 {338 fn abi_read(&mut self) -> Result<($($ident,)+)> {339 let mut subresult = self.subresult()?;340 Ok((341 $(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+342 ))343 }344 }345 #[allow(non_snake_case)]346 impl<$($ident),+> AbiWrite for &($($ident,)+)347 where348 $($ident: AbiWrite,)+349 {350 fn abi_write(&self, writer: &mut AbiWriter) {351 let ($($ident,)+) = self;352 $($ident.abi_write(writer);)+353 }354 }355 };356}357358impl_tuples! {A}359impl_tuples! {A B}360impl_tuples! {A B C}361impl_tuples! {A B C D}362impl_tuples! {A B C D E}363impl_tuples! {A B C D E F}364impl_tuples! {A B C D E F G}365impl_tuples! {A B C D E F G H}366impl_tuples! {A B C D E F G H I}367impl_tuples! {A B C D E F G H I J}368369pub trait AbiWrite {370 fn abi_write(&self, writer: &mut AbiWriter);371 fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {372 let mut writer = AbiWriter::new();373 self.abi_write(&mut writer);374 Ok(writer.into())375 }376}377378impl<T: AbiWrite> AbiWrite for ResultWithPostInfo<T> {379 // this particular AbiWrite implementation should be split to another trait,380 // which only implements [`to_result`]381 //382 // But due to lack of specialization feature in stable Rust, we can't have383 // blanket impl of this trait `for T where T: AbiWrite`, so here we abusing384 // default trait methods for it385 fn abi_write(&self, _writer: &mut AbiWriter) {386 debug_assert!(false, "shouldn't be called, see comment")387 }388 fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {389 match self {390 Ok(v) => Ok(WithPostDispatchInfo {391 post_info: v.post_info.clone(),392 data: {393 let mut out = AbiWriter::new();394 v.data.abi_write(&mut out);395 out396 },397 }),398 Err(e) => Err(e.clone()),399 }400 }401}402403macro_rules! impl_abi_writeable {404 ($ty:ty, $method:ident) => {405 impl AbiWrite for $ty {406 fn abi_write(&self, writer: &mut AbiWriter) {407 writer.$method(&self)408 }409 }410 };411}412413impl_abi_writeable!(u8, uint8);414impl_abi_writeable!(u32, uint32);415impl_abi_writeable!(u128, uint128);416impl_abi_writeable!(U256, uint256);417impl_abi_writeable!(H160, address);418impl_abi_writeable!(bool, bool);419impl_abi_writeable!(&str, string);420impl AbiWrite for &string {421 fn abi_write(&self, writer: &mut AbiWriter) {422 writer.string(self)423 }424}425impl AbiWrite for &Vec<u8> {426 fn abi_write(&self, writer: &mut AbiWriter) {427 writer.bytes(self)428 }429}430431impl AbiWrite for () {432 fn abi_write(&self, _writer: &mut AbiWriter) {}433}434435#[macro_export]436macro_rules! abi_decode {437 ($reader:expr, $($name:ident: $typ:ident),+ $(,)?) => {438 $(439 let $name = $reader.$typ()?;440 )+441 }442}443#[macro_export]444macro_rules! abi_encode {445 ($($typ:ident($value:expr)),* $(,)?) => {{446 #[allow(unused_mut)]447 let mut writer = ::evm_coder::abi::AbiWriter::new();448 $(449 writer.$typ($value);450 )*451 writer452 }};453 (call $val:expr; $($typ:ident($value:expr)),* $(,)?) => {{454 #[allow(unused_mut)]455 let mut writer = ::evm_coder::abi::AbiWriter::new_call($val);456 $(457 writer.$typ($value);458 )*459 writer460 }}461}462463#[cfg(test)]464pub mod test {465 use crate::{466 abi::AbiRead,467 types::{string, uint256},468 };469470 use super::{AbiReader, AbiWriter};471 use hex_literal::hex;472473 #[test]474 fn dynamic_after_static() {475 let mut encoder = AbiWriter::new();476 encoder.bool(&true);477 encoder.string("test");478 let encoded = encoder.finish();479480 let mut encoder = AbiWriter::new();481 encoder.bool(&true);482 // Offset to subresult483 encoder.uint32(&(32 * 2));484 // Len of "test"485 encoder.uint32(&4);486 encoder.write_padright(&[b't', b'e', b's', b't']);487 let alternative_encoded = encoder.finish();488489 assert_eq!(encoded, alternative_encoded);490491 let mut decoder = AbiReader::new(&encoded);492 assert_eq!(decoder.bool().unwrap(), true);493 assert_eq!(decoder.string().unwrap(), "test");494 }495496 #[test]497 fn mint_sample() {498 let (call, mut decoder) = AbiReader::new_call(&hex!(499 "500 50bb4e7f501 000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374502 0000000000000000000000000000000000000000000000000000000000000001503 0000000000000000000000000000000000000000000000000000000000000060504 0000000000000000000000000000000000000000000000000000000000000008505 5465737420555249000000000000000000000000000000000000000000000000506 "507 ))508 .unwrap();509 assert_eq!(call, u32::to_be_bytes(0x50bb4e7f));510 assert_eq!(511 format!("{:?}", decoder.address().unwrap()),512 "0xad2c0954693c2b5404b7e50967d3481bea432374"513 );514 assert_eq!(decoder.uint32().unwrap(), 1);515 assert_eq!(decoder.string().unwrap(), "Test URI");516 }517518 #[test]519 fn mint_bulk() {520 let (call, mut decoder) = AbiReader::new_call(&hex!(521 "522 36543006523 00000000000000000000000053744e6da587ba10b32a2554d2efdcd985bc27a3 // address524 0000000000000000000000000000000000000000000000000000000000000040 // offset of (uint256, string)[]525 0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[]526527 0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem528 00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem529 0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem530531 0000000000000000000000000000000000000000000000000000000000000001 // first token id? #60532 0000000000000000000000000000000000000000000000000000000000000040 // offset of string533 000000000000000000000000000000000000000000000000000000000000000a // size of string534 5465737420555249203000000000000000000000000000000000000000000000 // string535536 000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11? #e0537 0000000000000000000000000000000000000000000000000000000000000040 // offset of string538 000000000000000000000000000000000000000000000000000000000000000a // size of string539 5465737420555249203100000000000000000000000000000000000000000000 // string540541 000000000000000000000000000000000000000000000000000000000000000c // third token id? Why ==12? #160542 0000000000000000000000000000000000000000000000000000000000000040 // offset of string543 000000000000000000000000000000000000000000000000000000000000000a // size of string544 5465737420555249203200000000000000000000000000000000000000000000 // string545 "546 ))547 .unwrap();548 assert_eq!(call, u32::to_be_bytes(0x36543006));549 let _ = decoder.address().unwrap();550 let data =551 <AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();552 assert_eq!(553 data,554 vec![555 (1.into(), "Test URI 0".to_string()),556 (11.into(), "Test URI 1".to_string()),557 (12.into(), "Test URI 2".to_string())558 ]559 );560 }561}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! Implementation of EVM RLP reader/writer1819#![allow(dead_code)]2021#[cfg(not(feature = "std"))]22use alloc::vec::Vec;23use evm_core::ExitError;24use primitive_types::{H160, U256};2526use crate::{27 execution::{Error, ResultWithPostInfo, WithPostDispatchInfo},28 types::{string, self},29};30use crate::execution::Result;3132const ABI_ALIGNMENT: usize = 32;3334/// View into RLP data, which provides method to read typed items from it35#[derive(Clone)]36pub struct AbiReader<'i> {37 buf: &'i [u8],38 subresult_offset: usize,39 offset: usize,40}41impl<'i> AbiReader<'i> {42 /// Start reading RLP buffer, assuming there is no padding bytes43 pub fn new(buf: &'i [u8]) -> Self {44 Self {45 buf,46 subresult_offset: 0,47 offset: 0,48 }49 }50 /// Start reading RLP buffer, parsing first 4 bytes as selector51 pub fn new_call(buf: &'i [u8]) -> Result<(types::bytes4, Self)> {52 if buf.len() < 4 {53 return Err(Error::Error(ExitError::OutOfOffset));54 }55 let mut method_id = [0; 4];56 method_id.copy_from_slice(&buf[0..4]);5758 Ok((59 method_id,60 Self {61 buf,62 subresult_offset: 4,63 offset: 4,64 },65 ))66 }6768 fn read_pad<const S: usize>(69 buf: &[u8],70 offset: usize,71 pad_start: usize,72 pad_size: usize,73 block_start: usize,74 block_size: usize,75 ) -> Result<[u8; S]> {76 if buf.len() - offset < ABI_ALIGNMENT {77 return Err(Error::Error(ExitError::OutOfOffset));78 }79 let mut block = [0; S];80 // Verify padding is empty81 if !buf[pad_start..pad_size].iter().all(|&v| v == 0) {82 return Err(Error::Error(ExitError::InvalidRange));83 }84 block.copy_from_slice(&buf[block_start..block_size]);85 Ok(block)86 }8788 fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {89 let offset = self.offset;90 self.offset += ABI_ALIGNMENT;91 Self::read_pad(92 self.buf,93 offset,94 offset,95 offset + ABI_ALIGNMENT - S,96 offset + ABI_ALIGNMENT - S,97 offset + ABI_ALIGNMENT,98 )99 }100101 fn read_padright<const S: usize>(&mut self) -> Result<[u8; S]> {102 let offset = self.offset;103 self.offset += ABI_ALIGNMENT;104 Self::read_pad(105 self.buf,106 offset,107 offset + S,108 offset + ABI_ALIGNMENT,109 offset,110 offset + S,111 )112 }113114 /// Read [`H160`] at current position, then advance115 pub fn address(&mut self) -> Result<H160> {116 Ok(H160(self.read_padleft()?))117 }118119 /// Read [`bool`] at current position, then advance120 pub fn bool(&mut self) -> Result<bool> {121 let data: [u8; 1] = self.read_padleft()?;122 match data[0] {123 0 => Ok(false),124 1 => Ok(true),125 _ => Err(Error::Error(ExitError::InvalidRange)),126 }127 }128129 /// Read [`[u8; 4]`] at current position, then advance130 pub fn bytes4(&mut self) -> Result<[u8; 4]> {131 self.read_padright()132 }133134 /// Read [`Vec<u8>`] at current position, then advance135 pub fn bytes(&mut self) -> Result<Vec<u8>> {136 let mut subresult = self.subresult()?;137 let length = subresult.uint32()? as usize;138 if subresult.buf.len() < subresult.offset + length {139 return Err(Error::Error(ExitError::OutOfOffset));140 }141 Ok(subresult.buf[subresult.offset..subresult.offset + length].into())142 }143144 /// Read [`string`] at current position, then advance145 pub fn string(&mut self) -> Result<string> {146 string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))147 }148149 /// Read [`u8`] at current position, then advance150 pub fn uint8(&mut self) -> Result<u8> {151 Ok(self.read_padleft::<1>()?[0])152 }153154 /// Read [`u32`] at current position, then advance155 pub fn uint32(&mut self) -> Result<u32> {156 Ok(u32::from_be_bytes(self.read_padleft()?))157 }158159 /// Read [`u128`] at current position, then advance160 pub fn uint128(&mut self) -> Result<u128> {161 Ok(u128::from_be_bytes(self.read_padleft()?))162 }163164 /// Read [`U256`] at current position, then advance165 pub fn uint256(&mut self) -> Result<U256> {166 let buf: [u8; 32] = self.read_padleft()?;167 Ok(U256::from_big_endian(&buf))168 }169170 /// Read [`u64`] at current position, then advance171 pub fn uint64(&mut self) -> Result<u64> {172 Ok(u64::from_be_bytes(self.read_padleft()?))173 }174175 /// Read [`usize`] at current position, then advance176 #[deprecated = "dangerous, as usize may have different width in wasm and native execution"]177 pub fn read_usize(&mut self) -> Result<usize> {178 Ok(usize::from_be_bytes(self.read_padleft()?))179 }180181 /// Slice recursive buffer, advance one word for buffer offset182 fn subresult(&mut self) -> Result<AbiReader<'i>> {183 let offset = self.uint32()? as usize;184 if offset + self.subresult_offset > self.buf.len() {185 return Err(Error::Error(ExitError::InvalidRange));186 }187 Ok(AbiReader {188 buf: self.buf,189 subresult_offset: offset + self.subresult_offset,190 offset: offset + self.subresult_offset,191 })192 }193194 /// Is this parser reached end of buffer?195 pub fn is_finished(&self) -> bool {196 self.buf.len() == self.offset197 }198}199200/// Writer for RLP encoded data201#[derive(Default)]202pub struct AbiWriter {203 static_part: Vec<u8>,204 dynamic_part: Vec<(usize, AbiWriter)>,205 had_call: bool,206}207impl AbiWriter {208 /// Initialize internal buffers for output data, assuming no padding required209 pub fn new() -> Self {210 Self::default()211 }212 /// Initialize internal buffers, inserting method selector at beginning213 pub fn new_call(method_id: u32) -> Self {214 let mut val = Self::new();215 val.static_part.extend(&method_id.to_be_bytes());216 val.had_call = true;217 val218 }219220 fn write_padleft(&mut self, block: &[u8]) {221 assert!(block.len() <= ABI_ALIGNMENT);222 self.static_part223 .extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - block.len()]);224 self.static_part.extend(block);225 }226227 fn write_padright(&mut self, bytes: &[u8]) {228 assert!(bytes.len() <= ABI_ALIGNMENT);229 self.static_part.extend(bytes);230 self.static_part231 .extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - bytes.len()]);232 }233234 /// Write [`H160`] to end of buffer235 pub fn address(&mut self, address: &H160) {236 self.write_padleft(&address.0)237 }238239 /// Write [`bool`] to end of buffer240 pub fn bool(&mut self, value: &bool) {241 self.write_padleft(&[if *value { 1 } else { 0 }])242 }243244 /// Write [`u8`] to end of buffer245 pub fn uint8(&mut self, value: &u8) {246 self.write_padleft(&[*value])247 }248249 /// Write [`u32`] to end of buffer250 pub fn uint32(&mut self, value: &u32) {251 self.write_padleft(&u32::to_be_bytes(*value))252 }253254 /// Write [`u128`] to end of buffer255 pub fn uint128(&mut self, value: &u128) {256 self.write_padleft(&u128::to_be_bytes(*value))257 }258259 /// Write [`U256`] to end of buffer260 pub fn uint256(&mut self, value: &U256) {261 let mut out = [0; 32];262 value.to_big_endian(&mut out);263 self.write_padleft(&out)264 }265266 /// Write [`usize`] to end of buffer267 #[deprecated = "dangerous, as usize may have different width in wasm and native execution"]268 pub fn write_usize(&mut self, value: &usize) {269 self.write_padleft(&usize::to_be_bytes(*value))270 }271272 /// Append recursive data, writing pending offset at end of buffer273 pub fn write_subresult(&mut self, result: Self) {274 self.dynamic_part.push((self.static_part.len(), result));275 // Empty block, to be filled later276 self.write_padleft(&[]);277 }278279 fn memory(&mut self, value: &[u8]) {280 let mut sub = Self::new();281 sub.uint32(&(value.len() as u32));282 for chunk in value.chunks(ABI_ALIGNMENT) {283 sub.write_padright(chunk);284 }285 self.write_subresult(sub);286 }287288 /// Append recursive [`str`] at end of buffer289 pub fn string(&mut self, value: &str) {290 self.memory(value.as_bytes())291 }292293 /// Append recursive [`[u8]`] at end of buffer294 pub fn bytes(&mut self, value: &[u8]) {295 self.memory(value)296 }297298 /// Finish writer, concatenating all internal buffers299 pub fn finish(mut self) -> Vec<u8> {300 for (static_offset, part) in self.dynamic_part {301 let part_offset = self.static_part.len() - self.had_call.then(|| 4).unwrap_or(0);302303 let encoded_dynamic_offset = usize::to_be_bytes(part_offset);304 self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()305 ..static_offset + ABI_ALIGNMENT]306 .copy_from_slice(&encoded_dynamic_offset);307 self.static_part.extend(part.finish())308 }309 self.static_part310 }311}312313/// [`AbiReader`] implements reading of many types, but it should314/// be limited to types defined in spec315///316/// As this trait can't be made sealed,317/// instead of having `impl AbiRead for T`, we have `impl AbiRead<T> for AbiReader`318pub trait AbiRead<T> {319 /// Read item from current position, advanding decoder320 fn abi_read(&mut self) -> Result<T>;321}322323macro_rules! impl_abi_readable {324 ($ty:ty, $method:ident) => {325 impl AbiRead<$ty> for AbiReader<'_> {326 fn abi_read(&mut self) -> Result<$ty> {327 self.$method()328 }329 }330 };331}332333impl_abi_readable!(u8, uint8);334impl_abi_readable!(u32, uint32);335impl_abi_readable!(u64, uint64);336impl_abi_readable!(u128, uint128);337impl_abi_readable!(U256, uint256);338impl_abi_readable!([u8; 4], bytes4);339impl_abi_readable!(H160, address);340impl_abi_readable!(Vec<u8>, bytes);341impl_abi_readable!(bool, bool);342impl_abi_readable!(string, string);343344mod sealed {345 /// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead346 pub trait CanBePlacedInVec {}347}348349impl sealed::CanBePlacedInVec for U256 {}350impl sealed::CanBePlacedInVec for string {}351impl sealed::CanBePlacedInVec for H160 {}352353impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>354where355 Self: AbiRead<R>,356{357 fn abi_read(&mut self) -> Result<Vec<R>> {358 let mut sub = self.subresult()?;359 let size = sub.uint32()? as usize;360 sub.subresult_offset = sub.offset;361 let mut out = Vec::with_capacity(size);362 for _ in 0..size {363 out.push(<Self as AbiRead<R>>::abi_read(&mut sub)?);364 }365 Ok(out)366 }367}368369macro_rules! impl_tuples {370 ($($ident:ident)+) => {371 impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}372 impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>373 where374 $(Self: AbiRead<$ident>),+375 {376 fn abi_read(&mut self) -> Result<($($ident,)+)> {377 let mut subresult = self.subresult()?;378 Ok((379 $(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+380 ))381 }382 }383 #[allow(non_snake_case)]384 impl<$($ident),+> AbiWrite for &($($ident,)+)385 where386 $($ident: AbiWrite,)+387 {388 fn abi_write(&self, writer: &mut AbiWriter) {389 let ($($ident,)+) = self;390 $($ident.abi_write(writer);)+391 }392 }393 };394}395396impl_tuples! {A}397impl_tuples! {A B}398impl_tuples! {A B C}399impl_tuples! {A B C D}400impl_tuples! {A B C D E}401impl_tuples! {A B C D E F}402impl_tuples! {A B C D E F G}403impl_tuples! {A B C D E F G H}404impl_tuples! {A B C D E F G H I}405impl_tuples! {A B C D E F G H I J}406407/// For questions about inability to provide custom implementations,408/// see [`AbiRead`]409pub trait AbiWrite {410 /// Write value to end of specified encoder411 fn abi_write(&self, writer: &mut AbiWriter);412 /// Specialization for [`crate::solidity_interface`] implementation,413 /// see comment in `impl AbiWrite for ResultWithPostInfo`414 fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {415 let mut writer = AbiWriter::new();416 self.abi_write(&mut writer);417 Ok(writer.into())418 }419}420421/// This particular AbiWrite implementation should be split to another trait,422/// which only implements `to_result`, but due to lack of specialization feature423/// in stable Rust, we can't have blanket impl of this trait `for T where T: AbiWrite`,424/// so here we abusing default trait methods for it425impl<T: AbiWrite> AbiWrite for ResultWithPostInfo<T> {426 fn abi_write(&self, _writer: &mut AbiWriter) {427 debug_assert!(false, "shouldn't be called, see comment")428 }429 fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {430 match self {431 Ok(v) => Ok(WithPostDispatchInfo {432 post_info: v.post_info.clone(),433 data: {434 let mut out = AbiWriter::new();435 v.data.abi_write(&mut out);436 out437 },438 }),439 Err(e) => Err(e.clone()),440 }441 }442}443444macro_rules! impl_abi_writeable {445 ($ty:ty, $method:ident) => {446 impl AbiWrite for $ty {447 fn abi_write(&self, writer: &mut AbiWriter) {448 writer.$method(&self)449 }450 }451 };452}453454impl_abi_writeable!(u8, uint8);455impl_abi_writeable!(u32, uint32);456impl_abi_writeable!(u128, uint128);457impl_abi_writeable!(U256, uint256);458impl_abi_writeable!(H160, address);459impl_abi_writeable!(bool, bool);460impl_abi_writeable!(&str, string);461impl AbiWrite for &string {462 fn abi_write(&self, writer: &mut AbiWriter) {463 writer.string(self)464 }465}466impl AbiWrite for &Vec<u8> {467 fn abi_write(&self, writer: &mut AbiWriter) {468 writer.bytes(self)469 }470}471472impl AbiWrite for () {473 fn abi_write(&self, _writer: &mut AbiWriter) {}474}475476/// Helper macros to parse reader into variables477#[deprecated]478#[macro_export]479macro_rules! abi_decode {480 ($reader:expr, $($name:ident: $typ:ident),+ $(,)?) => {481 $(482 let $name = $reader.$typ()?;483 )+484 }485}486487/// Helper macros to construct RLP-encoded buffer488#[deprecated]489#[macro_export]490macro_rules! abi_encode {491 ($($typ:ident($value:expr)),* $(,)?) => {{492 #[allow(unused_mut)]493 let mut writer = ::evm_coder::abi::AbiWriter::new();494 $(495 writer.$typ($value);496 )*497 writer498 }};499 (call $val:expr; $($typ:ident($value:expr)),* $(,)?) => {{500 #[allow(unused_mut)]501 let mut writer = ::evm_coder::abi::AbiWriter::new_call($val);502 $(503 writer.$typ($value);504 )*505 writer506 }}507}508509#[cfg(test)]510pub mod test {511 use crate::{512 abi::AbiRead,513 types::{string, uint256},514 };515516 use super::{AbiReader, AbiWriter};517 use hex_literal::hex;518519 #[test]520 fn dynamic_after_static() {521 let mut encoder = AbiWriter::new();522 encoder.bool(&true);523 encoder.string("test");524 let encoded = encoder.finish();525526 let mut encoder = AbiWriter::new();527 encoder.bool(&true);528 // Offset to subresult529 encoder.uint32(&(32 * 2));530 // Len of "test"531 encoder.uint32(&4);532 encoder.write_padright(&[b't', b'e', b's', b't']);533 let alternative_encoded = encoder.finish();534535 assert_eq!(encoded, alternative_encoded);536537 let mut decoder = AbiReader::new(&encoded);538 assert_eq!(decoder.bool().unwrap(), true);539 assert_eq!(decoder.string().unwrap(), "test");540 }541542 #[test]543 fn mint_sample() {544 let (call, mut decoder) = AbiReader::new_call(&hex!(545 "546 50bb4e7f547 000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374548 0000000000000000000000000000000000000000000000000000000000000001549 0000000000000000000000000000000000000000000000000000000000000060550 0000000000000000000000000000000000000000000000000000000000000008551 5465737420555249000000000000000000000000000000000000000000000000552 "553 ))554 .unwrap();555 assert_eq!(call, u32::to_be_bytes(0x50bb4e7f));556 assert_eq!(557 format!("{:?}", decoder.address().unwrap()),558 "0xad2c0954693c2b5404b7e50967d3481bea432374"559 );560 assert_eq!(decoder.uint32().unwrap(), 1);561 assert_eq!(decoder.string().unwrap(), "Test URI");562 }563564 #[test]565 fn mint_bulk() {566 let (call, mut decoder) = AbiReader::new_call(&hex!(567 "568 36543006569 00000000000000000000000053744e6da587ba10b32a2554d2efdcd985bc27a3 // address570 0000000000000000000000000000000000000000000000000000000000000040 // offset of (uint256, string)[]571 0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[]572573 0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem574 00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem575 0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem576577 0000000000000000000000000000000000000000000000000000000000000001 // first token id? #60578 0000000000000000000000000000000000000000000000000000000000000040 // offset of string579 000000000000000000000000000000000000000000000000000000000000000a // size of string580 5465737420555249203000000000000000000000000000000000000000000000 // string581582 000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11? #e0583 0000000000000000000000000000000000000000000000000000000000000040 // offset of string584 000000000000000000000000000000000000000000000000000000000000000a // size of string585 5465737420555249203100000000000000000000000000000000000000000000 // string586587 000000000000000000000000000000000000000000000000000000000000000c // third token id? Why ==12? #160588 0000000000000000000000000000000000000000000000000000000000000040 // offset of string589 000000000000000000000000000000000000000000000000000000000000000a // size of string590 5465737420555249203200000000000000000000000000000000000000000000 // string591 "592 ))593 .unwrap();594 assert_eq!(call, u32::to_be_bytes(0x36543006));595 let _ = decoder.address().unwrap();596 let data =597 <AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();598 assert_eq!(599 data,600 vec![601 (1.into(), "Test URI 0".to_string()),602 (11.into(), "Test URI 1".to_string()),603 (12.into(), "Test URI 2".to_string())604 ]605 );606 }607}crates/evm-coder/src/events.rsdiffbeforeafterboth--- a/crates/evm-coder/src/events.rs
+++ b/crates/evm-coder/src/events.rs
@@ -19,11 +19,23 @@
use crate::types::*;
+/// Implementation of this trait should not be written manually,
+/// instead use [`crate::ToLog`] proc macros.
+///
+/// See also [`evm_coder_procedural::ToLog`], [solidity docs on events](https://docs.soliditylang.org/en/develop/contracts.html#events)
pub trait ToLog {
+ /// Convert event to [`ethereum::Log`].
+ /// Because event by itself doesn't contains current contract
+ /// address, it should be specified manually.
fn to_log(&self, contract: H160) -> Log;
}
+/// Only items implementing `ToTopic` may be used as `#[indexed]` field
+/// in [`crate::ToLog`] macro usage.
+///
+/// See also (solidity docs on events)[<https://docs.soliditylang.org/en/develop/contracts.html#events>]
pub trait ToTopic {
+ /// Convert value to topic to be used in [`ethereum::Log`]
fn to_topic(&self) -> H256;
}
crates/evm-coder/src/execution.rsdiffbeforeafterboth--- a/crates/evm-coder/src/execution.rs
+++ b/crates/evm-coder/src/execution.rs
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! Contract execution related types
+
#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
use evm_core::{ExitError, ExitFatal};
@@ -22,10 +24,14 @@
use crate::Weight;
+/// Execution error, should be convertible between EVM and Substrate.
#[derive(Debug, Clone)]
pub enum Error {
+ /// Non-fatal contract error occured
Revert(String),
+ /// EVM fatal error
Fatal(ExitFatal),
+ /// EVM normal error
Error(ExitError),
}
@@ -38,9 +44,12 @@
}
}
+/// To be used in [`crate::solidity_interface`] implementation.
pub type Result<T> = core::result::Result<T, Error>;
+/// Static information collected from [`crate::weight`].
pub struct DispatchInfo {
+ /// Statically predicted call weight
pub weight: Weight,
}
@@ -55,16 +64,22 @@
}
}
+/// Weight information that is only available post dispatch.
+/// Note: This can only be used to reduce the weight or fee, not increase it.
#[derive(Default, Clone)]
pub struct PostDispatchInfo {
+ /// Actual weight consumed by call
actual_weight: Option<Weight>,
}
impl PostDispatchInfo {
+ /// Calculate amount to be returned back to user
pub fn calc_unspent(&self, info: &DispatchInfo) -> Weight {
info.weight - self.calc_actual_weight(info)
}
+ /// Calculate actual cansumed weight, saturating to weight reported
+ /// pre-dispatch
pub fn calc_actual_weight(&self, info: &DispatchInfo) -> Weight {
if let Some(actual_weight) = self.actual_weight {
actual_weight.min(info.weight)
@@ -74,9 +89,12 @@
}
}
+/// Wrapper for PostDispatchInfo and any user-provided data
#[derive(Clone)]
pub struct WithPostDispatchInfo<T> {
+ /// User provided data
pub data: T,
+ /// Info known after dispatch
pub post_info: PostDispatchInfo,
}
@@ -89,5 +107,6 @@
}
}
+/// Return type of items in [`crate::solidity_interface`] definition
pub type ResultWithPostInfo<T> =
core::result::Result<WithPostDispatchInfo<T>, WithPostDispatchInfo<Error>>;
crates/evm-coder/src/lib.rsdiffbeforeafterboth--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -14,22 +14,102 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+#![doc = include_str!("../README.md")]
+#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
use abi::{AbiRead, AbiReader, AbiWriter};
-pub use evm_coder_macros::{event_topic, fn_selector, solidity_interface, solidity, weight, ToLog};
+pub use evm_coder_procedural::{event_topic, fn_selector};
pub mod abi;
-pub mod events;
-pub use events::ToLog;
+pub use events::{ToLog, ToTopic};
use execution::DispatchInfo;
pub mod execution;
+
+/// Derives call enum implementing [`crate::Callable`], [`crate::Weighted`]
+/// and [`crate::Call`] from impl block.
+///
+/// ## Macro syntax
+///
+/// `#[solidity_interface(name, is, inline_is, events)]`
+/// - *name* - used in generated code, and for Call enum name
+/// - *is* - used to provide call inheritance, not found methods will be delegated to all contracts
+/// specified in is/inline_is
+/// - *inline_is* - same as is, but selectors for passed contracts will be used by derived ERC165
+/// implementation
+///
+/// `#[weight(value)]`
+/// Can be added to every method of impl block, used for deriving [`crate::Weighted`], which
+/// is used by substrate bridge.
+/// - *value*: expression, which evaluates to weight required to call this method.
+/// This expression can use call arguments to calculate non-constant execution time.
+/// This expression should evaluate faster than actual execution does, and may provide worser case
+/// than one is called.
+///
+/// `#[solidity_interface(rename_selector)]`
+/// - *rename_selector* - by default, selector name will be generated by transforming method name
+/// from snake_case to camelCase. Use this option, if other naming convention is required.
+/// I.e: method `token_uri` will be automatically renamed to `tokenUri` in selector, but name
+/// required by ERC721 standard is `tokenURI`, thus we need to specify `rename_selector = "tokenURI"`
+/// explicitly.
+///
+/// Both contract and contract methods may have doccomments, which will end up in a generated
+/// solidity interface file, thus you should use [solidity syntax](https://docs.soliditylang.org/en/latest/natspec-format.html) for writing documentation in this macro
+///
+/// ## Example
+///
+/// ```ignore
+/// struct SuperContract;
+/// struct InlineContract;
+/// struct Contract;
+///
+/// #[derive(ToLog)]
+/// enum ContractEvents {
+/// Event(#[indexed] uint32),
+/// }
+///
+/// /// @dev This contract provides function to multiply two numbers
+/// #[solidity_interface(name = MyContract, is(SuperContract), inline_is(InlineContract))]
+/// impl Contract {
+/// /// Multiply two numbers
+/// /// @param a First number
+/// /// @param b Second number
+/// /// @return uint32 Product of two passed numbers
+/// /// @dev This function returns error in case of overflow
+/// #[weight(200 + a + b)]
+/// #[solidity_interface(rename_selector = "mul")]
+/// fn mul(&mut self, a: uint32, b: uint32) -> Result<uint32> {
+/// Ok(a.checked_mul(b).ok_or("overflow")?)
+/// }
+/// }
+/// ```
+pub use evm_coder_procedural::solidity_interface;
+/// See [`solidity_interface`]
+pub use evm_coder_procedural::solidity;
+/// See [`solidity_interface`]
+pub use evm_coder_procedural::weight;
+
+/// Derives [`ToLog`] for enum
+///
+/// Selectors will be derived from variant names, there is currently no way to have custom naming
+/// for them
+///
+/// `#[indexed]`
+/// Marks this field as indexed, so it will appear in [`ethereum::Log`] topics instead of data
+pub use evm_coder_procedural::ToLog;
+
+// Api of those modules shouldn't be consumed directly, it is only exported for usage in proc macros
+#[doc(hidden)]
+pub mod events;
+#[doc(hidden)]
pub mod solidity;
-/// Solidity type definitions
+/// Solidity type definitions (aliases from solidity name to rust type)
+/// To be used in [`solidity_interface`] definitions, to make sure there is no
+/// type conflict between Rust code and generated definitions
pub mod types {
- #![allow(non_camel_case_types)]
+ #![allow(non_camel_case_types, missing_docs)]
#[cfg(not(feature = "std"))]
use alloc::{vec::Vec};
@@ -54,6 +134,8 @@
pub type string = ::std::string::String;
pub type bytes = Vec<u8>;
+ /// Solidity doesn't have `void` type, however we have special implementation
+ /// for empty tuple return type
pub type void = ();
//#region Special types
@@ -63,36 +145,64 @@
pub type caller = address;
//#endregion
+ /// Ethereum typed call message, similar to solidity
+ /// `msg` object.
pub struct Msg<C> {
pub call: C,
+ /// Address of user, which called this contract.
pub caller: H160,
+ /// Payment amount to contract.
+ /// Contract should reject payment, if target call is not payable,
+ /// and there is no `receiver()` function defined.
pub value: U256,
}
}
+/// Parseable EVM call, this trait should be implemented with [`solidity_interface`] macro
pub trait Call: Sized {
+ /// Parse call buffer into typed call enum
fn parse(selector: types::bytes4, input: &mut AbiReader) -> execution::Result<Option<Self>>;
}
+/// Intended to be used as `#[weight]` output type
+/// Should be same between evm-coder and substrate to avoid confusion
+///
+/// Isn't same thing as gas, some mapping is required between those types
pub type Weight = u64;
+/// In substrate, we have benchmarking, which allows
+/// us to not rely on gas metering, but instead predict amount of gas to execute call
pub trait Weighted: Call {
+ /// Predict weight of this call
fn weight(&self) -> DispatchInfo;
}
+/// Type callable with ethereum message, may be implemented by [`solidity_interface`] macro
+/// on interface implementation, or for externally-owned real EVM contract
pub trait Callable<C: Call> {
+ /// Call contract using specified call data
fn call(&mut self, call: types::Msg<C>) -> execution::ResultWithPostInfo<AbiWriter>;
}
-/// Implementation is implicitly provided for all interfaces
+/// Implementation of ERC165 is implicitly generated for all interfaces in [`solidity_interface`],
+/// this structure holds parsed data for ERC165Call subvariant
+///
+/// Note: no [`Callable`] implementation is provided, call implementation is inlined into every
+/// implementing contract
///
-/// Note: no Callable implementation is provided
+/// See <https://eips.ethereum.org/EIPS/eip-165>
#[derive(Debug)]
pub enum ERC165Call {
- SupportsInterface { interface_id: types::bytes4 },
+ /// ERC165 provides single method, which returns true, if contract
+ /// implements specified interface
+ SupportsInterface {
+ /// Requested interface
+ interface_id: types::bytes4,
+ },
}
impl ERC165Call {
+ /// ERC165 selector is provided by standard
pub const INTERFACE_ID: types::bytes4 = u32::to_be_bytes(0x01ffc9a7);
}
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -14,15 +14,15 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-//! Implementation detail of [`evm_coder::solidity_interface`] macro code-generation
+//! Implementation detail of [`crate::solidity_interface`] macro code-generation.
+//! You should not rely on any public item from this module, as it is only intended to be used
+//! by procedural macro, API and output format may be changed at any time.
+//!
+//! Purpose of this module is to receive solidity contract definition in module-specified
+//! format, and then output string, representing interface of this contract in solidity language
#[cfg(not(feature = "std"))]
-use alloc::{
- string::String,
- vec::Vec,
- collections::BTreeMap,
- format,
-};
+use alloc::{string::String, vec::Vec, collections::BTreeMap, format};
#[cfg(feature = "std")]
use std::collections::BTreeMap;
use core::{
@@ -81,8 +81,10 @@
pub trait SolidityTypeName: 'static {
fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
+ /// "simple" types are stored inline, no `memory` modifier should be used in solidity
fn is_simple() -> bool;
fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
+ /// Specialization
fn is_void() -> bool {
false
}
@@ -133,6 +135,10 @@
}
mod sealed {
+ /// Not every type should be directly placed in vec.
+ /// Vec encoding is not memory efficient, as every item will be padded
+ /// to 32 bytes.
+ /// Instead you should use specialized types (`bytes` in case of `Vec<u8>`)
pub trait CanBePlacedInVec {}
}
@@ -427,7 +433,11 @@
for doc in self.docs {
writeln!(writer, "\t///{}", doc)?;
}
- writeln!(writer, "\t/// @dev EVM selector for this function is: 0x{:0>8x},", self.selector)?;
+ writeln!(
+ writer,
+ "\t/// @dev EVM selector for this function is: 0x{:0>8x},",
+ self.selector
+ )?;
writeln!(writer, "\t/// or in textual repr: {}", self.selector_str)?;
write!(writer, "\tfunction {}(", self.name)?;
self.args.solidity_name(writer, tc)?;