evm_coder/
lib.rs

1//! # evm-coder
2//!
3//! Library for seamless call translation between Rust and Solidity code
4//!
5//! By encoding solidity definitions in Rust, this library also provides generation of
6//! solidity interfaces for ethereum developers
7//!
8//! ## Overview
9//!
10//! Most of this library functionality shouldn't be used directly, but via macros
11//!
12//! - [`solidity_interface`]
13//! - [`ToLog`]
14//! - [`AbiCoder`]
15
16// #![deny(missing_docs)]
17#![macro_use]
18#![cfg_attr(not(feature = "std"), no_std)]
19#[cfg(not(feature = "std"))]
20extern crate alloc;
21
22extern crate self as evm_coder;
23
24pub use evm_coder_procedural::{event_topic, fn_selector};
25pub mod abi;
26pub use events::{ToLog, ToTopic};
27#[macro_use]
28pub mod custom_signature;
29
30/// Reexported for macro
31#[doc(hidden)]
32pub use ethereum;
33/// Derives call enum implementing [`crate::Callable`] and [`crate::Call`] from impl block.
34///
35/// ## Macro syntax
36///
37/// `#[solidity_interface(name, is, inline_is, events)]`
38/// - **`name`** - used in generated code, and for Call enum name
39/// - **`is`** - used to provide inheritance in Solidity
40/// - **`inline_is`** - same as **`is`**, but `ERC165::SupportsInterface` will work differently: For `is` SupportsInterface(A) will return true
41///   if A is one of the interfaces the contract is inherited from (e.g. B is created as `is(A)`). If B is created as `inline_is(A)`
42///   SupportsInterface(A) will internally create a new interface that combines all methods of A and B, so SupportsInterface(A) will return
43///   false.
44///
45/// `#[solidity_interface(rename_selector)]`
46/// - **`rename_selector`** - by default, selector name will be generated by transforming method name
47/// from `snake_case` to `camelCase`. Use this option, if other naming convention is required.
48/// I.e: method `token_uri` will be automatically renamed to `tokenUri` in selector, but name
49/// required by ERC721 standard is `tokenURI`, thus we need to specify `rename_selector = "tokenURI"`
50/// explicitly.
51///
52/// Both contract and contract methods may have doccomments, which will end up in a generated
53/// solidity interface file, thus you should use [solidity syntax](https://docs.soliditylang.org/en/latest/natspec-format.html) for writing documentation in this macro
54///
55/// ## Example
56///
57/// ```ignore
58/// struct SuperContract;
59/// struct InlineContract;
60/// struct Contract;
61///
62/// #[derive(ToLog)]
63/// enum ContractEvents {
64///     Event(#[indexed] uint32),
65/// }
66///
67/// /// @dev This contract provides function to multiply two numbers
68/// #[solidity_interface(name = MyContract, is(SuperContract), inline_is(InlineContract))]
69/// impl Contract {
70///     /// Multiply two numbers
71///     /// @param a First number
72///     /// @param b Second number
73///     /// @return uint32 Product of two passed numbers
74///     /// @dev This function returns error in case of overflow
75///     #[weight(200 + a + b)]
76///     #[solidity_interface(rename_selector = "mul")]
77///     fn mul(&mut self, a: uint32, b: uint32) -> Result<uint32> {
78///         Ok(a.checked_mul(b).ok_or("overflow")?)
79///     }
80/// }
81/// ```
82pub use evm_coder_procedural::solidity_interface;
83/// Macro to include support for structures and enums in Solidity.
84///
85/// ### Overview
86/// This macro is used to include support for structures and enums in Solidity.
87/// This allows them to encode and decode in \ from the Solidity Abi format, as well as create their views into Solidity lang.
88///
89/// ### Implemented trais
90/// - [`AbiType`](abi::AbiType)
91/// - [`AbiRead`](abi::AbiRead)
92/// - [`AbiWrite`](abi::AbiWrite)
93/// - [`SolidityTypeName`](solidity::SolidityTypeName)
94/// - [`SolidityStructTy`](solidity::SolidityStructTy) - for struct
95/// - [`SolidityEnumTy`](solidity::SolidityEnumTy) - for enum
96///
97/// ### Limitations
98/// - All struct fields must implement traits listed above.
99/// - Enum must have u8 layout.
100/// - Enum must implement folowing traits: Default, Copy, Clone
101///
102/// ### Example
103/// ```
104/// use evm_coder::AbiCoder;
105///
106/// #[derive(AbiCoder)]
107/// struct Foo {
108///     a: u8,
109///     b: String
110/// }
111///
112/// #[derive(AbiCoder, Default, Clone, Copy)]
113/// #[repr(u8)]
114/// enum Color {
115///     Red,
116///     Green,
117///     #[default]
118///     Blue,
119/// }
120/// ```
121pub use evm_coder_procedural::AbiCoder;
122#[cfg(feature = "bondrewd")]
123pub use evm_coder_procedural::AbiCoderFlags;
124/// Derives [`ToLog`] for enum
125///
126/// Selectors will be derived from variant names, there is currently no way to have custom naming
127/// for them
128///
129/// `#[indexed]`
130/// Marks this field as indexed, so it will appear in [`ethereum::Log`] topics instead of data
131pub use evm_coder_procedural::ToLog;
132/// Reexported for macro
133#[doc(hidden)]
134pub use sha3_const;
135
136pub use self::abi::{AbiDecode, AbiDecoder, AbiEncode, AbiEncoder};
137use self::{abi::Error, types::*};
138
139// Api of those modules shouldn't be consumed directly, it is only exported for usage in proc macros
140#[doc(hidden)]
141pub mod events;
142#[doc(hidden)]
143#[cfg(feature = "stubgen")]
144pub mod solidity;
145
146/// Solidity type definitions (aliases from solidity name to rust type)
147/// To be used in [`solidity_interface`] definitions, to make sure there is no
148/// type conflict between Rust code and generated definitions
149pub mod types {
150	#![allow(non_camel_case_types, missing_docs)]
151
152	#[cfg(not(feature = "std"))]
153	pub use alloc::{vec, vec::Vec};
154	use core::marker::PhantomData;
155	#[cfg(feature = "std")]
156	pub use std::vec::Vec;
157
158	use primitive_types::{H160, H256, U256};
159
160	use crate::abi::AbiDecodeZero;
161
162	pub type Address = H160;
163	pub type Topic = H256;
164
165	#[cfg(not(feature = "std"))]
166	pub type String = ::alloc::string::String;
167	#[cfg(feature = "std")]
168	pub type String = ::std::string::String;
169
170	#[derive(Default, Debug, PartialEq, Eq, Clone)]
171	pub struct Bytes(pub Vec<u8>);
172	#[derive(Debug, PartialEq, Eq, Clone, Copy)]
173	pub struct BytesFixed<const S: usize>(pub [u8; S]);
174	pub type Bytes4 = BytesFixed<4>;
175
176	/// Enforce value to be zero.
177	/// This type will always encode as evm zero, and will fail on decoding if not zero.
178	#[derive(Debug, PartialEq, Clone)]
179	pub struct Zero<T>(PhantomData<T>);
180	impl<T> Zero<T> {
181		pub fn new() -> Self {
182			Self(PhantomData)
183		}
184	}
185	impl<T> Default for Zero<T> {
186		fn default() -> Self {
187			Self::new()
188		}
189	}
190
191	pub enum MaybeZero<T: AbiDecodeZero> {
192		Zero(Zero<T>),
193		NonZero(T),
194	}
195
196	//#region Special types
197	/// Makes function payable
198	pub type Value = U256;
199	/// Makes function caller-sensitive
200	pub type Caller = Address;
201	//#endregion
202
203	/// Ethereum typed call message, similar to solidity
204	/// `msg` object.
205	pub struct Msg<C> {
206		pub call: C,
207		/// Address of user, which called this contract.
208		pub caller: H160,
209		/// Payment amount to contract.
210		/// Contract should reject payment, if target call is not payable,
211		/// and there is no `receiver()` function defined.
212		pub value: U256,
213	}
214
215	impl From<Vec<u8>> for Bytes {
216		fn from(src: Vec<u8>) -> Self {
217			Self(src)
218		}
219	}
220
221	#[allow(clippy::from_over_into)]
222	impl Into<Vec<u8>> for Bytes {
223		fn into(self) -> Vec<u8> {
224			self.0
225		}
226	}
227
228	impl Bytes {
229		#[must_use]
230		pub fn len(&self) -> usize {
231			self.0.len()
232		}
233
234		#[must_use]
235		pub fn is_empty(&self) -> bool {
236			self.len() == 0
237		}
238	}
239
240	impl<const S: usize> Default for BytesFixed<S> {
241		fn default() -> Self {
242			Self([0; S])
243		}
244	}
245	// This can't be implemented the other way
246	#[allow(clippy::from_over_into)]
247	impl<const S: usize> Into<Vec<u8>> for BytesFixed<S> {
248		fn into(self) -> Vec<u8> {
249			self.0.into()
250		}
251	}
252}
253
254/// Parseable EVM call, this trait should be implemented with [`solidity_interface`] macro
255pub trait Call: Sized {
256	/// Parse call buffer into typed call enum
257	///
258	/// # Errors
259	///
260	/// One of call arguments has bad encoding, or value is invalid for the target type
261	fn parse(selector: Bytes4, input: &[u8]) -> abi::Result<Option<Self>>;
262	fn parse_full(input: &[u8]) -> abi::Result<Option<Self>> {
263		if input.len() < 4 {
264			return Err(Error::OutOfOffset);
265		}
266		let mut selector = [0; 4];
267		selector.copy_from_slice(&input[..4]);
268
269		Self::parse(BytesFixed(selector), &input[4..])
270	}
271}
272
273/// Type callable with ethereum message, may be implemented by [`solidity_interface`] macro
274/// on interface implementation, or for externally-owned real EVM contract
275pub trait Callable<C: Call>: Contract {
276	/// Call contract using specified call data
277	fn call(&mut self, call: types::Msg<C>) -> ResultWithPostInfoOf<Self, Vec<u8>>;
278}
279
280/// Contract specific result type
281pub type ResultOf<C, R> = <C as Contract>::Result<R, <C as Contract>::Error>;
282/// Contract specific result type
283pub type ResultWithPostInfoOf<C, R> = <C as Contract>::Result<
284	<C as Contract>::WithPostInfo<R>,
285	<C as Contract>::WithPostInfo<<C as Contract>::Error>,
286>;
287
288/// Contract configuration
289pub trait Contract {
290	/// Contract error type
291	type Error: From<&'static str>;
292	/// Wrapper for Result Ok/Err value
293	type WithPostInfo<T>;
294	/// Return value of [`Callable`], expected to be of [`core::result::Result`] type
295	type Result<T, E>;
296
297	/// Map `WithPostInfo` value
298	fn map_post<I, O>(
299		v: Self::WithPostInfo<I>,
300		mapper: impl FnOnce(I) -> O,
301	) -> Self::WithPostInfo<O>;
302	/// Wrap value with default post info
303	fn with_default_post<T>(v: T) -> Self::WithPostInfo<T>;
304}
305
306/// Example of `PostInfo`, used in tests
307pub struct DummyPost<T>(pub T);
308/// Implement dummy Contract trait, used for tests
309/// Allows contract methods to return either T, or Result<T, String> for any T
310#[macro_export]
311macro_rules! dummy_contract {
312	(
313		macro_rules! $res:ident {...}
314		impl$(<$($gen:ident),+ $(,)?>)? Contract for $ty:ty {...}
315	) => {
316		/// Generate macro to convert function return value into Contract result
317		/// This macro uses autoref specialization technique, described here: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md
318		macro_rules! $res {
319			($i:expr) => {{
320				use ::evm_coder::DummyPost;
321				struct Wrapper<T>(core::cell::Cell<Option<T>>);
322				type O<T> = ::core::result::Result<DummyPost<T>, DummyPost<String>>;
323				trait Matcher<T> {
324					fn convert(&self) -> O<T>;
325				}
326				impl<T> Matcher<T> for &Wrapper<::core::result::Result<T, String>> {
327					fn convert(&self) -> O<T> {
328						let i = self.0.take().unwrap();
329						i.map(DummyPost).map_err(DummyPost)
330					}
331				}
332				impl<T> Matcher<T> for Wrapper<T> {
333					fn convert(&self) -> O<T> {
334						let i = self.0.take().unwrap();
335						Ok(DummyPost(i))
336					}
337				}
338				(&&Wrapper(core::cell::Cell::new(Some($i)))).convert()
339			}};
340		}
341		impl $(<$($gen),+>)? $crate::Contract for $ty {
342			type Error = String;
343			type WithPostInfo<RR> = $crate::DummyPost<RR>;
344			type Result<RR, EE> = core::result::Result<RR, EE>;
345			fn map_post<II, OO>(v: Self::WithPostInfo<II>, mapper: impl FnOnce(II) -> OO) -> Self::WithPostInfo<OO> {
346				$crate::DummyPost(mapper(v.0))
347			}
348			/// Wrap value with default post info
349			fn with_default_post<TT>(v: TT) -> Self::WithPostInfo<TT> {
350				$crate::DummyPost(v)
351			}
352		}
353	};
354}
355
356/// Implementation of ERC165 is implicitly generated for all interfaces in [`solidity_interface`],
357/// this structure holds parsed data for `ERC165Call` subvariant
358///
359/// Note: no [`Callable`] implementation is provided, call implementation is inlined into every
360/// implementing contract
361///
362/// See <https://eips.ethereum.org/EIPS/eip-165>
363#[derive(Debug, PartialEq)]
364pub enum ERC165Call {
365	/// ERC165 provides single method, which returns true, if contract
366	/// implements specified interface
367	SupportsInterface {
368		/// Requested interface
369		interface_id: Bytes4,
370	},
371}
372
373impl ERC165Call {
374	/// ERC165 selector is provided by standard
375	pub const INTERFACE_ID: Bytes4 = BytesFixed(u32::to_be_bytes(0x01ff_c9a7));
376}
377
378impl Call for ERC165Call {
379	fn parse(selector: Bytes4, input: &[u8]) -> abi::Result<Option<Self>> {
380		if selector != Self::INTERFACE_ID {
381			return Ok(None);
382		}
383		Ok(Some(Self::SupportsInterface {
384			interface_id: Bytes4::abi_decode(input)?,
385		}))
386	}
387}
388
389/// Generate "tests", which will generate solidity code on execution and print it to stdout
390/// Script at `.maintain/scripts/generate_api.sh` can split this output from test runtime
391///
392/// This macro receives type usage as second argument, but you can use anything as generics,
393/// because no bounds are implied
394#[macro_export]
395macro_rules! generate_stubgen {
396	($name:ident, $decl:ty, $is_impl:literal) => {
397		#[cfg(feature = "stubgen")]
398		#[test]
399		#[ignore]
400		fn $name() {
401			use evm_coder::solidity::TypeCollector;
402			let mut out = TypeCollector::new();
403			<$decl>::generate_solidity_interface(&mut out, $is_impl);
404			println!("=== SNIP START ===");
405			println!("// SPDX-License-Identifier: OTHER");
406			println!("// This code is automatically generated");
407			println!();
408			println!("pragma solidity >=0.8.0 <0.9.0;");
409			println!();
410			for b in out.finish() {
411				println!("{}", b);
412			}
413			println!("=== SNIP END ===");
414		}
415	};
416}
417
418#[cfg(test)]
419mod tests {
420	use super::*;
421
422	#[test]
423	fn function_selector_generation() {
424		assert_eq!(
425			fn_selector!(transfer(address, uint256)),
426			BytesFixed(u32::to_be_bytes(0xa9059cbb))
427		);
428	}
429
430	#[test]
431	fn event_topic_generation() {
432		assert_eq!(
433			hex::encode(&event_topic!(Transfer(address, address, uint256))[..]),
434			"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
435		);
436	}
437}