├── .gitignore ├── procedural ├── src │ ├── structs │ │ ├── mod.rs │ │ └── parse.rs │ ├── abi_derive │ │ ├── mod.rs │ │ ├── derive_enum.rs │ │ ├── derive_flags.rs │ │ └── derive_struct.rs │ ├── lib.rs │ └── to_log.rs └── Cargo.toml ├── .rustfmt.toml ├── tests ├── build_failed │ ├── abi_derive_flags_generation.stderr │ ├── abi_derive_struct_generation.rs │ ├── abi_derive_struct_generation.stderr │ ├── abi_derive_enum_generation.rs │ ├── abi_derive_enum_generation.stderr │ ├── abi_derive_flags_generation.rs │ ├── custom_signature_over_max_size.rs │ └── custom_signature_over_max_size.stderr ├── log.rs ├── derive.rs ├── bondrewd_generation.rs ├── generics.rs ├── conditional_is.rs ├── solidity_generation.rs └── random.rs ├── .taplo.toml ├── scripts ├── generate_abi.sh ├── compile_stub.sh └── generate_sol.sh ├── .github └── workflows │ └── ci.yaml ├── LICENSE-MIT ├── src ├── events.rs ├── solidity │ ├── traits.rs │ ├── impls.rs │ └── mod.rs ├── abi │ ├── traits.rs │ ├── mod.rs │ ├── impls.rs │ └── test.rs ├── custom_signature.rs └── lib.rs ├── Cargo.toml ├── CHANGELOG.md ├── LICENSE-APACHE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode 3 | -------------------------------------------------------------------------------- /procedural/src/structs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod common; 2 | pub mod parse; 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "stdexternalcrate" 2 | hard_tabs = true 3 | imports_granularity = "crate" 4 | -------------------------------------------------------------------------------- /tests/build_failed/abi_derive_flags_generation.stderr: -------------------------------------------------------------------------------- 1 | error: Empty structs not supported 2 | --> tests/build_failed/abi_derive_flags_generation.rs:5:8 3 | | 4 | 5 | struct EmptyStruct {} 5 | | ^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | [formatting] 2 | array_auto_collapse = true 3 | array_auto_expand = true 4 | array_trailing_comma = true 5 | column_width = 120 6 | indent_string = "\t" 7 | reorder_arrays = true 8 | reorder_keys = true 9 | trailing_newline = true 10 | -------------------------------------------------------------------------------- /tests/build_failed/abi_derive_struct_generation.rs: -------------------------------------------------------------------------------- 1 | use evm_coder_procedural::AbiCoder; 2 | 3 | #[derive(AbiCoder, PartialEq, Debug)] 4 | struct EmptyStruct {} 5 | 6 | #[derive(AbiCoder, PartialEq, Debug)] 7 | struct EmptyTupleStruct(); 8 | 9 | fn main() { 10 | assert!(false); 11 | } 12 | -------------------------------------------------------------------------------- /scripts/generate_abi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | dir=$PWD 5 | 6 | tmp=$(mktemp -d) 7 | echo "Tmp file: $tmp/input.sol" 8 | cd $tmp 9 | cp $dir/$INPUT input.sol 10 | solcjs --abi -p input.sol 11 | 12 | NAME=input_sol_$(basename $INPUT .sol) 13 | mv $NAME.abi $NAME.json 14 | prettier $NAME.json > $dir/$OUTPUT 15 | -------------------------------------------------------------------------------- /scripts/compile_stub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | dir=$PWD 5 | 6 | tmp=$(mktemp -d) 7 | cd $tmp 8 | cp $dir/$INPUT input.sol 9 | echo "Tmp file: $tmp/input.sol" 10 | solcjs --optimize --bin input.sol -o $PWD 11 | 12 | mv input_sol_$(basename $OUTPUT .raw).bin out.bin 13 | xxd -r -p out.bin out.raw 14 | 15 | mv out.raw $dir/$OUTPUT 16 | -------------------------------------------------------------------------------- /tests/log.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use evm_coder::{types::*, ToLog}; 4 | use primitive_types::U256; 5 | 6 | #[derive(ToLog)] 7 | enum ERC721Log { 8 | Transfer { 9 | #[indexed] 10 | from: Address, 11 | #[indexed] 12 | to: Address, 13 | value: U256, 14 | }, 15 | Eee { 16 | #[indexed] 17 | aaa: Address, 18 | bbb: U256, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /tests/build_failed/abi_derive_struct_generation.stderr: -------------------------------------------------------------------------------- 1 | error: Empty structs not supported 2 | --> tests/build_failed/abi_derive_struct_generation.rs:4:8 3 | | 4 | 4 | struct EmptyStruct {} 5 | | ^^^^^^^^^^^ 6 | 7 | error: Empty structs not supported 8 | --> tests/build_failed/abi_derive_struct_generation.rs:7:8 9 | | 10 | 7 | struct EmptyTupleStruct(); 11 | | ^^^^^^^^^^^^^^^^ 12 | -------------------------------------------------------------------------------- /tests/derive.rs: -------------------------------------------------------------------------------- 1 | use derivative::Derivative; 2 | use evm_coder::{dummy_contract, solidity_interface}; 3 | 4 | type Result = core::result::Result; 5 | 6 | pub struct Contract(bool); 7 | dummy_contract! { 8 | macro_rules! Contract_result {...} 9 | impl Contract for Contract {...} 10 | } 11 | 12 | #[solidity_interface(name = A, enum(derive(Derivative)), enum(derivative(PartialEq)))] 13 | impl Contract { 14 | fn method_a() -> Result<()> { 15 | Ok(()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/build_failed/abi_derive_enum_generation.rs: -------------------------------------------------------------------------------- 1 | use evm_coder_procedural::AbiCoder; 2 | 3 | #[derive(AbiCoder)] 4 | enum NonRepr { 5 | A, 6 | B, 7 | C, 8 | } 9 | 10 | #[derive(AbiCoder)] 11 | #[repr(u32)] 12 | enum NonReprU8 { 13 | A, 14 | B, 15 | C, 16 | } 17 | 18 | #[derive(AbiCoder)] 19 | #[repr(u8)] 20 | enum RustEnum { 21 | A(u128), 22 | B, 23 | C, 24 | } 25 | 26 | #[derive(AbiCoder)] 27 | #[repr(u8)] 28 | enum WithExplicit { 29 | A = 128, 30 | B, 31 | C, 32 | } 33 | 34 | fn main() { 35 | assert!(false); 36 | } 37 | -------------------------------------------------------------------------------- /scripts/generate_sol.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | PRETTIER_CONFIG="$(pwd)""/.prettierrc" 5 | 6 | tmp=$(mktemp) 7 | cargo test --package=$PACKAGE --features=stubgen -- $NAME --exact --nocapture --ignored | tee $tmp 8 | raw=$(mktemp --suffix .sol) 9 | sed -n '/=== SNIP START ===/, /=== SNIP END ===/{ /=== SNIP START ===/! { /=== SNIP END ===/! p } }' $tmp > $raw 10 | 11 | formatted=$(mktemp) 12 | prettier --config $PRETTIER_CONFIG $raw > $formatted 13 | 14 | sed -i -E -e "s/.+\/\/ FORMATTING: FORCE NEWLINE//g" $formatted 15 | 16 | mv $formatted $OUTPUT 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | RUSTFLAGS: -Dwarnings 9 | 10 | jobs: 11 | test: 12 | name: Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: dtolnay/rust-toolchain@nightly 17 | - run: cargo test 18 | 19 | clippy: 20 | name: Clippy 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: dtolnay/rust-toolchain@clippy 25 | - run: cargo clippy --all -- -Dclippy::all -Dclippy::pedantic 26 | -------------------------------------------------------------------------------- /procedural/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "Implementation of proc-macro for evm-coder" 3 | documentation.workspace = true 4 | edition = "2021" 5 | license.workspace = true 6 | name = "evm-coder-procedural" 7 | repository.workspace = true 8 | version.workspace = true 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | # Ethereum uses keccak (=sha3) for selectors 15 | sha3 = "0.10.1" 16 | # Value formatting 17 | Inflector = "0.11.4" 18 | hex = "0.4.3" 19 | # General proc-macro utilities 20 | proc-macro2 = "1.0" 21 | quote = "1.0" 22 | syn = { version = "1.0", features = ["full"] } 23 | 24 | [features] 25 | bondrewd = [] -------------------------------------------------------------------------------- /tests/build_failed/abi_derive_enum_generation.stderr: -------------------------------------------------------------------------------- 1 | error: Enum is not "repr(u8)" 2 | --> tests/build_failed/abi_derive_enum_generation.rs:4:6 3 | | 4 | 4 | enum NonRepr { 5 | | ^^^^^^^ 6 | 7 | error: Enum is not "repr(u8)" 8 | --> tests/build_failed/abi_derive_enum_generation.rs:11:8 9 | | 10 | 11 | #[repr(u32)] 11 | | ^^^ 12 | 13 | error: Enumeration parameters should not have fields 14 | --> tests/build_failed/abi_derive_enum_generation.rs:21:2 15 | | 16 | 21 | A(u128), 17 | | ^ 18 | 19 | error: Enumeration options should not have an explicit specified value 20 | --> tests/build_failed/abi_derive_enum_generation.rs:29:2 21 | | 22 | 29 | A = 128, 23 | | ^ 24 | -------------------------------------------------------------------------------- /tests/build_failed/abi_derive_flags_generation.rs: -------------------------------------------------------------------------------- 1 | use evm_coder_procedural::AbiCoderFlags; 2 | use bondrewd::Bitfields; 3 | 4 | #[derive(AbiCoderFlags, Bitfields)] 5 | struct EmptyStruct {} 6 | 7 | 8 | #[derive(AbiCoderFlags, Bitfields)] 9 | struct OneField { 10 | #[bondrewd(bits = "0..1")] 11 | a: bool, 12 | } 13 | 14 | #[derive(AbiCoderFlags, Bitfields)] 15 | struct ReserveField { 16 | #[bondrewd(reserve, bits = "0..1")] 17 | a: bool, 18 | } 19 | 20 | #[derive(AbiCoderFlags, Bitfields)] 21 | struct MultipleFields { 22 | #[bondrewd(bits = "0..1")] 23 | a: bool, 24 | #[bondrewd(bits = "1..2")] 25 | b: bool, 26 | #[bondrewd(bits = "2..8")] 27 | c: u8, 28 | } 29 | 30 | fn main() { 31 | assert!(false); 32 | } 33 | -------------------------------------------------------------------------------- /tests/build_failed/custom_signature_over_max_size.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::str::from_utf8; 3 | 4 | use evm_coder::{ 5 | make_signature, 6 | custom_signature::{SignatureUnit, SIGNATURE_SIZE_LIMIT}, 7 | }; 8 | 9 | trait Name { 10 | const SIGNATURE: SignatureUnit; 11 | 12 | fn name() -> &'static str { 13 | from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8") 14 | } 15 | } 16 | 17 | impl Name for Vec { 18 | const SIGNATURE: SignatureUnit = 19 | evm_coder::make_signature!(new nameof(T::SIGNATURE) fixed("[]")); 20 | } 21 | 22 | struct MaxSize(); 23 | impl Name for MaxSize { 24 | const SIGNATURE: SignatureUnit = SignatureUnit { 25 | data: [b'!'; SIGNATURE_SIZE_LIMIT], 26 | len: SIGNATURE_SIZE_LIMIT, 27 | }; 28 | } 29 | 30 | const NAME: SignatureUnit = >::SIGNATURE; 31 | 32 | fn main() { 33 | assert!(false); 34 | } 35 | -------------------------------------------------------------------------------- /tests/bondrewd_generation.rs: -------------------------------------------------------------------------------- 1 | use bondrewd::Bitfields; 2 | use evm_coder::{abi::AbiType, dummy_contract, generate_stubgen, solidity_interface, types::*}; 3 | use evm_coder_procedural::AbiCoderFlags; 4 | 5 | pub struct CollectionHelper; 6 | dummy_contract! { 7 | macro_rules! CollectionHelper_result {...} 8 | impl Contract for CollectionHelper {...} 9 | } 10 | 11 | #[solidity_interface(name = CollectionHelper)] 12 | impl CollectionHelper { 13 | fn create_collection(_flags: CollectionFlags) -> u8 { 14 | unreachable!() 15 | } 16 | } 17 | 18 | #[derive(Bitfields, Debug, AbiCoderFlags, Clone, Copy)] 19 | pub struct CollectionFlags { 20 | #[bondrewd(bits = "0..1")] 21 | pub foreign: bool, 22 | #[bondrewd(bits = "7..8")] 23 | pub external: bool, 24 | #[bondrewd(bits = "2..7")] 25 | pub reserved: u8, 26 | } 27 | 28 | generate_stubgen!(gen_impl, CollectionHelperCall, true); 29 | generate_stubgen!(gen_iface, CollectionHelperCall, false); 30 | -------------------------------------------------------------------------------- /tests/generics.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use evm_coder::{dummy_contract, generate_stubgen, solidity_interface, types::*}; 4 | use primitive_types::U256; 5 | 6 | type Result = core::result::Result; 7 | 8 | pub struct Generic(PhantomData); 9 | 10 | dummy_contract! { 11 | macro_rules! Generic_result {...} 12 | impl Contract for Generic {...} 13 | } 14 | 15 | #[solidity_interface(name = GenericIs)] 16 | impl Generic { 17 | fn test_1(&self) -> Result { 18 | unreachable!() 19 | } 20 | } 21 | 22 | #[solidity_interface(name = Generic, is(GenericIs))] 23 | impl> Generic { 24 | fn test_2(&self) -> Result { 25 | unreachable!() 26 | } 27 | } 28 | 29 | generate_stubgen!(gen_iface, GenericCall<()>, false); 30 | 31 | #[solidity_interface(name = GenericWhere)] 32 | impl Generic 33 | where 34 | T: core::fmt::Debug, 35 | { 36 | fn test_3(&self) -> U256 { 37 | unreachable!() 38 | } 39 | } 40 | 41 | generate_stubgen!(gen_where_iface, GenericWhereCall<()>, false); 42 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /tests/build_failed/custom_signature_over_max_size.stderr: -------------------------------------------------------------------------------- 1 | warning: unused import: `make_signature` 2 | --> tests/build_failed/custom_signature_over_max_size.rs:5:2 3 | | 4 | 5 | make_signature, 5 | | ^^^^^^^^^^^^^^ 6 | | 7 | = note: `#[warn(unused_imports)]` on by default 8 | 9 | error[E0080]: evaluation of ` as Name>::SIGNATURE` failed 10 | --> tests/build_failed/custom_signature_over_max_size.rs:19:3 11 | | 12 | 19 | evm_coder::make_signature!(new nameof(T::SIGNATURE) fixed("[]")); 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ index out of bounds: the length is 256 but the index is 256 14 | | 15 | = note: this error originates in the macro `$crate::make_signature` which comes from the expansion of the macro `evm_coder::make_signature` (in Nightly builds, run with -Z macro-backtrace for more info) 16 | 17 | note: erroneous constant used 18 | --> tests/build_failed/custom_signature_over_max_size.rs:30:29 19 | | 20 | 30 | const NAME: SignatureUnit = >::SIGNATURE; 21 | | ^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | -------------------------------------------------------------------------------- /tests/conditional_is.rs: -------------------------------------------------------------------------------- 1 | use evm_coder::{dummy_contract, solidity_interface, types::*}; 2 | 3 | type Result = core::result::Result; 4 | 5 | pub struct Contract(bool); 6 | dummy_contract! { 7 | macro_rules! Contract_result {...} 8 | impl Contract for Contract {...} 9 | } 10 | 11 | #[solidity_interface(name = A)] 12 | impl Contract { 13 | fn method_a() -> Result<()> { 14 | Ok(()) 15 | } 16 | } 17 | 18 | #[solidity_interface(name = B)] 19 | impl Contract { 20 | fn method_b() -> Result<()> { 21 | Ok(()) 22 | } 23 | } 24 | 25 | #[solidity_interface(name = Contract, is( 26 | A(if(this.0)), 27 | B(if(!this.0)), 28 | ))] 29 | impl Contract {} 30 | 31 | #[test] 32 | fn conditional_erc165() { 33 | assert!(ContractCall::supports_interface( 34 | &Contract(true), 35 | ACall::METHOD_A 36 | )); 37 | assert!(!ContractCall::supports_interface( 38 | &Contract(false), 39 | ACall::METHOD_A 40 | )); 41 | 42 | assert!(ContractCall::supports_interface( 43 | &Contract(false), 44 | BCall::METHOD_B 45 | )); 46 | assert!(!ContractCall::supports_interface( 47 | &Contract(true), 48 | BCall::METHOD_B 49 | )); 50 | } 51 | -------------------------------------------------------------------------------- /tests/solidity_generation.rs: -------------------------------------------------------------------------------- 1 | use evm_coder::{abi::AbiType, dummy_contract, generate_stubgen, solidity_interface, types::*}; 2 | use primitive_types::U256; 3 | 4 | type Result = core::result::Result; 5 | 6 | pub struct ERC20; 7 | dummy_contract! { 8 | macro_rules! ERC20_result {...} 9 | impl Contract for ERC20 {...} 10 | } 11 | 12 | #[solidity_interface(name = ERC20)] 13 | impl ERC20 { 14 | fn decimals(&self) -> Result { 15 | unreachable!() 16 | } 17 | /// Get balance of specified owner 18 | fn balance_of(&self, _owner: Address) -> Result { 19 | unreachable!() 20 | } 21 | fn transfer(&mut self, _caller: Caller, _to: Address, _value: U256) -> Result { 22 | unreachable!() 23 | } 24 | fn transfer_from( 25 | &mut self, 26 | _caller: Caller, 27 | _from: Address, 28 | _to: Address, 29 | _value: U256, 30 | ) -> Result { 31 | unreachable!() 32 | } 33 | fn approve(&mut self, _caller: Caller, _spender: Address, _value: U256) -> Result { 34 | unreachable!() 35 | } 36 | fn allowance(&self, _owner: Address, _spender: Address) -> Result { 37 | unreachable!() 38 | } 39 | } 40 | 41 | generate_stubgen!(gen_impl, ERC20Call, true); 42 | generate_stubgen!(gen_iface, ERC20Call, false); 43 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | use ethereum::Log; 2 | use primitive_types::{H160, H256, U256}; 3 | 4 | use crate::types::Address; 5 | 6 | /// Implementation of this trait should not be written manually, 7 | /// instead use [`crate::ToLog`] proc macros. 8 | /// 9 | /// See also [`evm_coder_procedural::ToLog`], [solidity docs on events](https://docs.soliditylang.org/en/develop/contracts.html#events) 10 | pub trait ToLog { 11 | /// Convert event to [`ethereum::Log`]. 12 | /// Because event by itself doesn't contains current contract 13 | /// address, it should be specified manually. 14 | fn to_log(&self, contract: H160) -> Log; 15 | } 16 | 17 | /// Only items implementing `ToTopic` may be used as `#[indexed]` field 18 | /// in [`crate::ToLog`] macro usage. 19 | /// 20 | /// See also (solidity docs on events)[] 21 | pub trait ToTopic { 22 | /// Convert value to topic to be used in [`ethereum::Log`] 23 | fn to_topic(&self) -> H256; 24 | } 25 | 26 | impl ToTopic for H256 { 27 | fn to_topic(&self) -> H256 { 28 | *self 29 | } 30 | } 31 | 32 | impl ToTopic for U256 { 33 | fn to_topic(&self) -> H256 { 34 | let mut out = [0u8; 32]; 35 | self.to_big_endian(&mut out); 36 | H256(out) 37 | } 38 | } 39 | 40 | impl ToTopic for Address { 41 | fn to_topic(&self) -> H256 { 42 | let mut out = [0u8; 32]; 43 | out[12..32].copy_from_slice(&self.0); 44 | H256(out) 45 | } 46 | } 47 | 48 | impl ToTopic for u32 { 49 | fn to_topic(&self) -> H256 { 50 | let mut out = [0u8; 32]; 51 | out[28..32].copy_from_slice(&self.to_be_bytes()); 52 | H256(out) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /procedural/src/abi_derive/mod.rs: -------------------------------------------------------------------------------- 1 | mod derive_enum; 2 | #[cfg(feature = "bondrewd")] 3 | mod derive_flags; 4 | mod derive_struct; 5 | 6 | use derive_enum::expand_enum; 7 | #[cfg(feature = "bondrewd")] 8 | use derive_flags::expand_flags; 9 | use derive_struct::expand_struct; 10 | 11 | pub(crate) fn impl_abi_macro(ast: &syn::DeriveInput) -> syn::Result { 12 | let name = &ast.ident; 13 | match &ast.data { 14 | syn::Data::Struct(ds) => expand_struct(ds, ast), 15 | syn::Data::Enum(de) => expand_enum(de, ast), 16 | syn::Data::Union(_) => Err(syn::Error::new(name.span(), "Unions not supported")), 17 | } 18 | } 19 | 20 | #[cfg(feature = "bondrewd")] 21 | pub(crate) fn impl_abi_flags_macro( 22 | ast: &syn::DeriveInput, 23 | ) -> syn::Result { 24 | let name = &ast.ident; 25 | match &ast.data { 26 | syn::Data::Struct(ds) => expand_flags(ds, ast), 27 | _ => Err(syn::Error::new( 28 | name.span(), 29 | "Unions and enums aren't supported", 30 | )), 31 | } 32 | } 33 | 34 | pub fn extract_docs(attrs: &[syn::Attribute]) -> syn::Result> { 35 | attrs 36 | .iter() 37 | .filter_map(|attr| { 38 | if let Some(ps) = attr.path.segments.first() { 39 | if ps.ident == "doc" { 40 | let meta = match attr.parse_meta() { 41 | Ok(meta) => meta, 42 | Err(e) => return Some(Err(e)), 43 | }; 44 | match meta { 45 | syn::Meta::NameValue(mnv) => match &mnv.lit { 46 | syn::Lit::Str(ls) => return Some(Ok(ls.value())), 47 | _ => unreachable!(), 48 | }, 49 | _ => unreachable!(), 50 | } 51 | } 52 | } 53 | None 54 | }) 55 | .collect() 56 | } 57 | -------------------------------------------------------------------------------- /tests/random.rs: -------------------------------------------------------------------------------- 1 | //! This test only checks that macros is not panicking 2 | #![allow(dead_code)] 3 | 4 | use evm_coder::{abi::AbiType, dummy_contract, solidity_interface, types::*, ToLog}; 5 | use primitive_types::U256; 6 | 7 | type Result = core::result::Result; 8 | 9 | pub struct Impls; 10 | dummy_contract! { 11 | macro_rules! Impls_result {...} 12 | impl Contract for Impls {...} 13 | } 14 | 15 | #[solidity_interface(name = OurInterface)] 16 | impl Impls { 17 | fn fn_a(&self, _input: U256) -> Result { 18 | unreachable!() 19 | } 20 | } 21 | 22 | #[solidity_interface(name = OurInterface1)] 23 | impl Impls { 24 | fn fn_b(&self, _input: u128) -> Result { 25 | unreachable!() 26 | } 27 | } 28 | 29 | #[derive(ToLog)] 30 | enum OurEvents { 31 | Event1 { 32 | field1: u32, 33 | }, 34 | Event2 { 35 | field1: u32, 36 | #[indexed] 37 | field2: u32, 38 | }, 39 | } 40 | 41 | #[solidity_interface( 42 | name = OurInterface2, 43 | is(OurInterface), 44 | inline_is(OurInterface1), 45 | events(OurEvents) 46 | )] 47 | impl Impls { 48 | #[solidity(rename_selector = "fnK")] 49 | fn fn_c(&self, _input: u32) -> Result { 50 | unreachable!() 51 | } 52 | fn fn_d(&self, _value: u32) -> Result { 53 | unreachable!() 54 | } 55 | 56 | fn caller_sensitive(&self, _caller: Caller) -> Result { 57 | unreachable!() 58 | } 59 | fn payable(&mut self, _value: Value) -> Result { 60 | unreachable!() 61 | } 62 | 63 | /// Doccoment example 64 | fn with_doc(&self) -> Result<()> { 65 | unreachable!() 66 | } 67 | } 68 | 69 | #[solidity_interface( 70 | name = ValidSelector, 71 | expect_selector = 0x00000000, 72 | )] 73 | impl Impls {} 74 | -------------------------------------------------------------------------------- /src/solidity/traits.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use crate::solidity::TypeCollector; 4 | 5 | pub trait SolidityTypeName: 'static { 6 | fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; 7 | /// "simple" types are stored inline, no `memory` modifier should be used in solidity 8 | fn is_simple() -> bool; 9 | fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; 10 | /// Specialization 11 | fn is_void() -> bool { 12 | false 13 | } 14 | } 15 | 16 | pub trait SolidityTupleTy: 'static { 17 | fn fields(tc: &TypeCollector) -> Vec; 18 | fn len() -> usize; 19 | } 20 | pub trait SolidityStructTy: 'static { 21 | fn generate_solidity_interface(tc: &TypeCollector) -> String; 22 | } 23 | pub trait SolidityEnumTy: 'static { 24 | fn generate_solidity_interface(tc: &TypeCollector) -> String; 25 | fn solidity_option(&self) -> &str; 26 | } 27 | 28 | pub trait SolidityArguments { 29 | fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; 30 | fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result; 31 | fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; 32 | fn is_empty(&self) -> bool { 33 | self.len() == 0 34 | } 35 | fn len(&self) -> usize; 36 | } 37 | 38 | pub trait SolidityFunctions { 39 | fn solidity_name( 40 | &self, 41 | is_impl: bool, 42 | writer: &mut impl fmt::Write, 43 | tc: &TypeCollector, 44 | ) -> fmt::Result; 45 | } 46 | 47 | pub trait SolidityItems { 48 | fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; 49 | // For PhantomData fields 50 | // fn is_void() 51 | } 52 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ['./procedural'] 3 | package.documentation = "https://docs.rs/evm-coder" 4 | package.license = "MIT OR Apache-2.0" 5 | package.repository = "https://github.com/UniqueNetwork/evm-coder" 6 | package.version = "0.4.3" 7 | resolver = "2" 8 | 9 | [workspace.dependencies] 10 | evm-coder-procedural = { path = "./procedural", version = "0.4.3" } 11 | 12 | [package] 13 | description = "EVM call decoding/encoding proc macros" 14 | documentation.workspace = true 15 | edition = "2021" 16 | license.workspace = true 17 | name = "evm-coder" 18 | repository.workspace = true 19 | version.workspace = true 20 | 21 | [dependencies] 22 | sha3-const = { version = "0.1.1", default-features = false } 23 | # evm-coder reexports those proc-macro 24 | evm-coder-procedural = { workspace = true } 25 | # Evm uses primitive-types for H160, H256 and others 26 | primitive-types = { version = "0.12.1", default-features = false } 27 | # Evm doesn't have reexports for log and others 28 | ethereum = { version = "0.15.0", default-features = false } 29 | # We have tuple-heavy code in solidity.rs 30 | impl-trait-for-tuples = "0.2.2" 31 | 32 | [dev-dependencies] 33 | bondrewd = { version = "0.1.14", features = ["derive"], default-features = false } 34 | # We want to assert some large binary blobs equality in tests 35 | hex = "0.4.3" 36 | hex-literal = "0.3.4" 37 | similar-asserts = "1.4" 38 | trybuild = "1.0" 39 | # Used to demonstrate enum derive feature 40 | derivative = { version = "2.2" } 41 | evm-coder-procedural = { workspace = true, features = ["bondrewd"] } 42 | 43 | [features] 44 | default = ["std"] 45 | bondrewd = ["evm-coder-procedural/bondrewd"] 46 | std = ["ethereum/std", "primitive-types/std"] 47 | # Stub/interface generation 48 | stubgen = [] 49 | -------------------------------------------------------------------------------- /src/abi/traits.rs: -------------------------------------------------------------------------------- 1 | use core::str::from_utf8; 2 | 3 | use super::{AbiDecoder, AbiEncoder, Error}; 4 | use crate::{abi::Result, custom_signature::SignatureUnit, types::*}; 5 | 6 | /// Helper for type. 7 | pub trait AbiType { 8 | /// Signature for Ethereum ABI. 9 | const SIGNATURE: SignatureUnit; 10 | /// Is this a dynamic type, per spec. 11 | const IS_DYNAMIC: bool; 12 | /// How many AbiWords static data this type should occupy 13 | const HEAD_WORDS: u32; 14 | 15 | /// Signature as str. 16 | #[must_use] 17 | fn signature() -> &'static str { 18 | from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8") 19 | } 20 | } 21 | impl AbiType for &T 22 | where 23 | T: AbiType, 24 | { 25 | const SIGNATURE: SignatureUnit = T::SIGNATURE; 26 | const IS_DYNAMIC: bool = T::IS_DYNAMIC; 27 | const HEAD_WORDS: u32 = T::HEAD_WORDS; 28 | } 29 | 30 | /// Encode value using ABI encoding. 31 | pub trait AbiEncode: Sized + AbiType { 32 | fn enc(&self, out: &mut AbiEncoder); 33 | fn abi_encode(&self) -> Vec { 34 | let mut encoder = AbiEncoder::new(vec![], 0, 0); 35 | encoder.encode_tail(self); 36 | encoder.into_inner() 37 | } 38 | fn abi_encode_call(&self, selector: Bytes4) -> Vec { 39 | let mut encoder = AbiEncoder::new(selector.into(), 4, 4); 40 | encoder.encode_tail(self); 41 | encoder.into_inner() 42 | } 43 | } 44 | impl AbiEncode for &T 45 | where 46 | T: AbiEncode, 47 | { 48 | fn enc(&self, out: &mut AbiEncoder) { 49 | (*self).enc(out); 50 | } 51 | } 52 | pub trait AbiEncodeZero: AbiEncode { 53 | fn enc_zero(out: &mut AbiEncoder); 54 | } 55 | impl AbiEncodeZero for T { 56 | fn enc_zero(out: &mut AbiEncoder) { 57 | T::default().enc(out) 58 | } 59 | } 60 | 61 | /// Decode ABI value. 62 | pub trait AbiDecode: Sized + AbiType { 63 | fn dec(input: &mut AbiDecoder<'_>) -> Result; 64 | fn abi_decode(input: &[u8]) -> Result { 65 | let mut decoder = AbiDecoder::new(input, 0)?; 66 | Self::dec(&mut decoder) 67 | } 68 | fn abi_decode_call(input: &[u8]) -> Result<(Bytes4, Self)> { 69 | let mut num = [0; 4]; 70 | num.copy_from_slice(&input[..4]); 71 | Ok((BytesFixed(num), Self::abi_decode(&input[4..])?)) 72 | } 73 | } 74 | /// Assert read value is zero. 75 | pub trait AbiDecodeZero: AbiDecode { 76 | fn dec_zero(input: &mut AbiDecoder<'_>) -> Result<()>; 77 | } 78 | impl AbiDecodeZero for T { 79 | fn dec_zero(input: &mut AbiDecoder<'_>) -> Result<()> { 80 | let value = T::dec(input)?; 81 | if value != T::default() { 82 | return Err(Error::InvalidRange); 83 | } 84 | Ok(()) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | 6 | ## [v0.3.1] 2023-02-10 7 | 8 | ### Added features 9 | 10 | - Impl SolidityTypeName for Result a331f9605f0a063121c8f0c32f90bc2e8efe4ea4 11 | 12 | ### Bugfixes 13 | 14 | - Remove Default bound on UnnamedArgument a19009d12bba22cd85aab5807e41377bb2d50c5c 15 | 16 | ### Other changes 17 | 18 | - style: Fix clippy warnings b585dc3d01253631989139159d2a618b8de798a0 19 | 20 | ## [v0.3.0] 2023-02-02 21 | 22 | ### Added features 23 | 24 | - Slightly improve ergonomics 0cdc26eaca5d6102466266e587aa95902c71cf6d 25 | 26 | - Custom return value transform b062b56b4bb4e0c83e5c18f114b71bdf9b1e6c36 27 | 28 | - Passthru custom attributes to generated call enum 8dd34e67eec554c5e5d5d654c81e47c107f60138 29 | 30 | - Separate abi decoding error enum fa588a149d3a660e64bf731b7d29c6246c750dd1 31 | 32 | ### Other changes 33 | 34 | - build: Use workspace package keys 4a5ce68b0a6aeed14316245987051c03e97ee3d3 35 | 36 | - style: Enforce stricter formatting 139d86d8531d1e430612acafc20c6f5974c3b939 37 | 38 | - test: Use PostInfo for dummy ab341f32d074e97ec23cc8cf36abb3d2f84f8655 39 | 40 | - test: Fix is_dynamic failure 7cba253412370a5c315082dd65de292db0289d4f 41 | 42 | - style: Fix clippy warnings bcad9995ec79a69a8a701c286e8d39c750e11dd2 43 | 44 | - build: Add lockfile 4f7b250dfc1c5df4bcdd71b86cc54de5e4d54b48 45 | 46 | - refactor: Remove builtin #[weight] attribute 40517578109812bbff31bb902d990fa4f1c2e4bf 47 | 48 | - refactor: Properly process method attributes 9dfe6159aad2f8cd1c4979e84a1b5ad216b70d9f 49 | 50 | Instead of requiring dummy attributes to be imported and used, 51 | remove them from the method 52 | 53 | - refactor: Drop execution module 8410e224f7a212f0ea60f1443157a145eb8eaa94 54 | 55 | ## [v0.1.6] - 2023-01-12 56 | 57 | ### Added 58 | - Support Option type. 59 | ### Removed 60 | - Frontier dependency. 61 | 62 | ## [v0.1.5] - 2022-11-30 63 | 64 | ### Added 65 | - Derive macro to support structures and enums. 66 | 67 | ## [v0.1.4] - 2022-11-02 68 | 69 | ### Added 70 | 71 | - Named structures support. 72 | 73 | ## [v0.1.3] - 2022-08-29 74 | 75 | ### Fixed 76 | 77 | - Parsing simple values. 78 | 79 | ## [v0.1.2] 2022-08-19 80 | 81 | ### Added 82 | 83 | - Implementation `AbiWrite` for tuples. 84 | 85 | ### Fixes 86 | 87 | - Tuple generation for solidity. 88 | 89 | ## [v0.1.1] 2022-08-16 90 | 91 | ### Other changes 92 | 93 | - build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a 94 | 95 | - build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8 96 | 97 | - build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b 98 | -------------------------------------------------------------------------------- /src/abi/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of EVM RLP reader/writer 2 | #![allow(clippy::missing_errors_doc)] 3 | 4 | mod traits; 5 | use core::{fmt, mem, result}; 6 | 7 | pub use traits::*; 8 | mod impls; 9 | 10 | #[cfg(test)] 11 | mod test; 12 | 13 | #[cfg(not(feature = "std"))] 14 | use alloc::vec::Vec; 15 | 16 | /// Aligment for every simple type in bytes. 17 | pub const ABI_ALIGNMENT: usize = 32; 18 | pub const ABI_WORD_SIZE: u32 = 32; 19 | pub type AbiWord = [u8; ABI_WORD_SIZE as usize]; 20 | 21 | /// Abi parsing result 22 | pub type Result = result::Result; 23 | 24 | /// Generic decode failure 25 | #[derive(Debug)] 26 | pub enum Error { 27 | /// Input was shorter than expected 28 | OutOfOffset, 29 | /// Something is off about paddings 30 | InvalidRange, 31 | /// Custom parsing error 32 | Custom(&'static str), 33 | } 34 | impl fmt::Display for Error { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | match self { 37 | Error::OutOfOffset => write!(f, "out of offset"), 38 | Error::InvalidRange => write!(f, "invalid range"), 39 | Error::Custom(m) => write!(f, "{m}"), 40 | } 41 | } 42 | } 43 | impl From<&'static str> for Error { 44 | fn from(value: &'static str) -> Self { 45 | Self::Custom(value) 46 | } 47 | } 48 | 49 | /// New abicoder 50 | #[derive(Debug)] 51 | pub struct AbiEncoder { 52 | out: Vec, 53 | offset: usize, 54 | dynamic_offset: usize, 55 | } 56 | impl AbiEncoder { 57 | fn new(out: Vec, offset: usize, dynamic_offset: usize) -> Self { 58 | assert_eq!((dynamic_offset - offset) % 32, 0); 59 | Self { 60 | out, 61 | offset, 62 | dynamic_offset, 63 | } 64 | } 65 | pub fn reserve_head(&mut self, words: u32) { 66 | assert_eq!(self.offset, self.dynamic_offset); 67 | assert_eq!(self.dynamic_offset, self.out.len()); 68 | self.out 69 | .resize(self.out.len() + words as usize * ABI_WORD_SIZE as usize, 0); 70 | self.dynamic_offset += words as usize * ABI_WORD_SIZE as usize; 71 | } 72 | pub fn append_head(&mut self, word: AbiWord) { 73 | assert!(self.offset < self.dynamic_offset); 74 | self.out[self.offset..self.offset + 32].copy_from_slice(&word); 75 | self.offset += 32; 76 | } 77 | fn append_tail(&mut self, word: AbiWord) { 78 | self.out.extend_from_slice(&word); 79 | } 80 | fn tail_size(&self) -> u32 { 81 | self.out.len() as u32 - self.dynamic_offset as u32 82 | } 83 | fn encode_tail(&mut self, data: &T) { 84 | let offset = self.out.len(); 85 | let mut out = mem::take(&mut self.out); 86 | let size = T::HEAD_WORDS as usize * ABI_WORD_SIZE as usize; 87 | out.resize(out.len() + size, 0); 88 | let mut encoder = AbiEncoder::new( 89 | out, 90 | offset, 91 | offset + T::HEAD_WORDS as usize * ABI_WORD_SIZE as usize, 92 | ); 93 | data.enc(&mut encoder); 94 | self.out = encoder.into_inner() 95 | } 96 | fn into_inner(self) -> Vec { 97 | self.out 98 | } 99 | } 100 | 101 | #[derive(Clone)] 102 | pub struct AbiDecoder<'d> { 103 | data: &'d [u8], 104 | offset: usize, 105 | global_frame_offset: usize, 106 | } 107 | impl<'d> AbiDecoder<'d> { 108 | fn new(data: &'d [u8], global_frame_offset: usize) -> Result { 109 | if data.len() % 32 != 0 { 110 | return Err(Error::OutOfOffset); 111 | } 112 | Ok(Self { 113 | data, 114 | offset: 0, 115 | global_frame_offset, 116 | }) 117 | } 118 | pub fn get_head(&mut self) -> Result { 119 | if self.offset >= self.data.len() { 120 | return Err(Error::OutOfOffset); 121 | } 122 | let mut word = [0; ABI_WORD_SIZE as usize]; 123 | word.copy_from_slice(&self.data[self.offset..self.offset + 32]); 124 | self.offset += 32; 125 | Ok(word) 126 | } 127 | pub fn start_frame(&self) -> Self { 128 | self.dynamic_at(self.offset as u32).expect("not oob") 129 | } 130 | pub fn dynamic_at(&self, offset: u32) -> Result { 131 | if offset % 32 != 0 || self.data.len() < offset as usize { 132 | // Technically, allowed by spec, yet nothing has such offsets 133 | return Err(Error::OutOfOffset); 134 | } 135 | Self::new( 136 | &self.data[offset as usize..], 137 | self.global_frame_offset + offset as usize, 138 | ) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /procedural/src/lib.rs: -------------------------------------------------------------------------------- 1 | use inflector::cases; 2 | use proc_macro::TokenStream; 3 | use quote::quote; 4 | use sha3::{Digest, Keccak256}; 5 | use syn::{ 6 | parse_macro_input, spanned::Spanned, DeriveInput, Ident, ItemImpl, Pat, Path, PathSegment, Type, 7 | }; 8 | 9 | mod abi_derive; 10 | mod solidity_interface; 11 | #[cfg(feature = "bondrewd")] 12 | mod structs; 13 | mod to_log; 14 | 15 | fn fn_selector_str(input: &str) -> u32 { 16 | let mut hasher = Keccak256::new(); 17 | hasher.update(input.as_bytes()); 18 | let result = hasher.finalize(); 19 | 20 | let mut selector_bytes = [0; 4]; 21 | selector_bytes.copy_from_slice(&result[0..4]); 22 | 23 | u32::from_be_bytes(selector_bytes) 24 | } 25 | 26 | /// Returns solidity function selector (first 4 bytes of hash) by its 27 | /// textual representation 28 | /// 29 | /// ```ignore 30 | /// use evm_coder_macros::fn_selector; 31 | /// 32 | /// assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb); 33 | /// ``` 34 | #[proc_macro] 35 | pub fn fn_selector(input: TokenStream) -> TokenStream { 36 | let input = input.to_string().replace(' ', ""); 37 | let selector = fn_selector_str(&input); 38 | 39 | (quote! { 40 | ::evm_coder::types::BytesFixed(u32::to_be_bytes(#selector)) 41 | }) 42 | .into() 43 | } 44 | 45 | fn event_selector_str(input: &str) -> [u8; 32] { 46 | let mut hasher = Keccak256::new(); 47 | hasher.update(input.as_bytes()); 48 | let result = hasher.finalize(); 49 | 50 | let mut selector_bytes = [0; 32]; 51 | selector_bytes.copy_from_slice(&result[0..32]); 52 | selector_bytes 53 | } 54 | 55 | /// Returns solidity topic (hash) by its textual representation 56 | /// 57 | /// ```ignore 58 | /// use evm_coder_macros::event_topic; 59 | /// 60 | /// assert_eq!( 61 | /// format!("{:x}", event_topic!(Transfer(address, address, uint256))), 62 | /// "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 63 | /// ); 64 | /// ``` 65 | #[proc_macro] 66 | pub fn event_topic(stream: TokenStream) -> TokenStream { 67 | let input = stream.to_string().replace(' ', ""); 68 | let selector_bytes = event_selector_str(&input); 69 | 70 | (quote! { 71 | ::primitive_types::H256([#( 72 | #selector_bytes, 73 | )*]) 74 | }) 75 | .into() 76 | } 77 | 78 | pub(crate) fn parse_path(ty: &Type) -> syn::Result<&Path> { 79 | match &ty { 80 | syn::Type::Path(pat) => { 81 | if let Some(qself) = &pat.qself { 82 | return Err(syn::Error::new(qself.ty.span(), "no receiver expected")); 83 | } 84 | Ok(&pat.path) 85 | } 86 | _ => Err(syn::Error::new(ty.span(), "expected ty to be path")), 87 | } 88 | } 89 | 90 | fn parse_path_segment(path: &Path) -> syn::Result<&PathSegment> { 91 | if path.segments.len() != 1 { 92 | return Err(syn::Error::new( 93 | path.span(), 94 | "expected path to have only one segment", 95 | )); 96 | } 97 | let last_segment = &path.segments.last().unwrap(); 98 | Ok(last_segment) 99 | } 100 | 101 | fn parse_ident_from_pat(pat: &Pat) -> syn::Result<&Ident> { 102 | match pat { 103 | Pat::Ident(i) => Ok(&i.ident), 104 | _ => Err(syn::Error::new(pat.span(), "expected pat ident")), 105 | } 106 | } 107 | 108 | fn parse_ident_from_segment(segment: &PathSegment, allow_generics: bool) -> syn::Result<&Ident> { 109 | if !segment.arguments.is_none() && !allow_generics { 110 | return Err(syn::Error::new( 111 | segment.arguments.span(), 112 | "unexpected generic type", 113 | )); 114 | } 115 | Ok(&segment.ident) 116 | } 117 | 118 | fn parse_ident_from_path(path: &Path, allow_generics: bool) -> syn::Result<&Ident> { 119 | let segment = parse_path_segment(path)?; 120 | parse_ident_from_segment(segment, allow_generics) 121 | } 122 | 123 | fn parse_ident_from_type(ty: &Type, allow_generics: bool) -> syn::Result<&Ident> { 124 | let path = parse_path(ty)?; 125 | parse_ident_from_path(path, allow_generics) 126 | } 127 | 128 | fn pascal_ident_to_call(ident: &Ident) -> Ident { 129 | let name = format!("{ident}Call"); 130 | Ident::new(&name, ident.span()) 131 | } 132 | fn snake_ident_to_pascal(ident: &Ident) -> Ident { 133 | let name = ident.to_string(); 134 | let name = cases::pascalcase::to_pascal_case(&name); 135 | Ident::new(&name, ident.span()) 136 | } 137 | fn snake_ident_to_screaming(ident: &Ident) -> Ident { 138 | let name = ident.to_string(); 139 | let name = cases::screamingsnakecase::to_screaming_snake_case(&name); 140 | Ident::new(&name, ident.span()) 141 | } 142 | 143 | /// See documentation for this proc-macro reexported in `evm-coder` crate 144 | #[proc_macro_attribute] 145 | pub fn solidity_interface(args: TokenStream, stream: TokenStream) -> TokenStream { 146 | let args = parse_macro_input!(args as solidity_interface::InterfaceInfo); 147 | 148 | let mut input: ItemImpl = match syn::parse(stream) { 149 | Ok(t) => t, 150 | Err(e) => return e.to_compile_error().into(), 151 | }; 152 | 153 | let expanded = match solidity_interface::SolidityInterface::try_from(args, &mut input) { 154 | Ok(v) => v.expand(), 155 | Err(e) => e.to_compile_error(), 156 | }; 157 | 158 | (quote! { 159 | #input 160 | 161 | #expanded 162 | }) 163 | .into() 164 | } 165 | 166 | /// See documentation for this proc-macro reexported in `evm-coder` crate 167 | #[proc_macro_derive(ToLog, attributes(indexed))] 168 | pub fn to_log(value: TokenStream) -> TokenStream { 169 | let input = parse_macro_input!(value as DeriveInput); 170 | 171 | match to_log::Events::try_from(&input) { 172 | Ok(e) => e.expand(), 173 | Err(e) => e.to_compile_error(), 174 | } 175 | .into() 176 | } 177 | 178 | /// See documentation for this proc-macro reexported in `evm-coder` crate 179 | #[proc_macro_derive(AbiCoder)] 180 | pub fn abi_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 181 | let ast = parse_macro_input!(input as DeriveInput); 182 | let ts = match abi_derive::impl_abi_macro(&ast) { 183 | Ok(e) => e, 184 | Err(e) => e.to_compile_error(), 185 | }; 186 | ts.into() 187 | } 188 | 189 | #[cfg(feature = "bondrewd")] 190 | /// See documentation for this proc-macro reexported in `evm-coder` crate 191 | #[proc_macro_derive(AbiCoderFlags, attributes(bondrewd,))] 192 | pub fn abi_flags_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 193 | let ast = parse_macro_input!(input as DeriveInput); 194 | let ts = match abi_derive::impl_abi_flags_macro(&ast) { 195 | Ok(e) => e, 196 | Err(e) => e.to_compile_error(), 197 | }; 198 | ts.into() 199 | } 200 | -------------------------------------------------------------------------------- /procedural/src/abi_derive/derive_enum.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | 3 | use super::extract_docs; 4 | 5 | pub fn impl_solidity_option<'a>( 6 | docs: &[String], 7 | name: &proc_macro2::Ident, 8 | enum_options: impl Iterator + Clone, 9 | ) -> proc_macro2::TokenStream { 10 | let variant_names = enum_options.clone().map(|opt| { 11 | let opt = &opt.ident; 12 | let s = name.to_string() + "." + opt.to_string().as_str(); 13 | let as_string = proc_macro2::Literal::string(s.as_str()); 14 | quote!(#name::#opt => #as_string,) 15 | }); 16 | let solidity_name = name.to_string(); 17 | 18 | let solidity_fields = enum_options.map(|v| { 19 | let docs = extract_docs(&v.attrs).expect("TODO: handle bad docs"); 20 | let name = v.ident.to_string(); 21 | quote! { 22 | SolidityEnumVariant { 23 | docs: &[#(#docs),*], 24 | name: #name, 25 | } 26 | } 27 | }); 28 | 29 | quote!( 30 | #[cfg(feature = "stubgen")] 31 | impl ::evm_coder::solidity::SolidityEnumTy for #name { 32 | fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector) -> String { 33 | use evm_coder::solidity::*; 34 | use core::fmt::Write; 35 | let interface = SolidityEnum { 36 | docs: &[#(#docs),*], 37 | name: #solidity_name, 38 | fields: &[#( 39 | #solidity_fields, 40 | )*], 41 | }; 42 | let mut out = String::new(); 43 | let _ = interface.format(&mut out, tc); 44 | tc.collect(out); 45 | #solidity_name.to_string() 46 | } 47 | fn solidity_option(&self) -> &str { 48 | match self { 49 | #(#variant_names)* 50 | } 51 | } 52 | } 53 | ) 54 | } 55 | 56 | pub fn impl_enum_from_u8<'a>( 57 | name: &proc_macro2::Ident, 58 | enum_options: impl Iterator, 59 | ) -> proc_macro2::TokenStream { 60 | let error_str = format!("value not convertible into enum \"{name}\""); 61 | let error_str = proc_macro2::Literal::string(&error_str); 62 | let enum_options = enum_options.enumerate().map(|(i, opt)| { 63 | let opt = &opt.ident; 64 | let n = proc_macro2::Literal::u8_suffixed(u8::try_from(i).expect("checked can cast")); 65 | quote! {#n => Ok(#name::#opt),} 66 | }); 67 | 68 | quote!( 69 | impl TryFrom for #name { 70 | type Error = &'static str; 71 | 72 | fn try_from(value: u8) -> ::core::result::Result { 73 | const err: &'static str = #error_str; 74 | match value { 75 | #(#enum_options)* 76 | _ => Err(err) 77 | } 78 | } 79 | } 80 | ) 81 | } 82 | 83 | pub fn impl_enum_abi_type(name: &syn::Ident) -> proc_macro2::TokenStream { 84 | quote! { 85 | impl ::evm_coder::abi::AbiType for #name { 86 | const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = ::SIGNATURE; 87 | const IS_DYNAMIC: bool = ::IS_DYNAMIC; 88 | const HEAD_WORDS: u32 = ::HEAD_WORDS; 89 | } 90 | } 91 | } 92 | 93 | pub fn impl_enum_abi_read(name: &syn::Ident) -> proc_macro2::TokenStream { 94 | quote!( 95 | impl ::evm_coder::abi::AbiDecode for #name { 96 | fn dec(reader: &mut ::evm_coder::abi::AbiDecoder) -> ::evm_coder::abi::Result { 97 | Ok( 98 | ::dec(reader)? 99 | .try_into()? 100 | ) 101 | } 102 | } 103 | ) 104 | } 105 | 106 | pub fn impl_enum_abi_write(name: &syn::Ident) -> proc_macro2::TokenStream { 107 | quote!( 108 | impl ::evm_coder::abi::AbiEncode for #name { 109 | fn enc(&self, writer: &mut ::evm_coder::abi::AbiEncoder) { 110 | ::evm_coder::abi::AbiEncode::enc(&(*self as u8), writer); 111 | } 112 | } 113 | ) 114 | } 115 | 116 | pub fn impl_enum_solidity_type_name(name: &syn::Ident) -> proc_macro2::TokenStream { 117 | quote!( 118 | #[cfg(feature = "stubgen")] 119 | impl ::evm_coder::solidity::SolidityTypeName for #name { 120 | fn solidity_name( 121 | writer: &mut impl ::core::fmt::Write, 122 | tc: &::evm_coder::solidity::TypeCollector, 123 | ) -> ::core::fmt::Result { 124 | write!(writer, "{}", tc.collect_enum::()) 125 | } 126 | 127 | fn is_simple() -> bool { 128 | true 129 | } 130 | 131 | fn solidity_default( 132 | writer: &mut impl ::core::fmt::Write, 133 | tc: &::evm_coder::solidity::TypeCollector, 134 | ) -> ::core::fmt::Result { 135 | write!(writer, "{}", <#name as ::evm_coder::solidity::SolidityEnumTy>::solidity_option(&<#name>::default())) 136 | } 137 | } 138 | ) 139 | } 140 | 141 | pub fn check_enum_fields(de: &syn::DataEnum) -> syn::Result<()> { 142 | for v in &de.variants { 143 | if !v.fields.is_empty() { 144 | return Err(syn::Error::new( 145 | v.ident.span(), 146 | "Enumeration parameters should not have fields", 147 | )); 148 | } else if v.discriminant.is_some() { 149 | return Err(syn::Error::new( 150 | v.ident.span(), 151 | "Enumeration options should not have an explicit specified value", 152 | )); 153 | } 154 | } 155 | 156 | Ok(()) 157 | } 158 | 159 | pub fn check_repr_u8(name: &syn::Ident, attrs: &[syn::Attribute]) -> syn::Result<()> { 160 | let mut has_repr = false; 161 | for attr in attrs { 162 | if attr.path.is_ident("repr") { 163 | has_repr = true; 164 | let meta = attr.parse_meta()?; 165 | check_meta_u8(&meta)?; 166 | } 167 | } 168 | 169 | if !has_repr { 170 | return Err(syn::Error::new(name.span(), "Enum is not \"repr(u8)\"")); 171 | } 172 | 173 | Ok(()) 174 | } 175 | 176 | fn check_meta_u8(meta: &syn::Meta) -> Result<(), syn::Error> { 177 | if let syn::Meta::List(p) = meta { 178 | for nm in &p.nested { 179 | if let syn::NestedMeta::Meta(syn::Meta::Path(p)) = nm { 180 | if !p.is_ident("u8") { 181 | return Err(syn::Error::new( 182 | p.segments 183 | .first() 184 | .expect("repr segments are empty") 185 | .ident 186 | .span(), 187 | "Enum is not \"repr(u8)\"", 188 | )); 189 | } 190 | } 191 | } 192 | } 193 | Ok(()) 194 | } 195 | 196 | pub fn expand_enum( 197 | de: &syn::DataEnum, 198 | ast: &syn::DeriveInput, 199 | ) -> syn::Result { 200 | let name = &ast.ident; 201 | check_repr_u8(name, &ast.attrs)?; 202 | check_enum_fields(de)?; 203 | let docs = extract_docs(&ast.attrs)?; 204 | let enum_options = de.variants.iter(); 205 | 206 | let from = impl_enum_from_u8(name, enum_options.clone()); 207 | let solidity_option = impl_solidity_option(&docs, name, enum_options.clone()); 208 | let abi_type = impl_enum_abi_type(name); 209 | let abi_read = impl_enum_abi_read(name); 210 | let abi_write = impl_enum_abi_write(name); 211 | let solidity_type_name = impl_enum_solidity_type_name(name); 212 | 213 | Ok(quote! { 214 | #from 215 | #solidity_option 216 | #abi_type 217 | #abi_read 218 | #abi_write 219 | #solidity_type_name 220 | }) 221 | } 222 | -------------------------------------------------------------------------------- /procedural/src/to_log.rs: -------------------------------------------------------------------------------- 1 | use inflector::cases; 2 | use quote::quote; 3 | use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Variant}; 4 | 5 | use crate::{parse_ident_from_path, parse_ident_from_type, snake_ident_to_screaming}; 6 | 7 | struct EventField { 8 | name: Ident, 9 | camel_name: String, 10 | ty: Ident, 11 | indexed: bool, 12 | } 13 | 14 | impl EventField { 15 | fn try_from(field: &Field) -> syn::Result { 16 | let name = field.ident.as_ref().unwrap(); 17 | let ty = parse_ident_from_type(&field.ty, false)?; 18 | let mut indexed = false; 19 | for attr in &field.attrs { 20 | if let Ok(ident) = parse_ident_from_path(&attr.path, false) { 21 | if ident == "indexed" { 22 | indexed = true; 23 | } 24 | } 25 | } 26 | Ok(Self { 27 | name: name.clone(), 28 | camel_name: cases::camelcase::to_camel_case(&name.to_string()), 29 | ty: ty.clone(), 30 | indexed, 31 | }) 32 | } 33 | fn expand_solidity_argument(&self) -> proc_macro2::TokenStream { 34 | let camel_name = &self.camel_name; 35 | let ty = &self.ty; 36 | let indexed = self.indexed; 37 | quote! { 38 | >::new(#indexed, #camel_name) 39 | } 40 | } 41 | } 42 | 43 | struct Event { 44 | name: Ident, 45 | name_screaming: Ident, 46 | fields: Vec, 47 | selector: proc_macro2::TokenStream, 48 | } 49 | 50 | impl Event { 51 | fn try_from(variant: &Variant) -> syn::Result { 52 | let name = &variant.ident; 53 | let name_lit = proc_macro2::Literal::string(name.to_string().as_str()); 54 | let name_screaming = snake_ident_to_screaming(name); 55 | 56 | let Fields::Named(named) = &variant.fields else { 57 | return Err(syn::Error::new( 58 | variant.fields.span(), 59 | "expected named fields", 60 | )); 61 | }; 62 | let mut fields = Vec::new(); 63 | for field in &named.named { 64 | fields.push(EventField::try_from(field)?); 65 | } 66 | if fields.iter().filter(|f| f.indexed).count() > 3 { 67 | return Err(syn::Error::new( 68 | variant.fields.span(), 69 | "events can have at most 4 indexed fields (1 indexed field is reserved for event signature)" 70 | )); 71 | } 72 | 73 | let args = fields.iter().map(|f| { 74 | let ty = &f.ty; 75 | quote! {nameof(<#ty as ::evm_coder::abi::AbiType>::SIGNATURE) fixed(",")} 76 | }); 77 | // Remove trailing comma 78 | let shift = (!fields.is_empty()).then(|| quote! {shift_left(1)}); 79 | 80 | let signature = quote! { ::evm_coder::make_signature!(new fixed(#name_lit) fixed("(") #(#args)* #shift fixed(")")) }; 81 | let selector = quote! { 82 | { 83 | let signature = #signature; 84 | let mut sum = ::evm_coder::sha3_const::Keccak256::new(); 85 | let mut pos = 0; 86 | while pos < signature.len { 87 | sum = sum.update(&[signature.data[pos]; 1]); 88 | pos += 1; 89 | } 90 | let a = sum.finalize(); 91 | let mut selector_bytes = [0; 32]; 92 | let mut i = 0; 93 | while i != 32 { 94 | selector_bytes[i] = a[i]; 95 | i += 1; 96 | } 97 | selector_bytes 98 | } 99 | }; 100 | 101 | Ok(Self { 102 | name: name.clone(), 103 | name_screaming, 104 | fields, 105 | selector, 106 | }) 107 | } 108 | 109 | fn expand_serializers(&self) -> proc_macro2::TokenStream { 110 | let name = &self.name; 111 | let name_screaming = &self.name_screaming; 112 | let fields = self.fields.iter().map(|f| &f.name); 113 | 114 | let indexed = self.fields.iter().filter(|f| f.indexed).map(|f| &f.name); 115 | let plain = self.fields.iter().filter(|f| !f.indexed).map(|f| &f.name); 116 | 117 | quote! { 118 | Self::#name {#( 119 | #fields, 120 | )*} => { 121 | topics.push(::evm_coder::types::Topic::from(Self::#name_screaming)); 122 | #( 123 | topics.push(#indexed.to_topic()); 124 | )* 125 | ::evm_coder::ethereum::Log { 126 | address: contract, 127 | topics, 128 | data: (#(#plain,)*).abi_encode(), 129 | } 130 | } 131 | } 132 | } 133 | 134 | fn expand_consts(&self) -> proc_macro2::TokenStream { 135 | let name_screaming = &self.name_screaming; 136 | let selector = &self.selector; 137 | 138 | quote! { 139 | const #name_screaming: [u8; 32] = #selector; 140 | } 141 | } 142 | 143 | fn expand_solidity_function(&self) -> proc_macro2::TokenStream { 144 | let name = self.name.to_string(); 145 | let args = self.fields.iter().map(EventField::expand_solidity_argument); 146 | quote! { 147 | SolidityEvent { 148 | name: #name, 149 | args: ( 150 | #( 151 | #args, 152 | )* 153 | ), 154 | } 155 | } 156 | } 157 | } 158 | 159 | pub struct Events { 160 | name: Ident, 161 | events: Vec, 162 | } 163 | 164 | impl Events { 165 | pub fn try_from(data: &DeriveInput) -> syn::Result { 166 | let name = &data.ident; 167 | let Data::Enum(en) = &data.data else { 168 | return Err(syn::Error::new(data.span(), "expected enum")); 169 | }; 170 | let mut events = Vec::new(); 171 | for variant in &en.variants { 172 | events.push(Event::try_from(variant)?); 173 | } 174 | Ok(Self { 175 | name: name.clone(), 176 | events, 177 | }) 178 | } 179 | pub fn expand(&self) -> proc_macro2::TokenStream { 180 | let name = &self.name; 181 | 182 | let consts = self.events.iter().map(Event::expand_consts); 183 | let serializers = self.events.iter().map(Event::expand_serializers); 184 | let solidity_name = self.name.to_string(); 185 | let solidity_functions = self.events.iter().map(Event::expand_solidity_function); 186 | 187 | quote! { 188 | impl #name { 189 | #( 190 | #consts 191 | )* 192 | 193 | /// Generate solidity definitions for methods described in this interface 194 | #[cfg(feature = "stubgen")] 195 | pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) { 196 | use evm_coder::solidity::*; 197 | use core::fmt::Write; 198 | let interface = SolidityInterface { 199 | docs: &[], 200 | selector: ::evm_coder::types::BytesFixed([0; 4]), 201 | name: #solidity_name, 202 | is: &[], 203 | functions: (#( 204 | #solidity_functions, 205 | )*), 206 | }; 207 | let mut out = ::evm_coder::types::String::new(); 208 | out.push_str("/// @dev inlined interface\n"); 209 | let _ = interface.format(is_impl, &mut out, tc); 210 | tc.collect(out); 211 | } 212 | } 213 | 214 | #[automatically_derived] 215 | impl ::evm_coder::events::ToLog for #name { 216 | fn to_log(&self, contract: Address) -> ::evm_coder::ethereum::Log { 217 | use ::evm_coder::events::ToTopic; 218 | use ::evm_coder::abi::AbiEncode; 219 | let mut topics = Vec::new(); 220 | match self { 221 | #( 222 | #serializers, 223 | )* 224 | } 225 | } 226 | } 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/solidity/impls.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use primitive_types::{H160, U256}; 4 | 5 | use crate::{ 6 | solidity::{SolidityTupleTy, SolidityTypeName, TypeCollector}, 7 | types::*, 8 | }; 9 | 10 | macro_rules! solidity_type_name { 11 | ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => { 12 | $( 13 | impl SolidityTypeName for $ty { 14 | fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result { 15 | write!(writer, $name) 16 | } 17 | fn is_simple() -> bool { 18 | $simple 19 | } 20 | fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result { 21 | write!(writer, $default) 22 | } 23 | } 24 | )* 25 | }; 26 | } 27 | 28 | solidity_type_name! { 29 | u8 => "uint8" true = "0", 30 | u32 => "uint32" true = "0", 31 | u64 => "uint64" true = "0", 32 | u128 => "uint128" true = "0", 33 | U256 => "uint256" true = "0", 34 | H160 => "address" true = "0x0000000000000000000000000000000000000000", 35 | String => "string" false = "\"\"", 36 | Bytes => "bytes" false = "hex\"\"", 37 | bool => "bool" true = "false", 38 | } 39 | 40 | impl SolidityTypeName for BytesFixed { 41 | fn solidity_name(writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { 42 | writer.write_fmt(format_args!("bytes{S}")) 43 | } 44 | 45 | fn is_simple() -> bool { 46 | true 47 | } 48 | 49 | fn solidity_default(writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { 50 | writer.write_fmt(format_args!("bytes{S}(0)")) 51 | } 52 | } 53 | 54 | impl SolidityTypeName for () { 55 | fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { 56 | Ok(()) 57 | } 58 | fn is_simple() -> bool { 59 | true 60 | } 61 | fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { 62 | Ok(()) 63 | } 64 | fn is_void() -> bool { 65 | true 66 | } 67 | } 68 | 69 | impl SolidityTypeName for Result { 70 | fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 71 | T::solidity_name(writer, tc) 72 | } 73 | fn is_simple() -> bool { 74 | T::is_simple() 75 | } 76 | fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 77 | T::solidity_default(writer, tc) 78 | } 79 | fn is_void() -> bool { 80 | T::is_void() 81 | } 82 | } 83 | 84 | impl SolidityTypeName for Vec { 85 | fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 86 | T::solidity_name(writer, tc)?; 87 | write!(writer, "[]") 88 | } 89 | fn is_simple() -> bool { 90 | false 91 | } 92 | fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 93 | write!(writer, "new ")?; 94 | T::solidity_name(writer, tc)?; 95 | write!(writer, "[](0)") 96 | } 97 | } 98 | 99 | macro_rules! count { 100 | () => (0usize); 101 | ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*)); 102 | } 103 | 104 | macro_rules! impl_tuples { 105 | ($($ident:ident)+) => { 106 | impl<$($ident: SolidityTypeName + 'static),+> SolidityTupleTy for ($($ident,)+) { 107 | fn fields(tc: &TypeCollector) -> Vec { 108 | let mut collected = Vec::with_capacity(Self::len()); 109 | $({ 110 | let mut out = String::new(); 111 | $ident::solidity_name(&mut out, tc).expect("no fmt error"); 112 | collected.push(out); 113 | })*; 114 | collected 115 | } 116 | 117 | fn len() -> usize { 118 | count!($($ident)*) 119 | } 120 | } 121 | impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) { 122 | fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 123 | write!(writer, "{}", tc.collect_tuple::()) 124 | } 125 | fn is_simple() -> bool { 126 | false 127 | } 128 | #[allow(unused_assignments)] 129 | fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 130 | write!(writer, "{}(", tc.collect_tuple::())?; 131 | let mut first = true; 132 | $( 133 | if !first { 134 | write!(writer, ",")?; 135 | } else { 136 | first = false; 137 | } 138 | <$ident>::solidity_default(writer, tc)?; 139 | )* 140 | write!(writer, ")") 141 | } 142 | } 143 | }; 144 | } 145 | 146 | impl_tuples! {A} 147 | impl_tuples! {A B} 148 | impl_tuples! {A B C} 149 | impl_tuples! {A B C D} 150 | impl_tuples! {A B C D E} 151 | impl_tuples! {A B C D E F} 152 | impl_tuples! {A B C D E F G} 153 | impl_tuples! {A B C D E F G H} 154 | impl_tuples! {A B C D E F G H I} 155 | impl_tuples! {A B C D E F G H I J} 156 | impl_tuples! {A B C D E F G H I J K} 157 | impl_tuples! {A B C D E F G H I J K L} 158 | impl_tuples! {A B C D E F G H I J K L M} 159 | impl_tuples! {A B C D E F G H I J K L M N} 160 | impl_tuples! {A B C D E F G H I J K L M N O} 161 | impl_tuples! {A B C D E F G H I J K L M N O P} 162 | 163 | //----- impls for Option ----- 164 | impl SolidityTypeName for Option { 165 | fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 166 | write!(writer, "{}", tc.collect_struct::()) 167 | } 168 | fn is_simple() -> bool { 169 | false 170 | } 171 | fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 172 | write!(writer, "{}(", tc.collect_struct::())?; 173 | bool::solidity_default(writer, tc)?; 174 | write!(writer, ", ")?; 175 | T::solidity_default(writer, tc)?; 176 | write!(writer, ")") 177 | } 178 | } 179 | 180 | impl super::SolidityStructTy for Option { 181 | fn generate_solidity_interface(tc: &TypeCollector) -> String { 182 | let mut solidity_name = "Option".to_string(); 183 | let mut generic_name = String::new(); 184 | T::solidity_name(&mut generic_name, tc).unwrap(); 185 | solidity_name.push( 186 | generic_name 187 | .chars() 188 | .next() 189 | .expect("Generic name is empty") 190 | .to_ascii_uppercase(), 191 | ); 192 | solidity_name.push_str(&generic_name[1..]); 193 | 194 | let interface = super::SolidityStruct { 195 | docs: &[" Optional value"], 196 | name: solidity_name.as_str(), 197 | fields: ( 198 | super::SolidityStructField:: { 199 | docs: &[" Shows the status of accessibility of value"], 200 | name: "status", 201 | ty: ::core::marker::PhantomData, 202 | }, 203 | super::SolidityStructField:: { 204 | docs: &[" Actual value if `status` is true"], 205 | name: "value", 206 | ty: ::core::marker::PhantomData, 207 | }, 208 | ), 209 | }; 210 | 211 | let mut out = String::new(); 212 | let _ = interface.format(&mut out, tc); 213 | tc.collect(out); 214 | 215 | solidity_name.to_string() 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /procedural/src/abi_derive/derive_flags.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use super::extract_docs; 5 | use crate::structs::common::{BitMath, Endianness, FieldInfo, StructInfo}; 6 | 7 | fn align_size(name: &syn::Ident, total_bytes: usize) -> syn::Result { 8 | Ok(match total_bytes { 9 | 1 => 1, 10 | 2..=4 => 4, 11 | 5..=8 => 8, 12 | _ => { 13 | return Err(syn::Error::new( 14 | name.span(), 15 | format!("Unsupported struct size: {total_bytes}"), 16 | )) 17 | } 18 | }) 19 | } 20 | 21 | fn align_type(name: &syn::Ident, total_bytes: usize) -> syn::Result { 22 | Ok(match total_bytes { 23 | 1 => quote! { u8 }, 24 | 2..=4 => quote! { u32 }, 25 | 5..=8 => quote! { u64 }, 26 | _ => { 27 | return Err(syn::Error::new( 28 | name.span(), 29 | format!("Unsupported struct size: {total_bytes}"), 30 | )) 31 | } 32 | }) 33 | } 34 | 35 | pub fn impl_struct_abi_type(name: &syn::Ident, total_bytes: usize) -> syn::Result { 36 | let align_type = align_type(name, total_bytes)?; 37 | Ok(quote! { 38 | impl ::evm_coder::abi::AbiType for #name { 39 | const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = <(#align_type) as ::evm_coder::abi::AbiType>::SIGNATURE; 40 | const IS_DYNAMIC: bool = <(#align_type) as ::evm_coder::abi::AbiType>::IS_DYNAMIC; 41 | const HEAD_WORDS: u32 = <(#align_type) as ::evm_coder::abi::AbiType>::HEAD_WORDS; 42 | } 43 | }) 44 | } 45 | 46 | pub fn impl_struct_abi_read(name: &syn::Ident, total_bytes: usize) -> syn::Result { 47 | let aligned_size = align_size(name, total_bytes)?; 48 | let bytes = (0..total_bytes).map(|i| { 49 | quote! { value[#i] } 50 | }); 51 | Ok(quote!( 52 | impl ::evm_coder::abi::AbiDecode for #name { 53 | fn dec(reader: &mut ::evm_coder::abi::AbiDecoder) -> ::evm_coder::abi::Result { 54 | use ::evm_coder::abi::ABI_WORD_SIZE; 55 | let word = reader.get_head()?; 56 | let mut value = [0; #aligned_size]; 57 | value.copy_from_slice(&word[ABI_WORD_SIZE as usize - #aligned_size..ABI_WORD_SIZE as usize]); 58 | if word[0..(ABI_WORD_SIZE as usize - #aligned_size)] 59 | .iter() 60 | .any(|&b| b != 0) 61 | { 62 | return Err(::evm_coder::abi::Error::InvalidRange); 63 | }; 64 | Ok(#name::from_bytes([#(#bytes),*])) 65 | } 66 | } 67 | )) 68 | } 69 | 70 | pub fn impl_struct_abi_write(name: &syn::Ident, total_bytes: usize) -> syn::Result { 71 | let aligned_size = align_size(name, total_bytes)?; 72 | Ok(quote!( 73 | impl ::evm_coder::abi::AbiEncode for #name { 74 | fn enc(&self, writer: &mut ::evm_coder::abi::AbiEncoder) { 75 | use ::evm_coder::abi::ABI_WORD_SIZE; 76 | let value = self.clone().into_bytes(); 77 | let mut word = [0; ABI_WORD_SIZE as usize]; 78 | word[ABI_WORD_SIZE as usize - #aligned_size..ABI_WORD_SIZE as usize].copy_from_slice(&value); 79 | writer.append_head(word); 80 | } 81 | } 82 | )) 83 | } 84 | 85 | pub fn impl_struct_solidity_type<'a>( 86 | name: &syn::Ident, 87 | docs: &[String], 88 | total_bytes: usize, 89 | fields: impl Iterator + Clone, 90 | ) -> syn::Result { 91 | let aligned_size = align_size(name, total_bytes)?; 92 | let solidity_name = name.to_string(); 93 | let solidity_fields = fields.map(|f| { 94 | let name = f.ident.as_ref().to_string(); 95 | let docs = f.docs.clone(); 96 | let (amount_of_bits, zeros_on_left, _, starting_inject_byte) = BitMath::from_field(f) 97 | .map(|math| math.into_tuple()) 98 | .unwrap_or((0, 0, 0, 0)); 99 | assert!( 100 | aligned_size * 8 >= (zeros_on_left + amount_of_bits), 101 | "{aligned_size} {zeros_on_left} {amount_of_bits} {starting_inject_byte}" 102 | ); 103 | let zeros_on_right = aligned_size * 8 - (zeros_on_left + amount_of_bits); 104 | if amount_of_bits == 0 { 105 | quote! { 106 | SolidityFlagsField::Bool(SolidityFlagsBool { 107 | docs: &[#(#docs),*], 108 | name: #name, 109 | value: 0, 110 | }) 111 | } 112 | } else if amount_of_bits == 1 { 113 | quote! { 114 | SolidityFlagsField::Bool(SolidityFlagsBool { 115 | docs: &[#(#docs),*], 116 | name: #name, 117 | shift: #zeros_on_right, 118 | }) 119 | } 120 | } else { 121 | quote! { 122 | SolidityFlagsField::Number(SolidityFlagsNumber { 123 | docs: &[#(#docs),*], 124 | name: #name, 125 | start_bit: #zeros_on_right, 126 | amount_of_bits: #amount_of_bits, 127 | }) 128 | } 129 | } 130 | }); 131 | Ok(quote! { 132 | #[cfg(feature = "stubgen")] 133 | impl ::evm_coder::solidity::SolidityStructTy for #name { 134 | /// Generate solidity definitions for methods described in this struct 135 | fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector) -> String { 136 | use evm_coder::solidity::*; 137 | use core::fmt::Write; 138 | let interface = SolidityLibrary { 139 | docs: &[#(#docs),*], 140 | name: #solidity_name, 141 | total_bytes: #total_bytes, 142 | fields: Vec::from([#( 143 | #solidity_fields, 144 | )*]), 145 | }; 146 | let mut out = String::new(); 147 | let _ = interface.format(&mut out); 148 | tc.collect(out); 149 | #solidity_name.to_string() 150 | } 151 | } 152 | }) 153 | } 154 | 155 | pub fn impl_struct_solidity_type_name(name: &syn::Ident) -> TokenStream { 156 | quote! { 157 | #[cfg(feature = "stubgen")] 158 | impl ::evm_coder::solidity::SolidityTypeName for #name { 159 | fn solidity_name( 160 | writer: &mut impl ::core::fmt::Write, 161 | tc: &::evm_coder::solidity::TypeCollector, 162 | ) -> ::core::fmt::Result { 163 | write!(writer, "{}", tc.collect_struct::()) 164 | } 165 | 166 | fn is_simple() -> bool { 167 | false 168 | } 169 | 170 | fn solidity_default( 171 | writer: &mut impl ::core::fmt::Write, 172 | tc: &::evm_coder::solidity::TypeCollector, 173 | ) -> ::core::fmt::Result { 174 | write!(writer, "{}.wrap(0)", tc.collect_struct::()) 175 | } 176 | } 177 | } 178 | } 179 | 180 | pub fn expand_flags(ds: &syn::DataStruct, ast: &syn::DeriveInput) -> syn::Result { 181 | let name = &ast.ident; 182 | let docs = extract_docs(&ast.attrs)?; 183 | let params_count = match ds.fields { 184 | syn::Fields::Named(ref fields) => Ok(fields.named.len()), 185 | syn::Fields::Unnamed(ref fields) => Ok(fields.unnamed.len()), 186 | syn::Fields::Unit => Err(syn::Error::new(name.span(), "Unit structs not supported")), 187 | }?; 188 | 189 | if params_count == 0 { 190 | return Err(syn::Error::new(name.span(), "Empty structs not supported")); 191 | }; 192 | 193 | // parse the input into a StructInfo which contains all the information we 194 | // along with some helpful structures to generate our Bitfield code. 195 | let struct_info = match StructInfo::parse(ast) { 196 | Ok(parsed_struct) => parsed_struct, 197 | Err(err) => { 198 | return Ok(err.to_compile_error()); 199 | } 200 | }; 201 | 202 | if struct_info.lsb_zero { 203 | return Err(syn::Error::new( 204 | struct_info.name.span(), 205 | "read_from = 'lsb0' is not supported", 206 | )); 207 | } 208 | 209 | if let Some(field) = struct_info 210 | .fields 211 | .iter() 212 | .find(|field| field.bit_size() > 8 && *field.attrs.endianness != Endianness::Big) 213 | { 214 | return Err(syn::Error::new( 215 | field.name.span(), 216 | "only big endian fields are supported", 217 | )); 218 | } 219 | 220 | let total_bytes = struct_info.total_bytes(); 221 | let abi_type = impl_struct_abi_type(name, total_bytes)?; 222 | let abi_read = impl_struct_abi_read(name, total_bytes)?; 223 | let abi_write = impl_struct_abi_write(name, total_bytes)?; 224 | let solidity_type = 225 | impl_struct_solidity_type(name, &docs, total_bytes, struct_info.fields.iter())?; 226 | let solidity_type_name = impl_struct_solidity_type_name(name); 227 | Ok(quote! { 228 | #abi_type 229 | #abi_read 230 | #abi_write 231 | #solidity_type 232 | #solidity_type_name 233 | }) 234 | } 235 | -------------------------------------------------------------------------------- /procedural/src/abi_derive/derive_struct.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::Field; 4 | 5 | use super::extract_docs; 6 | 7 | pub fn tuple_type<'a>(field_types: impl Iterator + Clone) -> TokenStream { 8 | let field_types = field_types.map(|ty| quote!(#ty,)); 9 | quote! {(#(#field_types)*)} 10 | } 11 | 12 | pub fn tuple_ref_type<'a>(field_types: impl Iterator + Clone) -> TokenStream { 13 | let field_types = field_types.map(|ty| quote!(&#ty,)); 14 | quote! {(#(#field_types)*)} 15 | } 16 | 17 | pub fn tuple_data_as_ref( 18 | is_named_fields: bool, 19 | field_names: impl Iterator + Clone, 20 | ) -> TokenStream { 21 | let field_names = field_names.enumerate().map(|(i, field)| { 22 | if is_named_fields { 23 | quote!(&self.#field,) 24 | } else { 25 | let field = proc_macro2::Literal::usize_unsuffixed(i); 26 | quote!(&self.#field,) 27 | } 28 | }); 29 | quote! {(#(#field_names)*)} 30 | } 31 | 32 | pub fn tuple_names( 33 | is_named_fields: bool, 34 | field_names: impl Iterator + Clone, 35 | ) -> TokenStream { 36 | let field_names = field_names.enumerate().map(|(i, field)| { 37 | if is_named_fields { 38 | quote!(#field,) 39 | } else { 40 | let field = proc_macro2::Ident::new( 41 | format!("field{i}").as_str(), 42 | proc_macro2::Span::call_site(), 43 | ); 44 | quote!(#field,) 45 | } 46 | }); 47 | quote! {(#(#field_names)*)} 48 | } 49 | 50 | pub fn struct_from_tuple( 51 | name: &syn::Ident, 52 | is_named_fields: bool, 53 | field_names: impl Iterator + Clone, 54 | ) -> TokenStream { 55 | let field_names = field_names.enumerate().map(|(i, field)| { 56 | if is_named_fields { 57 | quote!(#field,) 58 | } else { 59 | let field = proc_macro2::Ident::new( 60 | format!("field{i}").as_str(), 61 | proc_macro2::Span::call_site(), 62 | ); 63 | quote!(#field,) 64 | } 65 | }); 66 | 67 | if is_named_fields { 68 | quote! {#name {#(#field_names)*}} 69 | } else { 70 | quote! {#name (#(#field_names)*)} 71 | } 72 | } 73 | 74 | pub fn map_field_to_name(field: (usize, &syn::Field)) -> syn::Ident { 75 | if let Some(name) = field.1.ident.as_ref() { 76 | return name.clone(); 77 | } 78 | let mut name = "field".to_string(); 79 | name.push_str(field.0.to_string().as_str()); 80 | syn::Ident::new(name.as_str(), proc_macro2::Span::call_site()) 81 | } 82 | 83 | pub fn map_field_to_type(field: &syn::Field) -> &syn::Type { 84 | &field.ty 85 | } 86 | 87 | pub fn impl_struct_abi_type(name: &syn::Ident, tuple_type: &TokenStream) -> TokenStream { 88 | quote! { 89 | impl ::evm_coder::abi::AbiType for #name { 90 | const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = <#tuple_type as ::evm_coder::abi::AbiType>::SIGNATURE; 91 | const IS_DYNAMIC: bool = <#tuple_type as ::evm_coder::abi::AbiType>::IS_DYNAMIC; 92 | const HEAD_WORDS: u32 = <#tuple_type as ::evm_coder::abi::AbiType>::HEAD_WORDS; 93 | } 94 | } 95 | } 96 | 97 | pub fn impl_struct_abi_read( 98 | name: &syn::Ident, 99 | tuple_type: &TokenStream, 100 | tuple_names: &TokenStream, 101 | struct_from_tuple: &TokenStream, 102 | ) -> TokenStream { 103 | quote!( 104 | impl ::evm_coder::abi::AbiDecode for #name { 105 | fn dec(reader: &mut ::evm_coder::abi::AbiDecoder) -> ::evm_coder::abi::Result { 106 | let #tuple_names = <#tuple_type as ::evm_coder::abi::AbiDecode>::dec(reader)?; 107 | Ok(#struct_from_tuple) 108 | } 109 | } 110 | ) 111 | } 112 | 113 | pub fn impl_struct_abi_write( 114 | name: &syn::Ident, 115 | _is_named_fields: bool, 116 | tuple_type: &TokenStream, 117 | tuple_data: &TokenStream, 118 | ) -> TokenStream { 119 | quote!( 120 | impl ::evm_coder::abi::AbiEncode for #name { 121 | fn enc(&self, writer: &mut ::evm_coder::abi::AbiEncoder) { 122 | <#tuple_type as ::evm_coder::abi::AbiEncode>::enc(&#tuple_data, writer) 123 | } 124 | } 125 | ) 126 | } 127 | 128 | pub fn impl_struct_solidity_type<'a>( 129 | name: &syn::Ident, 130 | docs: &[String], 131 | fields: impl Iterator + Clone, 132 | ) -> TokenStream { 133 | let solidity_name = name.to_string(); 134 | let solidity_fields = fields.enumerate().map(|(i, f)| { 135 | let name = f 136 | .ident 137 | .as_ref() 138 | .map_or_else(|| format!("field_{i}"), ToString::to_string); 139 | let ty = &f.ty; 140 | let docs = extract_docs(&f.attrs).expect("TODO: handle bad docs"); 141 | quote! { 142 | SolidityStructField::<#ty> { 143 | docs: &[#(#docs),*], 144 | name: #name, 145 | ty: ::core::marker::PhantomData, 146 | } 147 | } 148 | }); 149 | quote! { 150 | #[cfg(feature = "stubgen")] 151 | impl ::evm_coder::solidity::SolidityStructTy for #name { 152 | /// Generate solidity definitions for methods described in this struct 153 | fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector) -> String { 154 | use evm_coder::solidity::*; 155 | use core::fmt::Write; 156 | let interface = SolidityStruct { 157 | docs: &[#(#docs),*], 158 | name: #solidity_name, 159 | fields: (#( 160 | #solidity_fields, 161 | )*), 162 | }; 163 | let mut out = String::new(); 164 | let _ = interface.format(&mut out, tc); 165 | tc.collect(out); 166 | #solidity_name.to_string() 167 | } 168 | } 169 | } 170 | } 171 | 172 | pub fn impl_struct_solidity_type_name<'a>( 173 | name: &syn::Ident, 174 | field_types: impl Iterator + Clone, 175 | params_count: usize, 176 | ) -> TokenStream { 177 | let arg_dafaults = field_types.enumerate().map(|(i, ty)| { 178 | let mut defult_value = quote!(<#ty as ::evm_coder::solidity::SolidityTypeName 179 | >::solidity_default(writer, tc)?;); 180 | let last_item = params_count - 1; 181 | if i != last_item { 182 | defult_value.extend(quote! {write!(writer, ",")?;}); 183 | } 184 | defult_value 185 | }); 186 | 187 | quote! { 188 | #[cfg(feature = "stubgen")] 189 | impl ::evm_coder::solidity::SolidityTypeName for #name { 190 | fn solidity_name( 191 | writer: &mut impl ::core::fmt::Write, 192 | tc: &::evm_coder::solidity::TypeCollector, 193 | ) -> ::core::fmt::Result { 194 | write!(writer, "{}", tc.collect_struct::()) 195 | } 196 | 197 | fn is_simple() -> bool { 198 | false 199 | } 200 | 201 | fn solidity_default( 202 | writer: &mut impl ::core::fmt::Write, 203 | tc: &::evm_coder::solidity::TypeCollector, 204 | ) -> ::core::fmt::Result { 205 | write!(writer, "{}(", tc.collect_struct::())?; 206 | 207 | #(#arg_dafaults)* 208 | 209 | write!(writer, ")") 210 | } 211 | } 212 | } 213 | } 214 | 215 | pub fn expand_struct( 216 | ds: &syn::DataStruct, 217 | ast: &syn::DeriveInput, 218 | ) -> syn::Result { 219 | let name = &ast.ident; 220 | let docs = extract_docs(&ast.attrs)?; 221 | let (is_named_fields, field_names, field_types, params_count) = match ds.fields { 222 | syn::Fields::Named(ref fields) => Ok(( 223 | true, 224 | fields.named.iter().enumerate().map(map_field_to_name), 225 | fields.named.iter().map(map_field_to_type), 226 | fields.named.len(), 227 | )), 228 | syn::Fields::Unnamed(ref fields) => Ok(( 229 | false, 230 | fields.unnamed.iter().enumerate().map(map_field_to_name), 231 | fields.unnamed.iter().map(map_field_to_type), 232 | fields.unnamed.len(), 233 | )), 234 | syn::Fields::Unit => Err(syn::Error::new(name.span(), "Unit structs not supported")), 235 | }?; 236 | 237 | if params_count == 0 { 238 | return Err(syn::Error::new(name.span(), "Empty structs not supported")); 239 | }; 240 | 241 | let tuple_type = tuple_type(field_types.clone()); 242 | let tuple_ref_type = tuple_ref_type(field_types.clone()); 243 | let tuple_data = tuple_data_as_ref(is_named_fields, field_names.clone()); 244 | let tuple_names = tuple_names(is_named_fields, field_names.clone()); 245 | let struct_from_tuple = struct_from_tuple(name, is_named_fields, field_names.clone()); 246 | 247 | let abi_type = impl_struct_abi_type(name, &tuple_type); 248 | let abi_read = impl_struct_abi_read(name, &tuple_type, &tuple_names, &struct_from_tuple); 249 | let abi_write = impl_struct_abi_write(name, is_named_fields, &tuple_ref_type, &tuple_data); 250 | let solidity_type = impl_struct_solidity_type(name, &docs, ds.fields.iter()); 251 | let solidity_type_name = 252 | impl_struct_solidity_type_name(name, field_types.clone(), params_count); 253 | 254 | Ok(quote! { 255 | #abi_type 256 | #abi_read 257 | #abi_write 258 | #solidity_type 259 | #solidity_type_name 260 | }) 261 | } 262 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/custom_signature.rs: -------------------------------------------------------------------------------- 1 | //! # A module for custom signature support. 2 | //! 3 | //! ## Overview 4 | //! This module allows you to create arbitrary signatures for types and functions in compile time. 5 | //! 6 | //! ### Type signatures 7 | //! To create the desired type signature, you need to create your own trait with the `SIGNATURE` constant. 8 | //! Then in the implementation, for the required type, use the macro [`make_signature`](crate::make_signature) 9 | //! #### Example 10 | //! ``` 11 | //! use std::str::from_utf8; 12 | //! use evm_coder::{custom_signature::SignatureUnit, make_signature}; 13 | //! 14 | //! // Create trait for our signature 15 | //! trait SoliditySignature { 16 | //! const SIGNATURE: SignatureUnit; 17 | //! 18 | //! fn name() -> &'static str { 19 | //! from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8") 20 | //! } 21 | //! } 22 | //! 23 | //! // Make signatures for some types 24 | //! impl SoliditySignature for u8 { 25 | //! const SIGNATURE: SignatureUnit = make_signature!(new fixed("uint8")); 26 | //! } 27 | //! impl SoliditySignature for u32 { 28 | //! const SIGNATURE: SignatureUnit = make_signature!(new fixed("uint32")); 29 | //! } 30 | //! impl SoliditySignature for Vec { 31 | //! const SIGNATURE: SignatureUnit = make_signature!(new nameof(T::SIGNATURE) fixed("[]")); 32 | //! } 33 | //! impl SoliditySignature for (A, B) { 34 | //! const SIGNATURE: SignatureUnit = make_signature!(new fixed("(") nameof(A::SIGNATURE) fixed(",") nameof(B::SIGNATURE) fixed(")")); 35 | //! } 36 | //! impl SoliditySignature for (A,) { 37 | //! const SIGNATURE: SignatureUnit = make_signature!(new fixed("(") nameof(A::SIGNATURE) fixed(",") shift_left(1) fixed(")")); 38 | //! } 39 | //! 40 | //! assert_eq!(u8::name(), "uint8"); 41 | //! assert_eq!(>::name(), "uint8[]"); 42 | //! assert_eq!(<(u32, u8)>::name(), "(uint32,uint8)"); 43 | //! ``` 44 | //! 45 | //! #### Example 46 | //! ``` 47 | //! use core::str::from_utf8; 48 | //! use evm_coder::{custom_signature::SignatureUnit, make_signature}; 49 | //! // Trait for our signature 50 | //! trait SoliditySignature { 51 | //! const SIGNATURE: SignatureUnit; 52 | //! 53 | //! fn name() -> &'static str { 54 | //! from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8") 55 | //! } 56 | //! } 57 | //! 58 | //! // Make signatures for some types 59 | //! impl SoliditySignature for u8 { 60 | //! const SIGNATURE: SignatureUnit = make_signature!(new fixed("uint8")); 61 | //! } 62 | //! impl SoliditySignature for Vec { 63 | //! const SIGNATURE: SignatureUnit = make_signature!(new nameof(T::SIGNATURE) fixed("[]")); 64 | //! } 65 | //! ``` 66 | 67 | /// The maximum length of the signature. 68 | pub const SIGNATURE_SIZE_LIMIT: usize = 256; 69 | 70 | /// Storage for the signature or its elements. 71 | #[derive(Debug)] 72 | pub struct SignatureUnit { 73 | /// Signature data. 74 | pub data: [u8; SIGNATURE_SIZE_LIMIT], 75 | /// The actual size of the data. 76 | pub len: usize, 77 | } 78 | 79 | impl SignatureUnit { 80 | /// Create a signature from `&str`. 81 | #[must_use] 82 | pub const fn new(name: &'static str) -> SignatureUnit { 83 | let mut signature = [0_u8; SIGNATURE_SIZE_LIMIT]; 84 | let name = name.as_bytes(); 85 | let name_len = name.len(); 86 | let mut dst_offset = 0; 87 | crate::make_signature!(@copy(name, signature, name_len, dst_offset)); 88 | SignatureUnit { 89 | data: signature, 90 | len: name_len, 91 | } 92 | } 93 | /// String conversion 94 | #[must_use] 95 | pub fn as_str(&self) -> Option<&str> { 96 | core::str::from_utf8(&self.data[0..self.len]).ok() 97 | } 98 | } 99 | 100 | /// ### Macro to create signatures of types and functions. 101 | /// 102 | /// Format for creating a type of signature: 103 | /// ```ignore 104 | /// make_signature!(new fixed("uint8")); // Simple type 105 | /// make_signature!(new fixed("(") nameof(u8) fixed(",") nameof(u8) fixed(")")); // Composite type 106 | /// ``` 107 | #[macro_export] 108 | macro_rules! make_signature { 109 | (new $($tt:tt)*) => { 110 | ($crate::custom_signature::SignatureUnit { 111 | data: { 112 | let mut out = [0u8; $crate::custom_signature::SIGNATURE_SIZE_LIMIT]; 113 | let mut dst_offset = 0; 114 | $crate::make_signature!(@data(out, dst_offset); $($tt)*); 115 | out 116 | }, 117 | len: {0 + $crate::make_signature!(@size; $($tt)*)}, 118 | }) 119 | }; 120 | 121 | (@size;) => { 122 | 0 123 | }; 124 | (@size; fixed($expr:expr) $($tt:tt)*) => { 125 | $expr.len() + $crate::make_signature!(@size; $($tt)*) 126 | }; 127 | (@size; nameof($expr:expr) $($tt:tt)*) => { 128 | $expr.len + $crate::make_signature!(@size; $($tt)*) 129 | }; 130 | (@size; numof($expr:expr) $($tt:tt)*) => { 131 | { 132 | let mut out = 0; 133 | let mut v = $expr; 134 | if v == 0 { 135 | out = 1; 136 | } else { 137 | while v > 0 { 138 | out += 1; 139 | v /= 10; 140 | } 141 | } 142 | out 143 | } + $crate::make_signature!(@size; $($tt)*) 144 | }; 145 | (@size; shift_left($expr:expr) $($tt:tt)*) => { 146 | $crate::make_signature!(@size; $($tt)*) - $expr 147 | }; 148 | 149 | (@data($dst:ident, $dst_offset:ident);) => {}; 150 | (@data($dst:ident, $dst_offset:ident); fixed($expr:expr) $($tt:tt)*) => { 151 | { 152 | let data = $expr.as_bytes(); 153 | let data_len = data.len(); 154 | $crate::make_signature!(@copy(data, $dst, data_len, $dst_offset)); 155 | } 156 | $crate::make_signature!(@data($dst, $dst_offset); $($tt)*) 157 | }; 158 | (@data($dst:ident, $dst_offset:ident); nameof($expr:expr) $($tt:tt)*) => { 159 | { 160 | $crate::make_signature!(@copy(&$expr.data, $dst, $expr.len, $dst_offset)); 161 | } 162 | $crate::make_signature!(@data($dst, $dst_offset); $($tt)*) 163 | }; 164 | (@data($dst:ident, $dst_offset:ident); numof($expr:expr) $($tt:tt)*) => { 165 | { 166 | let mut v = $expr; 167 | let mut need_to_swap = 0; 168 | if v == 0 { 169 | $dst[$dst_offset] = b'0'; 170 | $dst_offset += 1; 171 | } else { 172 | while v > 0 { 173 | let n = (v % 10) as u8; 174 | $dst[$dst_offset] = b'0' + n; 175 | v /= 10; 176 | need_to_swap += 1; 177 | $dst_offset += 1; 178 | } 179 | } 180 | let mut i = 0; 181 | #[allow(clippy::manual_swap)] 182 | while i < need_to_swap / 2 { 183 | let a = $dst_offset - i - 1; 184 | let b = $dst_offset - need_to_swap + i; 185 | let v = $dst[a]; 186 | $dst[a] = $dst[b]; 187 | $dst[b] = v; 188 | i += 1; 189 | } 190 | } 191 | $crate::make_signature!(@data($dst, $dst_offset); $($tt)*) 192 | }; 193 | (@data($dst:ident, $dst_offset:ident); shift_left($expr:expr) $($tt:tt)*) => { 194 | $dst_offset -= $expr; 195 | $crate::make_signature!(@data($dst, $dst_offset); $($tt)*) 196 | }; 197 | 198 | (@copy($src:expr, $dst:expr, $src_len:expr, $dst_offset:ident)) => { 199 | { 200 | let mut src_offset = 0; 201 | let src_len: usize = $src_len; 202 | while src_offset < src_len { 203 | $dst[$dst_offset] = $src[src_offset]; 204 | $dst_offset += 1; 205 | src_offset += 1; 206 | } 207 | } 208 | } 209 | } 210 | 211 | #[cfg(test)] 212 | mod tests { 213 | use core::str::from_utf8; 214 | 215 | use super::{SignatureUnit, SIGNATURE_SIZE_LIMIT}; 216 | 217 | trait Name { 218 | const NAME: SignatureUnit; 219 | 220 | fn name() -> &'static str { 221 | from_utf8(&Self::NAME.data[..Self::NAME.len]).expect("bad utf-8") 222 | } 223 | } 224 | 225 | impl Name for u8 { 226 | const NAME: SignatureUnit = make_signature!(new fixed("uint8")); 227 | } 228 | impl Name for u32 { 229 | const NAME: SignatureUnit = make_signature!(new fixed("uint32")); 230 | } 231 | impl Name for Vec { 232 | const NAME: SignatureUnit = make_signature!(new nameof(T::NAME) fixed("[]")); 233 | } 234 | impl Name for (A, B) { 235 | const NAME: SignatureUnit = 236 | make_signature!(new fixed("(") nameof(A::NAME) fixed(",") nameof(B::NAME) fixed(")")); 237 | } 238 | impl Name for (A,) { 239 | const NAME: SignatureUnit = 240 | make_signature!(new fixed("(") nameof(A::NAME) fixed(",") shift_left(1) fixed(")")); 241 | } 242 | impl Name for [A; SIZE] { 243 | const NAME: SignatureUnit = 244 | make_signature!(new nameof(A::NAME) fixed("[") numof(SIZE) fixed("]")); 245 | } 246 | 247 | struct MaxSize(); 248 | impl Name for MaxSize { 249 | const NAME: SignatureUnit = SignatureUnit { 250 | data: [b'!'; SIGNATURE_SIZE_LIMIT], 251 | len: SIGNATURE_SIZE_LIMIT, 252 | }; 253 | } 254 | 255 | #[test] 256 | fn simple() { 257 | assert_eq!(u8::name(), "uint8"); 258 | assert_eq!(u32::name(), "uint32"); 259 | } 260 | 261 | #[test] 262 | fn vector_of_simple() { 263 | assert_eq!(>::name(), "uint8[]"); 264 | assert_eq!(>::name(), "uint32[]"); 265 | } 266 | 267 | #[test] 268 | fn vector_of_vector() { 269 | assert_eq!(>>::name(), "uint8[][]"); 270 | } 271 | 272 | #[test] 273 | fn tuple_of_simple() { 274 | assert_eq!(<(u32, u8)>::name(), "(uint32,uint8)"); 275 | } 276 | 277 | #[test] 278 | fn tuple_of_tuple() { 279 | assert_eq!( 280 | <((u32, u8), (u8, u32))>::name(), 281 | "((uint32,uint8),(uint8,uint32))" 282 | ); 283 | } 284 | 285 | #[test] 286 | fn vector_of_tuple() { 287 | assert_eq!(>::name(), "(uint32,uint8)[]"); 288 | } 289 | 290 | #[test] 291 | fn tuple_of_vector() { 292 | assert_eq!(<(Vec, u8)>::name(), "(uint32[],uint8)"); 293 | } 294 | 295 | #[test] 296 | fn complex() { 297 | assert_eq!( 298 | <(Vec, (u32, Vec))>::name(), 299 | "(uint32[],(uint32,uint8[]))" 300 | ); 301 | } 302 | 303 | #[test] 304 | fn max_size() { 305 | assert_eq!(::name(), "!".repeat(SIGNATURE_SIZE_LIMIT)); 306 | } 307 | 308 | #[test] 309 | fn shift() { 310 | assert_eq!(<(u32,)>::name(), "(uint32)"); 311 | } 312 | 313 | #[test] 314 | fn num() { 315 | assert_eq!(<[u8; 0]>::name(), "uint8[0]"); 316 | assert_eq!(<[u8; 1234]>::name(), "uint8[1234]"); 317 | assert_eq!(<[u8; 12345]>::name(), "uint8[12345]"); 318 | } 319 | 320 | #[test] 321 | #[ignore = "fails in ci due to rust version"] 322 | fn over_max_size() { 323 | let t = trybuild::TestCases::new(); 324 | t.compile_fail("tests/build_failed/custom_signature_over_max_size.rs"); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # evm-coder [![Build Status]][actions] [![Latest Version]][crates.io] 2 | 3 | [Build Status]: https://img.shields.io/github/actions/workflow/status/uniquenetwork/evm-coder/ci.yaml?branch=master 4 | [actions]: https://github.com/uniquenetwork/evm-coder/actions?query=branch%3Amaster 5 | [Latest Version]: https://img.shields.io/crates/v/evm-coder.svg 6 | [crates.io]: https://crates.io/crates/evm-coder 7 | 8 | ## Overview 9 | Library for seamless call translation between Rust and Solidity code. 10 | 11 | By encoding Solidity definitions in Rust, this library also provides generation of 12 | Solidity interfaces for Ethereum developers. 13 | 14 | ## Usage 15 | To create a contract in Substrate, make use of the `solidity_interface` attribute. This attribute should be applied to the implementation of the structure that represents your contract. It offers various parameters that enable features such as inheritance, interface validation during compilation, and other functionalities. 16 | 17 | There is also support for function overloading using the atribute `#[solidity(rename="funcName")]`. 18 | 19 | ## Installation 20 | Add the following line to your `Cargo.toml` project file. 21 | ```toml 22 | [dependencies] 23 | evm-coder = "0.3" 24 | ``` 25 | 26 | ## Example 27 | Consider this example where we're creating a contract that supports ERC721 along with an additional extension interface. 28 | 29 | To begin, we define the interface of our contract using the following Rust code: 30 | ```rust 31 | struct ContractHandle; 32 | 33 | #[solidity_interface( 34 | name = MyContract, 35 | is( 36 | ERC721, 37 | CustomContract, 38 | ) 39 | )] 40 | impl ContractHandle{} 41 | ``` 42 | 43 | The code above defines a contract named MyContract that implements two interfaces, namely, ERC721 and CustomContract. 44 | 45 | Moving forward, we proceed to actually implement the ERC721 interface: 46 | ```rust 47 | // This docs will be included into the generated `sol` file. 48 | /// @title ERC-721 Non-Fungible Token Standard 49 | /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md 50 | #[solidity_interface( 51 | name = ERC721, // Contract name 52 | events(ERC721Events), // Include events 53 | expect_selector = 0x80ac58cd // Expected selector of contract (will be matched at compile time) 54 | )] 55 | impl ContractHandle { 56 | 57 | // This docs will be included into the generated `sol` file. 58 | /// @notice Count all NFTs assigned to an owner 59 | /// @dev NFTs assigned to the zero address are considered invalid, and this 60 | /// function throws for queries about the zero address. 61 | /// @param owner An address for whom to query the balance 62 | /// @return The number of NFTs owned by `owner`, possibly zero 63 | fn balance_of(&self, owner: Address) -> Result { 64 | todo!() 65 | } 66 | 67 | fn owner_of(&self, token_id: U256) -> Result
{ 68 | todo!() 69 | } 70 | 71 | #[solidity(rename_selector = "safeTransferFrom")] 72 | fn safe_transfer_from_with_data(&mut self, from: Address, to: Address, token_id: U256, data: Bytes) -> Result<()> { 73 | todo!() 74 | } 75 | 76 | fn safe_transfer_from(&mut self, from: Address, to: Address, token_id: U256) -> Result<()> { 77 | todo!() 78 | } 79 | 80 | fn transfer_from(&mut self, caller: Caller, from: Address, to: Address, token_id: U256) -> Result<()> { 81 | todo!() 82 | } 83 | 84 | fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> { 85 | todo!() 86 | } 87 | 88 | fn set_approval_for_all(&mut self, caller: Caller, operator: Address, approved: bool) -> Result<()> { 89 | todo!() 90 | } 91 | 92 | fn get_approved(&self, token_id: U256) -> Result
{ 93 | todo!() 94 | } 95 | 96 | fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result { 97 | todo!() 98 | } 99 | } 100 | ``` 101 | 102 | In this implementation of the interface, we have included the events of `ERC721Events` that will trigger during the respective calls. To ensure seamless implementation of standard interfaces, the `expect_selector` directive in the `solidity_interface` annotation checks the contract selector at compile time, thereby preventing errors. 103 | 104 | Now, let's proceed with the creation of events for ERC721: 105 | ```rust 106 | #[derive(ToLog)] 107 | pub enum ERC721Events { 108 | // This docs will be included into the generated `sol` file. 109 | /// @dev This emits when ownership of any NFT changes by any mechanism. 110 | Transfer { 111 | #[indexed] // This field will be indexed 112 | from: Address, 113 | #[indexed] 114 | to: Address, 115 | #[indexed] 116 | token_id: U256, 117 | }, 118 | 119 | Approval { 120 | #[indexed] 121 | owner: Address, 122 | #[indexed] 123 | approved: Address, 124 | #[indexed] 125 | token_id: U256, 126 | }, 127 | 128 | ApprovalForAll { 129 | #[indexed] 130 | owner: Address, 131 | #[indexed] 132 | operator: Address, 133 | approved: bool, 134 | }, 135 | } 136 | ``` 137 | 138 | Let's create our extension: 139 | ```rust 140 | #[solidity_interface(name = CustomContract) 141 | impl ContractHandle { 142 | #[solidity(rename_selector = "doSome")] 143 | fn do_some_0(&mut self, caller: Caller, param: bool) -> Result<()> { 144 | todo!() 145 | } 146 | 147 | #[solidity(rename_selector = "doSome")] 148 | fn do_some_1(&mut self, caller: Caller, param: u8) -> Result<()> { 149 | todo!() 150 | } 151 | 152 | #[solidity(hide)] 153 | fn do_another(&mut self, caller: Caller, param: bool) -> Result<()> { 154 | todo!() 155 | } 156 | 157 | fn do_magic(&mut self, caller: Caller, param1: Enum, param2: Struct) -> Result> { 158 | todo!() 159 | } 160 | } 161 | ``` 162 | The methods `do_some_0` and `do_some_1` have been annotated with the macro `#[solidity(rename_selector = "doSome")]`. This allows them to be presented in the solidity interface as a **single** overloaded method named doSome. Meanwhile, the `do_another` method will be included in the `.sol` file but commented out. Lastly, the `do_magic` method utilizes custom types -- we can do that too! 163 | 164 | Let's make our types available in *solidity* (`Option` is available by default): 165 | ```rust 166 | #[derive(AbiCoder)] 167 | struct Struct { 168 | a: u8, 169 | b: String 170 | } 171 | 172 | #[derive(AbiCoder, Default, Clone, Copy)] 173 | #[repr(u8)] 174 | enum Enum { 175 | First, 176 | Second, 177 | #[default] 178 | Third, 179 | } 180 | ``` 181 | It's so easy to maintain your types with the `AbiCoder` derived macro. 182 | 183 | And at the end we will specify the generators of the `sol` files: 184 | ```rust 185 | generate_stubgen!(gen_impl, ContractHandleCall<()>, true); 186 | generate_stubgen!(gen_iface, ContractHandleCall<()>, false); 187 | ``` 188 | 189 | The *scripts* folder contains a set of scripts for generating the interface, `sol` stub, `json abi` and the compiled contract. To do this, create the following `make` file: 190 | ```make 191 | MyContract.sol: 192 | PACKAGE=package-name NAME=erc::gen_iface OUTPUT=/path/to/iface/$@ $(PATH_TO_SCRIPTS)/generate_sol.sh 193 | PACKAGE=package-name NAME=erc::gen_impl OUTPUT=/patch/to/stub/$@ $(PATH_TO_SCRIPTS)/generate_sol.sh 194 | 195 | MyContract: MyContract.sol 196 | INPUT=/patch/to/stub/$< OUTPUT=/patch/to/compiled/contract/MyContract.raw ./.maintain/scripts/compile_stub.sh 197 | INPUT=/patch/to/stub/$< OUTPUT=/patch/to/abi ./.maintain/scripts/generate_abi.sh 198 | ``` 199 | 200 | As a result, we get the following `sol` interface file: 201 | ```sol 202 | // SPDX-License-Identifier: OTHER 203 | // This code is automatically generated 204 | 205 | pragma solidity >=0.8.0 <0.9.0; 206 | 207 | /// @dev common stubs holder 208 | contract Dummy { 209 | } 210 | 211 | contract ERC165 is Dummy { 212 | function supportsInterface(bytes4 interfaceID) external view returns (bool); 213 | } 214 | 215 | struct Struct { 216 | a uint8; 217 | b string; 218 | } 219 | 220 | enum Enum { 221 | First, 222 | Second, 223 | Third 224 | } 225 | 226 | /// Optional value 227 | struct OptionUint256 { 228 | /// Shows the status of accessibility of value 229 | bool status; 230 | /// Actual value if `status` is true 231 | uint256 value; 232 | } 233 | 234 | /// @title A contract that allows you to work with collections. 235 | /// @dev the ERC-165 identifier for this interface is 0x738a0043 236 | contract CustomContract is Dummy, ERC165 { 237 | /// @dev EVM selector for this function is: 0x5465a527, 238 | /// or in textual repr: doSome(bool) 239 | function doSome(bool param) public; 240 | 241 | /// @dev EVM selector for this function is: 0x58a93f40, 242 | /// or in textual repr: doSome(uint8) 243 | function doSome(uint8 param) public; 244 | 245 | // /// @dev EVM selector for this function is: 0xf41a813e, 246 | // /// or in textual repr: doAnother(bool) 247 | // function doAnother(bool param) public; 248 | 249 | /// @dev EVM selector for this function is: 0x8b5c1b1a, 250 | /// or in textual repr: doMagic(uint8,(uint8,string)) 251 | function doSome(Enum param1, Struct param2) public returns (OptionUint256); 252 | } 253 | 254 | /// @dev inlined interface 255 | contract ERC721Events { 256 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 257 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 258 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 259 | } 260 | 261 | /// @title ERC-721 Non-Fungible Token Standard 262 | /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md 263 | /// @dev the ERC-165 identifier for this interface is 0x80ac58cd 264 | contract ERC721 is Dummy, ERC165, ERC721Events { 265 | /// @notice Count all NFTs assigned to an owner 266 | /// @dev NFTs assigned to the zero address are considered invalid, and this 267 | /// function throws for queries about the zero address. 268 | /// @param owner An address for whom to query the balance 269 | /// @return The number of NFTs owned by `owner`, possibly zero 270 | /// @dev EVM selector for this function is: 0x70a08231, 271 | /// or in textual repr: balanceOf(address) 272 | function balanceOf(address owner) public view returns (uint256); 273 | 274 | /// @dev EVM selector for this function is: 0x6352211e, 275 | /// or in textual repr: ownerOf(uint256) 276 | function ownerOf(uint256 tokenId) public view returns (address); 277 | 278 | /// @dev EVM selector for this function is: 0xb88d4fde, 279 | /// or in textual repr: safeTransferFrom(address,address,uint256,bytes) 280 | function safeTransferFrom( 281 | address from, 282 | address to, 283 | uint256 tokenId, 284 | bytes memory data 285 | ) public; 286 | 287 | /// @dev EVM selector for this function is: 0x42842e0e, 288 | /// or in textual repr: safeTransferFrom(address,address,uint256) 289 | function safeTransferFrom( 290 | address from, 291 | address to, 292 | uint256 tokenId 293 | ) public; 294 | 295 | /// @dev EVM selector for this function is: 0x23b872dd, 296 | /// or in textual repr: transferFrom(address,address,uint256) 297 | function transferFrom( 298 | address from, 299 | address to, 300 | uint256 tokenId 301 | ) public; 302 | 303 | /// @dev EVM selector for this function is: 0x095ea7b3, 304 | /// or in textual repr: approve(address,uint256) 305 | function approve(address approved, uint256 tokenId) public; 306 | 307 | /// @dev EVM selector for this function is: 0xa22cb465, 308 | /// or in textual repr: setApprovalForAll(address,bool) 309 | function setApprovalForAll(address operator, bool approved) public; 310 | 311 | /// @dev EVM selector for this function is: 0x081812fc, 312 | /// or in textual repr: getApproved(uint256) 313 | function getApproved(uint256 tokenId) public view returns (address); 314 | 315 | /// @dev EVM selector for this function is: 0xe985e9c5, 316 | /// or in textual repr: isApprovedForAll(address,address) 317 | function isApprovedForAll(address owner, address operator) public view returns (bool); 318 | } 319 | 320 | contract MyContract is 321 | Dummy, 322 | ERC165, 323 | ERC721, 324 | CustomContract 325 | {} 326 | 327 | ``` 328 | 329 | ## License 330 | Licensed under either of Apache License, Version 331 | 2.0 or MIT license at your option. 332 | 333 | Unless you explicitly state otherwise, any contribution intentionally submitted 334 | for inclusion in evm-coder by you, as defined in the Apache-2.0 license, shall be 335 | dual licensed as above, without any additional terms or conditions. 336 | -------------------------------------------------------------------------------- /src/abi/impls.rs: -------------------------------------------------------------------------------- 1 | use primitive_types::{H160, U256}; 2 | 3 | use super::{ 4 | AbiDecode, AbiDecodeZero, AbiDecoder, AbiEncode, AbiEncodeZero, AbiEncoder, ABI_WORD_SIZE, 5 | }; 6 | use crate::{ 7 | abi::{traits::AbiType, Error, Result}, 8 | custom_signature::SignatureUnit, 9 | make_signature, 10 | types::*, 11 | }; 12 | 13 | impl AbiType for Vec { 14 | const SIGNATURE: SignatureUnit = make_signature!(new nameof(T::SIGNATURE) fixed("[]")); 15 | const HEAD_WORDS: u32 = 1; 16 | const IS_DYNAMIC: bool = true; 17 | } 18 | impl AbiEncode for Vec { 19 | fn enc(&self, out: &mut AbiEncoder) { 20 | (self.len() as u32).enc(out); 21 | if T::IS_DYNAMIC { 22 | out.reserve_head(self.len() as u32); 23 | for v in self { 24 | (self.len() as u32 * ABI_WORD_SIZE + out.tail_size()).enc(out); 25 | out.encode_tail(v); 26 | } 27 | } else { 28 | for v in self { 29 | out.encode_tail(v); 30 | } 31 | } 32 | } 33 | } 34 | impl AbiDecode for Vec { 35 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 36 | let len = u32::dec(input)?; 37 | // Not using with_capacity, len may be too big 38 | let mut out = Vec::new(); 39 | let mut input = input.start_frame(); 40 | if T::IS_DYNAMIC { 41 | for _ in 0..len { 42 | let offset = u32::dec(&mut input)?; 43 | out.push(T::dec(&mut input.dynamic_at(offset)?)?) 44 | } 45 | } else { 46 | for _ in 0..len { 47 | out.push(T::dec(&mut input)?) 48 | } 49 | } 50 | Ok(out) 51 | } 52 | } 53 | 54 | impl AbiType for [T; S] { 55 | const SIGNATURE: SignatureUnit = 56 | make_signature!(new nameof(T::SIGNATURE) fixed("[") numof(S) fixed("]")); 57 | const HEAD_WORDS: u32 = if T::IS_DYNAMIC { 58 | S as u32 59 | } else { 60 | T::HEAD_WORDS * S as u32 61 | }; 62 | const IS_DYNAMIC: bool = T::IS_DYNAMIC; 63 | } 64 | impl AbiEncode for [T; S] { 65 | fn enc(&self, out: &mut AbiEncoder) { 66 | if T::IS_DYNAMIC { 67 | for v in self { 68 | (Self::HEAD_WORDS * self.len() as u32 + out.tail_size()).enc(out); 69 | out.encode_tail(v); 70 | } 71 | } else { 72 | for v in self { 73 | v.enc(out) 74 | } 75 | } 76 | } 77 | } 78 | impl AbiDecode for [T; S] { 79 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 80 | let mut out = Vec::with_capacity(S); 81 | if T::IS_DYNAMIC { 82 | for _ in 0..S { 83 | let offset = u32::dec(input)?; 84 | let mut data = input.dynamic_at(offset)?; 85 | out.push(T::dec(&mut data)?); 86 | } 87 | } else { 88 | for _ in 0..S { 89 | out.push(T::dec(input)?); 90 | } 91 | } 92 | out.try_into().map_err(|_| Error::InvalidRange) 93 | } 94 | } 95 | 96 | impl AbiType for &str { 97 | const SIGNATURE: SignatureUnit = make_signature!(new fixed("string")); 98 | const HEAD_WORDS: u32 = 1; 99 | const IS_DYNAMIC: bool = true; 100 | } 101 | impl AbiEncode for &str { 102 | fn enc(&self, out: &mut AbiEncoder) { 103 | (self.len() as u32).enc(out); 104 | for ele in self.as_bytes().chunks(32) { 105 | let mut word = [0; ABI_WORD_SIZE as usize]; 106 | word[0..ele.len()].copy_from_slice(ele); 107 | out.append_tail(word); 108 | } 109 | } 110 | } 111 | 112 | impl AbiType for String { 113 | const SIGNATURE: SignatureUnit = <&str>::SIGNATURE; 114 | const HEAD_WORDS: u32 = <&str>::HEAD_WORDS; 115 | const IS_DYNAMIC: bool = <&str>::IS_DYNAMIC; 116 | } 117 | impl AbiEncode for String { 118 | fn enc(&self, out: &mut AbiEncoder) { 119 | self.as_str().enc(out) 120 | } 121 | } 122 | impl AbiDecode for String { 123 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 124 | let bytes = Bytes::dec(input)?; 125 | String::from_utf8(bytes.0).map_err(|_| Error::InvalidRange) 126 | } 127 | } 128 | 129 | impl AbiType for Bytes { 130 | const SIGNATURE: SignatureUnit = make_signature!(new fixed("bytes")); 131 | const HEAD_WORDS: u32 = 1; 132 | const IS_DYNAMIC: bool = true; 133 | } 134 | impl AbiEncode for Bytes { 135 | fn enc(&self, out: &mut AbiEncoder) { 136 | (self.len() as u32).enc(out); 137 | for ele in self.0.chunks(32) { 138 | let mut word = [0; ABI_WORD_SIZE as usize]; 139 | word[0..ele.len()].copy_from_slice(ele); 140 | out.append_tail(word); 141 | } 142 | } 143 | } 144 | impl AbiDecode for Bytes { 145 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 146 | let len = u32::dec(input)?; 147 | // Not using with_capacity: len might be bad 148 | let mut out = Vec::new(); 149 | // Next multiple of 32 150 | let full_words = len / 32; 151 | for _ in 0..full_words { 152 | let word = input.get_head()?; 153 | out.extend_from_slice(&word); 154 | } 155 | let leftovers = len % 32; 156 | if leftovers != 0 { 157 | let word = input.get_head()?; 158 | out.extend_from_slice(&word[..leftovers as usize]); 159 | for i in leftovers..32 { 160 | if word[i as usize] != 0 { 161 | return Err(Error::InvalidRange); 162 | } 163 | } 164 | } 165 | Ok(Self(out)) 166 | } 167 | } 168 | 169 | impl AbiType for BytesFixed { 170 | const SIGNATURE: SignatureUnit = make_signature!(new fixed("bytes") numof(S)); 171 | // Next multiple of 32 172 | const HEAD_WORDS: u32 = (S as u32 + 31) & !31; 173 | const IS_DYNAMIC: bool = false; 174 | } 175 | impl AbiEncode for BytesFixed { 176 | fn enc(&self, out: &mut AbiEncoder) { 177 | for ele in self.0.chunks(32) { 178 | let mut word = [0; ABI_WORD_SIZE as usize]; 179 | word[0..ele.len()].copy_from_slice(ele); 180 | out.append_tail(word); 181 | } 182 | } 183 | } 184 | impl AbiDecode for BytesFixed { 185 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 186 | // Not using with_capacity: len might be bad 187 | let mut out = Vec::new(); 188 | // Next multiple of 32 189 | let full_words = S / 32; 190 | for _ in 0..full_words { 191 | let word = input.get_head()?; 192 | out.extend_from_slice(&word); 193 | } 194 | let leftovers = S % 32; 195 | if leftovers != 0 { 196 | let word = input.get_head()?; 197 | out.extend_from_slice(&word[..leftovers]); 198 | if word[leftovers..ABI_WORD_SIZE as usize] 199 | .iter() 200 | .any(|&v| v != 0) 201 | { 202 | return Err(Error::InvalidRange); 203 | } 204 | } 205 | out.try_into().map(Self).map_err(|_| Error::InvalidRange) 206 | } 207 | } 208 | 209 | impl AbiType for () { 210 | const SIGNATURE: SignatureUnit = make_signature!(new fixed("()")); 211 | const HEAD_WORDS: u32 = 0; 212 | const IS_DYNAMIC: bool = false; 213 | } 214 | impl AbiEncode for () { 215 | fn enc(&self, _out: &mut AbiEncoder) {} 216 | } 217 | impl AbiDecode for () { 218 | fn dec(_input: &mut AbiDecoder<'_>) -> Result { 219 | Ok(()) 220 | } 221 | } 222 | 223 | const fn tuple_comp_head_words() -> u32 { 224 | if T::IS_DYNAMIC { 225 | 1 226 | } else { 227 | T::HEAD_WORDS 228 | } 229 | } 230 | fn encode_tuple_comp(comp: &T, total_head: u32, out: &mut AbiEncoder) { 231 | if T::IS_DYNAMIC { 232 | let head = total_head * ABI_WORD_SIZE + out.tail_size(); 233 | head.enc(out); 234 | out.encode_tail(comp); 235 | } else { 236 | comp.enc(out); 237 | } 238 | } 239 | fn decode_tuple_comp(input: &mut AbiDecoder) -> Result { 240 | if T::IS_DYNAMIC { 241 | let head = u32::dec(input)?; 242 | let mut dynamic = input.dynamic_at(head)?; 243 | T::dec(&mut dynamic) 244 | } else { 245 | T::dec(input) 246 | } 247 | } 248 | macro_rules! impl_tuples { 249 | ($($gen:ident)+) => { 250 | impl<$($gen: AbiType,)*> AbiType for ($($gen,)*) 251 | where $( 252 | $gen: AbiType, 253 | )* 254 | { 255 | const SIGNATURE: SignatureUnit = make_signature!( 256 | new fixed("(") 257 | $(nameof(<$gen>::SIGNATURE) fixed(","))+ 258 | shift_left(1) 259 | fixed(")") 260 | ); 261 | const HEAD_WORDS: u32 = 0 $(+ tuple_comp_head_words::<$gen>())*; 262 | const IS_DYNAMIC: bool = false $(|| $gen::IS_DYNAMIC)*; 263 | } 264 | 265 | #[allow(non_snake_case)] 266 | impl<$($gen: AbiEncode,)*> AbiEncode for ($($gen,)*) { 267 | #[allow(unused_variables)] 268 | fn enc(&self, out: &mut AbiEncoder) { 269 | #[allow(non_snake_case)] 270 | let ($($gen,)*) = self; 271 | $(encode_tuple_comp($gen, Self::HEAD_WORDS, out);)* 272 | } 273 | } 274 | 275 | #[allow(non_snake_case)] 276 | impl<$($gen: AbiDecode,)*> AbiDecode for ($($gen,)*) { 277 | fn dec(input: &mut AbiDecoder) -> Result<($($gen,)*)> { 278 | Ok(( 279 | $({ 280 | #[allow(unused_variables)] 281 | let $gen = 0; 282 | decode_tuple_comp::<$gen>(input)? 283 | },)* 284 | )) 285 | } 286 | } 287 | }; 288 | ($($cur:ident)* @ $c:ident $($rest:ident)*) => { 289 | impl_tuples!($($cur)*); 290 | impl_tuples!($($cur)* $c @ $($rest)*); 291 | }; 292 | ($($cur:ident)* @) => { 293 | impl_tuples!($($cur)*); 294 | }; 295 | } 296 | impl_tuples!(A @ B C D E F G H I J K L M N O P); 297 | 298 | //----- impls for Option ----- 299 | impl AbiType for Option { 300 | const SIGNATURE: SignatureUnit = <(bool, T)>::SIGNATURE; 301 | const HEAD_WORDS: u32 = <(bool, T)>::HEAD_WORDS; 302 | const IS_DYNAMIC: bool = <(bool, T)>::IS_DYNAMIC; 303 | } 304 | impl AbiEncode for Option { 305 | fn enc(&self, out: &mut AbiEncoder) { 306 | match self { 307 | Some(v) => (true, v).enc(out), 308 | None => (false, >::new()).enc(out), 309 | } 310 | } 311 | } 312 | impl AbiDecode for Option { 313 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 314 | let has_value = bool::dec(input)?; 315 | if T::IS_DYNAMIC { 316 | let off = u32::dec(input)?; 317 | let mut input = input.dynamic_at(off)?; 318 | if has_value { 319 | Some(T::dec(&mut input)).transpose() 320 | } else { 321 | >::dec(&mut input)?; 322 | Ok(None) 323 | } 324 | } else if has_value { 325 | Some(T::dec(input)).transpose() 326 | } else { 327 | >::dec(input)?; 328 | Ok(None) 329 | } 330 | } 331 | } 332 | 333 | impl AbiType for Zero { 334 | const SIGNATURE: SignatureUnit = T::SIGNATURE; 335 | const HEAD_WORDS: u32 = T::HEAD_WORDS; 336 | const IS_DYNAMIC: bool = T::IS_DYNAMIC; 337 | } 338 | impl AbiEncode for Zero { 339 | fn enc(&self, out: &mut AbiEncoder) { 340 | T::enc_zero(out) 341 | } 342 | } 343 | impl AbiDecode for Zero { 344 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 345 | T::dec_zero(input)?; 346 | Ok(Self::new()) 347 | } 348 | } 349 | 350 | macro_rules! impl_num_abicode { 351 | ($pref:literal $($t:ty)*) => {$( 352 | impl AbiType for $t { 353 | const SIGNATURE: SignatureUnit = make_signature!(new fixed($pref) numof(<$t>::BITS)); 354 | const IS_DYNAMIC: bool = false; 355 | const HEAD_WORDS: u32 = 1; 356 | } 357 | impl AbiEncode for $t { 358 | // const HEAD_WORDS: u32 = 1; 359 | // const IS_DYNAMIC: bool = false; 360 | fn enc(&self, out: &mut AbiEncoder) { 361 | let bytes = self.to_be_bytes(); 362 | let mut word = [0; ABI_WORD_SIZE as usize]; 363 | word[ABI_WORD_SIZE as usize - bytes.len()..ABI_WORD_SIZE as usize].copy_from_slice(&bytes); 364 | out.append_head(word); 365 | } 366 | } 367 | impl AbiDecode for $t { 368 | fn dec(input: &mut AbiDecoder) -> Result<$t> { 369 | let head = input.get_head()?; 370 | let mut bytes = [0; <$t>::BITS as usize / 8]; 371 | let offset = 32-(<$t>::BITS as usize / 8); 372 | for i in 0..offset { 373 | if head[i] != 0 { 374 | return Err(Error::InvalidRange); 375 | } 376 | } 377 | bytes.copy_from_slice(&head[offset..32]); 378 | Ok(<$t>::from_be_bytes(bytes)) 379 | } 380 | } 381 | )*}; 382 | } 383 | impl_num_abicode!("uint" u8 u16 u32 u64 u128); 384 | impl_num_abicode!("int" i8 i16 i32 i64 i128); 385 | 386 | impl AbiType for bool { 387 | const SIGNATURE: SignatureUnit = make_signature!(new fixed("bool")); 388 | const IS_DYNAMIC: bool = false; 389 | const HEAD_WORDS: u32 = 1; 390 | } 391 | impl AbiEncode for bool { 392 | fn enc(&self, out: &mut AbiEncoder) { 393 | (*self as u32).enc(out) 394 | } 395 | } 396 | impl AbiDecode for bool { 397 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 398 | let v = u32::dec(input)?; 399 | Ok(match v { 400 | 0 => false, 401 | 1 => true, 402 | _ => return Err(Error::InvalidRange), 403 | }) 404 | } 405 | } 406 | impl AbiType for H160 { 407 | const SIGNATURE: SignatureUnit = make_signature!(new fixed("address")); 408 | const HEAD_WORDS: u32 = 1; 409 | const IS_DYNAMIC: bool = false; 410 | } 411 | impl AbiEncode for H160 { 412 | fn enc(&self, out: &mut AbiEncoder) { 413 | let mut word = [0; ABI_WORD_SIZE as usize]; 414 | word[12..].copy_from_slice(&self.0); 415 | out.append_head(word) 416 | } 417 | } 418 | impl AbiDecode for H160 { 419 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 420 | let data = input.get_head()?; 421 | let mut out = [0; 20]; 422 | out.copy_from_slice(&data[12..]); 423 | if data[0..12].iter().any(|&b| b != 0) { 424 | return Err(Error::InvalidRange); 425 | } 426 | Ok(H160(out)) 427 | } 428 | } 429 | 430 | impl AbiType for U256 { 431 | const SIGNATURE: SignatureUnit = make_signature!(new fixed("uint256")); 432 | const HEAD_WORDS: u32 = 1; 433 | const IS_DYNAMIC: bool = false; 434 | } 435 | impl AbiEncode for U256 { 436 | fn enc(&self, out: &mut AbiEncoder) { 437 | let mut word = [0; ABI_WORD_SIZE as usize]; 438 | self.to_big_endian(&mut word); 439 | out.append_head(word) 440 | } 441 | } 442 | impl AbiDecode for U256 { 443 | fn dec(input: &mut AbiDecoder<'_>) -> Result { 444 | let word = input.get_head()?; 445 | Ok(U256::from_big_endian(&word)) 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/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"))] 20 | extern crate alloc; 21 | 22 | extern crate self as evm_coder; 23 | 24 | pub use evm_coder_procedural::{event_topic, fn_selector}; 25 | pub mod abi; 26 | pub use events::{ToLog, ToTopic}; 27 | #[macro_use] 28 | pub mod custom_signature; 29 | 30 | /// Reexported for macro 31 | #[doc(hidden)] 32 | pub 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 { 78 | /// Ok(a.checked_mul(b).ok_or("overflow")?) 79 | /// } 80 | /// } 81 | /// ``` 82 | pub 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 | /// ``` 121 | pub use evm_coder_procedural::AbiCoder; 122 | #[cfg(feature = "bondrewd")] 123 | pub 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 131 | pub use evm_coder_procedural::ToLog; 132 | /// Reexported for macro 133 | #[doc(hidden)] 134 | pub use sha3_const; 135 | 136 | pub use self::abi::{AbiDecode, AbiDecoder, AbiEncode, AbiEncoder}; 137 | use 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)] 141 | pub mod events; 142 | #[doc(hidden)] 143 | #[cfg(feature = "stubgen")] 144 | pub 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 149 | pub 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); 172 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 173 | pub struct BytesFixed(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(PhantomData); 180 | impl Zero { 181 | pub fn new() -> Self { 182 | Self(PhantomData) 183 | } 184 | } 185 | impl Default for Zero { 186 | fn default() -> Self { 187 | Self::new() 188 | } 189 | } 190 | 191 | pub enum MaybeZero { 192 | Zero(Zero), 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 { 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> for Bytes { 216 | fn from(src: Vec) -> Self { 217 | Self(src) 218 | } 219 | } 220 | 221 | #[allow(clippy::from_over_into)] 222 | impl Into> for Bytes { 223 | fn into(self) -> Vec { 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 Default for BytesFixed { 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 Into> for BytesFixed { 248 | fn into(self) -> Vec { 249 | self.0.into() 250 | } 251 | } 252 | } 253 | 254 | /// Parseable EVM call, this trait should be implemented with [`solidity_interface`] macro 255 | pub 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>; 262 | fn parse_full(input: &[u8]) -> abi::Result> { 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 275 | pub trait Callable: Contract { 276 | /// Call contract using specified call data 277 | fn call(&mut self, call: types::Msg) -> ResultWithPostInfoOf>; 278 | } 279 | 280 | /// Contract specific result type 281 | pub type ResultOf = ::Result::Error>; 282 | /// Contract specific result type 283 | pub type ResultWithPostInfoOf = ::Result< 284 | ::WithPostInfo, 285 | ::WithPostInfo<::Error>, 286 | >; 287 | 288 | /// Contract configuration 289 | pub trait Contract { 290 | /// Contract error type 291 | type Error: From<&'static str>; 292 | /// Wrapper for Result Ok/Err value 293 | type WithPostInfo; 294 | /// Return value of [`Callable`], expected to be of [`core::result::Result`] type 295 | type Result; 296 | 297 | /// Map `WithPostInfo` value 298 | fn map_post( 299 | v: Self::WithPostInfo, 300 | mapper: impl FnOnce(I) -> O, 301 | ) -> Self::WithPostInfo; 302 | /// Wrap value with default post info 303 | fn with_default_post(v: T) -> Self::WithPostInfo; 304 | } 305 | 306 | /// Example of `PostInfo`, used in tests 307 | pub struct DummyPost(pub T); 308 | /// Implement dummy Contract trait, used for tests 309 | /// Allows contract methods to return either T, or Result for any T 310 | #[macro_export] 311 | macro_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(core::cell::Cell>); 322 | type O = ::core::result::Result, DummyPost>; 323 | trait Matcher { 324 | fn convert(&self) -> O; 325 | } 326 | impl Matcher for &Wrapper<::core::result::Result> { 327 | fn convert(&self) -> O { 328 | let i = self.0.take().unwrap(); 329 | i.map(DummyPost).map_err(DummyPost) 330 | } 331 | } 332 | impl Matcher for Wrapper { 333 | fn convert(&self) -> O { 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 = $crate::DummyPost; 344 | type Result = core::result::Result; 345 | fn map_post(v: Self::WithPostInfo, mapper: impl FnOnce(II) -> OO) -> Self::WithPostInfo { 346 | $crate::DummyPost(mapper(v.0)) 347 | } 348 | /// Wrap value with default post info 349 | fn with_default_post(v: TT) -> Self::WithPostInfo { 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 363 | #[derive(Debug, PartialEq)] 364 | pub 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 | 373 | impl ERC165Call { 374 | /// ERC165 selector is provided by standard 375 | pub const INTERFACE_ID: Bytes4 = BytesFixed(u32::to_be_bytes(0x01ff_c9a7)); 376 | } 377 | 378 | impl Call for ERC165Call { 379 | fn parse(selector: Bytes4, input: &[u8]) -> abi::Result> { 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] 395 | macro_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)] 419 | mod 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 | } 438 | -------------------------------------------------------------------------------- /src/solidity/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation detail of [`crate::solidity_interface`] macro code-generation. 2 | //! You should not rely on any public item from this module, as it is only intended to be used 3 | //! by procedural macro, API and output format may be changed at any time. 4 | //! 5 | //! Purpose of this module is to receive solidity contract definition in module-specified 6 | //! format, and then output string, representing interface of this contract in solidity language 7 | 8 | mod traits; 9 | pub use traits::*; 10 | mod impls; 11 | 12 | #[cfg(not(feature = "std"))] 13 | use alloc::{collections::BTreeMap, format, vec::Vec}; 14 | use core::{ 15 | cell::{Cell, RefCell}, 16 | cmp::Reverse, 17 | fmt::{self, Write}, 18 | marker::PhantomData, 19 | }; 20 | #[cfg(feature = "std")] 21 | use std::collections::BTreeMap; 22 | 23 | use impl_trait_for_tuples::impl_for_tuples; 24 | 25 | use crate::{custom_signature::SignatureUnit, types::*}; 26 | 27 | #[derive(Default)] 28 | pub struct TypeCollector { 29 | /// Code => id 30 | /// id ordering is required to perform topo-sort on the resulting data 31 | structs: RefCell>, 32 | anonymous: RefCell, usize>>, 33 | // generic: RefCell>, 34 | id: Cell, 35 | } 36 | impl TypeCollector { 37 | pub fn new() -> Self { 38 | Self::default() 39 | } 40 | pub fn collect(&self, item: String) { 41 | let id = self.next_id(); 42 | self.structs.borrow_mut().insert(item, id); 43 | } 44 | pub fn next_id(&self) -> usize { 45 | let v = self.id.get(); 46 | self.id.set(v + 1); 47 | v 48 | } 49 | /// Collect typle, deduplicating it by type, and returning generated name 50 | pub fn collect_tuple(&self) -> String { 51 | let names = T::fields(self); 52 | if let Some(id) = self.anonymous.borrow().get(&names).cloned() { 53 | return format!("Tuple{id}"); 54 | } 55 | let id = self.next_id(); 56 | let mut str = String::new(); 57 | writeln!(str, "/// @dev anonymous struct").unwrap(); 58 | writeln!(str, "struct Tuple{id} {{").unwrap(); 59 | for (i, name) in names.iter().enumerate() { 60 | writeln!(str, "\t{name} field_{i};").unwrap(); 61 | } 62 | writeln!(str, "}}").unwrap(); 63 | self.collect(str); 64 | self.anonymous.borrow_mut().insert(names, id); 65 | format!("Tuple{id}") 66 | } 67 | pub fn collect_struct(&self) -> String { 68 | T::generate_solidity_interface(self) 69 | } 70 | pub fn collect_enum(&self) -> String { 71 | T::generate_solidity_interface(self) 72 | } 73 | pub fn finish(self) -> Vec { 74 | let mut data = self.structs.into_inner().into_iter().collect::>(); 75 | data.sort_by_key(|(_, id)| Reverse(*id)); 76 | data.into_iter().map(|(code, _)| code).collect() 77 | } 78 | } 79 | pub struct UnnamedArgument(PhantomData<*const T>); 80 | impl Default for UnnamedArgument { 81 | fn default() -> Self { 82 | Self(PhantomData) 83 | } 84 | } 85 | impl SolidityArguments for UnnamedArgument { 86 | fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 87 | if !T::is_void() { 88 | T::solidity_name(writer, tc)?; 89 | if !T::is_simple() { 90 | write!(writer, " memory")?; 91 | } 92 | Ok(()) 93 | } else { 94 | Ok(()) 95 | } 96 | } 97 | fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result { 98 | Ok(()) 99 | } 100 | fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 101 | T::solidity_default(writer, tc) 102 | } 103 | fn len(&self) -> usize { 104 | if T::is_void() { 105 | 0 106 | } else { 107 | 1 108 | } 109 | } 110 | } 111 | 112 | pub struct NamedArgument(&'static str, PhantomData<*const T>); 113 | 114 | impl NamedArgument { 115 | pub fn new(name: &'static str) -> Self { 116 | Self(name, Default::default()) 117 | } 118 | } 119 | 120 | impl SolidityArguments for NamedArgument { 121 | fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 122 | if !T::is_void() { 123 | T::solidity_name(writer, tc)?; 124 | if !T::is_simple() { 125 | write!(writer, " memory")?; 126 | } 127 | write!(writer, " {}", self.0) 128 | } else { 129 | Ok(()) 130 | } 131 | } 132 | fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result { 133 | writeln!(writer, "\t{prefix}\t{};", self.0) 134 | } 135 | fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 136 | T::solidity_default(writer, tc) 137 | } 138 | fn len(&self) -> usize { 139 | if T::is_void() { 140 | 0 141 | } else { 142 | 1 143 | } 144 | } 145 | } 146 | 147 | pub struct SolidityEventArgument(pub bool, &'static str, PhantomData<*const T>); 148 | 149 | impl SolidityEventArgument { 150 | pub fn new(indexed: bool, name: &'static str) -> Self { 151 | Self(indexed, name, Default::default()) 152 | } 153 | } 154 | 155 | impl SolidityArguments for SolidityEventArgument { 156 | fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 157 | if !T::is_void() { 158 | T::solidity_name(writer, tc)?; 159 | if self.0 { 160 | write!(writer, " indexed")?; 161 | } 162 | write!(writer, " {}", self.1) 163 | } else { 164 | Ok(()) 165 | } 166 | } 167 | fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result { 168 | writeln!(writer, "\t{prefix}\t{};", self.1) 169 | } 170 | fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 171 | T::solidity_default(writer, tc) 172 | } 173 | fn len(&self) -> usize { 174 | if T::is_void() { 175 | 0 176 | } else { 177 | 1 178 | } 179 | } 180 | } 181 | 182 | impl SolidityArguments for () { 183 | fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { 184 | Ok(()) 185 | } 186 | fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result { 187 | Ok(()) 188 | } 189 | fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { 190 | Ok(()) 191 | } 192 | fn len(&self) -> usize { 193 | 0 194 | } 195 | } 196 | 197 | #[impl_for_tuples(1, 12)] 198 | impl SolidityArguments for Tuple { 199 | for_tuples!( where #( Tuple: SolidityArguments ),* ); 200 | 201 | fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 202 | let mut first = true; 203 | for_tuples!( #( 204 | if !Tuple.is_empty() { 205 | if !first { 206 | write!(writer, ", ")?; 207 | } 208 | first = false; 209 | Tuple.solidity_name(writer, tc)?; 210 | } 211 | )* ); 212 | Ok(()) 213 | } 214 | fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result { 215 | for_tuples!( #( 216 | Tuple.solidity_get(prefix, writer)?; 217 | )* ); 218 | Ok(()) 219 | } 220 | fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 221 | if self.is_empty() { 222 | Ok(()) 223 | } else if self.len() == 1 { 224 | for_tuples!( #( 225 | Tuple.solidity_default(writer, tc)?; 226 | )* ); 227 | Ok(()) 228 | } else { 229 | write!(writer, "(")?; 230 | let mut first = true; 231 | for_tuples!( #( 232 | if !Tuple.is_empty() { 233 | if !first { 234 | write!(writer, ", ")?; 235 | } 236 | first = false; 237 | Tuple.solidity_default(writer, tc)?; 238 | } 239 | )* ); 240 | write!(writer, ")")?; 241 | Ok(()) 242 | } 243 | } 244 | fn len(&self) -> usize { 245 | for_tuples!( #( Tuple.len() )+* ) 246 | } 247 | } 248 | 249 | pub enum SolidityMutability { 250 | Pure, 251 | View, 252 | Mutable, 253 | } 254 | pub struct SolidityFunction { 255 | pub docs: &'static [&'static str], 256 | pub selector: u32, 257 | pub hide: bool, 258 | pub custom_signature: SignatureUnit, 259 | pub name: &'static str, 260 | pub args: A, 261 | pub result: R, 262 | pub mutability: SolidityMutability, 263 | pub is_payable: bool, 264 | } 265 | impl SolidityFunctions for SolidityFunction { 266 | fn solidity_name( 267 | &self, 268 | is_impl: bool, 269 | writer: &mut impl fmt::Write, 270 | tc: &TypeCollector, 271 | ) -> fmt::Result { 272 | let hide_comment = if self.hide { "// " } else { "" }; 273 | for doc in self.docs { 274 | writeln!(writer, "\t{hide_comment}///{doc}")?; 275 | } 276 | writeln!( 277 | writer, 278 | "\t{hide_comment}/// @dev EVM selector for this function is: 0x{:0>8x},", 279 | self.selector 280 | )?; 281 | writeln!( 282 | writer, 283 | "\t{hide_comment}/// or in textual repr: {}", 284 | self.custom_signature.as_str().expect("bad utf-8") 285 | )?; 286 | write!(writer, "\t{hide_comment}function {}(", self.name)?; 287 | self.args.solidity_name(writer, tc)?; 288 | write!(writer, ")")?; 289 | if is_impl { 290 | write!(writer, " public")?; 291 | } else { 292 | write!(writer, " external")?; 293 | } 294 | match &self.mutability { 295 | SolidityMutability::Pure => write!(writer, " pure")?, 296 | SolidityMutability::View => write!(writer, " view")?, 297 | SolidityMutability::Mutable => {} 298 | } 299 | if self.is_payable { 300 | write!(writer, " payable")?; 301 | } 302 | if !self.result.is_empty() { 303 | write!(writer, " returns (")?; 304 | self.result.solidity_name(writer, tc)?; 305 | write!(writer, ")")?; 306 | } 307 | if is_impl { 308 | writeln!(writer, " {{")?; 309 | writeln!(writer, "\t{hide_comment}\trequire(false, stub_error);")?; 310 | self.args.solidity_get(hide_comment, writer)?; 311 | match &self.mutability { 312 | SolidityMutability::Pure => {} 313 | SolidityMutability::View => writeln!(writer, "\t{hide_comment}\tdummy;")?, 314 | SolidityMutability::Mutable => writeln!(writer, "\t{hide_comment}\tdummy = 0;")?, 315 | } 316 | if !self.result.is_empty() { 317 | write!(writer, "\t{hide_comment}\treturn ")?; 318 | self.result.solidity_default(writer, tc)?; 319 | writeln!(writer, ";")?; 320 | } 321 | writeln!(writer, "\t{hide_comment}}}")?; 322 | } else { 323 | writeln!(writer, ";")?; 324 | } 325 | if self.hide { 326 | writeln!(writer, "// FORMATTING: FORCE NEWLINE")?; 327 | } 328 | Ok(()) 329 | } 330 | } 331 | 332 | #[impl_for_tuples(0, 48)] 333 | impl SolidityFunctions for Tuple { 334 | for_tuples!( where #( Tuple: SolidityFunctions ),* ); 335 | 336 | fn solidity_name( 337 | &self, 338 | is_impl: bool, 339 | writer: &mut impl fmt::Write, 340 | tc: &TypeCollector, 341 | ) -> fmt::Result { 342 | let mut first = false; 343 | for_tuples!( #( 344 | Tuple.solidity_name(is_impl, writer, tc)?; 345 | )* ); 346 | Ok(()) 347 | } 348 | } 349 | 350 | pub struct SolidityInterface { 351 | pub docs: &'static [&'static str], 352 | pub selector: Bytes4, 353 | pub name: &'static str, 354 | pub is: &'static [&'static str], 355 | pub functions: F, 356 | } 357 | 358 | impl SolidityInterface { 359 | pub fn format( 360 | &self, 361 | is_impl: bool, 362 | out: &mut impl fmt::Write, 363 | tc: &TypeCollector, 364 | ) -> fmt::Result { 365 | const ZERO_BYTES: [u8; 4] = [0; 4]; 366 | for doc in self.docs { 367 | writeln!(out, "///{doc}")?; 368 | } 369 | if self.selector.0 != ZERO_BYTES { 370 | writeln!( 371 | out, 372 | "/// @dev the ERC-165 identifier for this interface is 0x{:0>8x}", 373 | u32::from_be_bytes(self.selector.0) 374 | )?; 375 | } 376 | if is_impl { 377 | write!(out, "contract ")?; 378 | } else { 379 | write!(out, "interface ")?; 380 | } 381 | write!(out, "{}", self.name)?; 382 | if !self.is.is_empty() { 383 | write!(out, " is")?; 384 | for (i, n) in self.is.iter().enumerate() { 385 | if i != 0 { 386 | write!(out, ",")?; 387 | } 388 | write!(out, " {n}")?; 389 | } 390 | } 391 | writeln!(out, " {{")?; 392 | self.functions.solidity_name(is_impl, out, tc)?; 393 | writeln!(out, "}}")?; 394 | Ok(()) 395 | } 396 | } 397 | 398 | pub struct SolidityEvent { 399 | pub name: &'static str, 400 | pub args: A, 401 | } 402 | 403 | impl SolidityFunctions for SolidityEvent { 404 | fn solidity_name( 405 | &self, 406 | _is_impl: bool, 407 | writer: &mut impl fmt::Write, 408 | tc: &TypeCollector, 409 | ) -> fmt::Result { 410 | write!(writer, "\tevent {}(", self.name)?; 411 | self.args.solidity_name(writer, tc)?; 412 | writeln!(writer, ");") 413 | } 414 | } 415 | 416 | #[impl_for_tuples(0, 48)] 417 | impl SolidityItems for Tuple { 418 | for_tuples!( where #( Tuple: SolidityItems ),* ); 419 | 420 | fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 421 | for_tuples!( #( 422 | Tuple.solidity_name(writer, tc)?; 423 | )* ); 424 | Ok(()) 425 | } 426 | } 427 | 428 | pub struct SolidityStructField { 429 | pub docs: &'static [&'static str], 430 | pub name: &'static str, 431 | pub ty: PhantomData<*const T>, 432 | } 433 | 434 | impl SolidityItems for SolidityStructField 435 | where 436 | T: SolidityTypeName, 437 | { 438 | fn solidity_name(&self, out: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 439 | for doc in self.docs { 440 | writeln!(out, "///{doc}")?; 441 | } 442 | write!(out, "\t")?; 443 | T::solidity_name(out, tc)?; 444 | let field_name = self.name; 445 | writeln!(out, " {field_name};",)?; 446 | Ok(()) 447 | } 448 | } 449 | pub struct SolidityStruct<'a, F> { 450 | pub docs: &'a [&'a str], 451 | // pub generics: 452 | pub name: &'a str, 453 | pub fields: F, 454 | } 455 | impl SolidityStruct<'_, F> 456 | where 457 | F: SolidityItems, 458 | { 459 | pub fn format(&self, out: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 460 | for doc in self.docs { 461 | writeln!(out, "///{doc}")?; 462 | } 463 | writeln!(out, "struct {} {{", self.name)?; 464 | self.fields.solidity_name(out, tc)?; 465 | writeln!(out, "}}")?; 466 | Ok(()) 467 | } 468 | } 469 | 470 | pub struct SolidityEnumVariant { 471 | pub docs: &'static [&'static str], 472 | pub name: &'static str, 473 | } 474 | impl SolidityItems for SolidityEnumVariant { 475 | fn solidity_name(&self, out: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { 476 | for doc in self.docs { 477 | writeln!(out, "///{doc}")?; 478 | } 479 | write!(out, "\t{}", self.name)?; 480 | Ok(()) 481 | } 482 | } 483 | pub struct SolidityEnum { 484 | pub docs: &'static [&'static str], 485 | pub name: &'static str, 486 | pub fields: &'static [SolidityEnumVariant], 487 | } 488 | impl SolidityEnum { 489 | pub fn format(&self, out: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { 490 | for doc in self.docs { 491 | writeln!(out, "///{doc}")?; 492 | } 493 | let name = self.name; 494 | write!(out, "enum {name} {{")?; 495 | for (i, field) in self.fields.iter().enumerate() { 496 | if i != 0 { 497 | write!(out, ",")?; 498 | } 499 | writeln!(out)?; 500 | field.solidity_name(out, tc)?; 501 | } 502 | writeln!(out)?; 503 | writeln!(out, "}}")?; 504 | Ok(()) 505 | } 506 | } 507 | 508 | pub enum SolidityFlagsField { 509 | Bool(SolidityFlagsBool), 510 | Number(SolidityFlagsNumber), 511 | } 512 | 513 | impl SolidityFlagsField { 514 | pub fn docs(&self) -> &'static [&'static str] { 515 | match self { 516 | Self::Bool(field) => field.docs, 517 | Self::Number(field) => field.docs, 518 | } 519 | } 520 | } 521 | 522 | pub struct SolidityFlagsBool { 523 | pub docs: &'static [&'static str], 524 | pub name: &'static str, 525 | pub shift: usize, 526 | } 527 | 528 | pub struct SolidityFlagsNumber { 529 | pub docs: &'static [&'static str], 530 | pub name: &'static str, 531 | pub start_bit: usize, 532 | pub amount_of_bits: usize, 533 | } 534 | 535 | pub struct SolidityLibrary { 536 | pub docs: &'static [&'static str], 537 | pub name: &'static str, 538 | pub total_bytes: usize, 539 | pub fields: Vec, 540 | } 541 | 542 | impl SolidityLibrary { 543 | pub fn format(&self, out: &mut impl fmt::Write) -> fmt::Result { 544 | for doc in self.docs { 545 | writeln!(out, "///{doc}")?; 546 | } 547 | let total_bytes = self.total_bytes; 548 | let abi_type = match total_bytes { 549 | 1 => "uint8", 550 | 2..=4 => "uint32", 551 | 5..=8 => "uint64", 552 | _ => return Err(fmt::Error), 553 | }; 554 | let lib_name = self.name; 555 | writeln!(out, "type {lib_name} is {abi_type};")?; 556 | write!(out, "library {lib_name}Lib {{")?; 557 | for field in self.fields.iter() { 558 | writeln!(out)?; 559 | for doc in field.docs() { 560 | writeln!(out, "///{doc}")?; 561 | } 562 | match field { 563 | SolidityFlagsField::Bool(field) => { 564 | let field_name = field.name; 565 | let field_value = 1u32 << field.shift; 566 | write!( 567 | out, 568 | "\t{lib_name} constant {field_name}Field = {lib_name}.wrap({field_value});" 569 | )?; 570 | } 571 | SolidityFlagsField::Number(field) => { 572 | let field_name = field.name; 573 | let amount_of_bits = field.amount_of_bits; 574 | let start_bit = field.start_bit; 575 | write!( 576 | out, 577 | "\tfunction {field_name}Field({abi_type} value) public pure returns ({lib_name}) {{\n\t\trequire(value < 1 << {amount_of_bits}, \"out of bound value\");\n\t\treturn {lib_name}.wrap(value << {start_bit});\n\t}}" 578 | )?; 579 | } 580 | } 581 | } 582 | writeln!(out)?; 583 | writeln!(out, "}}")?; 584 | Ok(()) 585 | } 586 | } 587 | -------------------------------------------------------------------------------- /src/abi/test.rs: -------------------------------------------------------------------------------- 1 | use hex_literal::hex; 2 | use primitive_types::{H160, U256}; 3 | 4 | use super::{AbiEncode, AbiType}; 5 | use crate::{types::*, AbiDecode}; 6 | 7 | pub fn to_lines(is_call: bool, data: impl AsRef<[u8]>) -> Vec { 8 | let mut data = data.as_ref(); 9 | let mut offset = 0; 10 | let mut out = Vec::new(); 11 | if is_call { 12 | offset += 4; 13 | out.push(hex::encode(&data[..4])); 14 | data = &data[4..]; 15 | } 16 | assert!(data.len() % 32 == 0); 17 | for _ in (offset..data.len()).step_by(32) { 18 | let chunk = &data[..32]; 19 | out.push(hex::encode(chunk)); 20 | data = &data[32..]; 21 | } 22 | 23 | out 24 | } 25 | 26 | fn test_impl(function_identifier: u32, decoded_data: T, encoded_data: &[u8]) 27 | where 28 | T: AbiEncode + AbiDecode + std::cmp::PartialEq + std::fmt::Debug, 29 | { 30 | let reencoded = decoded_data.abi_encode_call(BytesFixed(function_identifier.to_be_bytes())); 31 | similar_asserts::assert_eq!(to_lines(true, encoded_data), to_lines(true, reencoded)); 32 | let (call, data) = T::abi_decode_call(encoded_data).unwrap(); 33 | assert_eq!(call.0, u32::to_be_bytes(function_identifier)); 34 | assert_eq!(data, decoded_data); 35 | } 36 | 37 | macro_rules! test_impl_uint { 38 | ($type:ident) => { 39 | test_impl::<$type>( 40 | 0xdeadbeef, 41 | 255 as $type, 42 | &hex!( 43 | " 44 | deadbeef 45 | 00000000000000000000000000000000000000000000000000000000000000ff 46 | " 47 | ), 48 | ); 49 | }; 50 | } 51 | 52 | #[test] 53 | fn encode_decode_uint8() { 54 | test_impl_uint!(u8); 55 | } 56 | 57 | #[test] 58 | fn encode_decode_uint32() { 59 | test_impl_uint!(u32); 60 | } 61 | 62 | #[test] 63 | fn encode_decode_uint128() { 64 | test_impl_uint!(u128); 65 | } 66 | 67 | #[test] 68 | fn encode_decode_uint256() { 69 | test_impl::( 70 | 0xdeadbeef, 71 | U256([255, 0, 0, 0]), 72 | &hex!( 73 | " 74 | deadbeef 75 | 00000000000000000000000000000000000000000000000000000000000000ff 76 | " 77 | ), 78 | ); 79 | } 80 | 81 | #[test] 82 | fn encode_decode_string() { 83 | test_impl::<(String,)>( 84 | 0xdeadbeef, 85 | ("some string".to_string(),), 86 | &hex!( 87 | " 88 | deadbeef 89 | 0000000000000000000000000000000000000000000000000000000000000020 90 | 000000000000000000000000000000000000000000000000000000000000000b 91 | 736f6d6520737472696e67000000000000000000000000000000000000000000 92 | " 93 | ), 94 | ); 95 | } 96 | 97 | #[test] 98 | fn encode_decode_tuple_string() { 99 | test_impl::<(String,)>( 100 | 0xdeadbeef, 101 | ("some string".to_string(),), 102 | &hex!( 103 | " 104 | deadbeef 105 | 0000000000000000000000000000000000000000000000000000000000000020 106 | 000000000000000000000000000000000000000000000000000000000000000b 107 | 736f6d6520737472696e67000000000000000000000000000000000000000000 108 | " 109 | ), 110 | ); 111 | } 112 | 113 | #[test] 114 | fn encode_decode_vec_tuple_address_uint256() { 115 | test_impl::<(Vec<(Address, U256)>,)>( 116 | 0x1ACF2D55, 117 | (vec![ 118 | ( 119 | H160(hex!("2D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC")), 120 | U256([10, 0, 0, 0]), 121 | ), 122 | ( 123 | H160(hex!("AB8E3D9134955566483B11E6825C9223B6737B10")), 124 | U256([20, 0, 0, 0]), 125 | ), 126 | ( 127 | H160(hex!("8C582BDF2953046705FC56F189385255EFC1BE18")), 128 | U256([30, 0, 0, 0]), 129 | ), 130 | ],), 131 | &hex!( 132 | " 133 | 1ACF2D55 134 | 0000000000000000000000000000000000000000000000000000000000000020 // offset of (address, uint256)[] 135 | 0000000000000000000000000000000000000000000000000000000000000003 // length of (address, uint256)[] 136 | 137 | 0000000000000000000000002D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC // address 138 | 000000000000000000000000000000000000000000000000000000000000000A // uint256 139 | 140 | 000000000000000000000000AB8E3D9134955566483B11E6825C9223B6737B10 // address 141 | 0000000000000000000000000000000000000000000000000000000000000014 // uint256 142 | 143 | 0000000000000000000000008C582BDF2953046705FC56F189385255EFC1BE18 // address 144 | 000000000000000000000000000000000000000000000000000000000000001E // uint256 145 | " 146 | ), 147 | ); 148 | } 149 | 150 | #[derive(Debug, PartialEq)] 151 | struct TokenId(u32); 152 | impl AbiType for TokenId { 153 | const SIGNATURE: crate::custom_signature::SignatureUnit = u32::SIGNATURE; 154 | const IS_DYNAMIC: bool = u32::IS_DYNAMIC; 155 | const HEAD_WORDS: u32 = u32::HEAD_WORDS; 156 | } 157 | impl AbiDecode for TokenId { 158 | fn dec(input: &mut crate::AbiDecoder<'_>) -> super::Result { 159 | Ok(Self(u32::dec(input)?)) 160 | } 161 | } 162 | impl AbiEncode for TokenId { 163 | fn enc(&self, out: &mut crate::AbiEncoder) { 164 | self.0.enc(out) 165 | } 166 | } 167 | impl From for TokenId { 168 | fn from(value: u32) -> Self { 169 | Self(value) 170 | } 171 | } 172 | #[test] 173 | fn encode_decode_vec_tuple_uint256_string() { 174 | test_impl::<(Vec<(TokenId, String)>,)>( 175 | 0xdeadbeef, 176 | (vec![ 177 | (14u32.into(), "Test URI 0".to_string()), 178 | (11u32.into(), "Test URI 1".to_string()), 179 | (12u32.into(), "Test URI 2".to_string()), 180 | ],), 181 | &hex!( 182 | " 183 | deadbeef 184 | 0000000000000000000000000000000000000000000000000000000000000020 // offset of (uint256, string)[] 185 | 186 | 0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[] 187 | 0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem 188 | 00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem 189 | 0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem 190 | 191 | 000000000000000000000000000000000000000000000000000000000000000e // first token #60 192 | 0000000000000000000000000000000000000000000000000000000000000040 // offset of string 193 | 000000000000000000000000000000000000000000000000000000000000000a // size of string 194 | 5465737420555249203000000000000000000000000000000000000000000000 // string 195 | 196 | 000000000000000000000000000000000000000000000000000000000000000b // second token #e0 197 | 0000000000000000000000000000000000000000000000000000000000000040 // offset of string 198 | 000000000000000000000000000000000000000000000000000000000000000a // size of string 199 | 5465737420555249203100000000000000000000000000000000000000000000 // string 200 | 201 | 000000000000000000000000000000000000000000000000000000000000000c // third token #160 202 | 0000000000000000000000000000000000000000000000000000000000000040 // offset of string 203 | 000000000000000000000000000000000000000000000000000000000000000a // size of string 204 | 5465737420555249203200000000000000000000000000000000000000000000 // string 205 | " 206 | ), 207 | ); 208 | } 209 | 210 | #[test] 211 | fn mint_sample() { 212 | let (call, decoder) = <(Address, u32, String)>::abi_decode_call(&hex!( 213 | " 214 | 50bb4e7f 215 | 000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374 216 | 0000000000000000000000000000000000000000000000000000000000000001 217 | 0000000000000000000000000000000000000000000000000000000000000060 218 | 0000000000000000000000000000000000000000000000000000000000000008 219 | 5465737420555249000000000000000000000000000000000000000000000000 220 | " 221 | )) 222 | .unwrap(); 223 | assert_eq!(call, BytesFixed(u32::to_be_bytes(0x50bb4e7f))); 224 | assert_eq!( 225 | format!("{:?}", decoder.0), 226 | "0xad2c0954693c2b5404b7e50967d3481bea432374" 227 | ); 228 | assert_eq!(decoder.1, 1); 229 | assert_eq!(decoder.2, "Test URI"); 230 | } 231 | 232 | #[test] 233 | fn parse_vec_with_dynamic_type() { 234 | let decoded_data = ( 235 | 0x36543006, 236 | vec![ 237 | (1.into(), "Test URI 0".to_string()), 238 | (11.into(), "Test URI 1".to_string()), 239 | (12.into(), "Test URI 2".to_string()), 240 | ], 241 | ); 242 | 243 | let encoded_data = &hex!( 244 | " 245 | 36543006 246 | 00000000000000000000000053744e6da587ba10b32a2554d2efdcd985bc27a3 // address 247 | 0000000000000000000000000000000000000000000000000000000000000040 // offset of (uint256, string)[] 248 | 249 | 0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[] 250 | 251 | 0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem 252 | 00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem 253 | 0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem 254 | 255 | 0000000000000000000000000000000000000000000000000000000000000001 // first token id? #60 256 | 0000000000000000000000000000000000000000000000000000000000000040 // offset of string 257 | 000000000000000000000000000000000000000000000000000000000000000a // size of string 258 | 5465737420555249203000000000000000000000000000000000000000000000 // string 259 | 260 | 000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11? #e0 261 | 0000000000000000000000000000000000000000000000000000000000000040 // offset of string 262 | 000000000000000000000000000000000000000000000000000000000000000a // size of string 263 | 5465737420555249203100000000000000000000000000000000000000000000 // string 264 | 265 | 000000000000000000000000000000000000000000000000000000000000000c // third token id? Why ==12? #160 266 | 0000000000000000000000000000000000000000000000000000000000000040 // offset of string 267 | 000000000000000000000000000000000000000000000000000000000000000a // size of string 268 | 5465737420555249203200000000000000000000000000000000000000000000 // string 269 | " 270 | ); 271 | 272 | let (call, decoder) = <(Address, Vec<(U256, String)>)>::abi_decode_call(encoded_data).unwrap(); 273 | assert_eq!(call, BytesFixed(u32::to_be_bytes(decoded_data.0))); 274 | assert_eq!(decoder.1, decoded_data.1); 275 | 276 | let ed = (decoder.0, decoded_data.1).abi_encode_call(BytesFixed(decoded_data.0.to_be_bytes())); 277 | similar_asserts::assert_eq!(encoded_data, ed.as_slice()); 278 | } 279 | 280 | #[test] 281 | fn encode_decode_vec_tuple_string_bytes() { 282 | test_impl::<(Vec<(String, Bytes)>,)>( 283 | 0xdeadbeef, 284 | (vec![ 285 | ( 286 | "Test URI 0".to_string(), 287 | Bytes(vec![ 288 | 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 289 | 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 290 | 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 291 | 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 292 | ]), 293 | ), 294 | ( 295 | "Test URI 1".to_string(), 296 | Bytes(vec![ 297 | 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 298 | 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 299 | 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 300 | 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 301 | ]), 302 | ), 303 | ("Test URI 2".to_string(), Bytes(vec![0x33, 0x33])), 304 | ],), 305 | &hex!( 306 | " 307 | deadbeef 308 | 0000000000000000000000000000000000000000000000000000000000000020 309 | 0000000000000000000000000000000000000000000000000000000000000003 310 | 311 | 0000000000000000000000000000000000000000000000000000000000000060 // 32 312 | 0000000000000000000000000000000000000000000000000000000000000140 // 96 313 | 0000000000000000000000000000000000000000000000000000000000000220 // 544 314 | 315 | 0000000000000000000000000000000000000000000000000000000000000040 // 32 316 | 0000000000000000000000000000000000000000000000000000000000000080 317 | 000000000000000000000000000000000000000000000000000000000000000a // 64 318 | 5465737420555249203000000000000000000000000000000000000000000000 319 | 0000000000000000000000000000000000000000000000000000000000000030 320 | 1111111111111111111111111111111111111111111111111111111111111111 321 | 1111111111111111111111111111111100000000000000000000000000000000 322 | 323 | 0000000000000000000000000000000000000000000000000000000000000040 // 96 324 | 0000000000000000000000000000000000000000000000000000000000000080 325 | 000000000000000000000000000000000000000000000000000000000000000a 326 | 5465737420555249203100000000000000000000000000000000000000000000 327 | 000000000000000000000000000000000000000000000000000000000000002f 328 | 2222222222222222222222222222222222222222222222222222222222222222 329 | 2222222222222222222222222222220000000000000000000000000000000000 330 | 331 | 0000000000000000000000000000000000000000000000000000000000000040 // 544 332 | 0000000000000000000000000000000000000000000000000000000000000080 333 | 000000000000000000000000000000000000000000000000000000000000000a 334 | 5465737420555249203200000000000000000000000000000000000000000000 335 | 0000000000000000000000000000000000000000000000000000000000000002 336 | 3333000000000000000000000000000000000000000000000000000000000000 337 | " 338 | ), 339 | ); 340 | } 341 | 342 | #[test] 343 | // #[ignore = "reason"] 344 | fn encode_decode_tuple0_tuple1_uint8_tuple1_string_bytes_tuple1_uint8_bytes() { 345 | let int = 0xff; 346 | let by = Bytes(vec![0x11, 0x22, 0x33]); 347 | let string = "some string".to_string(); 348 | 349 | test_impl::<(((u8,), (String, Bytes), (u8, Bytes)),)>( 350 | 0xdeadbeef, 351 | (((int,), (string, by.clone()), (int, by)),), 352 | &hex!( 353 | " 354 | deadbeef 355 | 0000000000000000000000000000000000000000000000000000000000000020 356 | 00000000000000000000000000000000000000000000000000000000000000ff 357 | 0000000000000000000000000000000000000000000000000000000000000060 358 | 0000000000000000000000000000000000000000000000000000000000000120 359 | 0000000000000000000000000000000000000000000000000000000000000040 360 | 0000000000000000000000000000000000000000000000000000000000000080 361 | 000000000000000000000000000000000000000000000000000000000000000b 362 | 736f6d6520737472696e67000000000000000000000000000000000000000000 363 | 0000000000000000000000000000000000000000000000000000000000000003 364 | 1122330000000000000000000000000000000000000000000000000000000000 365 | 00000000000000000000000000000000000000000000000000000000000000ff 366 | 0000000000000000000000000000000000000000000000000000000000000040 367 | 0000000000000000000000000000000000000000000000000000000000000003 368 | 1122330000000000000000000000000000000000000000000000000000000000 369 | " 370 | ), 371 | ); 372 | } 373 | 374 | #[test] 375 | fn encode_decode_tuple0_tuple1_uint8_tuple1_uint8_uint8_tuple1_uint8_uint8() { 376 | test_impl::<((u8,), (u8, u8), (u8, u8))>( 377 | 0xdeadbeef, 378 | ((43,), (44, 45), (46, 47)), 379 | &hex!( 380 | " 381 | deadbeef 382 | 000000000000000000000000000000000000000000000000000000000000002b 383 | 000000000000000000000000000000000000000000000000000000000000002c 384 | 000000000000000000000000000000000000000000000000000000000000002d 385 | 000000000000000000000000000000000000000000000000000000000000002e 386 | 000000000000000000000000000000000000000000000000000000000000002f 387 | " 388 | ), 389 | ); 390 | } 391 | 392 | #[test] 393 | fn encode_decode_tuple0_tuple1_uint8_tuple1_uint8() { 394 | test_impl::<((u8,), (u8,))>( 395 | 0xdeadbeef, 396 | ((43,), (44,)), 397 | &hex!( 398 | " 399 | deadbeef 400 | 000000000000000000000000000000000000000000000000000000000000002b 401 | 000000000000000000000000000000000000000000000000000000000000002c 402 | " 403 | ), 404 | ); 405 | } 406 | 407 | #[test] 408 | fn encode_decode_tuple0_tuple1_uint8_uint8() { 409 | test_impl::<((u8, u8),)>( 410 | 0xdeadbeef, 411 | ((43, 44),), 412 | &hex!( 413 | " 414 | deadbeef 415 | 000000000000000000000000000000000000000000000000000000000000002b 416 | 000000000000000000000000000000000000000000000000000000000000002c 417 | " 418 | ), 419 | ); 420 | } 421 | 422 | #[test] 423 | fn encode_decode_tuple_uint8_uint8() { 424 | test_impl::<(u8, u8)>( 425 | 0xdeadbeef, 426 | (43, 44), 427 | &hex!( 428 | " 429 | deadbeef 430 | 000000000000000000000000000000000000000000000000000000000000002b 431 | 000000000000000000000000000000000000000000000000000000000000002c 432 | " 433 | ), 434 | ); 435 | } 436 | 437 | #[test] 438 | fn encode_decode_tuple0_tuple1_uint8_uint8_tuple1_uint8_uint8_and_uint8() { 439 | test_impl::<((u8, u8), (u8, u8), u8)>( 440 | 0xdeadbeef, 441 | ((10, 11), (12, 13), 14), 442 | &hex!( 443 | " 444 | deadbeef 445 | 000000000000000000000000000000000000000000000000000000000000000a 446 | 000000000000000000000000000000000000000000000000000000000000000b 447 | 000000000000000000000000000000000000000000000000000000000000000c 448 | 000000000000000000000000000000000000000000000000000000000000000d 449 | 000000000000000000000000000000000000000000000000000000000000000e 450 | " 451 | ), 452 | ); 453 | } 454 | 455 | #[test] 456 | fn encode_decode_tuple0_tuple1_string() { 457 | test_impl::<(((String,),),)>( 458 | 0xdeadbeef, 459 | ((("some string".to_string(),),),), 460 | &hex!( 461 | " 462 | deadbeef 463 | 0000000000000000000000000000000000000000000000000000000000000020 464 | 0000000000000000000000000000000000000000000000000000000000000020 465 | 0000000000000000000000000000000000000000000000000000000000000020 466 | 000000000000000000000000000000000000000000000000000000000000000b 467 | 736f6d6520737472696e67000000000000000000000000000000000000000000 468 | " 469 | ), 470 | ); 471 | } 472 | 473 | #[test] 474 | fn encode_decode_tuple0_tuple1_uint8_string() { 475 | test_impl::<(((u8, String),),)>( 476 | 0xdeadbeef, 477 | (((0xff, "some string".to_string()),),), 478 | &hex!( 479 | " 480 | deadbeef 481 | 0000000000000000000000000000000000000000000000000000000000000020 482 | 0000000000000000000000000000000000000000000000000000000000000020 483 | 00000000000000000000000000000000000000000000000000000000000000ff 484 | 0000000000000000000000000000000000000000000000000000000000000040 485 | 000000000000000000000000000000000000000000000000000000000000000b 486 | 736f6d6520737472696e67000000000000000000000000000000000000000000 487 | " 488 | ), 489 | ); 490 | } 491 | 492 | #[test] 493 | fn encode_decode_tuple0_tuple1_string_bytes() { 494 | test_impl::<((String, Bytes),)>( 495 | 0xdeadbeef, 496 | (("some string".to_string(), Bytes(vec![1, 2, 3])),), 497 | &hex!( 498 | " 499 | deadbeef 500 | 0000000000000000000000000000000000000000000000000000000000000020 501 | 0000000000000000000000000000000000000000000000000000000000000040 502 | 0000000000000000000000000000000000000000000000000000000000000080 503 | 000000000000000000000000000000000000000000000000000000000000000b 504 | 736f6d6520737472696e67000000000000000000000000000000000000000000 505 | 0000000000000000000000000000000000000000000000000000000000000003 506 | 0102030000000000000000000000000000000000000000000000000000000000 507 | " 508 | ), 509 | ); 510 | } 511 | 512 | #[test] 513 | fn encode_decode_tuple0_tuple1_uint8_tuple1_string() { 514 | test_impl::<(((u8,), (String,)),)>( 515 | 0xdeadbeef, 516 | (((0xff,), ("some string".to_string(),)),), 517 | &hex!( 518 | " 519 | deadbeef 520 | 0000000000000000000000000000000000000000000000000000000000000020 521 | 00000000000000000000000000000000000000000000000000000000000000ff 522 | 0000000000000000000000000000000000000000000000000000000000000040 523 | 0000000000000000000000000000000000000000000000000000000000000020 524 | 000000000000000000000000000000000000000000000000000000000000000b 525 | 736f6d6520737472696e67000000000000000000000000000000000000000000 526 | " 527 | ), 528 | ); 529 | } 530 | 531 | #[test] 532 | fn encode_decode_option_uint8_some() { 533 | test_impl::>( 534 | 0xdeadbeef, 535 | Some(44), 536 | &hex!( 537 | " 538 | deadbeef 539 | 0000000000000000000000000000000000000000000000000000000000000001 540 | 000000000000000000000000000000000000000000000000000000000000002c 541 | " 542 | ), 543 | ); 544 | } 545 | 546 | #[test] 547 | fn encode_decode_option_uint8_none() { 548 | test_impl::>( 549 | 0xdeadbeef, 550 | None, 551 | &hex!( 552 | " 553 | deadbeef 554 | 0000000000000000000000000000000000000000000000000000000000000000 555 | 0000000000000000000000000000000000000000000000000000000000000000 556 | " 557 | ), 558 | ); 559 | } 560 | 561 | #[test] 562 | fn encode_decode_option_string_some() { 563 | test_impl::<(Option,)>( 564 | 0xdeadbeef, 565 | (Some("some string".to_string()),), 566 | &hex!( 567 | " 568 | deadbeef 569 | 0000000000000000000000000000000000000000000000000000000000000020 570 | 0000000000000000000000000000000000000000000000000000000000000001 571 | 0000000000000000000000000000000000000000000000000000000000000040 572 | 000000000000000000000000000000000000000000000000000000000000000b 573 | 736f6d6520737472696e67000000000000000000000000000000000000000000 574 | " 575 | ), 576 | ); 577 | } 578 | 579 | #[test] 580 | fn encode_decode_option_string_none() { 581 | test_impl::<(Option,)>( 582 | 0xdeadbeef, 583 | (None,), 584 | &hex!( 585 | " 586 | deadbeef 587 | 0000000000000000000000000000000000000000000000000000000000000020 // Offset of option 588 | 0000000000000000000000000000000000000000000000000000000000000000 // Option value 589 | 0000000000000000000000000000000000000000000000000000000000000040 // Offset of string from option 590 | 0000000000000000000000000000000000000000000000000000000000000000 591 | " 592 | ), 593 | ); 594 | } 595 | -------------------------------------------------------------------------------- /procedural/src/structs/parse.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use proc_macro2::Span; 4 | use quote::format_ident; 5 | use syn::{parse::Error, Ident, Lit, Meta, NestedMeta}; 6 | 7 | use super::common::OverlapOptions; 8 | use crate::structs::common::{Endianness, FieldAttrs, FieldInfo, ReserveFieldOption}; 9 | 10 | pub struct TryFromAttrBuilderError { 11 | pub endianness: Box, 12 | pub reserve: ReserveFieldOption, 13 | pub overlap: OverlapOptions, 14 | } 15 | 16 | impl TryFromAttrBuilderError { 17 | pub fn fix(self, bit_range: Range) -> FieldAttrs { 18 | FieldAttrs { 19 | endianness: self.endianness, 20 | bit_range, 21 | reserve: self.reserve, 22 | overlap: self.overlap, 23 | } 24 | } 25 | } 26 | 27 | impl std::fmt::Display for TryFromAttrBuilderError { 28 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 29 | write!( 30 | fmt, 31 | "Did not provide enough information to determine bit_length" 32 | ) 33 | } 34 | } 35 | 36 | #[derive(Clone, Debug)] 37 | pub enum FieldAttrBuilderType { 38 | None, 39 | Struct(usize), 40 | Enum(usize, Ident), 41 | // amount of bits for each element. 42 | ElementArray(usize, Box>), 43 | BlockArray(Box>), 44 | } 45 | 46 | #[derive(Clone, Debug)] 47 | pub enum FieldBuilderRange { 48 | // a range of bits to use. 49 | Range(std::ops::Range), 50 | // used to pass on the last starting location to another part to figure out. 51 | LastEnd(usize), 52 | None, 53 | } 54 | 55 | impl Default for FieldBuilderRange { 56 | fn default() -> Self { 57 | Self::None 58 | } 59 | } 60 | 61 | #[derive(Clone, Debug)] 62 | pub struct FieldAttrBuilder { 63 | /// name is just so we can give better errors 64 | name: Box, 65 | pub endianness: Box, 66 | pub bit_range: FieldBuilderRange, 67 | pub ty: FieldAttrBuilderType, 68 | pub reserve: ReserveFieldOption, 69 | pub overlap: OverlapOptions, 70 | } 71 | 72 | impl FieldAttrBuilder { 73 | fn new(name: Box) -> Self { 74 | Self { 75 | name, 76 | endianness: Box::new(Endianness::None), 77 | bit_range: FieldBuilderRange::None, 78 | ty: FieldAttrBuilderType::None, 79 | reserve: ReserveFieldOption::NotReserve, 80 | overlap: OverlapOptions::None, 81 | } 82 | } 83 | 84 | fn span(&self) -> Span { 85 | self.name.span() 86 | } 87 | 88 | pub fn parse( 89 | field: &syn::Field, 90 | last_field: Option<&FieldInfo>, 91 | name: Box, 92 | ) -> syn::Result { 93 | let mut builder = FieldAttrBuilder::new(name); 94 | // TODO make this more compact. use match or something. 95 | // we are just looking for attrs that can fill in the details in the builder variable above 96 | // sometimes having the last field is useful for example the bit range the builder wants could be 97 | // filled in using the end of the previous field as the start, add the length in bits you get the 98 | // end ( this only works if a all bit fields are in order, ex. if a bit_range attribute defines a 99 | // complete range which occupies the same space as this field and that field is not the "last_field" 100 | // you will get a conflicting fields error returned to the user... hopefully ) 101 | for attr in field.attrs.iter() { 102 | let meta = attr.parse_meta()?; 103 | Self::parse_meta(meta, &last_field, &mut builder)?; 104 | } 105 | if let FieldBuilderRange::None = builder.bit_range { 106 | builder.bit_range = FieldBuilderRange::LastEnd(if let Some(last_value) = last_field { 107 | last_value.attrs.bit_range.end 108 | } else { 109 | 0 110 | }) 111 | } 112 | 113 | Ok(builder) 114 | } 115 | 116 | fn parse_meta( 117 | meta: Meta, 118 | last_field: &Option<&FieldInfo>, 119 | builder: &mut Self, 120 | ) -> syn::Result<()> { 121 | match meta { 122 | Meta::NameValue(value) => { 123 | if let Some(ident) = value.path.get_ident() { 124 | let ident_as_str = ident.to_string(); 125 | match ident_as_str.as_str() { 126 | "endianness" => { 127 | if let Lit::Str(val) = value.lit { 128 | builder.endianness = Box::new(match val.value().as_str() { 129 | "le" | "lsb" | "little" | "lil" => Endianness::Little, 130 | "be" | "msb" | "big" => Endianness::Big, 131 | "ne" | "native" => Endianness::None, 132 | _ => { 133 | return Err(syn::Error::new( 134 | builder.span(), 135 | "{} is not a valid endianness use le or be", 136 | )); 137 | } 138 | }); 139 | } 140 | } 141 | "bit_length" => { 142 | if let FieldBuilderRange::None = builder.bit_range { 143 | if let Lit::Int(val) = value.lit { 144 | match val.base10_parse::() { 145 | Ok(bit_length) => { 146 | let mut start = 0; 147 | if let Some(last_value) = last_field { 148 | start = last_value.attrs.bit_range.end; 149 | } 150 | builder.bit_range = FieldBuilderRange::Range( 151 | start..start + (bit_length), 152 | ); 153 | } 154 | Err(err) => { 155 | return Err(Error::new( 156 | builder.span(), 157 | format!("bit_length must be a number that can be parsed as a usize [{err}]"), 158 | )); 159 | } 160 | } 161 | } else { 162 | return Err(Error::new( 163 | builder.span(), 164 | "bit_length must use a literal usize", 165 | )); 166 | } 167 | } else { 168 | return Err(Error::new( 169 | builder.span(), 170 | "please don't double define bit_length", 171 | )); 172 | } 173 | } 174 | "byte_length" => { 175 | if let FieldBuilderRange::None = builder.bit_range { 176 | if let Lit::Int(val) = value.lit { 177 | match val.base10_parse::() { 178 | Ok(byte_length) => { 179 | let mut start = 0; 180 | if let Some(last_value) = last_field { 181 | start = last_value.attrs.bit_range.end; 182 | } 183 | builder.bit_range = FieldBuilderRange::Range( 184 | start..start + (byte_length * 8), 185 | ); 186 | } 187 | Err(err) => { 188 | return Err(Error::new( 189 | builder.span(), 190 | format!("bit length must be a number that can be parsed as a usize [{err}]"), 191 | )); 192 | } 193 | } 194 | } else { 195 | return Err(Error::new( 196 | builder.span(), 197 | "bit_length must use a literal usize", 198 | )); 199 | } 200 | } else { 201 | return Err(Error::new( 202 | builder.span(), 203 | "please don't double define bit width", 204 | )); 205 | } 206 | } 207 | "enum_primitive" => { 208 | if let Lit::Str(val) = value.lit { 209 | let mut ty = Some(match val.value().as_str() { 210 | "u8" => FieldAttrBuilderType::Enum(1, format_ident!("u8")), 211 | "u16" => FieldAttrBuilderType::Enum(2, format_ident!("u16")), 212 | "u32" => FieldAttrBuilderType::Enum(4, format_ident!("u32")), 213 | "u64" => FieldAttrBuilderType::Enum(8, format_ident!("u64")), 214 | "u128" => FieldAttrBuilderType::Enum(16, format_ident!("u128")), 215 | _ => { 216 | return Err(syn::Error::new( 217 | builder.span(), 218 | "primitives for enums must be an unsigned integer", 219 | )) 220 | } 221 | }); 222 | match builder.ty { 223 | FieldAttrBuilderType::BlockArray(ref mut sub_ty) => { 224 | std::mem::swap(&mut ty, sub_ty) 225 | } 226 | FieldAttrBuilderType::ElementArray(_, ref mut sub_ty) => { 227 | std::mem::swap(&mut ty, sub_ty) 228 | } 229 | _ => { 230 | builder.ty = ty.unwrap(); 231 | } 232 | } 233 | } else { 234 | return Err(Error::new( 235 | builder.span(), 236 | "defining a struct_size requires a Int Literal".to_string(), 237 | )); 238 | } 239 | } 240 | "struct_size" => { 241 | if let Lit::Int(val) = value.lit { 242 | let mut ty = Some(match val.base10_parse::() { 243 | Ok(byte_length) => FieldAttrBuilderType::Struct(byte_length), 244 | Err(err) => { 245 | return Err(Error::new( 246 | builder.span(), 247 | format!("struct_size must provided a number that can be parsed as a usize [{err}]"), 248 | )); 249 | } 250 | }); 251 | match builder.ty { 252 | FieldAttrBuilderType::BlockArray(ref mut sub_ty) => { 253 | std::mem::swap(&mut ty, sub_ty.as_mut()) 254 | } 255 | FieldAttrBuilderType::ElementArray(_, ref mut sub_ty) => { 256 | std::mem::swap(&mut ty, sub_ty.as_mut()) 257 | } 258 | _ => { 259 | builder.ty = ty.unwrap(); 260 | } 261 | } 262 | } else { 263 | return Err(Error::new( 264 | builder.span(), 265 | "defining a struct_size requires a Int Literal".to_string(), 266 | )); 267 | } 268 | } 269 | "bits" => { 270 | if let Lit::Str(val) = value.lit { 271 | let val_string = val.value(); 272 | let split = 273 | val_string.split("..").into_iter().collect::>(); 274 | if split.len() == 2 { 275 | match (split[0].parse::(), split[1].parse::()) { 276 | (Ok(start), Ok(end)) => match builder.bit_range { 277 | FieldBuilderRange::Range(ref range) => { 278 | if range.end - range.start == end - start { 279 | builder.bit_range = 280 | FieldBuilderRange::Range(start..end); 281 | } else { 282 | return Err(Error::new( 283 | builder.span(), 284 | "bits attribute didn't match bit range requirements", 285 | )); 286 | } 287 | } 288 | _ => { 289 | builder.bit_range = 290 | FieldBuilderRange::Range(start..end); 291 | } 292 | }, 293 | (Ok(_), Err(_)) => { 294 | return Err(Error::new( 295 | builder.span(), 296 | "failed paring ending index for range", 297 | )); 298 | } 299 | (Err(_), Ok(_)) => { 300 | return Err(Error::new( 301 | builder.span(), 302 | "failed paring starting index for range", 303 | )); 304 | } 305 | _ => { 306 | return Err(Error::new( 307 | builder.span(), 308 | "failed paring range", 309 | )); 310 | } 311 | } 312 | } else { 313 | return Err(Error::new( 314 | builder.span(), 315 | "bits attribute should have data like \"0..8\"", 316 | )); 317 | } 318 | } else { 319 | return Err(Error::new( 320 | builder.span(), 321 | "bits must use a literal str value with range inside quotes", 322 | )); 323 | } 324 | } 325 | "element_bit_length" => { 326 | if let Lit::Int(val) = value.lit { 327 | match val.base10_parse::() { 328 | Ok(bit_length) => { 329 | builder.bit_range = match std::mem::take(&mut builder.bit_range) { 330 | FieldBuilderRange::None => { 331 | builder.ty = match builder.ty { 332 | FieldAttrBuilderType::Struct(_) | 333 | FieldAttrBuilderType::Enum(_, _) => { 334 | FieldAttrBuilderType::ElementArray(bit_length, Box::new(Some(builder.ty.clone()))) 335 | } 336 | _ => FieldAttrBuilderType::ElementArray(bit_length, Box::new(None)), 337 | }; 338 | if let Some(last_value) = last_field { 339 | FieldBuilderRange::LastEnd(last_value.attrs.bit_range.end) 340 | }else{ 341 | FieldBuilderRange::LastEnd(0) 342 | } 343 | } 344 | FieldBuilderRange::Range(range) => { 345 | builder.ty = match builder.ty { 346 | FieldAttrBuilderType::Struct(_) | 347 | FieldAttrBuilderType::Enum(_, _) => { 348 | FieldAttrBuilderType::ElementArray(bit_length, Box::new(Some(builder.ty.clone()))) 349 | } 350 | _ => FieldAttrBuilderType::ElementArray(bit_length, Box::new(None)), 351 | }; 352 | FieldBuilderRange::Range(range) 353 | } 354 | _ => return Err(Error::new( 355 | builder.span(), 356 | "found Field bit range no_end while element_bit_length attribute which should never happen", 357 | )), 358 | }; 359 | } 360 | Err(err) => { 361 | return Err(Error::new( 362 | builder.span(), 363 | format!("bit_length must be a number that can be parsed as a usize [{err}]"), 364 | )); 365 | } 366 | } 367 | } else { 368 | return Err(Error::new( 369 | builder.span(), 370 | "bit_length must use a literal usize", 371 | )); 372 | } 373 | } 374 | "element_byte_length" => { 375 | if let Lit::Int(val) = value.lit { 376 | match val.base10_parse::() { 377 | Ok(byte_length) => { 378 | builder.bit_range = match std::mem::take(&mut builder.bit_range) { 379 | FieldBuilderRange::None => { 380 | builder.ty = match builder.ty { 381 | FieldAttrBuilderType::Struct(_) | 382 | FieldAttrBuilderType::Enum(_, _) => { 383 | FieldAttrBuilderType::ElementArray(byte_length * 8, Box::new(Some(builder.ty.clone()))) 384 | } 385 | _ => FieldAttrBuilderType::ElementArray(byte_length * 8, Box::new(None)), 386 | }; 387 | if let Some(last_value) = last_field { 388 | FieldBuilderRange::LastEnd(last_value.attrs.bit_range.end) 389 | }else{ 390 | FieldBuilderRange::LastEnd(0) 391 | } 392 | } 393 | FieldBuilderRange::Range(range) => { 394 | builder.ty = match builder.ty { 395 | FieldAttrBuilderType::Struct(_) | 396 | FieldAttrBuilderType::Enum(_, _) => { 397 | FieldAttrBuilderType::ElementArray(byte_length * 8, Box::new(Some(builder.ty.clone()))) 398 | } 399 | _ => FieldAttrBuilderType::ElementArray(byte_length * 8, Box::new(None)), 400 | }; 401 | FieldBuilderRange::Range(range) 402 | } 403 | _ => return Err(Error::new( 404 | builder.span(), 405 | "found Field bit range no_end while element_byte_length attribute which should never happen", 406 | )), 407 | }; 408 | } 409 | Err(err) => { 410 | return Err(Error::new( 411 | builder.span(), 412 | format!("bit_length must be a number that can be parsed as a usize [{err}]"), 413 | )); 414 | } 415 | } 416 | } else { 417 | return Err(Error::new( 418 | builder.span(), 419 | "bit_length must use a literal usize", 420 | )); 421 | } 422 | } 423 | "block_bit_length" => { 424 | if let Lit::Int(val) = value.lit { 425 | match val.base10_parse::() { 426 | Ok(bit_length) => { 427 | builder.bit_range = match std::mem::take(&mut builder.bit_range) { 428 | FieldBuilderRange::None => { 429 | builder.ty = match builder.ty { 430 | FieldAttrBuilderType::Struct(_) | 431 | FieldAttrBuilderType::Enum(_, _) => { 432 | FieldAttrBuilderType::BlockArray(Box::new(Some(builder.ty.clone()))) 433 | } 434 | _ => FieldAttrBuilderType::BlockArray(Box::new(None)), 435 | }; 436 | if let Some(last_value) = last_field { 437 | FieldBuilderRange::Range(last_value.attrs.bit_range.end..last_value.attrs.bit_range.end + (bit_length)) 438 | }else{ 439 | FieldBuilderRange::Range(0..bit_length) 440 | } 441 | } 442 | FieldBuilderRange::Range(range) => { 443 | builder.ty = match builder.ty { 444 | FieldAttrBuilderType::Struct(_) | 445 | FieldAttrBuilderType::Enum(_, _) => { 446 | FieldAttrBuilderType::BlockArray(Box::new(Some(builder.ty.clone()))) 447 | } 448 | _ => FieldAttrBuilderType::BlockArray(Box::new(None)), 449 | }; 450 | if range.end - range.start == bit_length{ 451 | FieldBuilderRange::Range(range) 452 | }else{ 453 | return Err(Error::new( 454 | builder.span(), 455 | "size of bit-range provided by (bits, bit_length, or byte_length) does not match array_bit_length", 456 | )); 457 | } 458 | } 459 | _ => return Err(Error::new( 460 | builder.span(), 461 | "found Field bit range no_end while array_bit_length attribute which should never happen", 462 | )), 463 | }; 464 | } 465 | Err(err) => { 466 | return Err(Error::new( 467 | builder.span(), 468 | format!("array_bit_length must be a number that can be parsed as a usize [{err}]"), 469 | )); 470 | } 471 | } 472 | } else { 473 | return Err(Error::new( 474 | builder.span(), 475 | "array_bit_length must use a literal usize", 476 | )); 477 | } 478 | } 479 | "block_byte_length" => { 480 | if let Lit::Int(val) = value.lit { 481 | match val.base10_parse::() { 482 | Ok(byte_length) => { 483 | builder.bit_range = match std::mem::take(&mut builder.bit_range) { 484 | FieldBuilderRange::None => { 485 | builder.ty = match builder.ty { 486 | FieldAttrBuilderType::Struct(_) | 487 | FieldAttrBuilderType::Enum(_, _) => { 488 | FieldAttrBuilderType::BlockArray(Box::new(Some(builder.ty.clone()))) 489 | } 490 | _ => FieldAttrBuilderType::BlockArray(Box::new(None)), 491 | }; 492 | if let Some(last_value) = last_field { 493 | FieldBuilderRange::Range(last_value.attrs.bit_range.end..last_value.attrs.bit_range.end + (byte_length * 8)) 494 | }else{ 495 | FieldBuilderRange::Range(0..byte_length*8) 496 | } 497 | } 498 | FieldBuilderRange::Range(range) => { 499 | builder.ty = match builder.ty { 500 | FieldAttrBuilderType::Struct(_) | 501 | FieldAttrBuilderType::Enum(_, _) => { 502 | FieldAttrBuilderType::BlockArray(Box::new(Some(builder.ty.clone()))) 503 | } 504 | _ => FieldAttrBuilderType::BlockArray(Box::new(None)), 505 | }; 506 | if range.end - range.start == byte_length * 8{ 507 | FieldBuilderRange::Range(range) 508 | }else{ 509 | return Err(Error::new( 510 | builder.span(), 511 | "size of bit-range provided by (bits, bit_length, or byte_length) does not match array_byte_length", 512 | )); 513 | } 514 | } 515 | _ => return Err(Error::new( 516 | builder.span(), 517 | "found Field bit range no_end while array_byte_length attribute which should never happen", 518 | )), 519 | }; 520 | } 521 | Err(err) => { 522 | return Err(Error::new( 523 | builder.span(), 524 | format!("array_byte_length must be a number that can be parsed as a usize [{err}]"), 525 | )); 526 | } 527 | } 528 | } else { 529 | return Err(Error::new( 530 | builder.span(), 531 | "array_byte_length must use a literal usize", 532 | )); 533 | } 534 | } 535 | "overlapping_bits" => { 536 | if let Lit::Int(val) = value.lit { 537 | match val.base10_parse::() { 538 | Ok(bits) => builder.overlap = OverlapOptions::Allow(bits), 539 | Err(err) => { 540 | return Err(Error::new( 541 | builder.span(), 542 | format!("overlapping_bits must provided a number that can be parsed as a usize [{err}]"), 543 | )); 544 | } 545 | }; 546 | } else { 547 | return Err(Error::new( 548 | builder.span(), 549 | "defining a overlapping_bits requires a Int Literal" 550 | .to_string(), 551 | )); 552 | } 553 | } 554 | _ => { 555 | if ident_as_str.as_str() != "doc" { 556 | return Err(Error::new( 557 | builder.span(), 558 | format!("\"{ident_as_str}\" is not a valid attribute"), 559 | )); 560 | } 561 | } 562 | } 563 | } 564 | } 565 | Meta::Path(path) => { 566 | if let Some(ident) = path.get_ident() { 567 | match ident.to_string().as_str() { 568 | "reserve" => { 569 | builder.reserve = ReserveFieldOption::ReserveField; 570 | } 571 | "read_only" => { 572 | builder.reserve = ReserveFieldOption::ReadOnly; 573 | } 574 | // TODO can not enable this until i figure out a way to express exactly the amount 575 | // of overlapping bits. 576 | /*"allow_overlap" => { 577 | builder.overlap = OverlapOptions::Allow; 578 | }*/ 579 | "redundant" => { 580 | builder.overlap = OverlapOptions::Redundant; 581 | builder.reserve = ReserveFieldOption::ReadOnly; 582 | } 583 | _ => {} 584 | } 585 | } 586 | } 587 | Meta::List(meta_list) => { 588 | if meta_list.path.is_ident("bondrewd") { 589 | for nested_meta in meta_list.nested { 590 | match nested_meta { 591 | NestedMeta::Meta(meta) => { 592 | Self::parse_meta(meta, last_field, builder)?; 593 | } 594 | NestedMeta::Lit(_) => {} 595 | } 596 | } 597 | } 598 | } 599 | } 600 | Ok(()) 601 | } 602 | } 603 | 604 | impl TryInto for FieldAttrBuilder { 605 | type Error = TryFromAttrBuilderError; 606 | fn try_into(self) -> std::result::Result { 607 | if let FieldBuilderRange::Range(bit_range) = self.bit_range { 608 | Ok(FieldAttrs { 609 | endianness: self.endianness, 610 | bit_range, 611 | reserve: self.reserve, 612 | overlap: self.overlap, 613 | }) 614 | } else { 615 | Err(TryFromAttrBuilderError { 616 | endianness: self.endianness, 617 | reserve: self.reserve, 618 | overlap: self.overlap, 619 | }) 620 | } 621 | } 622 | } 623 | --------------------------------------------------------------------------------