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}