├── .gitignore ├── Cargo.toml ├── rustfmt.toml ├── res ├── constructor.abi ├── test_rust_keywords.abi ├── foo.abi ├── event.abi ├── validators.sol ├── urlhint.abi ├── test.abi ├── Validators.abi ├── eip20.abi └── Operations.abi ├── tools ├── solc_compile.sh └── publish.sh ├── .editorconfig ├── tests ├── Cargo.toml └── src │ └── lib.rs ├── contract ├── Cargo.toml └── src │ └── lib.rs ├── ethabi ├── src │ ├── param_type │ │ ├── mod.rs │ │ ├── deserialize.rs │ │ ├── writer.rs │ │ ├── param_type.rs │ │ └── reader.rs │ ├── constructor.rs │ ├── signature.rs │ ├── errors.rs │ ├── log.rs │ ├── util.rs │ ├── lib.rs │ ├── token │ │ ├── lenient.rs │ │ ├── strict.rs │ │ ├── mod.rs │ │ └── token.rs │ ├── tuple_param.rs │ ├── function.rs │ ├── event_param.rs │ ├── operation.rs │ ├── contract.rs │ ├── param.rs │ ├── filter.rs │ └── event.rs └── Cargo.toml ├── derive ├── Cargo.toml └── src │ ├── contract.rs │ ├── constructor.rs │ ├── lib.rs │ ├── event.rs │ └── function.rs ├── cli ├── Cargo.toml └── src │ └── main.rs ├── CHANGELOG.md ├── .github └── workflows │ └── main.yml ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["ethabi", "cli", "derive", "contract", "tests"] 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | max_width = 120 3 | use_small_heuristics = "Max" 4 | edition = "2018" 5 | -------------------------------------------------------------------------------- /res/constructor.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "name": "a", 6 | "type": "address" 7 | } 8 | ], 9 | "type": "constructor" 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /tools/solc_compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd res 4 | 5 | for sol in *.sol; do 6 | solc --abi -o . --overwrite "$sol" 7 | done 8 | 9 | for abi in *.abi; do 10 | python -m json.tool "$abi" > tmp 11 | cat tmp > "$abi" 12 | done 13 | 14 | rm tmp 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style=tab 4 | indent_size=tab 5 | tab_width=4 6 | end_of_line=lf 7 | charset=utf-8 8 | trim_trailing_whitespace=true 9 | max_line_length=120 10 | insert_final_newline=true 11 | 12 | [*.json] 13 | indent_style=space 14 | indent_size=4 15 | 16 | -------------------------------------------------------------------------------- /res/test_rust_keywords.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "name": "self", 6 | "type": "bool" 7 | } 8 | ], 9 | "name": "foo", 10 | "outputs": [], 11 | "type": "function" 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ethabi-tests" 3 | version = "0.1.1" 4 | authors = ["debris "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ethabi = { path = "../ethabi" } 9 | ethabi-derive = { path = "../derive" } 10 | ethabi-contract = { path = "../contract" } 11 | hex = "0.4" 12 | hex-literal = "0.3" 13 | -------------------------------------------------------------------------------- /res/foo.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": false, 4 | "inputs": [ 5 | { 6 | "name": "hello", 7 | "type": "address" 8 | } 9 | ], 10 | "name": "bar", 11 | "outputs": [ 12 | { 13 | "name": "", 14 | "type": "bool" 15 | } 16 | ], 17 | "type": "function" 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /res/event.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": true, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "name": "a", 8 | "type": "bool" 9 | }, 10 | { 11 | "indexed": false, 12 | "name": "b", 13 | "type": "address" 14 | } 15 | ], 16 | "name": "Event", 17 | "type": "event" 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /tools/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -exu 4 | 5 | VERSION=$(grep "^version" ./ethabi/Cargo.toml | sed -e 's/.*"\(.*\)"/\1/') 6 | ORDER=(ethabi derive contract cli) 7 | 8 | echo "Publishing $VERSION" 9 | cargo clean 10 | 11 | for crate in ${ORDER[@]}; do 12 | echo "Publishing $crate@$VERSION" 13 | sleep 5 14 | cd $crate 15 | cargo publish $@ 16 | cd - 17 | done 18 | 19 | echo "Tagging version $VERSION" 20 | git tag -a v$VERSION -m "Version $VERSION" 21 | git push --tags 22 | -------------------------------------------------------------------------------- /contract/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ethabi-contract" 3 | version = "11.0.0" 4 | authors = [ 5 | "Parity Technologies ", 6 | "Artem Vorotnikov ", 7 | "Nicholas Rodrigues Lordello ", 8 | ] 9 | homepage = "https://github.com/rust-ethereum/ethabi" 10 | license = "MIT/Apache-2.0" 11 | keywords = ["ethereum", "eth", "abi", "solidity", "derive"] 12 | description = "Easy to use conversion of ethereum contract calls to bytecode." 13 | edition = "2018" 14 | -------------------------------------------------------------------------------- /res/validators.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | contract Validators { 4 | event Changed (address[] indexed validators); 5 | 6 | address[] public validators; 7 | 8 | function Validators (address[] newValidators) public { 9 | validators = newValidators; 10 | } 11 | 12 | function setValidators (address[] newValidators) public { 13 | validators = newValidators; 14 | } 15 | 16 | function addTwoValidators (address[2] newValidators) public { 17 | validators.push(newValidators[0]); 18 | validators.push(newValidators[1]); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ethabi/src/param_type/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Function and event param types. 10 | 11 | mod deserialize; 12 | mod param_type; 13 | mod reader; 14 | mod writer; 15 | 16 | pub use self::{param_type::ParamType, reader::Reader, writer::Writer}; 17 | -------------------------------------------------------------------------------- /res/urlhint.abi: -------------------------------------------------------------------------------- 1 | [ 2 | {"constant":false,"inputs":[{"name":"_content","type":"bytes32"},{"name":"_url","type":"string"}],"name":"hintURL","outputs":[],"type":"function"}, 3 | {"constant":false,"inputs":[{"name":"_content","type":"bytes32"},{"name":"_accountSlashRepo","type":"string"},{"name":"_commit","type":"bytes20"}],"name":"hint","outputs":[],"type":"function"}, 4 | {"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"entries","outputs":[{"name":"accountSlashRepo","type":"string"},{"name":"commit","type":"bytes20"},{"name":"owner","type":"address"}],"type":"function"}, 5 | {"constant":false,"inputs":[{"name":"_content","type":"bytes32"}],"name":"unhint","outputs":[],"type":"function"} 6 | ] 7 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ethabi-derive" 3 | version = "12.0.0" 4 | authors = ["Parity Technologies "] 5 | homepage = "https://github.com/paritytech/ethabi" 6 | license = "Apache-2.0" 7 | keywords = ["ethereum", "eth", "abi", "solidity", "derive"] 8 | description = "Easy to use conversion of ethereum contract calls to bytecode." 9 | edition = "2018" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | anyhow = "1" 16 | ethabi = { path = "../ethabi", version = "12.0.0" } 17 | heck = "0.3.1" 18 | syn = { version = "1.0.13", default-features = false, features = ["derive", "parsing", "printing", "proc-macro"] } 19 | quote = "1.0.2" 20 | proc-macro2 = "1.0.7" 21 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ethabi-cli" 3 | version = "12.0.0" 4 | authors = [ 5 | "Parity Technologies ", 6 | "Artem Vorotnikov ", 7 | "Nicholas Rodrigues Lordello ", 8 | ] 9 | homepage = "https://github.com/rust-ethereum/ethabi" 10 | keywords = ["ethereum", "eth", "abi", "solidity", "cli"] 11 | description = "Easy to use cli for conversion of ethereum contract calls to bytecode." 12 | license = "Apache-2.0" 13 | edition = "2018" 14 | 15 | [dependencies] 16 | anyhow = "1" 17 | ethabi = { version = "12.0.0", path = "../ethabi" } 18 | hex = "0.4" 19 | sha3 = "0.9" 20 | structopt = "0.3" 21 | itertools = "0.9" 22 | 23 | [[bin]] 24 | name = "ethabi" 25 | path = "src/main.rs" 26 | -------------------------------------------------------------------------------- /ethabi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ethabi" 3 | version = "12.0.0" 4 | authors = [ 5 | "Parity Technologies ", 6 | "Artem Vorotnikov ", 7 | "Nicholas Rodrigues Lordello ", 8 | ] 9 | homepage = "https://github.com/rust-ethereum/ethabi" 10 | license = "Apache-2.0" 11 | keywords = ["ethereum", "eth", "abi", "solidity"] 12 | description = "Easy to use conversion of ethereum contract calls to bytecode." 13 | edition = "2018" 14 | 15 | [dependencies] 16 | anyhow = "1" 17 | hex = "0.4" 18 | serde = { version = "1.0", features = ["derive"] } 19 | serde_json = "1.0" 20 | sha3 = "0.9" 21 | ethereum-types = "0.9.0" 22 | thiserror = "1" 23 | uint = "0.8.2" 24 | 25 | [dev-dependencies] 26 | hex-literal = "0.3" 27 | paste = "1" 28 | -------------------------------------------------------------------------------- /contract/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2019 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #[macro_export] 10 | macro_rules! use_contract { 11 | ($module: ident, $path: expr) => { 12 | #[allow(dead_code)] 13 | #[allow(missing_docs)] 14 | #[allow(unused_imports)] 15 | #[allow(unused_mut)] 16 | #[allow(unused_variables)] 17 | pub mod $module { 18 | #[derive(ethabi_derive::EthabiContract)] 19 | #[ethabi_contract_options(path = $path)] 20 | struct _Dummy; 21 | } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format is based on [Keep a Changelog]. 4 | 5 | [Keep a Changelog]: http://keepachangelog.com/en/1.0.0/ 6 | 7 | ## [12.0.0] 8 | ### Dependencies 9 | - Upgrade ethereum-types (PR #183) 10 | 11 | ## [11.0.0] - 2020-01-16 12 | ### Changed 13 | - Support overloaded contract functions (PR #166) 14 | - Removed `error_chain` and the `backtrace` feature. (#167) 15 | - Update to 2018 edition (PR #171, #172) 16 | - Fix handling of large ints (PR #173) 17 | - Support Tuple parameter types (structs in Solidity) (PR #174) 18 | ### Dependencies 19 | - Upgrade syn, proc-macro2, quote and heck crates (PR #169) 20 | 21 | ## [10.0.0] - 2020-01-08 22 | ### Changed 23 | - Replace docopt by structopt (clap) because of performance issue (#161) 24 | ### Fixed 25 | - Return an exit code 1 on failure, including wrong input parameters 26 | 27 | -------------------------------------------------------------------------------- /res/test.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "name": "a", 6 | "type": "bool" 7 | } 8 | ], 9 | "name": "foo", 10 | "outputs": [], 11 | "type": "function" 12 | }, 13 | { 14 | "inputs": [ 15 | { 16 | "name": "a", 17 | "type": "bool" 18 | } 19 | ], 20 | "name": "bar", 21 | "outputs": [ 22 | ], 23 | "type": "function" 24 | }, 25 | { 26 | "inputs": [ 27 | { 28 | "name": "a", 29 | "type": "string" 30 | } 31 | ], 32 | "name": "bar", 33 | "outputs": [ 34 | { 35 | "name": "b", 36 | "type": "uint256" 37 | } 38 | ], 39 | "type": "function" 40 | } 41 | ] 42 | -------------------------------------------------------------------------------- /ethabi/src/constructor.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Contract constructor call builder. 10 | use crate::{encode, Bytes, Error, Param, ParamType, Result, Token}; 11 | use serde::Deserialize; 12 | 13 | /// Contract constructor specification. 14 | #[derive(Debug, Clone, PartialEq, Deserialize)] 15 | pub struct Constructor { 16 | /// Constructor input. 17 | pub inputs: Vec, 18 | } 19 | 20 | impl Constructor { 21 | /// Returns all input params of given constructor. 22 | fn param_types(&self) -> Vec { 23 | self.inputs.iter().map(|p| p.kind.clone()).collect() 24 | } 25 | 26 | /// Prepares ABI constructor call with given input params. 27 | pub fn encode_input(&self, code: Bytes, tokens: &[Token]) -> Result { 28 | let params = self.param_types(); 29 | 30 | if Token::types_check(tokens, ¶ms) { 31 | Ok(code.into_iter().chain(encode(tokens)).collect()) 32 | } else { 33 | Err(Error::InvalidData) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | ci: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest, macOS-latest] 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | components: rustfmt, clippy 21 | 22 | - uses: actions-rs/cargo@v1 23 | with: 24 | command: fmt 25 | args: --all -- --check --config merge_imports=true 26 | 27 | - uses: actions-rs/cargo@v1 28 | with: 29 | command: install 30 | args: cargo-hack 31 | 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: hack 35 | args: check --all --ignore-private --each-feature --no-dev-deps 36 | 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: check 40 | args: --all --all-targets --all-features 41 | 42 | - uses: actions-rs/cargo@v1 43 | with: 44 | command: test 45 | args: --all --all-targets --all-features 46 | 47 | - uses: actions-rs/cargo@v1 48 | with: 49 | command: clippy 50 | args: --all --all-targets --all-features -- -D warnings 51 | -------------------------------------------------------------------------------- /ethabi/src/signature.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::{ 10 | param_type::{ParamType, Writer}, 11 | Hash, 12 | }; 13 | use sha3::{Digest, Keccak256}; 14 | 15 | pub fn short_signature(name: &str, params: &[ParamType]) -> [u8; 4] { 16 | let mut result = [0u8; 4]; 17 | fill_signature(name, params, &mut result); 18 | result 19 | } 20 | 21 | pub fn long_signature(name: &str, params: &[ParamType]) -> Hash { 22 | let mut result = [0u8; 32]; 23 | fill_signature(name, params, &mut result); 24 | result.into() 25 | } 26 | 27 | fn fill_signature(name: &str, params: &[ParamType], result: &mut [u8]) { 28 | let types = params.iter().map(Writer::write).collect::>().join(","); 29 | 30 | let data: Vec = From::from(format!("{}({})", name, types).as_str()); 31 | 32 | result.copy_from_slice(&Keccak256::digest(&data)[..result.len()]) 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::short_signature; 38 | use crate::ParamType; 39 | use hex_literal::hex; 40 | 41 | #[test] 42 | fn test_signature() { 43 | assert_eq!(hex!("cdcd77c0"), short_signature("baz", &[ParamType::Uint(32), ParamType::Bool])); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ethabi/src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use anyhow::anyhow; 10 | use std::{num, string}; 11 | use thiserror::Error; 12 | 13 | /// Ethabi result type 14 | pub type Result = std::result::Result; 15 | 16 | /// Ethabi errors 17 | #[derive(Debug, Error)] 18 | pub enum Error { 19 | /// Invalid entity such as a bad function name. 20 | #[error("Invalid name: {0}")] 21 | InvalidName(String), 22 | /// Invalid data. 23 | #[error("Invalid data")] 24 | InvalidData, 25 | /// Serialization error. 26 | #[error("Serialization error: {0}")] 27 | SerdeJson(#[from] serde_json::Error), 28 | /// Integer parsing error. 29 | #[error("Integer parsing error: {0}")] 30 | ParseInt(#[from] num::ParseIntError), 31 | /// UTF-8 parsing error. 32 | #[error("UTF-8 parsing error: {0}")] 33 | Utf8(#[from] string::FromUtf8Error), 34 | /// Hex string parsing error. 35 | #[error("Hex parsing error: {0}")] 36 | Hex(#[from] hex::FromHexError), 37 | /// Other errors. 38 | #[error("{0}")] 39 | Other(#[from] anyhow::Error), 40 | } 41 | 42 | impl From for Error { 43 | fn from(err: uint::FromDecStrErr) -> Self { 44 | use uint::FromDecStrErr::*; 45 | match err { 46 | InvalidCharacter => anyhow!("Uint parse error: InvalidCharacter"), 47 | InvalidLength => anyhow!("Uint parse error: InvalidLength"), 48 | } 49 | .into() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ethabi/src/log.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::{Bytes, Hash, Result, Token, TopicFilter}; 10 | 11 | /// Common filtering functions that are available for any event. 12 | pub trait LogFilter { 13 | /// Match any log parameters. 14 | fn wildcard_filter(&self) -> TopicFilter; 15 | } 16 | 17 | /// trait common to things (events) that have an associated `Log` type 18 | /// that can be parsed from a `RawLog` 19 | pub trait ParseLog { 20 | /// the associated `Log` type that can be parsed from a `RawLog` 21 | /// by calling `parse_log` 22 | type Log; 23 | 24 | /// parse the associated `Log` type from a `RawLog` 25 | fn parse_log(&self, log: RawLog) -> Result; 26 | } 27 | 28 | /// Ethereum log. 29 | #[derive(Debug, PartialEq, Clone)] 30 | pub struct RawLog { 31 | /// Indexed event params are represented as log topics. 32 | pub topics: Vec, 33 | /// Others are just plain data. 34 | pub data: Bytes, 35 | } 36 | 37 | impl From<(Vec, Bytes)> for RawLog { 38 | fn from(raw: (Vec, Bytes)) -> Self { 39 | RawLog { topics: raw.0, data: raw.1 } 40 | } 41 | } 42 | 43 | /// Decoded log param. 44 | #[derive(Debug, PartialEq, Clone)] 45 | pub struct LogParam { 46 | /// Decoded log name. 47 | pub name: String, 48 | /// Decoded log value. 49 | pub value: Token, 50 | } 51 | 52 | /// Decoded log. 53 | #[derive(Debug, PartialEq, Clone)] 54 | pub struct Log { 55 | /// Log params. 56 | pub params: Vec, 57 | } 58 | -------------------------------------------------------------------------------- /ethabi/src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Utils used by different modules. 10 | 11 | use crate::{Error, Word}; 12 | 13 | /// Converts a vector of bytes with len equal n * 32, to a vector of slices. 14 | pub fn slice_data(data: &[u8]) -> Result, Error> { 15 | if data.len() % 32 != 0 { 16 | return Err(Error::InvalidData); 17 | } 18 | 19 | let times = data.len() / 32; 20 | let mut result = Vec::with_capacity(times); 21 | for i in 0..times { 22 | let mut slice = [0u8; 32]; 23 | let offset = 32 * i; 24 | slice.copy_from_slice(&data[offset..offset + 32]); 25 | result.push(slice); 26 | } 27 | Ok(result) 28 | } 29 | 30 | /// Converts a u32 to a right aligned array of 32 bytes. 31 | pub fn pad_u32(value: u32) -> Word { 32 | let mut padded = [0u8; 32]; 33 | padded[28..32].copy_from_slice(&value.to_be_bytes()); 34 | padded 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::pad_u32; 40 | use hex_literal::hex; 41 | 42 | #[test] 43 | fn test_pad_u32() { 44 | // this will fail if endianness is not supported 45 | assert_eq!( 46 | pad_u32(0).to_vec(), 47 | hex!("0000000000000000000000000000000000000000000000000000000000000000").to_vec() 48 | ); 49 | assert_eq!( 50 | pad_u32(1).to_vec(), 51 | hex!("0000000000000000000000000000000000000000000000000000000000000001").to_vec() 52 | ); 53 | assert_eq!( 54 | pad_u32(0x100).to_vec(), 55 | hex!("0000000000000000000000000000000000000000000000000000000000000100").to_vec() 56 | ); 57 | assert_eq!( 58 | pad_u32(0xffffffff).to_vec(), 59 | hex!("00000000000000000000000000000000000000000000000000000000ffffffff").to_vec() 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ethabi/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Ethereum ABI encoding decoding library. 10 | 11 | #![allow(clippy::module_inception)] 12 | #![warn(missing_docs)] 13 | 14 | mod constructor; 15 | mod contract; 16 | mod decoder; 17 | mod encoder; 18 | mod errors; 19 | mod event; 20 | mod event_param; 21 | mod filter; 22 | mod function; 23 | mod log; 24 | mod operation; 25 | mod param; 26 | pub mod param_type; 27 | mod signature; 28 | pub mod token; 29 | mod tuple_param; 30 | mod util; 31 | 32 | #[cfg(test)] 33 | mod tests; 34 | 35 | pub use crate::{ 36 | constructor::Constructor, 37 | contract::{Contract, Events, Functions}, 38 | decoder::decode, 39 | encoder::encode, 40 | errors::{Error, Result}, 41 | event::Event, 42 | event_param::EventParam, 43 | filter::{RawTopicFilter, Topic, TopicFilter}, 44 | function::Function, 45 | log::{Log, LogFilter, LogParam, ParseLog, RawLog}, 46 | param::Param, 47 | param_type::ParamType, 48 | token::Token, 49 | tuple_param::TupleParam, 50 | }; 51 | 52 | /// ABI word. 53 | pub type Word = [u8; 32]; 54 | 55 | /// ABI address. 56 | pub type Address = ethereum_types::Address; 57 | 58 | /// ABI fixed bytes. 59 | pub type FixedBytes = Vec; 60 | 61 | /// ABI bytes. 62 | pub type Bytes = Vec; 63 | 64 | /// ABI signed integer. 65 | pub type Int = ethereum_types::U256; 66 | 67 | /// ABI unsigned integer. 68 | pub type Uint = ethereum_types::U256; 69 | 70 | /// Commonly used FixedBytes of size 32 71 | pub type Hash = ethereum_types::H256; 72 | 73 | /// Contract functions generated by ethabi-derive 74 | pub trait FunctionOutputDecoder { 75 | /// Output types of the contract function 76 | type Output; 77 | 78 | /// Decodes the given bytes output for the contract function 79 | fn decode(&self, _: &[u8]) -> Result; 80 | } 81 | -------------------------------------------------------------------------------- /res/Validators.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "name": "", 7 | "type": "uint256" 8 | } 9 | ], 10 | "name": "validators", 11 | "outputs": [ 12 | { 13 | "name": "", 14 | "type": "address" 15 | } 16 | ], 17 | "payable": false, 18 | "stateMutability": "view", 19 | "type": "function" 20 | }, 21 | { 22 | "constant": false, 23 | "inputs": [ 24 | { 25 | "name": "newValidators", 26 | "type": "address[2]" 27 | } 28 | ], 29 | "name": "addTwoValidators", 30 | "outputs": [], 31 | "payable": false, 32 | "stateMutability": "nonpayable", 33 | "type": "function" 34 | }, 35 | { 36 | "constant": false, 37 | "inputs": [ 38 | { 39 | "name": "newValidators", 40 | "type": "address[]" 41 | } 42 | ], 43 | "name": "setValidators", 44 | "outputs": [], 45 | "payable": false, 46 | "stateMutability": "nonpayable", 47 | "type": "function" 48 | }, 49 | { 50 | "inputs": [ 51 | { 52 | "name": "newValidators", 53 | "type": "address[]" 54 | } 55 | ], 56 | "payable": false, 57 | "stateMutability": "nonpayable", 58 | "type": "constructor" 59 | }, 60 | { 61 | "anonymous": false, 62 | "inputs": [ 63 | { 64 | "indexed": true, 65 | "name": "validators", 66 | "type": "address[]" 67 | } 68 | ], 69 | "name": "Changed", 70 | "type": "event" 71 | }, 72 | { 73 | "constant": false, 74 | "inputs": [ 75 | { 76 | "name": "title", 77 | "type": "string" 78 | } 79 | ], 80 | "name": "setTitle", 81 | "outputs": [], 82 | "payable": false, 83 | "stateMutability": "nonpayable", 84 | "type": "function" 85 | } 86 | ] 87 | -------------------------------------------------------------------------------- /ethabi/src/param_type/deserialize.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use super::{ParamType, Reader}; 10 | use serde::{ 11 | de::{Error as SerdeError, Visitor}, 12 | Deserialize, Deserializer, 13 | }; 14 | use std::fmt; 15 | 16 | impl<'a> Deserialize<'a> for ParamType { 17 | fn deserialize(deserializer: D) -> Result 18 | where 19 | D: Deserializer<'a>, 20 | { 21 | deserializer.deserialize_identifier(ParamTypeVisitor) 22 | } 23 | } 24 | 25 | struct ParamTypeVisitor; 26 | 27 | impl<'a> Visitor<'a> for ParamTypeVisitor { 28 | type Value = ParamType; 29 | 30 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 31 | write!(formatter, "a correct name of abi-encodable parameter type") 32 | } 33 | 34 | fn visit_str(self, value: &str) -> Result 35 | where 36 | E: SerdeError, 37 | { 38 | Reader::read(value).map_err(|e| SerdeError::custom(format!("{:?}", e).as_str())) 39 | } 40 | 41 | fn visit_string(self, value: String) -> Result 42 | where 43 | E: SerdeError, 44 | { 45 | self.visit_str(value.as_str()) 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use crate::ParamType; 52 | 53 | #[test] 54 | fn param_type_deserialization() { 55 | let s = 56 | r#"["address", "bytes", "bytes32", "bool", "string", "int", "uint", "address[]", "uint[3]", "bool[][5]"]"#; 57 | let deserialized: Vec = serde_json::from_str(s).unwrap(); 58 | assert_eq!( 59 | deserialized, 60 | vec![ 61 | ParamType::Address, 62 | ParamType::Bytes, 63 | ParamType::FixedBytes(32), 64 | ParamType::Bool, 65 | ParamType::String, 66 | ParamType::Int(256), 67 | ParamType::Uint(256), 68 | ParamType::Array(Box::new(ParamType::Address)), 69 | ParamType::FixedArray(Box::new(ParamType::Uint(256)), 3), 70 | ParamType::FixedArray(Box::new(ParamType::Array(Box::new(ParamType::Bool))), 5) 71 | ] 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ethabi/src/param_type/writer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::ParamType; 10 | 11 | /// Output formatter for param type. 12 | pub struct Writer; 13 | 14 | impl Writer { 15 | /// Returns string which is a formatted represenation of param. 16 | pub fn write(param: &ParamType) -> String { 17 | match *param { 18 | ParamType::Address => "address".to_owned(), 19 | ParamType::Bytes => "bytes".to_owned(), 20 | ParamType::FixedBytes(len) => format!("bytes{}", len), 21 | ParamType::Int(len) => format!("int{}", len), 22 | ParamType::Uint(len) => format!("uint{}", len), 23 | ParamType::Bool => "bool".to_owned(), 24 | ParamType::String => "string".to_owned(), 25 | ParamType::FixedArray(ref param, len) => format!("{}[{}]", Writer::write(param), len), 26 | ParamType::Array(ref param) => format!("{}[]", Writer::write(param)), 27 | ParamType::Tuple(ref params) => { 28 | format!("({})", params.iter().map(|ref t| format!("{}", t)).collect::>().join(",")) 29 | } 30 | } 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::Writer; 37 | use crate::ParamType; 38 | 39 | #[test] 40 | fn test_write_param() { 41 | assert_eq!(Writer::write(&ParamType::Address), "address".to_owned()); 42 | assert_eq!(Writer::write(&ParamType::Bytes), "bytes".to_owned()); 43 | assert_eq!(Writer::write(&ParamType::FixedBytes(32)), "bytes32".to_owned()); 44 | assert_eq!(Writer::write(&ParamType::Uint(256)), "uint256".to_owned()); 45 | assert_eq!(Writer::write(&ParamType::Int(64)), "int64".to_owned()); 46 | assert_eq!(Writer::write(&ParamType::Bool), "bool".to_owned()); 47 | assert_eq!(Writer::write(&ParamType::String), "string".to_owned()); 48 | assert_eq!(Writer::write(&ParamType::Array(Box::new(ParamType::Bool))), "bool[]".to_owned()); 49 | assert_eq!(Writer::write(&ParamType::FixedArray(Box::new(ParamType::String), 2)), "string[2]".to_owned()); 50 | assert_eq!( 51 | Writer::write(&ParamType::FixedArray(Box::new(ParamType::Array(Box::new(ParamType::Bool))), 2)), 52 | "bool[][2]".to_owned() 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ethabi/src/token/lenient.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::{ 10 | errors::Error, 11 | token::{StrictTokenizer, Tokenizer}, 12 | Uint, 13 | }; 14 | use anyhow::anyhow; 15 | 16 | /// Tries to parse string as a token. Does not require string to clearly represent the value. 17 | pub struct LenientTokenizer; 18 | 19 | impl Tokenizer for LenientTokenizer { 20 | fn tokenize_address(value: &str) -> Result<[u8; 20], Error> { 21 | StrictTokenizer::tokenize_address(value) 22 | } 23 | 24 | fn tokenize_string(value: &str) -> Result { 25 | StrictTokenizer::tokenize_string(value) 26 | } 27 | 28 | fn tokenize_bool(value: &str) -> Result { 29 | StrictTokenizer::tokenize_bool(value) 30 | } 31 | 32 | fn tokenize_bytes(value: &str) -> Result, Error> { 33 | StrictTokenizer::tokenize_bytes(value) 34 | } 35 | 36 | fn tokenize_fixed_bytes(value: &str, len: usize) -> Result, Error> { 37 | StrictTokenizer::tokenize_fixed_bytes(value, len) 38 | } 39 | 40 | fn tokenize_uint(value: &str) -> Result<[u8; 32], Error> { 41 | let result = StrictTokenizer::tokenize_uint(value); 42 | if result.is_ok() { 43 | return result; 44 | } 45 | 46 | let uint = Uint::from_dec_str(value)?; 47 | Ok(uint.into()) 48 | } 49 | 50 | // We don't have a proper signed int 256-bit long type, so here we're cheating. We build a U256 51 | // out of it and check that it's within the lower/upper bound of a hypothetical I256 type: half 52 | // the `U256::max_value(). 53 | fn tokenize_int(value: &str) -> Result<[u8; 32], Error> { 54 | let result = StrictTokenizer::tokenize_int(value); 55 | if result.is_ok() { 56 | return result; 57 | } 58 | 59 | let abs = Uint::from_dec_str(value.trim_start_matches('-'))?; 60 | let max = Uint::max_value() / 2; 61 | let int = if value.starts_with('-') { 62 | if abs.is_zero() { 63 | return Ok(abs.into()); 64 | } else if abs > max + 1 { 65 | return Err(anyhow!("int256 parse error: Underflow").into()); 66 | } 67 | !abs + 1 // two's complement 68 | } else { 69 | if abs > max { 70 | return Err(anyhow!("int256 parse error: Overflow").into()); 71 | } 72 | abs 73 | }; 74 | Ok(int.into()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /derive/src/contract.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2019 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use proc_macro2::TokenStream; 10 | use quote::quote; 11 | 12 | use crate::{constructor::Constructor, event::Event, function::Function}; 13 | 14 | /// Structure used to generate rust interface for solidity contract. 15 | pub struct Contract { 16 | constructor: Option, 17 | functions: Vec, 18 | events: Vec, 19 | } 20 | 21 | impl<'a> From<&'a ethabi::Contract> for Contract { 22 | fn from(c: &'a ethabi::Contract) -> Self { 23 | Contract { 24 | constructor: c.constructor.as_ref().map(Into::into), 25 | functions: c.functions().map(Into::into).collect(), 26 | events: c.events().map(Into::into).collect(), 27 | } 28 | } 29 | } 30 | 31 | impl Contract { 32 | /// Generates rust interface for a contract. 33 | pub fn generate(&self) -> TokenStream { 34 | let constructor = self.constructor.as_ref().map(Constructor::generate); 35 | let functions: Vec<_> = self.functions.iter().map(Function::generate).collect(); 36 | let events: Vec<_> = self.events.iter().map(Event::generate_event).collect(); 37 | let logs: Vec<_> = self.events.iter().map(Event::generate_log).collect(); 38 | quote! { 39 | use ethabi; 40 | const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; 41 | 42 | #constructor 43 | 44 | /// Contract's functions. 45 | pub mod functions { 46 | use super::INTERNAL_ERR; 47 | #(#functions)* 48 | } 49 | 50 | /// Contract's events. 51 | pub mod events { 52 | use super::INTERNAL_ERR; 53 | #(#events)* 54 | } 55 | 56 | /// Contract's logs. 57 | pub mod logs { 58 | use super::INTERNAL_ERR; 59 | use ethabi; 60 | #(#logs)* 61 | } 62 | } 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod test { 68 | use quote::quote; 69 | 70 | use super::Contract; 71 | 72 | #[test] 73 | fn test_no_body() { 74 | let ethabi_contract = ethabi::Contract { 75 | constructor: None, 76 | functions: Default::default(), 77 | events: Default::default(), 78 | fallback: false, 79 | }; 80 | 81 | let c = Contract::from(ðabi_contract); 82 | 83 | let expected = quote! { 84 | use ethabi; 85 | const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; 86 | 87 | /// Contract's functions. 88 | pub mod functions { 89 | use super::INTERNAL_ERR; 90 | } 91 | 92 | /// Contract's events. 93 | pub mod events { 94 | use super::INTERNAL_ERR; 95 | } 96 | 97 | /// Contract's logs. 98 | pub mod logs { 99 | use super::INTERNAL_ERR; 100 | use ethabi; 101 | } 102 | }; 103 | 104 | assert_eq!(expected.to_string(), c.generate().to_string()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ethabi/src/tuple_param.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Tuple param type. 10 | 11 | use crate::ParamType; 12 | use serde::{ 13 | de::{Error, MapAccess, Visitor}, 14 | Deserialize, Deserializer, 15 | }; 16 | use std::fmt; 17 | 18 | /// Tuple params specification 19 | #[derive(Debug, Clone, PartialEq)] 20 | pub struct TupleParam { 21 | /// Param name. 22 | pub name: Option, 23 | 24 | /// Param type. 25 | pub kind: ParamType, 26 | } 27 | 28 | impl<'a> Deserialize<'a> for TupleParam { 29 | fn deserialize(deserializer: D) -> Result 30 | where 31 | D: Deserializer<'a>, 32 | { 33 | deserializer.deserialize_any(TupleParamVisitor) 34 | } 35 | } 36 | 37 | struct TupleParamVisitor; 38 | 39 | impl<'a> Visitor<'a> for TupleParamVisitor { 40 | type Value = TupleParam; 41 | 42 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 43 | write!(formatter, "a valid tuple parameter spec") 44 | } 45 | 46 | fn visit_map(self, mut map: A) -> Result 47 | where 48 | A: MapAccess<'a>, 49 | { 50 | let mut name = None; 51 | let mut kind = None; 52 | let mut components = None; 53 | 54 | while let Some(ref key) = map.next_key::()? { 55 | match key.as_ref() { 56 | "name" => { 57 | if name.is_some() { 58 | return Err(Error::duplicate_field("name")); 59 | } 60 | name = Some(map.next_value()?); 61 | } 62 | "type" => { 63 | if kind.is_some() { 64 | return Err(Error::duplicate_field("type")); 65 | } 66 | kind = Some(map.next_value()?); 67 | } 68 | "components" => { 69 | if components.is_some() { 70 | return Err(Error::duplicate_field("components")); 71 | } 72 | let component: Vec = map.next_value()?; 73 | components = Some(component) 74 | } 75 | _ => {} 76 | } 77 | } 78 | 79 | let kind = kind.ok_or_else(|| Error::missing_field("kind")).and_then(|param_type| { 80 | if let ParamType::Tuple(_) = param_type { 81 | let tuple_params = components.ok_or_else(|| Error::missing_field("components"))?; 82 | Ok(ParamType::Tuple(tuple_params.into_iter().map(|param| param.kind).collect())) 83 | } else { 84 | Ok(param_type) 85 | } 86 | })?; 87 | 88 | Ok(TupleParam { name, kind }) 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use crate::{ParamType, TupleParam}; 95 | 96 | #[test] 97 | fn tuple_param_deserialization() { 98 | let s = r#"[{ 99 | "name": "foo", 100 | "type": "address" 101 | },{ 102 | "name": "bar", 103 | "type": "address" 104 | },{ 105 | "name": "baz", 106 | "type": "address" 107 | },{ 108 | "type": "bool" 109 | } 110 | ]"#; 111 | 112 | let deserialized: Vec = serde_json::from_str(s).unwrap(); 113 | 114 | assert_eq!( 115 | deserialized, 116 | vec![ 117 | TupleParam { name: Some(String::from("foo")), kind: ParamType::Address }, 118 | TupleParam { name: Some(String::from("bar")), kind: ParamType::Address }, 119 | TupleParam { name: Some(String::from("baz")), kind: ParamType::Address }, 120 | TupleParam { name: None, kind: ParamType::Bool }, 121 | ] 122 | ); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /ethabi/src/function.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Contract function call builder. 10 | 11 | use std::string::ToString; 12 | 13 | use crate::{decode, encode, signature::short_signature, Bytes, Error, Param, ParamType, Result, Token}; 14 | use serde::Deserialize; 15 | 16 | /// Contract function specification. 17 | #[derive(Debug, Clone, PartialEq, Deserialize)] 18 | pub struct Function { 19 | /// Function name. 20 | pub name: String, 21 | /// Function input. 22 | pub inputs: Vec, 23 | /// Function output. 24 | pub outputs: Vec, 25 | /// Constant function. 26 | #[serde(default)] 27 | pub constant: bool, 28 | } 29 | 30 | impl Function { 31 | /// Returns all input params of given function. 32 | fn input_param_types(&self) -> Vec { 33 | self.inputs.iter().map(|p| p.kind.clone()).collect() 34 | } 35 | 36 | /// Returns all output params of given function. 37 | fn output_param_types(&self) -> Vec { 38 | self.outputs.iter().map(|p| p.kind.clone()).collect() 39 | } 40 | 41 | /// Prepares ABI function call with given input params. 42 | pub fn encode_input(&self, tokens: &[Token]) -> Result { 43 | let params = self.input_param_types(); 44 | 45 | if !Token::types_check(tokens, ¶ms) { 46 | return Err(Error::InvalidData); 47 | } 48 | 49 | let signed = short_signature(&self.name, ¶ms).to_vec(); 50 | let encoded = encode(tokens); 51 | Ok(signed.into_iter().chain(encoded.into_iter()).collect()) 52 | } 53 | 54 | /// Parses the ABI function output to list of tokens. 55 | pub fn decode_output(&self, data: &[u8]) -> Result> { 56 | decode(&self.output_param_types(), &data) 57 | } 58 | 59 | /// Parses the ABI function input to a list of tokens. 60 | pub fn decode_input(&self, data: &[u8]) -> Result> { 61 | decode(&self.input_param_types(), &data) 62 | } 63 | 64 | /// Returns a signature that uniquely identifies this function. 65 | /// 66 | /// Examples: 67 | /// - `functionName()` 68 | /// - `functionName():(uint256)` 69 | /// - `functionName(bool):(uint256,string)` 70 | /// - `functionName(uint256,bytes32):(string,uint256)` 71 | pub fn signature(&self) -> String { 72 | let inputs = self.inputs.iter().map(|p| p.kind.to_string()).collect::>().join(","); 73 | 74 | let outputs = self.outputs.iter().map(|p| p.kind.to_string()).collect::>().join(","); 75 | 76 | match (inputs.len(), outputs.len()) { 77 | (_, 0) => format!("{}({})", self.name, inputs), 78 | (_, _) => format!("{}({}):({})", self.name, inputs, outputs), 79 | } 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use crate::{Function, Param, ParamType, Token}; 86 | use hex_literal::hex; 87 | 88 | #[test] 89 | fn test_function_encode_call() { 90 | let func = Function { 91 | name: "baz".to_owned(), 92 | inputs: vec![ 93 | Param { name: "a".to_owned(), kind: ParamType::Uint(32) }, 94 | Param { name: "b".to_owned(), kind: ParamType::Bool }, 95 | ], 96 | outputs: vec![], 97 | constant: false, 98 | }; 99 | 100 | let mut uint = [0u8; 32]; 101 | uint[31] = 69; 102 | let encoded = func.encode_input(&[Token::Uint(uint.into()), Token::Bool(true)]).unwrap(); 103 | let expected = hex!("cdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001").to_vec(); 104 | assert_eq!(encoded, expected); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ethabi/src/param_type/param_type.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Function and event param types. 10 | 11 | use super::Writer; 12 | use std::fmt; 13 | 14 | /// Function and event param types. 15 | #[derive(Debug, Clone, PartialEq)] 16 | pub enum ParamType { 17 | /// Address. 18 | Address, 19 | /// Bytes. 20 | Bytes, 21 | /// Signed integer. 22 | Int(usize), 23 | /// Unsigned integer. 24 | Uint(usize), 25 | /// Boolean. 26 | Bool, 27 | /// String. 28 | String, 29 | /// Array of unknown size. 30 | Array(Box), 31 | /// Vector of bytes with fixed size. 32 | FixedBytes(usize), 33 | /// Array with fixed size. 34 | FixedArray(Box, usize), 35 | /// Tuple containing different types 36 | Tuple(Vec), 37 | } 38 | 39 | impl fmt::Display for ParamType { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | write!(f, "{}", Writer::write(self)) 42 | } 43 | } 44 | 45 | impl ParamType { 46 | /// returns whether a zero length byte slice (`0x`) is 47 | /// a valid encoded form of this param type 48 | pub fn is_empty_bytes_valid_encoding(&self) -> bool { 49 | match self { 50 | ParamType::FixedBytes(len) => *len == 0, 51 | ParamType::FixedArray(_, len) => *len == 0, 52 | _ => false, 53 | } 54 | } 55 | 56 | /// returns whether a ParamType is dynamic 57 | /// used to decide how the ParamType should be encoded 58 | pub fn is_dynamic(&self) -> bool { 59 | match self { 60 | ParamType::Bytes | ParamType::String | ParamType::Array(_) => true, 61 | ParamType::FixedArray(elem_type, _) => elem_type.is_dynamic(), 62 | ParamType::Tuple(params) => params.iter().any(|param| param.is_dynamic()), 63 | _ => false, 64 | } 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use crate::ParamType; 71 | 72 | #[test] 73 | fn test_param_type_display() { 74 | assert_eq!(format!("{}", ParamType::Address), "address".to_owned()); 75 | assert_eq!(format!("{}", ParamType::Bytes), "bytes".to_owned()); 76 | assert_eq!(format!("{}", ParamType::FixedBytes(32)), "bytes32".to_owned()); 77 | assert_eq!(format!("{}", ParamType::Uint(256)), "uint256".to_owned()); 78 | assert_eq!(format!("{}", ParamType::Int(64)), "int64".to_owned()); 79 | assert_eq!(format!("{}", ParamType::Bool), "bool".to_owned()); 80 | assert_eq!(format!("{}", ParamType::String), "string".to_owned()); 81 | assert_eq!(format!("{}", ParamType::Array(Box::new(ParamType::Bool))), "bool[]".to_owned()); 82 | assert_eq!(format!("{}", ParamType::FixedArray(Box::new(ParamType::Uint(256)), 2)), "uint256[2]".to_owned()); 83 | assert_eq!(format!("{}", ParamType::FixedArray(Box::new(ParamType::String), 2)), "string[2]".to_owned()); 84 | assert_eq!( 85 | format!("{}", ParamType::FixedArray(Box::new(ParamType::Array(Box::new(ParamType::Bool))), 2)), 86 | "bool[][2]".to_owned() 87 | ); 88 | } 89 | 90 | #[test] 91 | fn test_is_dynamic() { 92 | assert_eq!(ParamType::Address.is_dynamic(), false); 93 | assert_eq!(ParamType::Bytes.is_dynamic(), true); 94 | assert_eq!(ParamType::FixedBytes(32).is_dynamic(), false); 95 | assert_eq!(ParamType::Uint(256).is_dynamic(), false); 96 | assert_eq!(ParamType::Int(64).is_dynamic(), false); 97 | assert_eq!(ParamType::Bool.is_dynamic(), false); 98 | assert_eq!(ParamType::String.is_dynamic(), true); 99 | assert_eq!(ParamType::Array(Box::new(ParamType::Bool)).is_dynamic(), true); 100 | assert_eq!(ParamType::FixedArray(Box::new(ParamType::Uint(256)), 2).is_dynamic(), false); 101 | assert_eq!(ParamType::FixedArray(Box::new(ParamType::String), 2).is_dynamic(), true); 102 | assert_eq!(ParamType::FixedArray(Box::new(ParamType::Array(Box::new(ParamType::Bool))), 2).is_dynamic(), true); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /res/eip20.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": false, 4 | "inputs": [ 5 | { 6 | "name": "_spender", 7 | "type": "address" 8 | }, 9 | { 10 | "name": "_value", 11 | "type": "uint256" 12 | } 13 | ], 14 | "name": "approve", 15 | "outputs": [ 16 | { 17 | "name": "success", 18 | "type": "bool" 19 | } 20 | ], 21 | "type": "function" 22 | }, 23 | { 24 | "constant": true, 25 | "inputs": [], 26 | "name": "totalSupply", 27 | "outputs": [ 28 | { 29 | "name": "total", 30 | "type": "uint256" 31 | } 32 | ], 33 | "type": "function" 34 | }, 35 | { 36 | "constant": false, 37 | "inputs": [ 38 | { 39 | "name": "_from", 40 | "type": "address" 41 | }, 42 | { 43 | "name": "_to", 44 | "type": "address" 45 | }, 46 | { 47 | "name": "_value", 48 | "type": "uint256" 49 | } 50 | ], 51 | "name": "transferFrom", 52 | "outputs": [ 53 | { 54 | "name": "success", 55 | "type": "bool" 56 | } 57 | ], 58 | "type": "function" 59 | }, 60 | { 61 | "constant": true, 62 | "inputs": [ 63 | { 64 | "name": "_owner", 65 | "type": "address" 66 | } 67 | ], 68 | "name": "balanceOf", 69 | "outputs": [ 70 | { 71 | "name": "balance", 72 | "type": "uint256" 73 | } 74 | ], 75 | "type": "function" 76 | }, 77 | { 78 | "constant": false, 79 | "inputs": [ 80 | { 81 | "name": "_to", 82 | "type": "address" 83 | }, 84 | { 85 | "name": "_value", 86 | "type": "uint256" 87 | } 88 | ], 89 | "name": "transfer", 90 | "outputs": [ 91 | { 92 | "name": "success", 93 | "type": "bool" 94 | } 95 | ], 96 | "type": "function" 97 | }, 98 | { 99 | "constant": true, 100 | "inputs": [ 101 | { 102 | "name": "_owner", 103 | "type": "address" 104 | }, 105 | { 106 | "name": "_spender", 107 | "type": "address" 108 | } 109 | ], 110 | "name": "allowance", 111 | "outputs": [ 112 | { 113 | "name": "remaining", 114 | "type": "uint256" 115 | } 116 | ], 117 | "type": "function" 118 | }, 119 | { 120 | "anonymous": false, 121 | "inputs": [ 122 | { 123 | "indexed": true, 124 | "name": "from", 125 | "type": "address" 126 | }, 127 | { 128 | "indexed": true, 129 | "name": "to", 130 | "type": "address" 131 | }, 132 | { 133 | "indexed": false, 134 | "name": "value", 135 | "type": "uint256" 136 | } 137 | ], 138 | "name": "Transfer", 139 | "type": "event" 140 | }, 141 | { 142 | "anonymous": false, 143 | "inputs": [ 144 | { 145 | "indexed": true, 146 | "name": "owner", 147 | "type": "address" 148 | }, 149 | { 150 | "indexed": true, 151 | "name": "spender", 152 | "type": "address" 153 | }, 154 | { 155 | "indexed": false, 156 | "name": "value", 157 | "type": "uint256" 158 | } 159 | ], 160 | "name": "Approval", 161 | "type": "event" 162 | } 163 | ] 164 | -------------------------------------------------------------------------------- /ethabi/src/event_param.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Event param specification. 10 | 11 | use crate::{ParamType, TupleParam}; 12 | use serde::{ 13 | de::{Error, MapAccess, Visitor}, 14 | Deserialize, Deserializer, 15 | }; 16 | use std::fmt; 17 | 18 | /// Event param specification. 19 | #[derive(Debug, Clone, PartialEq)] 20 | pub struct EventParam { 21 | /// Param name. 22 | pub name: String, 23 | /// Param type. 24 | pub kind: ParamType, 25 | /// Indexed flag. If true, param is used to build block bloom. 26 | pub indexed: bool, 27 | } 28 | 29 | impl<'a> Deserialize<'a> for EventParam { 30 | fn deserialize(deserializer: D) -> Result 31 | where 32 | D: Deserializer<'a>, 33 | { 34 | deserializer.deserialize_any(EventParamVisitor) 35 | } 36 | } 37 | 38 | struct EventParamVisitor; 39 | 40 | impl<'a> Visitor<'a> for EventParamVisitor { 41 | type Value = EventParam; 42 | 43 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 44 | write!(formatter, "a valid event parameter spec") 45 | } 46 | 47 | fn visit_map(self, mut map: V) -> Result 48 | where 49 | V: MapAccess<'a>, 50 | { 51 | let mut name = None; 52 | let mut kind = None; 53 | let mut indexed = None; 54 | let mut components = None; 55 | 56 | while let Some(ref key) = map.next_key::()? { 57 | match key.as_ref() { 58 | "name" => { 59 | if name.is_some() { 60 | return Err(Error::duplicate_field("name")); 61 | } 62 | name = Some(map.next_value()?); 63 | } 64 | "type" => { 65 | if kind.is_some() { 66 | return Err(Error::duplicate_field("kind")); 67 | } 68 | kind = Some(map.next_value()?); 69 | } 70 | "components" => { 71 | if components.is_some() { 72 | return Err(Error::duplicate_field("components")); 73 | } 74 | let component: Vec = map.next_value()?; 75 | components = Some(component) 76 | } 77 | "indexed" => { 78 | if indexed.is_some() { 79 | return Err(Error::duplicate_field("indexed")); 80 | } 81 | indexed = Some(map.next_value()?); 82 | } 83 | _ => {} 84 | } 85 | } 86 | let name = name.ok_or_else(|| Error::missing_field("name"))?; 87 | let kind = kind.ok_or_else(|| Error::missing_field("kind")).and_then(|param_type| { 88 | if let ParamType::Tuple(_) = param_type { 89 | let tuple_params = components.ok_or_else(|| Error::missing_field("components"))?; 90 | Ok(ParamType::Tuple(tuple_params.into_iter().map(|param| param.kind).collect())) 91 | } else { 92 | Ok(param_type) 93 | } 94 | })?; 95 | let indexed = indexed.unwrap_or(false); 96 | Ok(EventParam { name, kind, indexed }) 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use crate::{EventParam, ParamType}; 103 | 104 | #[test] 105 | fn event_param_deserialization() { 106 | let s = r#"{ 107 | "name": "foo", 108 | "type": "address", 109 | "indexed": true 110 | }"#; 111 | 112 | let deserialized: EventParam = serde_json::from_str(s).unwrap(); 113 | 114 | assert_eq!(deserialized, EventParam { name: "foo".to_owned(), kind: ParamType::Address, indexed: true }); 115 | } 116 | #[test] 117 | fn event_param_tuple_deserialization() { 118 | let s = r#"{ 119 | "name": "foo", 120 | "type": "tuple", 121 | "indexed": true, 122 | "components": [ 123 | { 124 | "name": "amount", 125 | "type": "uint48" 126 | }, 127 | { 128 | "name": "things", 129 | "type": "tuple", 130 | "components": [ 131 | { 132 | "name": "baseTupleParam", 133 | "type": "address" 134 | } 135 | ] 136 | } 137 | ] 138 | }"#; 139 | 140 | let deserialized: EventParam = serde_json::from_str(s).unwrap(); 141 | 142 | assert_eq!( 143 | deserialized, 144 | EventParam { 145 | name: "foo".to_owned(), 146 | kind: ParamType::Tuple(vec![ParamType::Uint(48), ParamType::Tuple(vec![ParamType::Address])]), 147 | indexed: true, 148 | } 149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /ethabi/src/operation.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Operation type. 10 | 11 | use crate::{Constructor, Event, Function}; 12 | use serde::{de::Error as SerdeError, Deserialize, Deserializer}; 13 | use serde_json::{value::from_value, Value}; 14 | 15 | /// Operation type. 16 | #[derive(Clone, Debug, PartialEq)] 17 | pub enum Operation { 18 | /// Contract constructor. 19 | Constructor(Constructor), 20 | /// Contract function. 21 | Function(Function), 22 | /// Contract event. 23 | Event(Event), 24 | /// Fallback, ignored. 25 | Fallback, 26 | } 27 | 28 | impl<'a> Deserialize<'a> for Operation { 29 | fn deserialize(deserializer: D) -> Result 30 | where 31 | D: Deserializer<'a>, 32 | { 33 | let v: Value = Deserialize::deserialize(deserializer)?; 34 | let map = v.as_object().ok_or_else(|| SerdeError::custom("Invalid operation"))?; 35 | let s = map.get("type").and_then(Value::as_str).ok_or_else(|| SerdeError::custom("Invalid operation type"))?; 36 | 37 | // This is a workaround to support non-spec compliant function and event names, 38 | // see: https://github.com/paritytech/parity/issues/4122 39 | fn sanitize_name(name: &mut String) { 40 | if let Some(i) = name.find('(') { 41 | name.truncate(i); 42 | } 43 | } 44 | 45 | let result = match s { 46 | "constructor" => from_value(v).map(Operation::Constructor), 47 | "function" => from_value(v).map(|mut f: Function| { 48 | sanitize_name(&mut f.name); 49 | Operation::Function(f) 50 | }), 51 | "event" => from_value(v).map(|mut e: Event| { 52 | sanitize_name(&mut e.name); 53 | Operation::Event(e) 54 | }), 55 | "fallback" => Ok(Operation::Fallback), 56 | _ => Err(SerdeError::custom("Invalid operation type.")), 57 | }; 58 | result.map_err(|e| D::Error::custom(e.to_string())) 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::Operation; 65 | use crate::{Function, Param, ParamType}; 66 | 67 | #[test] 68 | fn deserialize_operation() { 69 | let s = r#"{ 70 | "type":"function", 71 | "inputs": [{ 72 | "name":"a", 73 | "type":"address" 74 | }], 75 | "name":"foo", 76 | "outputs": [] 77 | }"#; 78 | 79 | let deserialized: Operation = serde_json::from_str(s).unwrap(); 80 | 81 | assert_eq!( 82 | deserialized, 83 | Operation::Function(Function { 84 | name: "foo".to_owned(), 85 | inputs: vec![Param { name: "a".to_owned(), kind: ParamType::Address }], 86 | outputs: vec![], 87 | constant: false, 88 | }) 89 | ); 90 | } 91 | 92 | #[test] 93 | fn deserialize_sanitize_function_name() { 94 | fn test_sanitize_function_name(name: &str, expected: &str) { 95 | let s = format!( 96 | r#"{{ 97 | "type":"function", 98 | "inputs": [{{ 99 | "name":"a", 100 | "type":"address" 101 | }}], 102 | "name":"{}", 103 | "outputs": [] 104 | }}"#, 105 | name 106 | ); 107 | 108 | let deserialized: Operation = serde_json::from_str(&s).unwrap(); 109 | let function = match deserialized { 110 | Operation::Function(f) => f, 111 | _ => panic!("expected funciton"), 112 | }; 113 | 114 | assert_eq!(function.name, expected); 115 | } 116 | 117 | test_sanitize_function_name("foo", "foo"); 118 | test_sanitize_function_name("foo()", "foo"); 119 | test_sanitize_function_name("()", ""); 120 | test_sanitize_function_name("", ""); 121 | } 122 | 123 | #[test] 124 | fn deserialize_sanitize_event_name() { 125 | fn test_sanitize_event_name(name: &str, expected: &str) { 126 | let s = format!( 127 | r#"{{ 128 | "type":"event", 129 | "inputs": [{{ 130 | "name":"a", 131 | "type":"address", 132 | "indexed":true 133 | }}], 134 | "name":"{}", 135 | "outputs": [], 136 | "anonymous": false 137 | }}"#, 138 | name 139 | ); 140 | 141 | let deserialized: Operation = serde_json::from_str(&s).unwrap(); 142 | let event = match deserialized { 143 | Operation::Event(e) => e, 144 | _ => panic!("expected event!"), 145 | }; 146 | 147 | assert_eq!(event.name, expected); 148 | } 149 | 150 | test_sanitize_event_name("foo", "foo"); 151 | test_sanitize_event_name("foo()", "foo"); 152 | test_sanitize_event_name("()", ""); 153 | test_sanitize_event_name("", ""); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /derive/src/constructor.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2019 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use proc_macro2::TokenStream; 10 | use quote::quote; 11 | 12 | use super::{ 13 | from_template_param, get_template_names, input_names, rust_type, template_param_type, to_ethabi_param_vec, to_token, 14 | }; 15 | 16 | /// Structure used to generate contract's constructor interface. 17 | pub struct Constructor { 18 | inputs_declarations: Vec, 19 | inputs_definitions: Vec, 20 | tokenize: Vec, 21 | recreate_inputs: TokenStream, 22 | } 23 | 24 | impl<'a> From<&'a ethabi::Constructor> for Constructor { 25 | fn from(c: &'a ethabi::Constructor) -> Self { 26 | // [param0, hello_world, param2] 27 | let input_names = input_names(&c.inputs); 28 | 29 | // [T0: Into, T1: Into, T2: IntoIterator, U2 = Into] 30 | let inputs_declarations = 31 | c.inputs.iter().enumerate().map(|(index, param)| template_param_type(¶m.kind, index)).collect(); 32 | 33 | // [Uint, Bytes, Vec] 34 | let kinds: Vec<_> = c.inputs.iter().map(|param| rust_type(¶m.kind)).collect(); 35 | 36 | // [T0, T1, T2] 37 | let template_names: Vec<_> = get_template_names(&kinds); 38 | 39 | // [param0: T0, hello_world: T1, param2: T2] 40 | let inputs_definitions = input_names 41 | .iter() 42 | .zip(template_names.iter()) 43 | .map(|(param_name, template_name)| quote! { #param_name: #template_name }); 44 | 45 | let inputs_definitions = Some(quote! { code: ethabi::Bytes }).into_iter().chain(inputs_definitions).collect(); 46 | 47 | // [Token::Uint(param0.into()), Token::Bytes(hello_world.into()), Token::Array(param2.into_iter().map(Into::into).collect())] 48 | let tokenize: Vec<_> = input_names 49 | .iter() 50 | .zip(c.inputs.iter()) 51 | .map(|(param_name, param)| to_token(&from_template_param(¶m.kind, ¶m_name), ¶m.kind)) 52 | .collect(); 53 | 54 | Constructor { 55 | inputs_declarations, 56 | inputs_definitions, 57 | tokenize, 58 | recreate_inputs: to_ethabi_param_vec(&c.inputs), 59 | } 60 | } 61 | } 62 | 63 | impl Constructor { 64 | /// Generates contract constructor interface. 65 | pub fn generate(&self) -> TokenStream { 66 | let declarations = &self.inputs_declarations; 67 | let definitions = &self.inputs_definitions; 68 | let tokenize = &self.tokenize; 69 | let recreate_inputs = &self.recreate_inputs; 70 | 71 | quote! { 72 | /// Encodes a call to contract's constructor. 73 | pub fn constructor<#(#declarations),*>(#(#definitions),*) -> ethabi::Bytes { 74 | let c = ethabi::Constructor { 75 | inputs: #recreate_inputs, 76 | }; 77 | let tokens = vec![#(#tokenize),*]; 78 | c.encode_input(code, &tokens).expect(INTERNAL_ERR) 79 | } 80 | } 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use super::Constructor; 87 | use quote::quote; 88 | 89 | #[test] 90 | fn test_no_params() { 91 | let ethabi_constructor = ethabi::Constructor { inputs: vec![] }; 92 | 93 | let c = Constructor::from(ðabi_constructor); 94 | 95 | let expected = quote! { 96 | /// Encodes a call to contract's constructor. 97 | pub fn constructor<>(code: ethabi::Bytes) -> ethabi::Bytes { 98 | let c = ethabi::Constructor { 99 | inputs: vec![], 100 | }; 101 | let tokens = vec![]; 102 | c.encode_input(code, &tokens).expect(INTERNAL_ERR) 103 | } 104 | }; 105 | 106 | assert_eq!(expected.to_string(), c.generate().to_string()); 107 | } 108 | 109 | #[test] 110 | fn test_one_param() { 111 | let ethabi_constructor = ethabi::Constructor { 112 | inputs: vec![ethabi::Param { name: "foo".into(), kind: ethabi::ParamType::Uint(256) }], 113 | }; 114 | 115 | let c = Constructor::from(ðabi_constructor); 116 | 117 | let expected = quote! { 118 | /// Encodes a call to contract's constructor. 119 | pub fn constructor >(code: ethabi::Bytes, foo: T0) -> ethabi::Bytes { 120 | let c = ethabi::Constructor { 121 | inputs: vec![ethabi::Param { 122 | name: "foo".to_owned(), 123 | kind: ethabi::ParamType::Uint(256usize) 124 | }], 125 | }; 126 | let tokens = vec![ethabi::Token::Uint(foo.into())]; 127 | c.encode_input(code, &tokens).expect(INTERNAL_ERR) 128 | } 129 | }; 130 | 131 | assert_eq!(expected.to_string(), c.generate().to_string()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /ethabi/src/contract.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::{errors, operation::Operation, Constructor, Error, Event, Function}; 10 | use serde::{ 11 | de::{SeqAccess, Visitor}, 12 | Deserialize, Deserializer, 13 | }; 14 | use std::{ 15 | collections::{hash_map::Values, HashMap}, 16 | fmt, io, 17 | iter::Flatten, 18 | }; 19 | 20 | /// API building calls to contracts ABI. 21 | #[derive(Clone, Debug, PartialEq)] 22 | pub struct Contract { 23 | /// Contract constructor. 24 | pub constructor: Option, 25 | /// Contract functions. 26 | pub functions: HashMap>, 27 | /// Contract events, maps signature to event. 28 | pub events: HashMap>, 29 | /// Contract has fallback function. 30 | pub fallback: bool, 31 | } 32 | 33 | impl<'a> Deserialize<'a> for Contract { 34 | fn deserialize(deserializer: D) -> Result 35 | where 36 | D: Deserializer<'a>, 37 | { 38 | deserializer.deserialize_any(ContractVisitor) 39 | } 40 | } 41 | 42 | struct ContractVisitor; 43 | 44 | impl<'a> Visitor<'a> for ContractVisitor { 45 | type Value = Contract; 46 | 47 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 48 | formatter.write_str("valid abi spec file") 49 | } 50 | 51 | fn visit_seq(self, mut seq: A) -> Result 52 | where 53 | A: SeqAccess<'a>, 54 | { 55 | let mut result = 56 | Contract { constructor: None, functions: HashMap::default(), events: HashMap::default(), fallback: false }; 57 | 58 | while let Some(operation) = seq.next_element()? { 59 | match operation { 60 | Operation::Constructor(constructor) => { 61 | result.constructor = Some(constructor); 62 | } 63 | Operation::Function(func) => { 64 | result.functions.entry(func.name.clone()).or_default().push(func); 65 | } 66 | Operation::Event(event) => { 67 | result.events.entry(event.name.clone()).or_default().push(event); 68 | } 69 | Operation::Fallback => { 70 | result.fallback = true; 71 | } 72 | } 73 | } 74 | 75 | Ok(result) 76 | } 77 | } 78 | 79 | impl Contract { 80 | /// Loads contract from json. 81 | pub fn load(reader: T) -> errors::Result { 82 | serde_json::from_reader(reader).map_err(From::from) 83 | } 84 | 85 | /// Creates constructor call builder. 86 | pub fn constructor(&self) -> Option<&Constructor> { 87 | self.constructor.as_ref() 88 | } 89 | 90 | /// Get the function named `name`, the first if there are overloaded 91 | /// versions of the same function. 92 | pub fn function(&self, name: &str) -> errors::Result<&Function> { 93 | self.functions.get(name).into_iter().flatten().next().ok_or_else(|| Error::InvalidName(name.to_owned())) 94 | } 95 | 96 | /// Get the contract event named `name`, the first if there are multiple. 97 | pub fn event(&self, name: &str) -> errors::Result<&Event> { 98 | self.events.get(name).into_iter().flatten().next().ok_or_else(|| Error::InvalidName(name.to_owned())) 99 | } 100 | 101 | /// Get all contract events named `name`. 102 | pub fn events_by_name(&self, name: &str) -> errors::Result<&Vec> { 103 | self.events.get(name).ok_or_else(|| Error::InvalidName(name.to_owned())) 104 | } 105 | 106 | /// Get all functions named `name`. 107 | pub fn functions_by_name(&self, name: &str) -> errors::Result<&Vec> { 108 | self.functions.get(name).ok_or_else(|| Error::InvalidName(name.to_owned())) 109 | } 110 | 111 | /// Iterate over all functions of the contract in arbitrary order. 112 | pub fn functions(&self) -> Functions { 113 | Functions(self.functions.values().flatten()) 114 | } 115 | 116 | /// Iterate over all events of the contract in arbitrary order. 117 | pub fn events(&self) -> Events { 118 | Events(self.events.values().flatten()) 119 | } 120 | 121 | /// Returns true if contract has fallback 122 | pub fn fallback(&self) -> bool { 123 | self.fallback 124 | } 125 | } 126 | 127 | /// Contract functions iterator. 128 | pub struct Functions<'a>(Flatten>>); 129 | 130 | impl<'a> Iterator for Functions<'a> { 131 | type Item = &'a Function; 132 | 133 | fn next(&mut self) -> Option { 134 | self.0.next() 135 | } 136 | } 137 | 138 | /// Contract events iterator. 139 | pub struct Events<'a>(Flatten>>); 140 | 141 | impl<'a> Iterator for Events<'a> { 142 | type Item = &'a Event; 143 | 144 | fn next(&mut self) -> Option { 145 | self.0.next() 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Test crate 2 | 3 | #![deny(missing_docs)] 4 | #![deny(dead_code)] 5 | #![deny(unused_imports)] 6 | 7 | use ethabi_contract::use_contract; 8 | 9 | use_contract!(eip20, "../res/eip20.abi"); 10 | use_contract!(constructor, "../res/constructor.abi"); 11 | use_contract!(validators, "../res/Validators.abi"); 12 | use_contract!(operations, "../res/Operations.abi"); 13 | use_contract!(urlhint, "../res/urlhint.abi"); 14 | use_contract!(test_rust_keywords, "../res/test_rust_keywords.abi"); 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use crate::{eip20, validators}; 19 | use ethabi::{Address, Uint}; 20 | use hex_literal::hex; 21 | 22 | struct Wrapper([u8; 20]); 23 | 24 | impl Into
for Wrapper { 25 | fn into(self) -> Address { 26 | self.0.into() 27 | } 28 | } 29 | 30 | #[test] 31 | fn test_encoding_function_input_as_array() { 32 | use validators::functions; 33 | 34 | let first = [0x11u8; 20]; 35 | let second = [0x22u8; 20]; 36 | 37 | let encoded_from_vec = functions::set_validators::encode_input(vec![first, second]); 38 | let encoded_from_vec_iter = functions::set_validators::encode_input(vec![first, second].into_iter()); 39 | let encoded_from_vec_wrapped = functions::set_validators::encode_input(vec![Wrapper(first), Wrapper(second)]); 40 | 41 | let expected = "9300c9260000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000011111111111111111111111111111111111111110000000000000000000000002222222222222222222222222222222222222222".to_owned(); 42 | assert_eq!(expected, hex::encode(&encoded_from_vec)); 43 | assert_eq!(expected, hex::encode(&encoded_from_vec_iter)); 44 | assert_eq!(expected, hex::encode(&encoded_from_vec_wrapped)); 45 | } 46 | 47 | #[test] 48 | fn test_decoding_function_output() { 49 | // Make sure that the output param type of the derived contract is correct 50 | 51 | // given 52 | 53 | let output = hex!("000000000000000000000000000000000000000000000000000000000036455B").to_vec(); 54 | 55 | // when 56 | let decoded_output = eip20::functions::total_supply::decode_output(&output).unwrap(); 57 | 58 | // then 59 | 60 | let expected_output: Uint = 0x36455b.into(); 61 | assert_eq!(expected_output, decoded_output); 62 | } 63 | 64 | #[test] 65 | fn test_encoding_constructor_as_array() { 66 | use validators::constructor; 67 | 68 | let code = Vec::new(); 69 | let first = [0x11u8; 20]; 70 | let second = [0x22u8; 20]; 71 | 72 | let encoded_from_vec = constructor(code.clone(), vec![first, second]); 73 | let encoded_from_vec_iter = constructor(code.clone(), vec![first, second].into_iter()); 74 | let encoded_from_vec_wrapped = constructor(code, vec![Wrapper(first), Wrapper(second)]); 75 | 76 | let expected = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000011111111111111111111111111111111111111110000000000000000000000002222222222222222222222222222222222222222".to_owned(); 77 | assert_eq!(expected, hex::encode(&encoded_from_vec)); 78 | assert_eq!(expected, hex::encode(&encoded_from_vec_iter)); 79 | assert_eq!(expected, hex::encode(&encoded_from_vec_wrapped)); 80 | } 81 | 82 | #[test] 83 | fn test_encoding_function_input_as_fixed_array() { 84 | use validators::functions; 85 | 86 | let first = [0x11u8; 20]; 87 | let second = [0x22u8; 20]; 88 | 89 | let encoded_from_array = functions::add_two_validators::encode_input([first, second]); 90 | let encoded_from_array_wrapped = functions::add_two_validators::encode_input([Wrapper(first), Wrapper(second)]); 91 | let encoded_from_string = functions::set_title::encode_input("foo"); 92 | 93 | let expected_array = "7de33d2000000000000000000000000011111111111111111111111111111111111111110000000000000000000000002222222222222222222222222222222222222222".to_owned(); 94 | let expected_string = "72910be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000".to_owned(); 95 | assert_eq!(expected_array, hex::encode(&encoded_from_array)); 96 | assert_eq!(expected_array, hex::encode(&encoded_from_array_wrapped)); 97 | assert_eq!(expected_string, hex::encode(&encoded_from_string)) 98 | } 99 | 100 | #[test] 101 | fn encoding_input_works() { 102 | let expected = "dd62ed3e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101".to_owned(); 103 | let owner = [0u8; 20]; 104 | let spender = [1u8; 20]; 105 | let encoded = eip20::functions::allowance::encode_input(owner, spender); 106 | // 4 bytes signature + 2 * 32 bytes for params 107 | assert_eq!(hex::encode(&encoded), expected); 108 | 109 | let from: Address = [2u8; 20].into(); 110 | let to: Address = [3u8; 20].into(); 111 | let to2: Address = [4u8; 20].into(); 112 | let _filter = eip20::events::transfer::filter(from, vec![to, to2]); 113 | let wildcard_filter = eip20::events::transfer::filter(None, None); 114 | let wildcard_filter_sugared = eip20::events::transfer::wildcard_filter(); 115 | assert_eq!(wildcard_filter, wildcard_filter_sugared); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /ethabi/src/token/strict.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::{errors::Error, token::Tokenizer}; 10 | 11 | /// Tries to parse string as a token. Require string to clearly represent the value. 12 | pub struct StrictTokenizer; 13 | 14 | impl Tokenizer for StrictTokenizer { 15 | fn tokenize_address(value: &str) -> Result<[u8; 20], Error> { 16 | let hex: Vec = hex::decode(value)?; 17 | match hex.len() == 20 { 18 | false => Err(Error::InvalidData), 19 | true => { 20 | let mut address = [0u8; 20]; 21 | address.copy_from_slice(&hex); 22 | Ok(address) 23 | } 24 | } 25 | } 26 | 27 | fn tokenize_string(value: &str) -> Result { 28 | Ok(value.to_owned()) 29 | } 30 | 31 | fn tokenize_bool(value: &str) -> Result { 32 | match value { 33 | "true" | "1" => Ok(true), 34 | "false" | "0" => Ok(false), 35 | _ => Err(Error::InvalidData), 36 | } 37 | } 38 | 39 | fn tokenize_bytes(value: &str) -> Result, Error> { 40 | hex::decode(value).map_err(Into::into) 41 | } 42 | 43 | fn tokenize_fixed_bytes(value: &str, len: usize) -> Result, Error> { 44 | let hex: Vec = hex::decode(value)?; 45 | match hex.len() == len { 46 | true => Ok(hex), 47 | false => Err(Error::InvalidData), 48 | } 49 | } 50 | 51 | fn tokenize_uint(value: &str) -> Result<[u8; 32], Error> { 52 | let hex: Vec = hex::decode(value)?; 53 | match hex.len() == 32 { 54 | true => { 55 | let mut uint = [0u8; 32]; 56 | uint.copy_from_slice(&hex); 57 | Ok(uint) 58 | } 59 | false => Err(Error::InvalidData), 60 | } 61 | } 62 | 63 | fn tokenize_int(value: &str) -> Result<[u8; 32], Error> { 64 | Self::tokenize_uint(value) 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use crate::{ 71 | token::{StrictTokenizer, Token, Tokenizer}, 72 | ParamType, 73 | }; 74 | 75 | #[test] 76 | fn tokenize_address() { 77 | assert_eq!( 78 | StrictTokenizer::tokenize(&ParamType::Address, "1111111111111111111111111111111111111111").unwrap(), 79 | Token::Address([0x11u8; 20].into()) 80 | ); 81 | assert_eq!( 82 | StrictTokenizer::tokenize(&ParamType::Address, "2222222222222222222222222222222222222222").unwrap(), 83 | Token::Address([0x22u8; 20].into()) 84 | ); 85 | } 86 | 87 | #[test] 88 | fn tokenize_string() { 89 | assert_eq!( 90 | StrictTokenizer::tokenize(&ParamType::String, "gavofyork").unwrap(), 91 | Token::String("gavofyork".to_owned()) 92 | ); 93 | assert_eq!(StrictTokenizer::tokenize(&ParamType::String, "hello").unwrap(), Token::String("hello".to_owned())); 94 | } 95 | 96 | #[test] 97 | fn tokenize_bool() { 98 | assert_eq!(StrictTokenizer::tokenize(&ParamType::Bool, "true").unwrap(), Token::Bool(true)); 99 | assert_eq!(StrictTokenizer::tokenize(&ParamType::Bool, "1").unwrap(), Token::Bool(true)); 100 | assert_eq!(StrictTokenizer::tokenize(&ParamType::Bool, "false").unwrap(), Token::Bool(false)); 101 | assert_eq!(StrictTokenizer::tokenize(&ParamType::Bool, "0").unwrap(), Token::Bool(false)); 102 | } 103 | 104 | #[test] 105 | fn tokenize_bytes() { 106 | assert_eq!( 107 | StrictTokenizer::tokenize(&ParamType::Bytes, "123456").unwrap(), 108 | Token::Bytes(vec![0x12, 0x34, 0x56]) 109 | ); 110 | assert_eq!(StrictTokenizer::tokenize(&ParamType::Bytes, "0017").unwrap(), Token::Bytes(vec![0x00, 0x17])); 111 | } 112 | 113 | #[test] 114 | fn tokenize_fixed_bytes() { 115 | assert_eq!( 116 | StrictTokenizer::tokenize(&ParamType::FixedBytes(3), "123456").unwrap(), 117 | Token::FixedBytes(vec![0x12, 0x34, 0x56]) 118 | ); 119 | assert_eq!( 120 | StrictTokenizer::tokenize(&ParamType::FixedBytes(2), "0017").unwrap(), 121 | Token::FixedBytes(vec![0x00, 0x17]) 122 | ); 123 | } 124 | 125 | #[test] 126 | fn tokenize_uint() { 127 | assert_eq!( 128 | StrictTokenizer::tokenize( 129 | &ParamType::Uint(256), 130 | "1111111111111111111111111111111111111111111111111111111111111111" 131 | ) 132 | .unwrap(), 133 | Token::Uint([0x11u8; 32].into()) 134 | ); 135 | 136 | assert_eq!( 137 | StrictTokenizer::tokenize( 138 | &ParamType::Uint(256), 139 | "2222222222222222222222222222222222222222222222222222222222222222" 140 | ) 141 | .unwrap(), 142 | Token::Uint([0x22u8; 32].into()) 143 | ); 144 | } 145 | 146 | #[test] 147 | fn tokenize_int() { 148 | assert_eq!( 149 | StrictTokenizer::tokenize( 150 | &ParamType::Int(256), 151 | "1111111111111111111111111111111111111111111111111111111111111111" 152 | ) 153 | .unwrap(), 154 | Token::Int([0x11u8; 32].into()) 155 | ); 156 | 157 | assert_eq!( 158 | StrictTokenizer::tokenize( 159 | &ParamType::Int(256), 160 | "2222222222222222222222222222222222222222222222222222222222222222" 161 | ) 162 | .unwrap(), 163 | Token::Int([0x22u8; 32].into()) 164 | ); 165 | } 166 | 167 | #[test] 168 | fn tokenize_empty_array() { 169 | assert_eq!( 170 | StrictTokenizer::tokenize(&ParamType::Array(Box::new(ParamType::Bool)), "[]").unwrap(), 171 | Token::Array(vec![]) 172 | ); 173 | } 174 | 175 | #[test] 176 | fn tokenize_bool_array() { 177 | assert_eq!( 178 | StrictTokenizer::tokenize(&ParamType::Array(Box::new(ParamType::Bool)), "[true,1,0,false]").unwrap(), 179 | Token::Array(vec![Token::Bool(true), Token::Bool(true), Token::Bool(false), Token::Bool(false)]) 180 | ); 181 | } 182 | 183 | #[test] 184 | fn tokenize_bool_array_of_arrays() { 185 | assert_eq!( 186 | StrictTokenizer::tokenize( 187 | &ParamType::Array(Box::new(ParamType::Array(Box::new(ParamType::Bool)))), 188 | "[[true,1,0],[false]]" 189 | ) 190 | .unwrap(), 191 | Token::Array(vec![ 192 | Token::Array(vec![Token::Bool(true), Token::Bool(true), Token::Bool(false)]), 193 | Token::Array(vec![Token::Bool(false)]) 194 | ]) 195 | ); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /ethabi/src/param.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Function param. 10 | 11 | use serde::{ 12 | de::{Error, MapAccess, Visitor}, 13 | Deserialize, Deserializer, 14 | }; 15 | use std::fmt; 16 | 17 | use crate::{ParamType, TupleParam}; 18 | 19 | /// Function param. 20 | #[derive(Debug, Clone, PartialEq)] 21 | pub struct Param { 22 | /// Param name. 23 | pub name: String, 24 | /// Param type. 25 | pub kind: ParamType, 26 | } 27 | 28 | impl<'a> Deserialize<'a> for Param { 29 | fn deserialize(deserializer: D) -> Result 30 | where 31 | D: Deserializer<'a>, 32 | { 33 | deserializer.deserialize_any(ParamVisitor) 34 | } 35 | } 36 | 37 | struct ParamVisitor; 38 | 39 | impl<'a> Visitor<'a> for ParamVisitor { 40 | type Value = Param; 41 | 42 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 43 | write!(formatter, "a valid event parameter spec") 44 | } 45 | 46 | fn visit_map(self, mut map: V) -> Result 47 | where 48 | V: MapAccess<'a>, 49 | { 50 | let mut name = None; 51 | let mut kind = None; 52 | let mut components = None; 53 | 54 | while let Some(ref key) = map.next_key::()? { 55 | match key.as_ref() { 56 | "name" => { 57 | if name.is_some() { 58 | return Err(Error::duplicate_field("name")); 59 | } 60 | name = Some(map.next_value()?); 61 | } 62 | "type" => { 63 | if kind.is_some() { 64 | return Err(Error::duplicate_field("kind")); 65 | } 66 | kind = Some(map.next_value()?); 67 | } 68 | "components" => { 69 | if components.is_some() { 70 | return Err(Error::duplicate_field("components")); 71 | } 72 | let component: Vec = map.next_value()?; 73 | components = Some(component) 74 | } 75 | _ => {} 76 | } 77 | } 78 | let name = name.ok_or_else(|| Error::missing_field("name"))?; 79 | let kind = 80 | kind.ok_or_else(|| Error::missing_field("kind")).and_then(|param_type: ParamType| match param_type { 81 | ParamType::Tuple(_) => { 82 | let tuple_params = components.ok_or_else(|| Error::missing_field("components"))?; 83 | Ok(ParamType::Tuple(tuple_params.into_iter().map(|param| param.kind).collect())) 84 | } 85 | ParamType::Array(inner_param_type) => match *inner_param_type { 86 | ParamType::Tuple(_) => { 87 | let tuple_params = components.ok_or_else(|| Error::missing_field("components"))?; 88 | Ok(ParamType::Array(Box::new(ParamType::Tuple( 89 | tuple_params.into_iter().map(|param| param.kind).collect(), 90 | )))) 91 | } 92 | _ => Ok(ParamType::Array(inner_param_type)), 93 | }, 94 | ParamType::FixedArray(inner_param_type, size) => match *inner_param_type { 95 | ParamType::Tuple(_) => { 96 | let tuple_params = components.ok_or_else(|| Error::missing_field("components"))?; 97 | Ok(ParamType::FixedArray( 98 | Box::new(ParamType::Tuple(tuple_params.into_iter().map(|param| param.kind).collect())), 99 | size, 100 | )) 101 | } 102 | _ => Ok(ParamType::FixedArray(inner_param_type, size)), 103 | }, 104 | _ => Ok(param_type), 105 | })?; 106 | Ok(Param { name, kind }) 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | mod tests { 112 | use crate::{Param, ParamType}; 113 | 114 | #[test] 115 | fn param_deserialization() { 116 | let s = r#"{ 117 | "name": "foo", 118 | "type": "address" 119 | }"#; 120 | 121 | let deserialized: Param = serde_json::from_str(s).unwrap(); 122 | 123 | assert_eq!(deserialized, Param { name: "foo".to_owned(), kind: ParamType::Address }); 124 | } 125 | 126 | #[test] 127 | fn param_tuple_deserialization() { 128 | let s = r#"{ 129 | "name": "foo", 130 | "type": "tuple", 131 | "components": [ 132 | { 133 | "name": "amount", 134 | "type": "uint48" 135 | }, 136 | { 137 | "name": "things", 138 | "type": "tuple", 139 | "components": [ 140 | { 141 | "name": "baseTupleParam", 142 | "type": "address" 143 | } 144 | ] 145 | } 146 | ] 147 | }"#; 148 | 149 | let deserialized: Param = serde_json::from_str(s).unwrap(); 150 | 151 | assert_eq!( 152 | deserialized, 153 | Param { 154 | name: "foo".to_owned(), 155 | kind: ParamType::Tuple(vec![ParamType::Uint(48), ParamType::Tuple(vec![ParamType::Address])]), 156 | } 157 | ); 158 | } 159 | 160 | #[test] 161 | fn param_tuple_array_deserialization() { 162 | let s = r#"{ 163 | "name": "foo", 164 | "type": "tuple[]", 165 | "components": [ 166 | { 167 | "name": "amount", 168 | "type": "uint48" 169 | }, 170 | { 171 | "name": "to", 172 | "type": "address" 173 | }, 174 | { 175 | "name": "from", 176 | "type": "address" 177 | } 178 | ] 179 | }"#; 180 | 181 | let deserialized: Param = serde_json::from_str(s).unwrap(); 182 | 183 | assert_eq!( 184 | deserialized, 185 | Param { 186 | name: "foo".to_owned(), 187 | kind: ParamType::Array(Box::new(ParamType::Tuple(vec![ 188 | ParamType::Uint(48), 189 | ParamType::Address, 190 | ParamType::Address 191 | ]))), 192 | } 193 | ); 194 | } 195 | 196 | #[test] 197 | fn param_tuple_fixed_array_deserialization() { 198 | let s = r#"{ 199 | "name": "foo", 200 | "type": "tuple[2]", 201 | "components": [ 202 | { 203 | "name": "amount", 204 | "type": "uint48" 205 | }, 206 | { 207 | "name": "to", 208 | "type": "address" 209 | }, 210 | { 211 | "name": "from", 212 | "type": "address" 213 | } 214 | ] 215 | }"#; 216 | 217 | let deserialized: Param = serde_json::from_str(s).unwrap(); 218 | 219 | assert_eq!( 220 | deserialized, 221 | Param { 222 | name: "foo".to_owned(), 223 | kind: ParamType::FixedArray( 224 | Box::new(ParamType::Tuple(vec![ParamType::Uint(48), ParamType::Address, ParamType::Address])), 225 | 2 226 | ), 227 | } 228 | ); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /ethabi/src/filter.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::{Hash, Token}; 10 | use serde::{Serialize, Serializer}; 11 | use serde_json::Value; 12 | use std::ops; 13 | 14 | /// Raw topic filter. 15 | #[derive(Debug, PartialEq, Default)] 16 | pub struct RawTopicFilter { 17 | /// Topic. 18 | pub topic0: Topic, 19 | /// Topic. 20 | pub topic1: Topic, 21 | /// Topic. 22 | pub topic2: Topic, 23 | } 24 | 25 | /// Topic filter. 26 | #[derive(Debug, PartialEq, Default)] 27 | pub struct TopicFilter { 28 | /// Usually (for not-anonymous transactions) the first topic is event signature. 29 | pub topic0: Topic, 30 | /// Second topic. 31 | pub topic1: Topic, 32 | /// Third topic. 33 | pub topic2: Topic, 34 | /// Fourth topic. 35 | pub topic3: Topic, 36 | } 37 | 38 | impl Serialize for TopicFilter { 39 | fn serialize(&self, serializer: S) -> Result 40 | where 41 | S: Serializer, 42 | { 43 | vec![&self.topic0, &self.topic1, &self.topic2, &self.topic3].serialize(serializer) 44 | } 45 | } 46 | 47 | /// Acceptable topic possibilities. 48 | #[derive(Debug, PartialEq)] 49 | pub enum Topic { 50 | /// Match any. 51 | Any, 52 | /// Match any of the hashes. 53 | OneOf(Vec), 54 | /// Match only this hash. 55 | This(T), 56 | } 57 | 58 | impl Topic { 59 | /// Map 60 | pub fn map(self, f: F) -> Topic 61 | where 62 | F: Fn(T) -> O, 63 | { 64 | match self { 65 | Topic::Any => Topic::Any, 66 | Topic::OneOf(topics) => Topic::OneOf(topics.into_iter().map(f).collect()), 67 | Topic::This(topic) => Topic::This(f(topic)), 68 | } 69 | } 70 | 71 | /// Returns true if topic is empty (Topic::Any) 72 | pub fn is_any(&self) -> bool { 73 | match *self { 74 | Topic::Any => true, 75 | Topic::This(_) | Topic::OneOf(_) => false, 76 | } 77 | } 78 | } 79 | 80 | impl Default for Topic { 81 | fn default() -> Self { 82 | Topic::Any 83 | } 84 | } 85 | 86 | impl From> for Topic { 87 | fn from(o: Option) -> Self { 88 | match o { 89 | Some(topic) => Topic::This(topic), 90 | None => Topic::Any, 91 | } 92 | } 93 | } 94 | 95 | impl From for Topic { 96 | fn from(topic: T) -> Self { 97 | Topic::This(topic) 98 | } 99 | } 100 | 101 | impl From> for Topic { 102 | fn from(topics: Vec) -> Self { 103 | Topic::OneOf(topics) 104 | } 105 | } 106 | 107 | impl Into> for Topic { 108 | fn into(self) -> Vec { 109 | match self { 110 | Topic::Any => vec![], 111 | Topic::This(topic) => vec![topic], 112 | Topic::OneOf(topics) => topics, 113 | } 114 | } 115 | } 116 | 117 | impl Serialize for Topic { 118 | fn serialize(&self, serializer: S) -> Result 119 | where 120 | S: Serializer, 121 | { 122 | let value = match *self { 123 | Topic::Any => Value::Null, 124 | Topic::OneOf(ref vec) => { 125 | let v = vec.iter().map(|h| format!("0x{:x}", h)).map(Value::String).collect(); 126 | Value::Array(v) 127 | } 128 | Topic::This(ref hash) => Value::String(format!("0x{:x}", hash)), 129 | }; 130 | value.serialize(serializer) 131 | } 132 | } 133 | 134 | impl ops::Index for Topic { 135 | type Output = T; 136 | 137 | fn index(&self, index: usize) -> &Self::Output { 138 | match *self { 139 | Topic::Any => panic!("Topic unavailable"), 140 | Topic::This(ref topic) => { 141 | if index != 0 { 142 | panic!("Topic unavailable"); 143 | } 144 | topic 145 | } 146 | Topic::OneOf(ref topics) => topics.index(index), 147 | } 148 | } 149 | } 150 | 151 | #[cfg(test)] 152 | mod tests { 153 | use super::{Topic, TopicFilter}; 154 | use crate::Hash; 155 | 156 | fn hash(s: &'static str) -> Hash { 157 | s.parse().unwrap() 158 | } 159 | 160 | #[test] 161 | fn test_topic_filter_serialization() { 162 | let expected = r#"["0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",null,["0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b","0x0000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebccc"],null]"#; 163 | 164 | let topic = TopicFilter { 165 | topic0: Topic::This(hash("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b")), 166 | topic1: Topic::Any, 167 | topic2: Topic::OneOf(vec![ 168 | hash("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"), 169 | hash("0000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebccc"), 170 | ]), 171 | topic3: Topic::Any, 172 | }; 173 | 174 | let topic_str = serde_json::to_string(&topic).unwrap(); 175 | assert_eq!(expected, &topic_str); 176 | } 177 | 178 | #[test] 179 | fn test_topic_from() { 180 | assert_eq!(Topic::Any as Topic, None.into()); 181 | assert_eq!(Topic::This(10u64), 10u64.into()); 182 | assert_eq!(Topic::OneOf(vec![10u64, 20]), vec![10u64, 20].into()); 183 | } 184 | 185 | #[test] 186 | fn test_topic_into_vec() { 187 | let expected: Vec = vec![]; 188 | let is: Vec = (Topic::Any as Topic).into(); 189 | assert_eq!(expected, is); 190 | let expected: Vec = vec![10]; 191 | let is: Vec = Topic::This(10u64).into(); 192 | assert_eq!(expected, is); 193 | let expected: Vec = vec![10, 20]; 194 | let is: Vec = Topic::OneOf(vec![10u64, 20]).into(); 195 | assert_eq!(expected, is); 196 | } 197 | 198 | #[test] 199 | fn test_topic_is_any() { 200 | assert!((Topic::Any as Topic).is_any()); 201 | assert!(!Topic::OneOf(vec![10u64, 20]).is_any()); 202 | assert!(!Topic::This(10u64).is_any()); 203 | } 204 | 205 | #[test] 206 | fn test_topic_index() { 207 | assert_eq!(Topic::OneOf(vec![10u64, 20])[0], 10); 208 | assert_eq!(Topic::OneOf(vec![10u64, 20])[1], 20); 209 | assert_eq!(Topic::This(10u64)[0], 10); 210 | } 211 | 212 | #[test] 213 | #[should_panic(expected = "Topic unavailable")] 214 | fn test_topic_index_panic() { 215 | let _ = (Topic::Any as Topic)[0]; 216 | } 217 | 218 | #[test] 219 | #[should_panic(expected = "Topic unavailable")] 220 | fn test_topic_index_panic2() { 221 | assert_eq!(Topic::This(10u64)[1], 10); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /ethabi/src/token/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! ABI param and parsing for it. 10 | 11 | mod lenient; 12 | mod strict; 13 | mod token; 14 | 15 | use std::cmp::Ordering::{Equal, Less}; 16 | 17 | pub use self::{lenient::LenientTokenizer, strict::StrictTokenizer, token::Token}; 18 | use crate::{Error, ParamType}; 19 | 20 | /// This trait should be used to parse string values as tokens. 21 | pub trait Tokenizer { 22 | /// Tries to parse a string as a token of given type. 23 | fn tokenize(param: &ParamType, value: &str) -> Result { 24 | match *param { 25 | ParamType::Address => Self::tokenize_address(value).map(|a| Token::Address(a.into())), 26 | ParamType::String => Self::tokenize_string(value).map(Token::String), 27 | ParamType::Bool => Self::tokenize_bool(value).map(Token::Bool), 28 | ParamType::Bytes => Self::tokenize_bytes(value).map(Token::Bytes), 29 | ParamType::FixedBytes(len) => Self::tokenize_fixed_bytes(value, len).map(Token::FixedBytes), 30 | ParamType::Uint(_) => Self::tokenize_uint(value).map(Into::into).map(Token::Uint), 31 | ParamType::Int(_) => Self::tokenize_int(value).map(Into::into).map(Token::Int), 32 | ParamType::Array(ref p) => Self::tokenize_array(value, p).map(Token::Array), 33 | ParamType::FixedArray(ref p, len) => Self::tokenize_fixed_array(value, p, len).map(Token::FixedArray), 34 | ParamType::Tuple(ref p) => Self::tokenize_struct(value, p).map(Token::Tuple), 35 | } 36 | } 37 | 38 | /// Tries to parse a value as a vector of tokens of fixed size. 39 | fn tokenize_fixed_array(value: &str, param: &ParamType, len: usize) -> Result, Error> { 40 | let result = Self::tokenize_array(value, param)?; 41 | match result.len() == len { 42 | true => Ok(result), 43 | false => Err(Error::InvalidData), 44 | } 45 | } 46 | 47 | /// Tried to parse a struct as a vector of tokens 48 | fn tokenize_struct(value: &str, param: &[ParamType]) -> Result, Error> { 49 | if !value.starts_with('(') || !value.ends_with(')') { 50 | return Err(Error::InvalidData); 51 | } 52 | 53 | if value.chars().count() == 2 { 54 | return Ok(vec![]); 55 | } 56 | 57 | let mut result = vec![]; 58 | let mut nested = 0isize; 59 | let mut ignore = false; 60 | let mut last_item = 1; 61 | let mut params = param.iter(); 62 | for (pos, ch) in value.chars().enumerate() { 63 | match ch { 64 | '(' if !ignore => { 65 | nested += 1; 66 | } 67 | ')' if !ignore => { 68 | nested -= 1; 69 | 70 | match nested.cmp(&0) { 71 | Less => { 72 | return Err(Error::InvalidData); 73 | } 74 | Equal => { 75 | let sub = &value[last_item..pos]; 76 | let token = Self::tokenize(params.next().ok_or(Error::InvalidData)?, sub)?; 77 | result.push(token); 78 | last_item = pos + 1; 79 | } 80 | _ => {} 81 | } 82 | } 83 | '"' => { 84 | ignore = !ignore; 85 | } 86 | ',' if nested == 1 && !ignore => { 87 | let sub = &value[last_item..pos]; 88 | let token = Self::tokenize(params.next().ok_or(Error::InvalidData)?, sub)?; 89 | result.push(token); 90 | last_item = pos + 1; 91 | } 92 | _ => (), 93 | } 94 | } 95 | 96 | if ignore { 97 | return Err(Error::InvalidData); 98 | } 99 | 100 | Ok(result) 101 | } 102 | 103 | /// Tries to parse a value as a vector of tokens. 104 | fn tokenize_array(value: &str, param: &ParamType) -> Result, Error> { 105 | if !value.starts_with('[') || !value.ends_with(']') { 106 | return Err(Error::InvalidData); 107 | } 108 | 109 | if value.chars().count() == 2 { 110 | return Ok(vec![]); 111 | } 112 | 113 | let mut result = vec![]; 114 | let mut nested = 0isize; 115 | let mut ignore = false; 116 | let mut last_item = 1; 117 | for (i, ch) in value.chars().enumerate() { 118 | match ch { 119 | '[' if !ignore => { 120 | nested += 1; 121 | } 122 | ']' if !ignore => { 123 | nested -= 1; 124 | match nested.cmp(&0) { 125 | Less => { 126 | return Err(Error::InvalidData); 127 | } 128 | Equal => { 129 | let sub = &value[last_item..i]; 130 | let token = Self::tokenize(param, sub)?; 131 | result.push(token); 132 | last_item = i + 1; 133 | } 134 | _ => {} 135 | } 136 | } 137 | '"' => { 138 | ignore = !ignore; 139 | } 140 | ',' if nested == 1 && !ignore => { 141 | let sub = &value[last_item..i]; 142 | let token = Self::tokenize(param, sub)?; 143 | result.push(token); 144 | last_item = i + 1; 145 | } 146 | _ => (), 147 | } 148 | } 149 | 150 | if ignore { 151 | return Err(Error::InvalidData); 152 | } 153 | 154 | Ok(result) 155 | } 156 | 157 | /// Tries to parse a value as an address. 158 | fn tokenize_address(value: &str) -> Result<[u8; 20], Error>; 159 | 160 | /// Tries to parse a value as a string. 161 | fn tokenize_string(value: &str) -> Result; 162 | 163 | /// Tries to parse a value as a bool. 164 | fn tokenize_bool(value: &str) -> Result; 165 | 166 | /// Tries to parse a value as bytes. 167 | fn tokenize_bytes(value: &str) -> Result, Error>; 168 | 169 | /// Tries to parse a value as bytes. 170 | fn tokenize_fixed_bytes(value: &str, len: usize) -> Result, Error>; 171 | 172 | /// Tries to parse a value as unsigned integer. 173 | fn tokenize_uint(value: &str) -> Result<[u8; 32], Error>; 174 | 175 | /// Tries to parse a value as signed integer. 176 | fn tokenize_int(value: &str) -> Result<[u8; 32], Error>; 177 | } 178 | 179 | #[cfg(test)] 180 | mod test { 181 | use super::{LenientTokenizer, ParamType, Tokenizer}; 182 | #[test] 183 | fn single_quoted_in_array_must_error() { 184 | assert!(LenientTokenizer::tokenize_array("[1,\"0,false]", &ParamType::Bool).is_err()); 185 | assert!(LenientTokenizer::tokenize_array("[false\"]", &ParamType::Bool).is_err()); 186 | assert!(LenientTokenizer::tokenize_array("[1,false\"]", &ParamType::Bool).is_err()); 187 | assert!(LenientTokenizer::tokenize_array("[1,\"0\",false]", &ParamType::Bool).is_err()); 188 | assert!(LenientTokenizer::tokenize_array("[1,0]", &ParamType::Bool).is_ok()); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ethabi 2 | 3 | The ABI, Application Binary Interface, is basically how you call functions in a contract and get data back. 4 | 5 | > An ABI determines such details as how functions are called and in which binary format information should be passed from one program component to the next... 6 | 7 | An Ethereum smart contract is bytecode, EVM, on the Ethereum blockchain. Among the EVM, there could be several functions in a contract. An ABI is necessary so that you can specify which function in the contract to invoke, as well as get a guarantee that the function will return data in the format you are expecting. [read more](http://ethereum.stackexchange.com/a/1171/394) 8 | 9 | This library encodes function calls and decodes their output. 10 | 11 | [Documentation](https://docs.rs/ethabi) 12 | 13 | ### Disclaimer 14 | 15 | This library intends to support only valid ABIs generated by recent Solidity versions. Specifically, we don't intend to support ABIs that are invalid due to known Solidity bugs or by external libraries that don't strictly follow the specification. 16 | Please make sure to pre-process your ABI in case it's not supported before using it with `ethabi`. 17 | 18 | ### Installation 19 | 20 | ``` 21 | cargo install ethabi-cli 22 | ``` 23 | 24 | ### Usage 25 | 26 | ``` 27 | Ethereum ABI coder. 28 | Copyright 2016-2017 Parity Technologies (UK) Limited 29 | 30 | Usage: 31 | ethabi encode function [-p ]... [-l | --lenient] 32 | ethabi encode params [-v ]... [-l | --lenient] 33 | ethabi decode function 34 | ethabi decode params [-t ]... 35 | ethabi decode log [-l ]... 36 | ethabi -h | --help 37 | 38 | Options: 39 | -h, --help Display this message and exit. 40 | -l, --lenient Allow short representation of input params. 41 | 42 | Commands: 43 | encode Encode ABI call. 44 | decode Decode ABI call result. 45 | function Load function from json ABI file. 46 | params Specify types of input params inline. 47 | log Decode event log. 48 | ``` 49 | 50 | ### Examples 51 | 52 | ``` 53 | ethabi encode params -v bool 1 54 | ``` 55 | 56 | > 0000000000000000000000000000000000000000000000000000000000000001 57 | 58 | -- 59 | 60 | ``` 61 | ethabi encode params -v bool 1 -v string gavofyork -v bool 0 62 | ``` 63 | 64 | > 00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096761766f66796f726b0000000000000000000000000000000000000000000000 65 | 66 | -- 67 | 68 | ``` 69 | ethabi encode params -v bool[] [1,0,false] 70 | ``` 71 | 72 | > 00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 73 | 74 | -- 75 | 76 | ``` 77 | ethabi encode params -v '(string,bool,string)' '(test,1,cyborg)' 78 | ``` 79 | 80 | > 00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066379626f72670000000000000000000000000000000000000000000000000000 81 | 82 | -- 83 | 84 | ``` 85 | ethabi encode function examples/test.json foo -p 1 86 | ``` 87 | 88 | ```json 89 | [{ 90 | "type":"function", 91 | "inputs": [{ 92 | "name":"a", 93 | "type":"bool" 94 | }], 95 | "name":"foo", 96 | "outputs": [] 97 | }] 98 | ``` 99 | 100 | > 455575780000000000000000000000000000000000000000000000000000000000000001 101 | 102 | -- 103 | 104 | ``` 105 | ethabi encode function examples/test.json foo(bool) -p 1 106 | ``` 107 | 108 | ```json 109 | [{ 110 | "type":"function", 111 | "inputs": [{ 112 | "name":"a", 113 | "type":"bool" 114 | }], 115 | "name":"foo", 116 | "outputs": [] 117 | }] 118 | ``` 119 | 120 | > 455575780000000000000000000000000000000000000000000000000000000000000001 121 | 122 | -- 123 | 124 | ``` 125 | ethabi encode function examples/test.json bar(bool) -p 1 126 | ``` 127 | 128 | ```json 129 | [{ 130 | "type":"function", 131 | "inputs": [{ 132 | "name":"a", 133 | "type":"bool" 134 | }], 135 | "name":"foo", 136 | "outputs": [] 137 | }] 138 | ``` 139 | 140 | > 6fae94120000000000000000000000000000000000000000000000000000000000000001 141 | 142 | -- 143 | 144 | ``` 145 | ethabi encode function examples/test.json bar(string):(uint256) -p 1 146 | ``` 147 | 148 | ```json 149 | [{ 150 | "type":"function", 151 | "inputs": [{ 152 | "type":"string" 153 | }], 154 | "name":"foo", 155 | "outputs": [{ 156 | "type": "uint256" 157 | }] 158 | }] 159 | ``` 160 | 161 | > d473a8ed000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000013100000000000000000000000000000000000000000000000000000000000000 162 | 163 | -- 164 | 165 | ``` 166 | ethabi decode params -t bool 0000000000000000000000000000000000000000000000000000000000000001 167 | ``` 168 | 169 | > bool true 170 | 171 | -- 172 | 173 | ``` 174 | ethabi decode params -t bool -t string -t bool 00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096761766f66796f726b0000000000000000000000000000000000000000000000 175 | ``` 176 | 177 | > bool true
178 | > string gavofyork
179 | > bool false 180 | 181 | -- 182 | 183 | ``` 184 | ethabi decode params -t bool[] 00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 185 | ``` 186 | 187 | > bool[] [true,false,false] 188 | 189 | -- 190 | 191 | ``` 192 | ethabi decode params -t '(string,bool,string)' 00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000673706972616c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067175617361720000000000000000000000000000000000000000000000000000``` 193 | ``` 194 | 195 | > (string,bool,string) (spiral,true,quasar) 196 | 197 | -- 198 | 199 | ``` 200 | ethabi decode function ./examples/foo.json bar 0000000000000000000000000000000000000000000000000000000000000001 201 | ``` 202 | 203 | ```json 204 | [{ 205 | "constant":false, 206 | "inputs":[{ 207 | "name":"hello", 208 | "type":"address" 209 | }], 210 | "name":"bar", 211 | "outputs":[{ 212 | "name":"", 213 | "type":"bool" 214 | }], 215 | "type":"function" 216 | }] 217 | ``` 218 | 219 | > bool true 220 | 221 | -- 222 | 223 | ``` 224 | ethabi decode log ./examples/event.json Event -l 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000004444444444444444444444444444444444444444 225 | ``` 226 | 227 | > a bool true
228 | > b address 4444444444444444444444444444444444444444 229 | -------------------------------------------------------------------------------- /ethabi/src/param_type/reader.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use crate::{Error, ParamType}; 10 | 11 | /// Used to convert param type represented as a string to rust structure. 12 | pub struct Reader; 13 | 14 | impl Reader { 15 | /// Converts string to param type. 16 | pub fn read(name: &str) -> Result { 17 | match name.chars().last() { 18 | // check if it is a struct 19 | Some(')') => { 20 | if !name.starts_with('(') { 21 | return Err(Error::InvalidName(name.to_owned())); 22 | }; 23 | 24 | let mut subtypes = Vec::new(); 25 | let mut subtuples = Vec::new(); 26 | let mut nested = 0isize; 27 | let mut last_item = 1; 28 | 29 | // Iterate over name and build the nested tuple structure 30 | for (pos, c) in name.chars().enumerate() { 31 | match c { 32 | '(' => { 33 | nested += 1; 34 | // If an '(' is encountered within the tuple 35 | // insert an empty subtuples vector to be filled 36 | if nested > 1 { 37 | subtuples.push(vec![]); 38 | last_item = pos + 1; 39 | } 40 | } 41 | ')' => { 42 | nested -= 1; 43 | // End parsing and return an error if parentheses aren't symmetrical 44 | if nested < 0 { 45 | return Err(Error::InvalidName(name.to_owned())); 46 | } 47 | // If there have not been any characters since the last item 48 | // increment position without inserting any subtypes 49 | else if name[last_item..pos].is_empty() { 50 | last_item = pos + 1; 51 | } 52 | // If the item is in the top level of the tuple insert it into subtypes 53 | else if nested == 0 { 54 | let sub = &name[last_item..pos]; 55 | let subtype = Reader::read(sub)?; 56 | subtypes.push(subtype); 57 | last_item = pos + 1; 58 | } 59 | // If the item is in a sublevel of the tuple: 60 | // insert it into the subtuple vector for the current depth level 61 | // process all the subtuple vectors created into sub tuples and insert 62 | // them into subtypes 63 | else if nested > 0 { 64 | let sub = &name[last_item..pos]; 65 | let subtype = Reader::read(sub)?; 66 | subtuples[(nested - 1) as usize].push(subtype); 67 | let initial_tuple_params = subtuples.remove(0); 68 | let tuple_params = subtuples.into_iter().fold( 69 | initial_tuple_params, 70 | |mut tuple_params, nested_param_set| { 71 | tuple_params.push(ParamType::Tuple(nested_param_set)); 72 | tuple_params 73 | }, 74 | ); 75 | subtypes.push(ParamType::Tuple(tuple_params)); 76 | subtuples = Vec::new(); 77 | last_item = pos + 1; 78 | } 79 | } 80 | ',' => { 81 | // If there have not been any characters since the last item 82 | // increment position without inserting any subtypes 83 | if name[last_item..pos].is_empty() { 84 | last_item = pos + 1 85 | } 86 | // If the item is in the top level of the tuple insert it into subtypes 87 | else if nested == 1 { 88 | let sub = &name[last_item..pos]; 89 | let subtype = Reader::read(sub)?; 90 | subtypes.push(subtype); 91 | last_item = pos + 1; 92 | } 93 | // If the item is in a sublevel of the tuple 94 | // insert it into the subtuple vector for the current depth level 95 | else if nested > 1 { 96 | let sub = &name[last_item..pos]; 97 | let subtype = Reader::read(sub)?; 98 | subtuples[(nested - 2) as usize].push(subtype); 99 | last_item = pos + 1; 100 | } 101 | } 102 | _ => (), 103 | } 104 | } 105 | return Ok(ParamType::Tuple(subtypes)); 106 | } 107 | // check if it is a fixed or dynamic array. 108 | Some(']') => { 109 | // take number part 110 | let num: String = 111 | name.chars().rev().skip(1).take_while(|c| *c != '[').collect::().chars().rev().collect(); 112 | 113 | let count = name.chars().count(); 114 | if num.is_empty() { 115 | // we already know it's a dynamic array! 116 | let subtype = Reader::read(&name[..count - 2])?; 117 | return Ok(ParamType::Array(Box::new(subtype))); 118 | } else { 119 | // it's a fixed array. 120 | let len = usize::from_str_radix(&num, 10)?; 121 | let subtype = Reader::read(&name[..count - num.len() - 2])?; 122 | return Ok(ParamType::FixedArray(Box::new(subtype), len)); 123 | } 124 | } 125 | _ => (), 126 | } 127 | 128 | let result = match name { 129 | "address" => ParamType::Address, 130 | "bytes" => ParamType::Bytes, 131 | "bool" => ParamType::Bool, 132 | "string" => ParamType::String, 133 | "int" => ParamType::Int(256), 134 | "tuple" => ParamType::Tuple(vec![]), 135 | "uint" => ParamType::Uint(256), 136 | s if s.starts_with("int") => { 137 | let len = usize::from_str_radix(&s[3..], 10)?; 138 | ParamType::Int(len) 139 | } 140 | s if s.starts_with("uint") => { 141 | let len = usize::from_str_radix(&s[4..], 10)?; 142 | ParamType::Uint(len) 143 | } 144 | s if s.starts_with("bytes") => { 145 | let len = usize::from_str_radix(&s[5..], 10)?; 146 | ParamType::FixedBytes(len) 147 | } 148 | _ => { 149 | return Err(Error::InvalidName(name.to_owned())); 150 | } 151 | }; 152 | 153 | Ok(result) 154 | } 155 | } 156 | 157 | #[cfg(test)] 158 | mod tests { 159 | use super::Reader; 160 | use crate::ParamType; 161 | 162 | #[test] 163 | fn test_read_param() { 164 | assert_eq!(Reader::read("address").unwrap(), ParamType::Address); 165 | assert_eq!(Reader::read("bytes").unwrap(), ParamType::Bytes); 166 | assert_eq!(Reader::read("bytes32").unwrap(), ParamType::FixedBytes(32)); 167 | assert_eq!(Reader::read("bool").unwrap(), ParamType::Bool); 168 | assert_eq!(Reader::read("string").unwrap(), ParamType::String); 169 | assert_eq!(Reader::read("int").unwrap(), ParamType::Int(256)); 170 | assert_eq!(Reader::read("uint").unwrap(), ParamType::Uint(256)); 171 | assert_eq!(Reader::read("int32").unwrap(), ParamType::Int(32)); 172 | assert_eq!(Reader::read("uint32").unwrap(), ParamType::Uint(32)); 173 | } 174 | 175 | #[test] 176 | fn test_read_array_param() { 177 | assert_eq!(Reader::read("address[]").unwrap(), ParamType::Array(Box::new(ParamType::Address))); 178 | assert_eq!(Reader::read("uint[]").unwrap(), ParamType::Array(Box::new(ParamType::Uint(256)))); 179 | assert_eq!(Reader::read("bytes[]").unwrap(), ParamType::Array(Box::new(ParamType::Bytes))); 180 | assert_eq!( 181 | Reader::read("bool[][]").unwrap(), 182 | ParamType::Array(Box::new(ParamType::Array(Box::new(ParamType::Bool)))) 183 | ); 184 | } 185 | 186 | #[test] 187 | fn test_read_fixed_array_param() { 188 | assert_eq!(Reader::read("address[2]").unwrap(), ParamType::FixedArray(Box::new(ParamType::Address), 2)); 189 | assert_eq!(Reader::read("bool[17]").unwrap(), ParamType::FixedArray(Box::new(ParamType::Bool), 17)); 190 | assert_eq!( 191 | Reader::read("bytes[45][3]").unwrap(), 192 | ParamType::FixedArray(Box::new(ParamType::FixedArray(Box::new(ParamType::Bytes), 45)), 3) 193 | ); 194 | } 195 | 196 | #[test] 197 | fn test_read_mixed_arrays() { 198 | assert_eq!( 199 | Reader::read("bool[][3]").unwrap(), 200 | ParamType::FixedArray(Box::new(ParamType::Array(Box::new(ParamType::Bool))), 3) 201 | ); 202 | assert_eq!( 203 | Reader::read("bool[3][]").unwrap(), 204 | ParamType::Array(Box::new(ParamType::FixedArray(Box::new(ParamType::Bool), 3))) 205 | ); 206 | } 207 | 208 | #[test] 209 | fn test_read_struct_param() { 210 | assert_eq!( 211 | Reader::read("(address,bool)").unwrap(), 212 | ParamType::Tuple(vec![ParamType::Address, ParamType::Bool]) 213 | ); 214 | assert_eq!( 215 | Reader::read("(bool[3],uint256)").unwrap(), 216 | ParamType::Tuple(vec![ParamType::FixedArray(Box::new(ParamType::Bool), 3), ParamType::Uint(256)]) 217 | ); 218 | } 219 | 220 | #[test] 221 | fn test_read_nested_struct_param() { 222 | assert_eq!( 223 | Reader::read("(address,bool,(bool,uint256))").unwrap(), 224 | ParamType::Tuple(vec![ 225 | ParamType::Address, 226 | ParamType::Bool, 227 | ParamType::Tuple(vec![ParamType::Bool, ParamType::Uint(256)]) 228 | ]) 229 | ); 230 | } 231 | 232 | #[test] 233 | fn test_read_complex_nested_struct_param() { 234 | assert_eq!( 235 | Reader::read("(address,bool,(bool,uint256,(bool,uint256)),(bool,uint256))").unwrap(), 236 | ParamType::Tuple(vec![ 237 | ParamType::Address, 238 | ParamType::Bool, 239 | ParamType::Tuple(vec![ 240 | ParamType::Bool, 241 | ParamType::Uint(256), 242 | ParamType::Tuple(vec![ParamType::Bool, ParamType::Uint(256)]) 243 | ]), 244 | ParamType::Tuple(vec![ParamType::Bool, ParamType::Uint(256)]) 245 | ]) 246 | ); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /ethabi/src/event.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Contract event. 10 | 11 | use serde::Deserialize; 12 | use sha3::{Digest, Keccak256}; 13 | use std::collections::HashMap; 14 | 15 | use crate::{ 16 | decode, encode, signature::long_signature, Error, EventParam, Hash, Log, LogParam, ParamType, RawLog, 17 | RawTopicFilter, Result, Token, Topic, TopicFilter, 18 | }; 19 | 20 | /// Contract event. 21 | #[derive(Clone, Debug, PartialEq, Deserialize)] 22 | pub struct Event { 23 | /// Event name. 24 | pub name: String, 25 | /// Event input. 26 | pub inputs: Vec, 27 | /// If anonymous, event cannot be found using `from` filter. 28 | pub anonymous: bool, 29 | } 30 | 31 | impl Event { 32 | /// Returns names of all params. 33 | fn params_names(&self) -> Vec { 34 | self.inputs.iter().map(|p| p.name.clone()).collect() 35 | } 36 | 37 | /// Returns types of all params. 38 | fn param_types(&self) -> Vec { 39 | self.inputs.iter().map(|p| p.kind.clone()).collect() 40 | } 41 | 42 | /// Returns all params of the event. 43 | fn indexed_params(&self, indexed: bool) -> Vec { 44 | self.inputs.iter().filter(|p| p.indexed == indexed).cloned().collect() 45 | } 46 | 47 | /// Event signature 48 | pub fn signature(&self) -> Hash { 49 | long_signature(&self.name, &self.param_types()) 50 | } 51 | 52 | /// Creates topic filter 53 | pub fn filter(&self, raw: RawTopicFilter) -> Result { 54 | fn convert_token(token: Token, kind: &ParamType) -> Result { 55 | if !token.type_check(kind) { 56 | return Err(Error::InvalidData); 57 | } 58 | let encoded = encode(&[token]); 59 | if encoded.len() == 32 { 60 | let mut data = [0u8; 32]; 61 | data.copy_from_slice(&encoded); 62 | Ok(data.into()) 63 | } else { 64 | Ok(Hash::from_slice(Keccak256::digest(&encoded).as_slice())) 65 | } 66 | } 67 | 68 | fn convert_topic(topic: Topic, kind: Option<&ParamType>) -> Result> { 69 | match topic { 70 | Topic::Any => Ok(Topic::Any), 71 | Topic::OneOf(tokens) => match kind { 72 | None => Err(Error::InvalidData), 73 | Some(kind) => { 74 | let topics = 75 | tokens.into_iter().map(|token| convert_token(token, kind)).collect::>>()?; 76 | Ok(Topic::OneOf(topics)) 77 | } 78 | }, 79 | Topic::This(token) => match kind { 80 | None => Err(Error::InvalidData), 81 | Some(kind) => Ok(Topic::This(convert_token(token, kind)?)), 82 | }, 83 | } 84 | } 85 | 86 | let kinds: Vec<_> = self.indexed_params(true).into_iter().map(|param| param.kind).collect(); 87 | let result = if self.anonymous { 88 | TopicFilter { 89 | topic0: convert_topic(raw.topic0, kinds.get(0))?, 90 | topic1: convert_topic(raw.topic1, kinds.get(1))?, 91 | topic2: convert_topic(raw.topic2, kinds.get(2))?, 92 | topic3: Topic::Any, 93 | } 94 | } else { 95 | TopicFilter { 96 | topic0: Topic::This(self.signature()), 97 | topic1: convert_topic(raw.topic0, kinds.get(0))?, 98 | topic2: convert_topic(raw.topic1, kinds.get(1))?, 99 | topic3: convert_topic(raw.topic2, kinds.get(2))?, 100 | } 101 | }; 102 | 103 | Ok(result) 104 | } 105 | 106 | // Converts param types for indexed parameters to bytes32 where appropriate 107 | // This applies to strings, arrays, structs and bytes to follow the encoding of 108 | // these indexed param types according to 109 | // https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters 110 | fn convert_topic_param_type(&self, kind: &ParamType) -> ParamType { 111 | match kind { 112 | ParamType::String 113 | | ParamType::Bytes 114 | | ParamType::Array(_) 115 | | ParamType::FixedArray(_, _) 116 | | ParamType::Tuple(_) => ParamType::FixedBytes(32), 117 | _ => kind.clone(), 118 | } 119 | } 120 | 121 | /// Parses `RawLog` and retrieves all log params from it. 122 | pub fn parse_log(&self, log: RawLog) -> Result { 123 | let topics = log.topics; 124 | let data = log.data; 125 | let topics_len = topics.len(); 126 | // obtains all params info 127 | let topic_params = self.indexed_params(true); 128 | let data_params = self.indexed_params(false); 129 | // then take first topic if event is not anonymous 130 | let to_skip = if self.anonymous { 131 | 0 132 | } else { 133 | // verify 134 | let event_signature = topics.get(0).ok_or(Error::InvalidData)?; 135 | if event_signature != &self.signature() { 136 | return Err(Error::InvalidData); 137 | } 138 | 1 139 | }; 140 | 141 | let topic_types = 142 | topic_params.iter().map(|p| self.convert_topic_param_type(&p.kind)).collect::>(); 143 | 144 | let flat_topics = topics.into_iter().skip(to_skip).flat_map(|t| t.as_ref().to_vec()).collect::>(); 145 | 146 | let topic_tokens = decode(&topic_types, &flat_topics)?; 147 | 148 | // topic may be only a 32 bytes encoded token 149 | if topic_tokens.len() != topics_len - to_skip { 150 | return Err(Error::InvalidData); 151 | } 152 | 153 | let topics_named_tokens = topic_params.into_iter().map(|p| p.name).zip(topic_tokens.into_iter()); 154 | 155 | let data_types = data_params.iter().map(|p| p.kind.clone()).collect::>(); 156 | 157 | let data_tokens = decode(&data_types, &data)?; 158 | 159 | let data_named_tokens = data_params.into_iter().map(|p| p.name).zip(data_tokens.into_iter()); 160 | 161 | let named_tokens = topics_named_tokens.chain(data_named_tokens).collect::>(); 162 | 163 | let decoded_params = self 164 | .params_names() 165 | .into_iter() 166 | .map(|name| LogParam { name: name.clone(), value: named_tokens[&name].clone() }) 167 | .collect(); 168 | 169 | let result = Log { params: decoded_params }; 170 | 171 | Ok(result) 172 | } 173 | } 174 | 175 | #[cfg(test)] 176 | mod tests { 177 | use crate::{ 178 | log::{Log, RawLog}, 179 | signature::long_signature, 180 | token::Token, 181 | Event, EventParam, LogParam, ParamType, 182 | }; 183 | use hex_literal::hex; 184 | 185 | #[test] 186 | fn test_decoding_event() { 187 | let event = Event { 188 | name: "foo".to_owned(), 189 | inputs: vec![ 190 | EventParam { name: "a".to_owned(), kind: ParamType::Int(256), indexed: false }, 191 | EventParam { name: "b".to_owned(), kind: ParamType::Int(256), indexed: true }, 192 | EventParam { name: "c".to_owned(), kind: ParamType::Address, indexed: false }, 193 | EventParam { name: "d".to_owned(), kind: ParamType::Address, indexed: true }, 194 | EventParam { name: "e".to_owned(), kind: ParamType::String, indexed: true }, 195 | EventParam { 196 | name: "f".to_owned(), 197 | kind: ParamType::Array(Box::new(ParamType::Int(256))), 198 | indexed: true, 199 | }, 200 | EventParam { 201 | name: "g".to_owned(), 202 | kind: ParamType::FixedArray(Box::new(ParamType::Address), 5), 203 | indexed: true, 204 | }, 205 | ], 206 | anonymous: false, 207 | }; 208 | 209 | let log = RawLog { 210 | topics: vec![ 211 | long_signature( 212 | "foo", 213 | &[ 214 | ParamType::Int(256), 215 | ParamType::Int(256), 216 | ParamType::Address, 217 | ParamType::Address, 218 | ParamType::String, 219 | ParamType::Array(Box::new(ParamType::Int(256))), 220 | ParamType::FixedArray(Box::new(ParamType::Address), 5), 221 | ], 222 | ), 223 | hex!("0000000000000000000000000000000000000000000000000000000000000002").into(), 224 | hex!("0000000000000000000000001111111111111111111111111111111111111111").into(), 225 | hex!("00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").into(), 226 | hex!("00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").into(), 227 | hex!("00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc").into(), 228 | ], 229 | data: hex!( 230 | " 231 | 0000000000000000000000000000000000000000000000000000000000000003 232 | 0000000000000000000000002222222222222222222222222222222222222222 233 | " 234 | ) 235 | .into(), 236 | }; 237 | let result = event.parse_log(log).unwrap(); 238 | 239 | assert_eq!( 240 | result, 241 | Log { 242 | params: [ 243 | ("a", Token::Int(hex!("0000000000000000000000000000000000000000000000000000000000000003").into()),), 244 | ("b", Token::Int(hex!("0000000000000000000000000000000000000000000000000000000000000002").into()),), 245 | ("c", Token::Address(hex!("2222222222222222222222222222222222222222").into())), 246 | ("d", Token::Address(hex!("1111111111111111111111111111111111111111").into())), 247 | ( 248 | "e", 249 | Token::FixedBytes( 250 | hex!("00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").into() 251 | ) 252 | ), 253 | ( 254 | "f", 255 | Token::FixedBytes( 256 | hex!("00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").into() 257 | ) 258 | ), 259 | ( 260 | "g", 261 | Token::FixedBytes( 262 | hex!("00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc").into() 263 | ) 264 | ), 265 | ] 266 | .iter() 267 | .cloned() 268 | .map(|(name, value)| LogParam { name: name.to_string(), value }) 269 | .collect::>() 270 | } 271 | ); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /res/Operations.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": false, 4 | "inputs": [ 5 | { 6 | "name": "_client", 7 | "type": "bytes32" 8 | }, 9 | { 10 | "name": "_newOwner", 11 | "type": "address" 12 | } 13 | ], 14 | "name": "resetClientOwner", 15 | "outputs": [], 16 | "payable": false, 17 | "type": "function" 18 | }, 19 | { 20 | "constant": true, 21 | "inputs": [ 22 | { 23 | "name": "_client", 24 | "type": "bytes32" 25 | }, 26 | { 27 | "name": "_release", 28 | "type": "bytes32" 29 | } 30 | ], 31 | "name": "isLatest", 32 | "outputs": [ 33 | { 34 | "name": "", 35 | "type": "bool" 36 | } 37 | ], 38 | "payable": false, 39 | "type": "function" 40 | }, 41 | { 42 | "constant": false, 43 | "inputs": [ 44 | { 45 | "name": "_txid", 46 | "type": "bytes32" 47 | } 48 | ], 49 | "name": "rejectTransaction", 50 | "outputs": [], 51 | "payable": false, 52 | "type": "function" 53 | }, 54 | { 55 | "constant": false, 56 | "inputs": [ 57 | { 58 | "name": "_newOwner", 59 | "type": "address" 60 | } 61 | ], 62 | "name": "setOwner", 63 | "outputs": [], 64 | "payable": false, 65 | "type": "function" 66 | }, 67 | { 68 | "constant": false, 69 | "inputs": [ 70 | { 71 | "name": "_number", 72 | "type": "uint32" 73 | }, 74 | { 75 | "name": "_name", 76 | "type": "bytes32" 77 | }, 78 | { 79 | "name": "_hard", 80 | "type": "bool" 81 | }, 82 | { 83 | "name": "_spec", 84 | "type": "bytes32" 85 | } 86 | ], 87 | "name": "proposeFork", 88 | "outputs": [], 89 | "payable": false, 90 | "type": "function" 91 | }, 92 | { 93 | "constant": false, 94 | "inputs": [ 95 | { 96 | "name": "_client", 97 | "type": "bytes32" 98 | } 99 | ], 100 | "name": "removeClient", 101 | "outputs": [], 102 | "payable": false, 103 | "type": "function" 104 | }, 105 | { 106 | "constant": true, 107 | "inputs": [ 108 | { 109 | "name": "_client", 110 | "type": "bytes32" 111 | }, 112 | { 113 | "name": "_release", 114 | "type": "bytes32" 115 | } 116 | ], 117 | "name": "release", 118 | "outputs": [ 119 | { 120 | "name": "o_forkBlock", 121 | "type": "uint32" 122 | }, 123 | { 124 | "name": "o_track", 125 | "type": "uint8" 126 | }, 127 | { 128 | "name": "o_semver", 129 | "type": "uint24" 130 | }, 131 | { 132 | "name": "o_critical", 133 | "type": "bool" 134 | } 135 | ], 136 | "payable": false, 137 | "type": "function" 138 | }, 139 | { 140 | "constant": true, 141 | "inputs": [ 142 | { 143 | "name": "_client", 144 | "type": "bytes32" 145 | }, 146 | { 147 | "name": "_checksum", 148 | "type": "bytes32" 149 | } 150 | ], 151 | "name": "build", 152 | "outputs": [ 153 | { 154 | "name": "o_release", 155 | "type": "bytes32" 156 | }, 157 | { 158 | "name": "o_platform", 159 | "type": "bytes32" 160 | } 161 | ], 162 | "payable": false, 163 | "type": "function" 164 | }, 165 | { 166 | "constant": false, 167 | "inputs": [], 168 | "name": "rejectFork", 169 | "outputs": [], 170 | "payable": false, 171 | "type": "function" 172 | }, 173 | { 174 | "constant": true, 175 | "inputs": [ 176 | { 177 | "name": "", 178 | "type": "bytes32" 179 | } 180 | ], 181 | "name": "client", 182 | "outputs": [ 183 | { 184 | "name": "owner", 185 | "type": "address" 186 | }, 187 | { 188 | "name": "required", 189 | "type": "bool" 190 | } 191 | ], 192 | "payable": false, 193 | "type": "function" 194 | }, 195 | { 196 | "constant": false, 197 | "inputs": [ 198 | { 199 | "name": "_newOwner", 200 | "type": "address" 201 | } 202 | ], 203 | "name": "setClientOwner", 204 | "outputs": [], 205 | "payable": false, 206 | "type": "function" 207 | }, 208 | { 209 | "constant": true, 210 | "inputs": [ 211 | { 212 | "name": "", 213 | "type": "uint32" 214 | } 215 | ], 216 | "name": "fork", 217 | "outputs": [ 218 | { 219 | "name": "name", 220 | "type": "bytes32" 221 | }, 222 | { 223 | "name": "spec", 224 | "type": "bytes32" 225 | }, 226 | { 227 | "name": "hard", 228 | "type": "bool" 229 | }, 230 | { 231 | "name": "ratified", 232 | "type": "bool" 233 | }, 234 | { 235 | "name": "requiredCount", 236 | "type": "uint256" 237 | } 238 | ], 239 | "payable": false, 240 | "type": "function" 241 | }, 242 | { 243 | "constant": false, 244 | "inputs": [ 245 | { 246 | "name": "_release", 247 | "type": "bytes32" 248 | }, 249 | { 250 | "name": "_platform", 251 | "type": "bytes32" 252 | }, 253 | { 254 | "name": "_checksum", 255 | "type": "bytes32" 256 | } 257 | ], 258 | "name": "addChecksum", 259 | "outputs": [], 260 | "payable": false, 261 | "type": "function" 262 | }, 263 | { 264 | "constant": false, 265 | "inputs": [ 266 | { 267 | "name": "_txid", 268 | "type": "bytes32" 269 | } 270 | ], 271 | "name": "confirmTransaction", 272 | "outputs": [ 273 | { 274 | "name": "txSuccess", 275 | "type": "uint256" 276 | } 277 | ], 278 | "payable": false, 279 | "type": "function" 280 | }, 281 | { 282 | "constant": true, 283 | "inputs": [ 284 | { 285 | "name": "", 286 | "type": "bytes32" 287 | } 288 | ], 289 | "name": "proxy", 290 | "outputs": [ 291 | { 292 | "name": "requiredCount", 293 | "type": "uint256" 294 | }, 295 | { 296 | "name": "to", 297 | "type": "address" 298 | }, 299 | { 300 | "name": "data", 301 | "type": "bytes" 302 | }, 303 | { 304 | "name": "value", 305 | "type": "uint256" 306 | }, 307 | { 308 | "name": "gas", 309 | "type": "uint256" 310 | } 311 | ], 312 | "payable": false, 313 | "type": "function" 314 | }, 315 | { 316 | "constant": false, 317 | "inputs": [ 318 | { 319 | "name": "_client", 320 | "type": "bytes32" 321 | }, 322 | { 323 | "name": "_owner", 324 | "type": "address" 325 | } 326 | ], 327 | "name": "addClient", 328 | "outputs": [], 329 | "payable": false, 330 | "type": "function" 331 | }, 332 | { 333 | "constant": true, 334 | "inputs": [ 335 | { 336 | "name": "", 337 | "type": "address" 338 | } 339 | ], 340 | "name": "clientOwner", 341 | "outputs": [ 342 | { 343 | "name": "", 344 | "type": "bytes32" 345 | } 346 | ], 347 | "payable": false, 348 | "type": "function" 349 | }, 350 | { 351 | "constant": false, 352 | "inputs": [ 353 | { 354 | "name": "_txid", 355 | "type": "bytes32" 356 | }, 357 | { 358 | "name": "_to", 359 | "type": "address" 360 | }, 361 | { 362 | "name": "_data", 363 | "type": "bytes" 364 | }, 365 | { 366 | "name": "_value", 367 | "type": "uint256" 368 | }, 369 | { 370 | "name": "_gas", 371 | "type": "uint256" 372 | } 373 | ], 374 | "name": "proposeTransaction", 375 | "outputs": [ 376 | { 377 | "name": "txSuccess", 378 | "type": "uint256" 379 | } 380 | ], 381 | "payable": false, 382 | "type": "function" 383 | }, 384 | { 385 | "constant": true, 386 | "inputs": [], 387 | "name": "grandOwner", 388 | "outputs": [ 389 | { 390 | "name": "", 391 | "type": "address" 392 | } 393 | ], 394 | "payable": false, 395 | "type": "function" 396 | }, 397 | { 398 | "constant": false, 399 | "inputs": [ 400 | { 401 | "name": "_release", 402 | "type": "bytes32" 403 | }, 404 | { 405 | "name": "_forkBlock", 406 | "type": "uint32" 407 | }, 408 | { 409 | "name": "_track", 410 | "type": "uint8" 411 | }, 412 | { 413 | "name": "_semver", 414 | "type": "uint24" 415 | }, 416 | { 417 | "name": "_critical", 418 | "type": "bool" 419 | } 420 | ], 421 | "name": "addRelease", 422 | "outputs": [], 423 | "payable": false, 424 | "type": "function" 425 | }, 426 | { 427 | "constant": false, 428 | "inputs": [], 429 | "name": "acceptFork", 430 | "outputs": [], 431 | "payable": false, 432 | "type": "function" 433 | }, 434 | { 435 | "constant": true, 436 | "inputs": [], 437 | "name": "clientsRequired", 438 | "outputs": [ 439 | { 440 | "name": "", 441 | "type": "uint32" 442 | } 443 | ], 444 | "payable": false, 445 | "type": "function" 446 | }, 447 | { 448 | "constant": true, 449 | "inputs": [ 450 | { 451 | "name": "_client", 452 | "type": "bytes32" 453 | }, 454 | { 455 | "name": "_release", 456 | "type": "bytes32" 457 | } 458 | ], 459 | "name": "track", 460 | "outputs": [ 461 | { 462 | "name": "", 463 | "type": "uint8" 464 | } 465 | ], 466 | "payable": false, 467 | "type": "function" 468 | }, 469 | { 470 | "constant": false, 471 | "inputs": [ 472 | { 473 | "name": "_client", 474 | "type": "bytes32" 475 | }, 476 | { 477 | "name": "_r", 478 | "type": "bool" 479 | } 480 | ], 481 | "name": "setClientRequired", 482 | "outputs": [], 483 | "payable": false, 484 | "type": "function" 485 | }, 486 | { 487 | "constant": true, 488 | "inputs": [], 489 | "name": "latestFork", 490 | "outputs": [ 491 | { 492 | "name": "", 493 | "type": "uint32" 494 | } 495 | ], 496 | "payable": false, 497 | "type": "function" 498 | }, 499 | { 500 | "constant": true, 501 | "inputs": [ 502 | { 503 | "name": "_client", 504 | "type": "bytes32" 505 | }, 506 | { 507 | "name": "_track", 508 | "type": "uint8" 509 | } 510 | ], 511 | "name": "latestInTrack", 512 | "outputs": [ 513 | { 514 | "name": "", 515 | "type": "bytes32" 516 | } 517 | ], 518 | "payable": false, 519 | "type": "function" 520 | }, 521 | { 522 | "constant": true, 523 | "inputs": [ 524 | { 525 | "name": "_client", 526 | "type": "bytes32" 527 | }, 528 | { 529 | "name": "_release", 530 | "type": "bytes32" 531 | }, 532 | { 533 | "name": "_platform", 534 | "type": "bytes32" 535 | } 536 | ], 537 | "name": "checksum", 538 | "outputs": [ 539 | { 540 | "name": "", 541 | "type": "bytes32" 542 | } 543 | ], 544 | "payable": false, 545 | "type": "function" 546 | }, 547 | { 548 | "constant": true, 549 | "inputs": [], 550 | "name": "proposedFork", 551 | "outputs": [ 552 | { 553 | "name": "", 554 | "type": "uint32" 555 | } 556 | ], 557 | "payable": false, 558 | "type": "function" 559 | } 560 | ] 561 | -------------------------------------------------------------------------------- /ethabi/src/token/token.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2020 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! Ethereum ABI params. 10 | use crate::{Address, Bytes, FixedBytes, ParamType, Uint}; 11 | use std::fmt; 12 | 13 | /// Ethereum ABI params. 14 | #[derive(Debug, PartialEq, Clone)] 15 | pub enum Token { 16 | /// Address. 17 | /// 18 | /// solidity name: address 19 | /// Encoded to left padded [0u8; 32]. 20 | Address(Address), 21 | /// Vector of bytes with known size. 22 | /// 23 | /// solidity name eg.: bytes8, bytes32, bytes64, bytes1024 24 | /// Encoded to right padded [0u8; ((N + 31) / 32) * 32]. 25 | FixedBytes(FixedBytes), 26 | /// Vector of bytes of unknown size. 27 | /// 28 | /// solidity name: bytes 29 | /// Encoded in two parts. 30 | /// Init part: offset of 'closing part`. 31 | /// Closing part: encoded length followed by encoded right padded bytes. 32 | Bytes(Bytes), 33 | /// Signed integer. 34 | /// 35 | /// solidity name: int 36 | Int(Uint), 37 | /// Unisnged integer. 38 | /// 39 | /// solidity name: uint 40 | Uint(Uint), 41 | /// Boolean value. 42 | /// 43 | /// solidity name: bool 44 | /// Encoded as left padded [0u8; 32], where last bit represents boolean value. 45 | Bool(bool), 46 | /// String. 47 | /// 48 | /// solidity name: string 49 | /// Encoded in the same way as bytes. Must be utf8 compliant. 50 | String(String), 51 | /// Array with known size. 52 | /// 53 | /// solidity name eg.: int[3], bool[3], address[][8] 54 | /// Encoding of array is equal to encoding of consecutive elements of array. 55 | FixedArray(Vec), 56 | /// Array of params with unknown size. 57 | /// 58 | /// solidity name eg. int[], bool[], address[5][] 59 | Array(Vec), 60 | /// Tuple of params of variable types. 61 | /// 62 | /// solidity name: tuple 63 | Tuple(Vec), 64 | } 65 | 66 | impl fmt::Display for Token { 67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 68 | match *self { 69 | Token::Bool(b) => write!(f, "{}", b), 70 | Token::String(ref s) => write!(f, "{}", s), 71 | Token::Address(ref a) => write!(f, "{:x}", a), 72 | Token::Bytes(ref bytes) | Token::FixedBytes(ref bytes) => write!(f, "{}", hex::encode(&bytes)), 73 | Token::Uint(ref i) | Token::Int(ref i) => write!(f, "{:x}", i), 74 | Token::Array(ref arr) | Token::FixedArray(ref arr) => { 75 | let s = arr.iter().map(|ref t| format!("{}", t)).collect::>().join(","); 76 | 77 | write!(f, "[{}]", s) 78 | } 79 | Token::Tuple(ref s) => { 80 | let s = s.iter().map(|ref t| format!("{}", t)).collect::>().join(","); 81 | 82 | write!(f, "({})", s) 83 | } 84 | } 85 | } 86 | } 87 | 88 | impl Token { 89 | /// Check whether the type of the token matches the given parameter type. 90 | /// 91 | /// Numeric types (`Int` and `Uint`) type check if the size of the token 92 | /// type is of greater or equal size than the provided parameter type. 93 | pub fn type_check(&self, param_type: &ParamType) -> bool { 94 | match *self { 95 | Token::Address(_) => *param_type == ParamType::Address, 96 | Token::Bytes(_) => *param_type == ParamType::Bytes, 97 | Token::Int(_) => { 98 | matches!(*param_type, ParamType::Int(_)) 99 | } 100 | Token::Uint(_) => { 101 | matches!(*param_type, ParamType::Uint(_)) 102 | } 103 | Token::Bool(_) => *param_type == ParamType::Bool, 104 | Token::String(_) => *param_type == ParamType::String, 105 | Token::FixedBytes(ref bytes) => { 106 | if let ParamType::FixedBytes(size) = *param_type { 107 | size >= bytes.len() 108 | } else { 109 | false 110 | } 111 | } 112 | Token::Array(ref tokens) => { 113 | if let ParamType::Array(ref param_type) = *param_type { 114 | tokens.iter().all(|t| t.type_check(param_type)) 115 | } else { 116 | false 117 | } 118 | } 119 | Token::FixedArray(ref tokens) => { 120 | if let ParamType::FixedArray(ref param_type, size) = *param_type { 121 | size == tokens.len() && tokens.iter().all(|t| t.type_check(param_type)) 122 | } else { 123 | false 124 | } 125 | } 126 | Token::Tuple(ref tokens) => { 127 | if let ParamType::Tuple(ref param_type) = *param_type { 128 | tokens.iter().enumerate().all(|(i, t)| t.type_check(¶m_type[i])) 129 | } else { 130 | false 131 | } 132 | } 133 | } 134 | } 135 | 136 | /// Converts token to... 137 | pub fn into_address(self) -> Option
{ 138 | match self { 139 | Token::Address(address) => Some(address), 140 | _ => None, 141 | } 142 | } 143 | 144 | /// Converts token to... 145 | pub fn into_fixed_bytes(self) -> Option> { 146 | match self { 147 | Token::FixedBytes(bytes) => Some(bytes), 148 | _ => None, 149 | } 150 | } 151 | 152 | /// Converts token to... 153 | pub fn into_bytes(self) -> Option> { 154 | match self { 155 | Token::Bytes(bytes) => Some(bytes), 156 | _ => None, 157 | } 158 | } 159 | 160 | /// Converts token to... 161 | pub fn into_int(self) -> Option { 162 | match self { 163 | Token::Int(int) => Some(int), 164 | _ => None, 165 | } 166 | } 167 | 168 | /// Converts token to... 169 | pub fn into_uint(self) -> Option { 170 | match self { 171 | Token::Uint(uint) => Some(uint), 172 | _ => None, 173 | } 174 | } 175 | 176 | /// Converts token to... 177 | pub fn into_bool(self) -> Option { 178 | match self { 179 | Token::Bool(b) => Some(b), 180 | _ => None, 181 | } 182 | } 183 | 184 | /// Converts token to... 185 | pub fn into_string(self) -> Option { 186 | match self { 187 | Token::String(s) => Some(s), 188 | _ => None, 189 | } 190 | } 191 | 192 | /// Converts token to... 193 | pub fn into_fixed_array(self) -> Option> { 194 | match self { 195 | Token::FixedArray(arr) => Some(arr), 196 | _ => None, 197 | } 198 | } 199 | 200 | /// Converts token to... 201 | pub fn into_array(self) -> Option> { 202 | match self { 203 | Token::Array(arr) => Some(arr), 204 | _ => None, 205 | } 206 | } 207 | 208 | /// Check if all the types of the tokens match the given parameter types. 209 | pub fn types_check(tokens: &[Token], param_types: &[ParamType]) -> bool { 210 | param_types.len() == tokens.len() && { 211 | param_types.iter().zip(tokens).all(|(param_type, token)| token.type_check(param_type)) 212 | } 213 | } 214 | 215 | /// Check if the token is a dynamic type resulting in prefixed encoding 216 | pub fn is_dynamic(&self) -> bool { 217 | match self { 218 | Token::Bytes(_) | Token::String(_) | Token::Array(_) => true, 219 | Token::FixedArray(tokens) => tokens.iter().any(|token| token.is_dynamic()), 220 | Token::Tuple(tokens) => tokens.iter().any(|token| token.is_dynamic()), 221 | _ => false, 222 | } 223 | } 224 | } 225 | 226 | #[cfg(test)] 227 | mod tests { 228 | use crate::{ParamType, Token}; 229 | 230 | #[test] 231 | fn test_type_check() { 232 | fn assert_type_check(tokens: Vec, param_types: Vec) { 233 | assert!(Token::types_check(&tokens, ¶m_types)) 234 | } 235 | 236 | fn assert_not_type_check(tokens: Vec, param_types: Vec) { 237 | assert!(!Token::types_check(&tokens, ¶m_types)) 238 | } 239 | 240 | assert_type_check(vec![Token::Uint(0.into()), Token::Bool(false)], vec![ParamType::Uint(256), ParamType::Bool]); 241 | assert_type_check(vec![Token::Uint(0.into()), Token::Bool(false)], vec![ParamType::Uint(32), ParamType::Bool]); 242 | 243 | assert_not_type_check(vec![Token::Uint(0.into())], vec![ParamType::Uint(32), ParamType::Bool]); 244 | assert_not_type_check(vec![Token::Uint(0.into()), Token::Bool(false)], vec![ParamType::Uint(32)]); 245 | assert_not_type_check( 246 | vec![Token::Bool(false), Token::Uint(0.into())], 247 | vec![ParamType::Uint(32), ParamType::Bool], 248 | ); 249 | 250 | assert_type_check(vec![Token::FixedBytes(vec![0, 0, 0, 0])], vec![ParamType::FixedBytes(4)]); 251 | assert_type_check(vec![Token::FixedBytes(vec![0, 0, 0])], vec![ParamType::FixedBytes(4)]); 252 | assert_not_type_check(vec![Token::FixedBytes(vec![0, 0, 0, 0])], vec![ParamType::FixedBytes(3)]); 253 | 254 | assert_type_check( 255 | vec![Token::Array(vec![Token::Bool(false), Token::Bool(true)])], 256 | vec![ParamType::Array(Box::new(ParamType::Bool))], 257 | ); 258 | assert_not_type_check( 259 | vec![Token::Array(vec![Token::Bool(false), Token::Uint(0.into())])], 260 | vec![ParamType::Array(Box::new(ParamType::Bool))], 261 | ); 262 | assert_not_type_check( 263 | vec![Token::Array(vec![Token::Bool(false), Token::Bool(true)])], 264 | vec![ParamType::Array(Box::new(ParamType::Address))], 265 | ); 266 | 267 | assert_type_check( 268 | vec![Token::FixedArray(vec![Token::Bool(false), Token::Bool(true)])], 269 | vec![ParamType::FixedArray(Box::new(ParamType::Bool), 2)], 270 | ); 271 | assert_not_type_check( 272 | vec![Token::FixedArray(vec![Token::Bool(false), Token::Bool(true)])], 273 | vec![ParamType::FixedArray(Box::new(ParamType::Bool), 3)], 274 | ); 275 | assert_not_type_check( 276 | vec![Token::FixedArray(vec![Token::Bool(false), Token::Uint(0.into())])], 277 | vec![ParamType::FixedArray(Box::new(ParamType::Bool), 2)], 278 | ); 279 | assert_not_type_check( 280 | vec![Token::FixedArray(vec![Token::Bool(false), Token::Bool(true)])], 281 | vec![ParamType::FixedArray(Box::new(ParamType::Address), 2)], 282 | ); 283 | } 284 | 285 | #[test] 286 | fn test_is_dynamic() { 287 | assert_eq!(Token::Address("0000000000000000000000000000000000000000".parse().unwrap()).is_dynamic(), false); 288 | assert_eq!(Token::Bytes(vec![0, 0, 0, 0]).is_dynamic(), true); 289 | assert_eq!(Token::FixedBytes(vec![0, 0, 0, 0]).is_dynamic(), false); 290 | assert_eq!(Token::Uint(0.into()).is_dynamic(), false); 291 | assert_eq!(Token::Int(0.into()).is_dynamic(), false); 292 | assert_eq!(Token::Bool(false).is_dynamic(), false); 293 | assert_eq!(Token::String("".into()).is_dynamic(), true); 294 | assert_eq!(Token::Array(vec![Token::Bool(false)]).is_dynamic(), true); 295 | assert_eq!(Token::FixedArray(vec![Token::Uint(0.into())]).is_dynamic(), false); 296 | assert_eq!(Token::FixedArray(vec![Token::String("".into())]).is_dynamic(), true); 297 | assert_eq!(Token::FixedArray(vec![Token::Array(vec![Token::Bool(false)])]).is_dynamic(), true); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2019 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | #![recursion_limit = "256"] 10 | 11 | extern crate proc_macro; 12 | 13 | mod constructor; 14 | mod contract; 15 | mod event; 16 | mod function; 17 | 18 | use anyhow::anyhow; 19 | use ethabi::{Contract, Param, ParamType, Result}; 20 | use heck::SnakeCase; 21 | use quote::quote; 22 | use std::{env, fs, path::PathBuf}; 23 | use syn::export::Span; 24 | 25 | const ERROR_MSG: &str = "`derive(EthabiContract)` failed"; 26 | 27 | #[proc_macro_derive(EthabiContract, attributes(ethabi_contract_options))] 28 | pub fn ethabi_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 29 | let ast = syn::parse(input).expect(ERROR_MSG); 30 | let gen = impl_ethabi_derive(&ast).expect(ERROR_MSG); 31 | gen.into() 32 | } 33 | 34 | fn impl_ethabi_derive(ast: &syn::DeriveInput) -> Result { 35 | let options = get_options(&ast.attrs, "ethabi_contract_options")?; 36 | let path = get_option(&options, "path")?; 37 | let normalized_path = normalize_path(&path)?; 38 | let source_file = fs::File::open(&normalized_path) 39 | .map_err(|_| anyhow!("Cannot load contract abi from `{}`", normalized_path.display()))?; 40 | let contract = Contract::load(source_file)?; 41 | let c = contract::Contract::from(&contract); 42 | Ok(c.generate()) 43 | } 44 | 45 | fn get_options(attrs: &[syn::Attribute], name: &str) -> Result> { 46 | let options = attrs.iter().flat_map(syn::Attribute::parse_meta).find(|meta| meta.path().is_ident(name)); 47 | 48 | match options { 49 | Some(syn::Meta::List(list)) => Ok(list.nested.into_iter().collect()), 50 | _ => Err(anyhow!("Unexpected meta item").into()), 51 | } 52 | } 53 | 54 | fn get_option(options: &[syn::NestedMeta], name: &str) -> Result { 55 | let item = options 56 | .iter() 57 | .flat_map(|nested| match *nested { 58 | syn::NestedMeta::Meta(ref meta) => Some(meta), 59 | _ => None, 60 | }) 61 | .find(|meta| meta.path().is_ident(name)) 62 | .ok_or_else(|| anyhow!("Expected to find option {}", name))?; 63 | 64 | str_value_of_meta_item(item, name) 65 | } 66 | 67 | fn str_value_of_meta_item(item: &syn::Meta, name: &str) -> Result { 68 | if let syn::Meta::NameValue(ref name_value) = *item { 69 | if let syn::Lit::Str(ref value) = name_value.lit { 70 | return Ok(value.value()); 71 | } 72 | } 73 | 74 | Err(anyhow!(r#"`{}` must be in the form `#[{}="something"]`"#, name, name).into()) 75 | } 76 | 77 | fn normalize_path(relative_path: &str) -> Result { 78 | // workaround for https://github.com/rust-lang/rust/issues/43860 79 | let cargo_toml_directory = env::var("CARGO_MANIFEST_DIR").map_err(|_| anyhow!("Cannot find manifest file"))?; 80 | let mut path: PathBuf = cargo_toml_directory.into(); 81 | path.push(relative_path); 82 | Ok(path) 83 | } 84 | 85 | fn to_syntax_string(param_type: ðabi::ParamType) -> proc_macro2::TokenStream { 86 | match *param_type { 87 | ParamType::Address => quote! { ethabi::ParamType::Address }, 88 | ParamType::Bytes => quote! { ethabi::ParamType::Bytes }, 89 | ParamType::Int(x) => quote! { ethabi::ParamType::Int(#x) }, 90 | ParamType::Uint(x) => quote! { ethabi::ParamType::Uint(#x) }, 91 | ParamType::Bool => quote! { ethabi::ParamType::Bool }, 92 | ParamType::String => quote! { ethabi::ParamType::String }, 93 | ParamType::Array(ref param_type) => { 94 | let param_type_quote = to_syntax_string(param_type); 95 | quote! { ethabi::ParamType::Array(Box::new(#param_type_quote)) } 96 | } 97 | ParamType::FixedBytes(x) => quote! { ethabi::ParamType::FixedBytes(#x) }, 98 | ParamType::FixedArray(ref param_type, ref x) => { 99 | let param_type_quote = to_syntax_string(param_type); 100 | quote! { ethabi::ParamType::FixedArray(Box::new(#param_type_quote), #x) } 101 | } 102 | ParamType::Tuple(_) => unimplemented!(), 103 | } 104 | } 105 | 106 | fn to_ethabi_param_vec<'a, P: 'a>(params: P) -> proc_macro2::TokenStream 107 | where 108 | P: IntoIterator, 109 | { 110 | let p = params 111 | .into_iter() 112 | .map(|x| { 113 | let name = &x.name; 114 | let kind = to_syntax_string(&x.kind); 115 | quote! { 116 | ethabi::Param { 117 | name: #name.to_owned(), 118 | kind: #kind 119 | } 120 | } 121 | }) 122 | .collect::>(); 123 | 124 | quote! { vec![ #(#p),* ] } 125 | } 126 | 127 | fn rust_type(input: &ParamType) -> proc_macro2::TokenStream { 128 | match *input { 129 | ParamType::Address => quote! { ethabi::Address }, 130 | ParamType::Bytes => quote! { ethabi::Bytes }, 131 | ParamType::FixedBytes(32) => quote! { ethabi::Hash }, 132 | ParamType::FixedBytes(size) => quote! { [u8; #size] }, 133 | ParamType::Int(_) => quote! { ethabi::Int }, 134 | ParamType::Uint(_) => quote! { ethabi::Uint }, 135 | ParamType::Bool => quote! { bool }, 136 | ParamType::String => quote! { String }, 137 | ParamType::Array(ref kind) => { 138 | let t = rust_type(&*kind); 139 | quote! { Vec<#t> } 140 | } 141 | ParamType::FixedArray(ref kind, size) => { 142 | let t = rust_type(&*kind); 143 | quote! { [#t, #size] } 144 | } 145 | ParamType::Tuple(_) => unimplemented!(), 146 | } 147 | } 148 | 149 | fn template_param_type(input: &ParamType, index: usize) -> proc_macro2::TokenStream { 150 | let t_ident = syn::Ident::new(&format!("T{}", index), Span::call_site()); 151 | let u_ident = syn::Ident::new(&format!("U{}", index), Span::call_site()); 152 | match *input { 153 | ParamType::Address => quote! { #t_ident: Into }, 154 | ParamType::Bytes => quote! { #t_ident: Into }, 155 | ParamType::FixedBytes(32) => quote! { #t_ident: Into }, 156 | ParamType::FixedBytes(size) => quote! { #t_ident: Into<[u8; #size]> }, 157 | ParamType::Int(_) => quote! { #t_ident: Into }, 158 | ParamType::Uint(_) => quote! { #t_ident: Into }, 159 | ParamType::Bool => quote! { #t_ident: Into }, 160 | ParamType::String => quote! { #t_ident: Into }, 161 | ParamType::Array(ref kind) => { 162 | let t = rust_type(&*kind); 163 | quote! { 164 | #t_ident: IntoIterator, #u_ident: Into<#t> 165 | } 166 | } 167 | ParamType::FixedArray(ref kind, size) => { 168 | let t = rust_type(&*kind); 169 | quote! { 170 | #t_ident: Into<[#u_ident; #size]>, #u_ident: Into<#t> 171 | } 172 | } 173 | ParamType::Tuple(_) => unimplemented!(), 174 | } 175 | } 176 | 177 | fn from_template_param(input: &ParamType, name: &syn::Ident) -> proc_macro2::TokenStream { 178 | match *input { 179 | ParamType::Array(_) => quote! { #name.into_iter().map(Into::into).collect::>() }, 180 | ParamType::FixedArray(_, _) => { 181 | quote! { (Box::new(#name.into()) as Box<[_]>).into_vec().into_iter().map(Into::into).collect::>() } 182 | } 183 | _ => quote! {#name.into() }, 184 | } 185 | } 186 | 187 | fn to_token(name: &proc_macro2::TokenStream, kind: &ParamType) -> proc_macro2::TokenStream { 188 | match *kind { 189 | ParamType::Address => quote! { ethabi::Token::Address(#name) }, 190 | ParamType::Bytes => quote! { ethabi::Token::Bytes(#name) }, 191 | ParamType::FixedBytes(_) => quote! { ethabi::Token::FixedBytes(#name.as_ref().to_vec()) }, 192 | ParamType::Int(_) => quote! { ethabi::Token::Int(#name) }, 193 | ParamType::Uint(_) => quote! { ethabi::Token::Uint(#name) }, 194 | ParamType::Bool => quote! { ethabi::Token::Bool(#name) }, 195 | ParamType::String => quote! { ethabi::Token::String(#name) }, 196 | ParamType::Array(ref kind) => { 197 | let inner_name = quote! { inner }; 198 | let inner_loop = to_token(&inner_name, kind); 199 | quote! { 200 | // note the double {{ 201 | { 202 | let v = #name.into_iter().map(|#inner_name| #inner_loop).collect(); 203 | ethabi::Token::Array(v) 204 | } 205 | } 206 | } 207 | ParamType::FixedArray(ref kind, _) => { 208 | let inner_name = quote! { inner }; 209 | let inner_loop = to_token(&inner_name, kind); 210 | quote! { 211 | // note the double {{ 212 | { 213 | let v = #name.into_iter().map(|#inner_name| #inner_loop).collect(); 214 | ethabi::Token::FixedArray(v) 215 | } 216 | } 217 | } 218 | ParamType::Tuple(_) => unimplemented!(), 219 | } 220 | } 221 | 222 | fn from_token(kind: &ParamType, token: &proc_macro2::TokenStream) -> proc_macro2::TokenStream { 223 | match *kind { 224 | ParamType::Address => quote! { #token.into_address().expect(INTERNAL_ERR) }, 225 | ParamType::Bytes => quote! { #token.into_bytes().expect(INTERNAL_ERR) }, 226 | ParamType::FixedBytes(32) => quote! { 227 | { 228 | let mut result = [0u8; 32]; 229 | let v = #token.into_fixed_bytes().expect(INTERNAL_ERR); 230 | result.copy_from_slice(&v); 231 | ethabi::Hash::from(result) 232 | } 233 | }, 234 | ParamType::FixedBytes(size) => { 235 | let size: syn::Index = size.into(); 236 | quote! { 237 | { 238 | let mut result = [0u8; #size]; 239 | let v = #token.into_fixed_bytes().expect(INTERNAL_ERR); 240 | result.copy_from_slice(&v); 241 | result 242 | } 243 | } 244 | } 245 | ParamType::Int(_) => quote! { #token.into_int().expect(INTERNAL_ERR) }, 246 | ParamType::Uint(_) => quote! { #token.into_uint().expect(INTERNAL_ERR) }, 247 | ParamType::Bool => quote! { #token.into_bool().expect(INTERNAL_ERR) }, 248 | ParamType::String => quote! { #token.into_string().expect(INTERNAL_ERR) }, 249 | ParamType::Array(ref kind) => { 250 | let inner = quote! { inner }; 251 | let inner_loop = from_token(kind, &inner); 252 | quote! { 253 | #token.into_array().expect(INTERNAL_ERR).into_iter() 254 | .map(|#inner| #inner_loop) 255 | .collect() 256 | } 257 | } 258 | ParamType::FixedArray(ref kind, size) => { 259 | let inner = quote! { inner }; 260 | let inner_loop = from_token(kind, &inner); 261 | let to_array = vec![quote! { iter.next() }; size]; 262 | quote! { 263 | { 264 | let iter = #token.to_array().expect(INTERNAL_ERR).into_iter() 265 | .map(|#inner| #inner_loop); 266 | [#(#to_array),*] 267 | } 268 | } 269 | } 270 | ParamType::Tuple(_) => unimplemented!(), 271 | } 272 | } 273 | 274 | fn input_names(inputs: &[Param]) -> Vec { 275 | inputs 276 | .iter() 277 | .enumerate() 278 | .map(|(index, param)| { 279 | if param.name.is_empty() { 280 | syn::Ident::new(&format!("param{}", index), Span::call_site()) 281 | } else { 282 | syn::Ident::new(&rust_variable(¶m.name), Span::call_site()) 283 | } 284 | }) 285 | .collect() 286 | } 287 | 288 | fn get_template_names(kinds: &[proc_macro2::TokenStream]) -> Vec { 289 | kinds.iter().enumerate().map(|(index, _)| syn::Ident::new(&format!("T{}", index), Span::call_site())).collect() 290 | } 291 | 292 | fn get_output_kinds(outputs: &[Param]) -> proc_macro2::TokenStream { 293 | match outputs.len() { 294 | 0 => quote! {()}, 295 | 1 => { 296 | let t = rust_type(&outputs[0].kind); 297 | quote! { #t } 298 | } 299 | _ => { 300 | let outs: Vec<_> = outputs.iter().map(|param| rust_type(¶m.kind)).collect(); 301 | quote! { (#(#outs),*) } 302 | } 303 | } 304 | } 305 | 306 | /// Convert input into a rust variable name. 307 | /// 308 | /// Avoid using keywords by escaping them. 309 | fn rust_variable(name: &str) -> String { 310 | // avoid keyword parameters 311 | match name { 312 | "self" => "_self".to_string(), 313 | other => other.to_snake_case(), 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /derive/src/event.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2019 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use heck::{CamelCase, SnakeCase}; 10 | use proc_macro2::TokenStream; 11 | use quote::quote; 12 | use syn::export::Span; 13 | 14 | use super::{from_token, get_template_names, rust_type, to_syntax_string, to_token}; 15 | 16 | /// Structure used to generate contract's event interface. 17 | pub struct Event { 18 | name: String, 19 | log_fields: Vec, 20 | recreate_inputs_quote: TokenStream, 21 | log_init: Vec, 22 | wildcard_filter_params: Vec, 23 | filter_declarations: Vec, 24 | filter_definitions: Vec, 25 | filter_init: Vec, 26 | anonymous: bool, 27 | } 28 | 29 | impl<'a> From<&'a ethabi::Event> for Event { 30 | fn from(e: &'a ethabi::Event) -> Self { 31 | let names: Vec<_> = e 32 | .inputs 33 | .iter() 34 | .enumerate() 35 | .map(|(index, param)| { 36 | if param.name.is_empty() { 37 | if param.indexed { 38 | syn::Ident::new(&format!("topic{}", index), Span::call_site()) 39 | } else { 40 | syn::Ident::new(&format!("param{}", index), Span::call_site()) 41 | } 42 | } else { 43 | syn::Ident::new(¶m.name.to_snake_case(), Span::call_site()) 44 | } 45 | }) 46 | .collect(); 47 | let kinds: Vec<_> = e.inputs.iter().map(|param| rust_type(¶m.kind)).collect(); 48 | let log_fields = 49 | names.iter().zip(kinds.iter()).map(|(param_name, kind)| quote! { pub #param_name: #kind }).collect(); 50 | 51 | let log_iter = quote! { log.next().expect(INTERNAL_ERR).value }; 52 | 53 | let to_log: Vec<_> = e.inputs.iter().map(|param| from_token(¶m.kind, &log_iter)).collect(); 54 | 55 | let log_init = 56 | names.iter().zip(to_log.iter()).map(|(param_name, convert)| quote! { #param_name: #convert }).collect(); 57 | 58 | let topic_kinds: Vec<_> = 59 | e.inputs.iter().filter(|param| param.indexed).map(|param| rust_type(¶m.kind)).collect(); 60 | let topic_names: Vec<_> = e 61 | .inputs 62 | .iter() 63 | .enumerate() 64 | .filter(|&(_, param)| param.indexed) 65 | .map(|(index, param)| { 66 | if param.name.is_empty() { 67 | syn::Ident::new(&format!("topic{}", index), Span::call_site()) 68 | } else { 69 | syn::Ident::new(¶m.name.to_snake_case(), Span::call_site()) 70 | } 71 | }) 72 | .collect(); 73 | 74 | // [T0, T1, T2] 75 | let template_names: Vec<_> = get_template_names(&topic_kinds); 76 | 77 | let filter_declarations: Vec<_> = topic_kinds 78 | .iter() 79 | .zip(template_names.iter()) 80 | .map(|(kind, template_name)| quote! { #template_name: Into> }) 81 | .collect(); 82 | 83 | let filter_definitions: Vec<_> = topic_names 84 | .iter() 85 | .zip(template_names.iter()) 86 | .map(|(param_name, template_name)| quote! { #param_name: #template_name }) 87 | .collect(); 88 | 89 | // The number of parameters that creates a filter which matches anything. 90 | let wildcard_filter_params: Vec<_> = filter_definitions.iter().map(|_| quote! { ethabi::Topic::Any }).collect(); 91 | 92 | let filter_init: Vec<_> = topic_names 93 | .iter() 94 | .zip(e.inputs.iter().filter(|p| p.indexed)) 95 | .enumerate() 96 | .take(3) 97 | .map(|(index, (param_name, param))| { 98 | let topic = syn::Ident::new(&format!("topic{}", index), Span::call_site()); 99 | let i = quote! { i }; 100 | let to_token = to_token(&i, ¶m.kind); 101 | quote! { #topic: #param_name.into().map(|#i| #to_token), } 102 | }) 103 | .collect(); 104 | 105 | let event_inputs = &e 106 | .inputs 107 | .iter() 108 | .map(|x| { 109 | let name = &x.name; 110 | let kind = to_syntax_string(&x.kind); 111 | let indexed = x.indexed; 112 | 113 | quote! { 114 | ethabi::EventParam { 115 | name: #name.to_owned(), 116 | kind: #kind, 117 | indexed: #indexed 118 | } 119 | } 120 | }) 121 | .collect::>(); 122 | let recreate_inputs_quote = quote! { vec![ #(#event_inputs),* ] }; 123 | 124 | Event { 125 | name: e.name.clone(), 126 | log_fields, 127 | recreate_inputs_quote, 128 | log_init, 129 | anonymous: e.anonymous, 130 | wildcard_filter_params, 131 | filter_declarations, 132 | filter_definitions, 133 | filter_init, 134 | } 135 | } 136 | } 137 | 138 | impl Event { 139 | /// Generates event log struct. 140 | pub fn generate_log(&self) -> TokenStream { 141 | let name = syn::Ident::new(&self.name.to_camel_case(), Span::call_site()); 142 | let log_fields = &self.log_fields; 143 | 144 | quote! { 145 | #[derive(Debug, Clone, PartialEq)] 146 | pub struct #name { 147 | #(#log_fields),* 148 | } 149 | } 150 | } 151 | 152 | /// Generates rust interface for contract's event. 153 | pub fn generate_event(&self) -> TokenStream { 154 | let name_as_string = &self.name.to_camel_case(); 155 | let name = syn::Ident::new(&self.name.to_snake_case(), Span::call_site()); 156 | let camel_name = syn::Ident::new(&self.name.to_camel_case(), Span::call_site()); 157 | let recreate_inputs_quote = &self.recreate_inputs_quote; 158 | let anonymous = &self.anonymous; 159 | let log_init = &self.log_init; 160 | let filter_init = &self.filter_init; 161 | let filter_declarations = &self.filter_declarations; 162 | let filter_definitions = &self.filter_definitions; 163 | let wildcard_filter_params = &self.wildcard_filter_params; 164 | 165 | quote! { 166 | pub mod #name { 167 | use ethabi; 168 | use super::INTERNAL_ERR; 169 | 170 | pub fn event() -> ethabi::Event { 171 | ethabi::Event { 172 | name: #name_as_string.into(), 173 | inputs: #recreate_inputs_quote, 174 | anonymous: #anonymous, 175 | } 176 | } 177 | 178 | pub fn filter<#(#filter_declarations),*>(#(#filter_definitions),*) -> ethabi::TopicFilter { 179 | let raw = ethabi::RawTopicFilter { 180 | #(#filter_init)* 181 | ..Default::default() 182 | }; 183 | 184 | let e = event(); 185 | e.filter(raw).expect(INTERNAL_ERR) 186 | } 187 | 188 | pub fn wildcard_filter() -> ethabi::TopicFilter { 189 | filter(#(#wildcard_filter_params),*) 190 | } 191 | 192 | pub fn parse_log(log: ethabi::RawLog) -> ethabi::Result { 193 | let e = event(); 194 | let mut log = e.parse_log(log)?.params.into_iter(); 195 | let result = super::super::logs::#camel_name { 196 | #(#log_init),* 197 | }; 198 | Ok(result) 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | #[cfg(test)] 206 | mod tests { 207 | use super::Event; 208 | use quote::quote; 209 | 210 | #[test] 211 | fn test_empty_log() { 212 | let ethabi_event = ethabi::Event { name: "hello".into(), inputs: vec![], anonymous: false }; 213 | 214 | let e = Event::from(ðabi_event); 215 | 216 | let expected = quote! { 217 | #[derive(Debug, Clone, PartialEq)] 218 | pub struct Hello {} 219 | }; 220 | 221 | assert_eq!(expected.to_string(), e.generate_log().to_string()); 222 | } 223 | 224 | #[test] 225 | fn test_empty_event() { 226 | let ethabi_event = ethabi::Event { name: "hello".into(), inputs: vec![], anonymous: false }; 227 | 228 | let e = Event::from(ðabi_event); 229 | 230 | let expected = quote! { 231 | pub mod hello { 232 | use ethabi; 233 | use super::INTERNAL_ERR; 234 | 235 | pub fn event() -> ethabi::Event { 236 | ethabi::Event { 237 | name: "Hello".into(), 238 | inputs: vec![], 239 | anonymous: false, 240 | } 241 | } 242 | 243 | pub fn filter<>() -> ethabi::TopicFilter { 244 | let raw = ethabi::RawTopicFilter { 245 | ..Default::default() 246 | }; 247 | 248 | let e = event(); 249 | e.filter(raw).expect(INTERNAL_ERR) 250 | } 251 | 252 | pub fn wildcard_filter() -> ethabi::TopicFilter { 253 | filter() 254 | } 255 | 256 | pub fn parse_log(log: ethabi::RawLog) -> ethabi::Result { 257 | let e = event(); 258 | let mut log = e.parse_log(log)?.params.into_iter(); 259 | let result = super::super::logs::Hello {}; 260 | Ok(result) 261 | } 262 | } 263 | }; 264 | 265 | assert_eq!(expected.to_string(), e.generate_event().to_string()); 266 | } 267 | 268 | #[test] 269 | fn test_event_with_one_input() { 270 | let ethabi_event = ethabi::Event { 271 | name: "one".into(), 272 | inputs: vec![ethabi::EventParam { name: "foo".into(), kind: ethabi::ParamType::Address, indexed: true }], 273 | anonymous: false, 274 | }; 275 | 276 | let e = Event::from(ðabi_event); 277 | 278 | let expected = quote! { 279 | pub mod one { 280 | use ethabi; 281 | use super::INTERNAL_ERR; 282 | 283 | pub fn event() -> ethabi::Event { 284 | ethabi::Event { 285 | name: "One".into(), 286 | inputs: vec![ethabi::EventParam { 287 | name: "foo".to_owned(), 288 | kind: ethabi::ParamType::Address, 289 | indexed: true 290 | }], 291 | anonymous: false, 292 | } 293 | } 294 | 295 | pub fn filter>>(foo: T0) -> ethabi::TopicFilter { 296 | let raw = ethabi::RawTopicFilter { 297 | topic0: foo.into().map(|i| ethabi::Token::Address(i)), 298 | ..Default::default() 299 | }; 300 | 301 | let e = event(); 302 | e.filter(raw).expect(INTERNAL_ERR) 303 | } 304 | 305 | pub fn wildcard_filter() -> ethabi::TopicFilter { 306 | filter(ethabi::Topic::Any) 307 | } 308 | 309 | pub fn parse_log(log: ethabi::RawLog) -> ethabi::Result { 310 | let e = event(); 311 | let mut log = e.parse_log(log)?.params.into_iter(); 312 | let result = super::super::logs::One { 313 | foo: log.next().expect(INTERNAL_ERR).value.into_address().expect(INTERNAL_ERR) 314 | }; 315 | Ok(result) 316 | } 317 | } 318 | }; 319 | 320 | assert_eq!(expected.to_string(), e.generate_event().to_string()); 321 | } 322 | 323 | #[test] 324 | fn test_log_with_one_field() { 325 | let ethabi_event = ethabi::Event { 326 | name: "one".into(), 327 | inputs: vec![ethabi::EventParam { name: "foo".into(), kind: ethabi::ParamType::Address, indexed: false }], 328 | anonymous: false, 329 | }; 330 | 331 | let e = Event::from(ðabi_event); 332 | 333 | let expected = quote! { 334 | #[derive(Debug, Clone, PartialEq)] 335 | pub struct One { 336 | pub foo: ethabi::Address 337 | } 338 | }; 339 | 340 | assert_eq!(expected.to_string(), e.generate_log().to_string()); 341 | } 342 | 343 | #[test] 344 | fn test_log_with_multiple_field() { 345 | let ethabi_event = ethabi::Event { 346 | name: "many".into(), 347 | inputs: vec![ 348 | ethabi::EventParam { name: "foo".into(), kind: ethabi::ParamType::Address, indexed: false }, 349 | ethabi::EventParam { 350 | name: "bar".into(), 351 | kind: ethabi::ParamType::Array(Box::new(ethabi::ParamType::String)), 352 | indexed: false, 353 | }, 354 | ethabi::EventParam { name: "xyz".into(), kind: ethabi::ParamType::Uint(256), indexed: false }, 355 | ], 356 | anonymous: false, 357 | }; 358 | 359 | let e = Event::from(ðabi_event); 360 | 361 | let expected = quote! { 362 | #[derive(Debug, Clone, PartialEq)] 363 | pub struct Many { 364 | pub foo: ethabi::Address, 365 | pub bar: Vec, 366 | pub xyz: ethabi::Uint 367 | } 368 | }; 369 | 370 | assert_eq!(expected.to_string(), e.generate_log().to_string()); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /derive/src/function.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2019 Parity Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use heck::SnakeCase; 10 | use proc_macro2::TokenStream; 11 | use quote::quote; 12 | use syn::export::Span; 13 | 14 | use super::{ 15 | from_template_param, from_token, get_output_kinds, get_template_names, input_names, rust_type, template_param_type, 16 | to_ethabi_param_vec, to_token, 17 | }; 18 | 19 | struct TemplateParam { 20 | /// Template param declaration. 21 | /// 22 | /// ```text 23 | /// [T0: Into, T1: Into, T2: IntoIterator, U2 = Into] 24 | /// ``` 25 | declaration: TokenStream, 26 | /// Template param definition. 27 | /// 28 | /// ```text 29 | /// [param0: T0, hello_world: T1, param2: T2] 30 | /// ``` 31 | definition: TokenStream, 32 | } 33 | 34 | struct Inputs { 35 | /// Collects template params into vector. 36 | /// 37 | /// ```text 38 | /// [Token::Uint(param0.into()), Token::Bytes(hello_world.into()), Token::Array(param2.into_iter().map(Into::into).collect())] 39 | /// ``` 40 | tokenize: Vec, 41 | /// Template params. 42 | template_params: Vec, 43 | /// Quote used to recreate `Vec` 44 | recreate_quote: TokenStream, 45 | } 46 | 47 | struct Outputs { 48 | /// Decoding implementation. 49 | implementation: TokenStream, 50 | /// Decode result. 51 | result: TokenStream, 52 | /// Quote used to recreate `Vec`. 53 | recreate_quote: TokenStream, 54 | } 55 | 56 | /// Structure used to generate contract's function interface. 57 | pub struct Function { 58 | /// Function name. 59 | name: String, 60 | /// Function input params. 61 | inputs: Inputs, 62 | /// Function output params. 63 | outputs: Outputs, 64 | /// Constant function. 65 | constant: bool, 66 | } 67 | 68 | impl<'a> From<&'a ethabi::Function> for Function { 69 | fn from(f: &'a ethabi::Function) -> Self { 70 | // [param0, hello_world, param2] 71 | let input_names = input_names(&f.inputs); 72 | 73 | // [T0: Into, T1: Into, T2: IntoIterator, U2 = Into] 74 | let declarations = f.inputs.iter().enumerate().map(|(index, param)| template_param_type(¶m.kind, index)); 75 | 76 | // [Uint, Bytes, Vec] 77 | let kinds: Vec<_> = f.inputs.iter().map(|param| rust_type(¶m.kind)).collect(); 78 | 79 | // [T0, T1, T2] 80 | let template_names: Vec<_> = get_template_names(&kinds); 81 | 82 | // [param0: T0, hello_world: T1, param2: T2] 83 | let definitions = input_names 84 | .iter() 85 | .zip(template_names.iter()) 86 | .map(|(param_name, template_name)| quote! { #param_name: #template_name }); 87 | 88 | let template_params = declarations 89 | .zip(definitions) 90 | .map(|(declaration, definition)| TemplateParam { declaration, definition }) 91 | .collect(); 92 | 93 | // [Token::Uint(param0.into()), Token::Bytes(hello_world.into()), Token::Array(param2.into_iter().map(Into::into).collect())] 94 | let tokenize: Vec<_> = input_names 95 | .iter() 96 | .zip(f.inputs.iter()) 97 | .map(|(param_name, param)| to_token(&from_template_param(¶m.kind, ¶m_name), ¶m.kind)) 98 | .collect(); 99 | 100 | let output_result = get_output_kinds(&f.outputs); 101 | 102 | let output_implementation = match f.outputs.len() { 103 | 0 => quote! { 104 | let _output = output; 105 | Ok(()) 106 | }, 107 | 1 => { 108 | let o = quote! { out }; 109 | let from_first = from_token(&f.outputs[0].kind, &o); 110 | quote! { 111 | let out = self.0.decode_output(output)?.into_iter().next().expect(INTERNAL_ERR); 112 | Ok(#from_first) 113 | } 114 | } 115 | _ => { 116 | let o = quote! { out.next().expect(INTERNAL_ERR) }; 117 | let outs: Vec<_> = f.outputs.iter().map(|param| from_token(¶m.kind, &o)).collect(); 118 | 119 | quote! { 120 | let mut out = self.0.decode_output(output)?.into_iter(); 121 | Ok(( #(#outs),* )) 122 | } 123 | } 124 | }; 125 | 126 | Function { 127 | name: f.name.clone(), 128 | inputs: Inputs { tokenize, template_params, recreate_quote: to_ethabi_param_vec(&f.inputs) }, 129 | outputs: Outputs { 130 | implementation: output_implementation, 131 | result: output_result, 132 | recreate_quote: to_ethabi_param_vec(&f.outputs), 133 | }, 134 | constant: f.constant, 135 | } 136 | } 137 | } 138 | 139 | impl Function { 140 | /// Generates the interface for contract's function. 141 | pub fn generate(&self) -> TokenStream { 142 | let name = &self.name; 143 | let module_name = syn::Ident::new(&self.name.to_snake_case(), Span::call_site()); 144 | let tokenize = &self.inputs.tokenize; 145 | let declarations: &Vec<_> = &self.inputs.template_params.iter().map(|i| &i.declaration).collect(); 146 | let definitions: &Vec<_> = &self.inputs.template_params.iter().map(|i| &i.definition).collect(); 147 | let recreate_inputs = &self.inputs.recreate_quote; 148 | let recreate_outputs = &self.outputs.recreate_quote; 149 | let constant = &self.constant; 150 | let outputs_result = &self.outputs.result; 151 | let outputs_implementation = &self.outputs.implementation; 152 | 153 | quote! { 154 | pub mod #module_name { 155 | use ethabi; 156 | use super::INTERNAL_ERR; 157 | 158 | fn function() -> ethabi::Function { 159 | ethabi::Function { 160 | name: #name.into(), 161 | inputs: #recreate_inputs, 162 | outputs: #recreate_outputs, 163 | constant: #constant, 164 | } 165 | } 166 | 167 | /// Generic function output decoder. 168 | pub struct Decoder(ethabi::Function); 169 | 170 | impl ethabi::FunctionOutputDecoder for Decoder { 171 | type Output = #outputs_result; 172 | 173 | fn decode(&self, output: &[u8]) -> ethabi::Result { 174 | #outputs_implementation 175 | } 176 | } 177 | 178 | /// Encodes function input. 179 | pub fn encode_input<#(#declarations),*>(#(#definitions),*) -> ethabi::Bytes { 180 | let f = function(); 181 | let tokens = vec![#(#tokenize),*]; 182 | f.encode_input(&tokens).expect(INTERNAL_ERR) 183 | } 184 | 185 | /// Decodes function output. 186 | pub fn decode_output(output: &[u8]) -> ethabi::Result<#outputs_result> { 187 | ethabi::FunctionOutputDecoder::decode(&Decoder(function()), output) 188 | } 189 | 190 | /// Encodes function output and creates a `Decoder` instance. 191 | pub fn call<#(#declarations),*>(#(#definitions),*) -> (ethabi::Bytes, Decoder) { 192 | let f = function(); 193 | let tokens = vec![#(#tokenize),*]; 194 | (f.encode_input(&tokens).expect(INTERNAL_ERR), Decoder(f)) 195 | } 196 | } 197 | } 198 | } 199 | } 200 | 201 | #[cfg(test)] 202 | mod tests { 203 | use super::Function; 204 | use quote::quote; 205 | 206 | #[test] 207 | fn test_no_params() { 208 | let ethabi_function = 209 | ethabi::Function { name: "empty".into(), inputs: vec![], outputs: vec![], constant: false }; 210 | 211 | let f = Function::from(ðabi_function); 212 | 213 | let expected = quote! { 214 | pub mod empty { 215 | use ethabi; 216 | use super::INTERNAL_ERR; 217 | 218 | fn function() -> ethabi::Function { 219 | ethabi::Function { 220 | name: "empty".into(), 221 | inputs: vec![], 222 | outputs: vec![], 223 | constant: false, 224 | } 225 | } 226 | 227 | /// Generic function output decoder. 228 | pub struct Decoder(ethabi::Function); 229 | 230 | impl ethabi::FunctionOutputDecoder for Decoder { 231 | type Output = (); 232 | 233 | fn decode(&self, output: &[u8]) -> ethabi::Result { 234 | let _output = output; 235 | Ok(()) 236 | } 237 | } 238 | 239 | /// Encodes function input. 240 | pub fn encode_input<>() -> ethabi::Bytes { 241 | let f = function(); 242 | let tokens = vec![]; 243 | f.encode_input(&tokens).expect(INTERNAL_ERR) 244 | } 245 | 246 | /// Decodes function output. 247 | pub fn decode_output(output: &[u8]) -> ethabi::Result<()> { 248 | ethabi::FunctionOutputDecoder::decode(&Decoder(function()), output) 249 | } 250 | 251 | /// Encodes function output and creates a `Decoder` instance. 252 | pub fn call<>() -> (ethabi::Bytes, Decoder) { 253 | let f = function(); 254 | let tokens = vec![]; 255 | (f.encode_input(&tokens).expect(INTERNAL_ERR), Decoder(f)) 256 | } 257 | } 258 | }; 259 | 260 | assert_eq!(expected.to_string(), f.generate().to_string()); 261 | } 262 | 263 | #[test] 264 | fn test_one_param() { 265 | let ethabi_function = ethabi::Function { 266 | name: "hello".into(), 267 | inputs: vec![ethabi::Param { name: "foo".into(), kind: ethabi::ParamType::Address }], 268 | outputs: vec![ethabi::Param { name: "bar".into(), kind: ethabi::ParamType::Uint(256) }], 269 | constant: false, 270 | }; 271 | 272 | let f = Function::from(ðabi_function); 273 | 274 | let expected = quote! { 275 | pub mod hello { 276 | use ethabi; 277 | use super::INTERNAL_ERR; 278 | 279 | fn function() -> ethabi::Function { 280 | ethabi::Function { 281 | name: "hello".into(), 282 | inputs: vec![ethabi::Param { 283 | name: "foo".to_owned(), 284 | kind: ethabi::ParamType::Address 285 | }], 286 | outputs: vec![ethabi::Param { 287 | name: "bar".to_owned(), 288 | kind: ethabi::ParamType::Uint(256usize) 289 | }], 290 | constant: false, 291 | } 292 | } 293 | 294 | /// Generic function output decoder. 295 | pub struct Decoder(ethabi::Function); 296 | 297 | impl ethabi::FunctionOutputDecoder for Decoder { 298 | type Output = ethabi::Uint; 299 | 300 | fn decode(&self, output: &[u8]) -> ethabi::Result { 301 | let out = self.0.decode_output(output)?.into_iter().next().expect(INTERNAL_ERR); 302 | Ok(out.into_uint().expect(INTERNAL_ERR)) 303 | } 304 | } 305 | 306 | /// Encodes function input. 307 | pub fn encode_input >(foo: T0) -> ethabi::Bytes { 308 | let f = function(); 309 | let tokens = vec![ethabi::Token::Address(foo.into())]; 310 | f.encode_input(&tokens).expect(INTERNAL_ERR) 311 | } 312 | 313 | /// Decodes function output. 314 | pub fn decode_output(output: &[u8]) -> ethabi::Result { 315 | ethabi::FunctionOutputDecoder::decode(&Decoder(function()), output) 316 | } 317 | 318 | /// Encodes function output and creates a `Decoder` instance. 319 | pub fn call >(foo: T0) -> (ethabi::Bytes, Decoder) { 320 | let f = function(); 321 | let tokens = vec![ethabi::Token::Address(foo.into())]; 322 | (f.encode_input(&tokens).expect(INTERNAL_ERR), Decoder(f)) 323 | } 324 | } 325 | }; 326 | 327 | assert_eq!(expected.to_string(), f.generate().to_string()); 328 | } 329 | 330 | #[test] 331 | fn test_multiple_params() { 332 | let ethabi_function = ethabi::Function { 333 | name: "multi".into(), 334 | inputs: vec![ 335 | ethabi::Param { 336 | name: "foo".into(), 337 | kind: ethabi::ParamType::FixedArray(Box::new(ethabi::ParamType::Address), 2), 338 | }, 339 | ethabi::Param { 340 | name: "bar".into(), 341 | kind: ethabi::ParamType::Array(Box::new(ethabi::ParamType::Uint(256))), 342 | }, 343 | ], 344 | outputs: vec![ 345 | ethabi::Param { name: "".into(), kind: ethabi::ParamType::Uint(256) }, 346 | ethabi::Param { name: "".into(), kind: ethabi::ParamType::String }, 347 | ], 348 | constant: false, 349 | }; 350 | 351 | let f = Function::from(ðabi_function); 352 | 353 | let expected = quote! { 354 | pub mod multi { 355 | use ethabi; 356 | use super::INTERNAL_ERR; 357 | 358 | fn function() -> ethabi::Function { 359 | ethabi::Function { 360 | name: "multi".into(), 361 | inputs: vec![ethabi::Param { 362 | name: "foo".to_owned(), 363 | kind: ethabi::ParamType::FixedArray(Box::new(ethabi::ParamType::Address), 2usize) 364 | }, ethabi::Param { 365 | name: "bar".to_owned(), 366 | kind: ethabi::ParamType::Array(Box::new(ethabi::ParamType::Uint(256usize))) 367 | }], 368 | outputs: vec![ethabi::Param { 369 | name: "".to_owned(), 370 | kind: ethabi::ParamType::Uint(256usize) 371 | }, ethabi::Param { 372 | name: "".to_owned(), 373 | kind: ethabi::ParamType::String 374 | }], 375 | constant: false, 376 | } 377 | } 378 | 379 | /// Generic function output decoder. 380 | pub struct Decoder(ethabi::Function); 381 | 382 | impl ethabi::FunctionOutputDecoder for Decoder { 383 | type Output = (ethabi::Uint, String); 384 | 385 | fn decode(&self, output: &[u8]) -> ethabi::Result { 386 | let mut out = self.0.decode_output(output)?.into_iter(); 387 | Ok((out.next().expect(INTERNAL_ERR).into_uint().expect(INTERNAL_ERR), out.next().expect(INTERNAL_ERR).into_string().expect(INTERNAL_ERR))) 388 | } 389 | } 390 | 391 | /// Encodes function input. 392 | pub fn encode_input, U0: Into, T1: IntoIterator, U1: Into >(foo: T0, bar: T1) -> ethabi::Bytes { 393 | let f = function(); 394 | let tokens = vec![{ 395 | let v = (Box::new(foo.into()) as Box<[_]>).into_vec().into_iter().map(Into::into).collect::>().into_iter().map(|inner| ethabi::Token::Address(inner)).collect(); 396 | ethabi::Token::FixedArray(v) 397 | }, { 398 | let v = bar.into_iter().map(Into::into).collect::>().into_iter().map(|inner| ethabi::Token::Uint(inner)).collect(); 399 | ethabi::Token::Array(v) 400 | }]; 401 | f.encode_input(&tokens).expect(INTERNAL_ERR) 402 | } 403 | 404 | /// Decodes function output. 405 | pub fn decode_output(output: &[u8]) -> ethabi::Result<(ethabi::Uint, String)> { 406 | ethabi::FunctionOutputDecoder::decode(&Decoder(function()), output) 407 | } 408 | 409 | /// Encodes function output and creates a `Decoder` instance. 410 | pub fn call, U0: Into, T1: IntoIterator, U1: Into >(foo: T0, bar: T1) -> (ethabi::Bytes, Decoder) { 411 | let f = function(); 412 | let tokens = vec![{ 413 | let v = (Box::new(foo.into()) as Box<[_]>).into_vec().into_iter().map(Into::into).collect::>().into_iter().map(|inner| ethabi::Token::Address(inner)).collect(); 414 | ethabi::Token::FixedArray(v) 415 | }, { 416 | let v = bar.into_iter().map(Into::into).collect::>().into_iter().map(|inner| ethabi::Token::Uint(inner)).collect(); 417 | ethabi::Token::Array(v) 418 | }]; 419 | (f.encode_input(&tokens).expect(INTERNAL_ERR), Decoder(f)) 420 | } 421 | } 422 | }; 423 | 424 | assert_eq!(expected.to_string(), f.generate().to_string()); 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use ethabi::{ 3 | decode, encode, 4 | param_type::{ParamType, Reader}, 5 | token::{LenientTokenizer, StrictTokenizer, Token, Tokenizer}, 6 | Contract, Event, Function, Hash, 7 | }; 8 | use itertools::Itertools; 9 | use sha3::{Digest, Keccak256}; 10 | use std::fs::File; 11 | use structopt::StructOpt; 12 | 13 | #[derive(StructOpt, Debug)] 14 | /// Ethereum ABI coder. 15 | enum Opt { 16 | /// Encode ABI call. 17 | Encode(Encode), 18 | /// Decode ABI call result. 19 | Decode(Decode), 20 | } 21 | 22 | #[derive(StructOpt, Debug)] 23 | enum Encode { 24 | /// Load function from JSON ABI file. 25 | Function { 26 | abi_path: String, 27 | function_name_or_signature: String, 28 | #[structopt(short, number_of_values = 1)] 29 | params: Vec, 30 | /// Allow short representation of input params. 31 | #[structopt(short, long)] 32 | lenient: bool, 33 | }, 34 | /// Specify types of input params inline. 35 | Params { 36 | /// Pairs of types directly followed by params in the form: 37 | /// 38 | /// -v -v ... 39 | #[structopt(short = "v", name = "type-or-param", number_of_values = 2, allow_hyphen_values = true)] 40 | params: Vec, 41 | /// Allow short representation of input params (numbers are in decimal form). 42 | #[structopt(short, long)] 43 | lenient: bool, 44 | }, 45 | } 46 | 47 | #[derive(StructOpt, Debug)] 48 | enum Decode { 49 | /// Load function from JSON ABI file. 50 | Function { abi_path: String, function_name_or_signature: String, data: String }, 51 | /// Specify types of input params inline. 52 | Params { 53 | #[structopt(short, name = "type", number_of_values = 1)] 54 | types: Vec, 55 | data: String, 56 | }, 57 | /// Decode event log. 58 | Log { 59 | abi_path: String, 60 | event_name_or_signature: String, 61 | #[structopt(short = "l", name = "topic", number_of_values = 1)] 62 | topics: Vec, 63 | data: String, 64 | }, 65 | } 66 | 67 | fn main() -> anyhow::Result<()> { 68 | println!("{}", execute(std::env::args())?); 69 | 70 | Ok(()) 71 | } 72 | 73 | fn execute(args: I) -> anyhow::Result 74 | where 75 | I: IntoIterator, 76 | I::Item: Into + Clone, 77 | { 78 | let opt = Opt::from_iter(args); 79 | 80 | match opt { 81 | Opt::Encode(Encode::Function { abi_path, function_name_or_signature, params, lenient }) => { 82 | encode_input(&abi_path, &function_name_or_signature, ¶ms, lenient) 83 | } 84 | Opt::Encode(Encode::Params { params, lenient }) => encode_params(¶ms, lenient), 85 | Opt::Decode(Decode::Function { abi_path, function_name_or_signature, data }) => { 86 | decode_call_output(&abi_path, &function_name_or_signature, &data) 87 | } 88 | Opt::Decode(Decode::Params { types, data }) => decode_params(&types, &data), 89 | Opt::Decode(Decode::Log { abi_path, event_name_or_signature, topics, data }) => { 90 | decode_log(&abi_path, &event_name_or_signature, &topics, &data) 91 | } 92 | } 93 | } 94 | 95 | fn load_function(path: &str, name_or_signature: &str) -> anyhow::Result { 96 | let file = File::open(path)?; 97 | let contract = Contract::load(file)?; 98 | let params_start = name_or_signature.find('('); 99 | 100 | match params_start { 101 | // It's a signature 102 | Some(params_start) => { 103 | let name = &name_or_signature[..params_start]; 104 | 105 | contract 106 | .functions_by_name(name)? 107 | .iter() 108 | .find(|f| f.signature() == name_or_signature) 109 | .cloned() 110 | .ok_or_else(|| anyhow!("invalid function signature `{}`", name_or_signature)) 111 | } 112 | 113 | // It's a name 114 | None => { 115 | let functions = contract.functions_by_name(name_or_signature)?; 116 | match functions.len() { 117 | 0 => unreachable!(), 118 | 1 => Ok(functions[0].clone()), 119 | _ => Err(anyhow!( 120 | "More than one function found for name `{}`, try providing the full signature", 121 | name_or_signature 122 | )), 123 | } 124 | } 125 | } 126 | } 127 | 128 | fn load_event(path: &str, name_or_signature: &str) -> anyhow::Result { 129 | let file = File::open(path)?; 130 | let contract = Contract::load(file)?; 131 | let params_start = name_or_signature.find('('); 132 | 133 | match params_start { 134 | // It's a signature. 135 | Some(params_start) => { 136 | let name = &name_or_signature[..params_start]; 137 | let signature = hash_signature(name_or_signature); 138 | contract 139 | .events_by_name(name)? 140 | .iter() 141 | .find(|event| event.signature() == signature) 142 | .cloned() 143 | .ok_or_else(|| anyhow!("Invalid signature `{}`", signature)) 144 | } 145 | 146 | // It's a name. 147 | None => { 148 | let events = contract.events_by_name(name_or_signature)?; 149 | match events.len() { 150 | 0 => unreachable!(), 151 | 1 => Ok(events[0].clone()), 152 | _ => Err(anyhow!( 153 | "More than one function found for name `{}`, try providing the full signature", 154 | name_or_signature 155 | )), 156 | } 157 | } 158 | } 159 | } 160 | 161 | fn parse_tokens(params: &[(ParamType, &str)], lenient: bool) -> anyhow::Result> { 162 | params 163 | .iter() 164 | .map(|&(ref param, value)| match lenient { 165 | true => LenientTokenizer::tokenize(param, value), 166 | false => StrictTokenizer::tokenize(param, value), 167 | }) 168 | .collect::>() 169 | .map_err(From::from) 170 | } 171 | 172 | fn encode_input(path: &str, name_or_signature: &str, values: &[String], lenient: bool) -> anyhow::Result { 173 | let function = load_function(path, name_or_signature)?; 174 | 175 | let params: Vec<_> = 176 | function.inputs.iter().map(|param| param.kind.clone()).zip(values.iter().map(|v| v as &str)).collect(); 177 | 178 | let tokens = parse_tokens(¶ms, lenient)?; 179 | let result = function.encode_input(&tokens)?; 180 | 181 | Ok(hex::encode(&result)) 182 | } 183 | 184 | fn encode_params(params: &[String], lenient: bool) -> anyhow::Result { 185 | assert_eq!(params.len() % 2, 0); 186 | 187 | let params = params 188 | .iter() 189 | .tuples::<(_, _)>() 190 | .map(|(x, y)| Reader::read(x).map(|z| (z, y.as_str()))) 191 | .collect::, _>>()?; 192 | 193 | let tokens = parse_tokens(params.as_slice(), lenient)?; 194 | let result = encode(&tokens); 195 | 196 | Ok(hex::encode(&result)) 197 | } 198 | 199 | fn decode_call_output(path: &str, name_or_signature: &str, data: &str) -> anyhow::Result { 200 | let function = load_function(path, name_or_signature)?; 201 | let data: Vec = hex::decode(&data)?; 202 | let tokens = function.decode_output(&data)?; 203 | let types = function.outputs; 204 | 205 | assert_eq!(types.len(), tokens.len()); 206 | 207 | let result = types 208 | .iter() 209 | .zip(tokens.iter()) 210 | .map(|(ty, to)| format!("{} {}", ty.kind, to)) 211 | .collect::>() 212 | .join("\n"); 213 | 214 | Ok(result) 215 | } 216 | 217 | fn decode_params(types: &[String], data: &str) -> anyhow::Result { 218 | let types: Vec = types.iter().map(|s| Reader::read(s)).collect::>()?; 219 | 220 | let data: Vec = hex::decode(&data)?; 221 | 222 | let tokens = decode(&types, &data)?; 223 | 224 | assert_eq!(types.len(), tokens.len()); 225 | 226 | let result = 227 | types.iter().zip(tokens.iter()).map(|(ty, to)| format!("{} {}", ty, to)).collect::>().join("\n"); 228 | 229 | Ok(result) 230 | } 231 | 232 | fn decode_log(path: &str, name_or_signature: &str, topics: &[String], data: &str) -> anyhow::Result { 233 | let event = load_event(path, name_or_signature)?; 234 | let topics: Vec = topics.iter().map(|t| t.parse()).collect::>()?; 235 | let data = hex::decode(data)?; 236 | let decoded = event.parse_log((topics, data).into())?; 237 | 238 | let result = decoded 239 | .params 240 | .into_iter() 241 | .map(|log_param| format!("{} {}", log_param.name, log_param.value)) 242 | .collect::>() 243 | .join("\n"); 244 | 245 | Ok(result) 246 | } 247 | 248 | fn hash_signature(sig: &str) -> Hash { 249 | Hash::from_slice(Keccak256::digest(&sig.replace(" ", "").as_bytes()).as_slice()) 250 | } 251 | 252 | #[cfg(test)] 253 | mod tests { 254 | use super::execute; 255 | 256 | #[test] 257 | fn simple_encode() { 258 | let command = "ethabi encode params -v bool 1".split(' '); 259 | let expected = "0000000000000000000000000000000000000000000000000000000000000001"; 260 | assert_eq!(execute(command).unwrap(), expected); 261 | } 262 | 263 | #[test] 264 | fn int_encode() { 265 | let command = "ethabi encode params -v int256 -2 --lenient".split(' '); 266 | let expected = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"; 267 | assert_eq!(execute(command).unwrap(), expected); 268 | 269 | let command = "ethabi encode params -v int256 -0 --lenient".split(' '); 270 | let expected = "0000000000000000000000000000000000000000000000000000000000000000"; 271 | assert_eq!(execute(command).unwrap(), expected); 272 | } 273 | 274 | #[test] 275 | fn uint_encode_must_be_positive() { 276 | let command = "ethabi encode params -v uint256 -2 --lenient".split(' '); 277 | assert!(execute(command).is_err()); 278 | } 279 | 280 | #[test] 281 | fn uint_encode_requires_decimal_inputs() { 282 | let command = "ethabi encode params -v uint256 123abc --lenient".split(' '); 283 | let result = execute(command); 284 | assert!(result.is_err()); 285 | let err = result.unwrap_err(); 286 | assert_eq!(err.to_string(), "Uint parse error: InvalidCharacter"); 287 | } 288 | 289 | #[test] 290 | fn uint_encode_big_numbers() { 291 | let command = 292 | "ethabi encode params -v uint256 100000000000000000000000000000000022222222222222221111111111111 --lenient" 293 | .split(' '); 294 | let expected = "0000000000003e3aeb4ae1383562f4b82261d96a3f7a5f62ca19599c1ad6d1c7"; 295 | assert_eq!(execute(command).unwrap(), expected); 296 | } 297 | 298 | #[test] 299 | fn int_encode_large_negative_numbers() { 300 | // i256::min_value() is ok 301 | let command = "ethabi encode params -v int256 -57896044618658097711785492504343953926634992332820282019728792003956564819968 --lenient".split(' '); 302 | let expected = "8000000000000000000000000000000000000000000000000000000000000000"; 303 | assert_eq!(execute(command).unwrap(), expected); 304 | 305 | // i256::min_value() - 1 is too much 306 | let command = "ethabi encode params -v int256 -57896044618658097711785492504343953926634992332820282019728792003956564819969 --lenient".split(' '); 307 | assert_eq!(execute(command).unwrap_err().to_string(), "int256 parse error: Underflow"); 308 | } 309 | 310 | #[test] 311 | fn int_encode_large_positive_numbers() { 312 | // Overflow 313 | let command = "ethabi encode params -v int256 100000000000000000000000000000000022222222222222221111111111111333333333344556 --lenient".split(' '); 314 | assert_eq!(execute(command).unwrap_err().to_string(), "int256 parse error: Overflow"); 315 | 316 | // i256::max_value() is ok 317 | let command = "ethabi encode params -v int256 57896044618658097711785492504343953926634992332820282019728792003956564819967 --lenient".split(' '); 318 | let expected = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 319 | assert_eq!(execute(command).unwrap(), expected); 320 | 321 | // i256::max_value() + 1 is too much 322 | let command = "ethabi encode params -v int256 57896044618658097711785492504343953926634992332820282019728792003956564819968 --lenient".split(' '); 323 | assert_eq!(execute(command).unwrap_err().to_string(), "int256 parse error: Overflow"); 324 | } 325 | 326 | #[test] 327 | fn multi_encode() { 328 | let command = "ethabi encode params -v bool 1 -v string gavofyork -v bool 0".split(' '); 329 | let expected = "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096761766f66796f726b0000000000000000000000000000000000000000000000"; 330 | assert_eq!(execute(command).unwrap(), expected); 331 | } 332 | 333 | #[test] 334 | fn array_encode() { 335 | let command = "ethabi encode params -v bool[] [1,0,false]".split(' '); 336 | let expected = "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; 337 | assert_eq!(execute(command).unwrap(), expected); 338 | } 339 | 340 | #[test] 341 | fn function_encode_by_name() { 342 | let command = "ethabi encode function ../res/test.abi foo -p 1".split(' '); 343 | let expected = "455575780000000000000000000000000000000000000000000000000000000000000001"; 344 | assert_eq!(execute(command).unwrap(), expected); 345 | } 346 | 347 | #[test] 348 | fn function_encode_by_signature() { 349 | let command = "ethabi encode function ../res/test.abi foo(bool) -p 1".split(' '); 350 | let expected = "455575780000000000000000000000000000000000000000000000000000000000000001"; 351 | assert_eq!(execute(command).unwrap(), expected); 352 | } 353 | 354 | #[test] 355 | fn nonexistent_function() { 356 | // This should fail because there is no function called 'nope' in the ABI 357 | let command = "ethabi encode function ../res/test.abi nope -p 1".split(' '); 358 | assert!(execute(command).is_err()); 359 | } 360 | 361 | #[test] 362 | fn overloaded_function_encode_by_name() { 363 | // This should fail because there are two definitions of `bar in the ABI 364 | let command = "ethabi encode function ../res/test.abi bar -p 1".split(' '); 365 | assert!(execute(command).is_err()); 366 | } 367 | 368 | #[test] 369 | fn overloaded_function_encode_by_first_signature() { 370 | let command = "ethabi encode function ../res/test.abi bar(bool) -p 1".split(' '); 371 | let expected = "6fae94120000000000000000000000000000000000000000000000000000000000000001"; 372 | assert_eq!(execute(command).unwrap(), expected); 373 | } 374 | 375 | #[test] 376 | fn overloaded_function_encode_by_second_signature() { 377 | let command = "ethabi encode function ../res/test.abi bar(string):(uint256) -p 1".split(' '); 378 | let expected = "d473a8ed0000000000000000000000000000000000000000000000000000000000000020\ 379 | 000000000000000000000000000000000000000000000000000000000000000131000000\ 380 | 00000000000000000000000000000000000000000000000000000000"; 381 | assert_eq!(execute(command).unwrap(), expected); 382 | } 383 | 384 | #[test] 385 | fn simple_decode() { 386 | let command = 387 | "ethabi decode params -t bool 0000000000000000000000000000000000000000000000000000000000000001".split(' '); 388 | let expected = "bool true"; 389 | assert_eq!(execute(command).unwrap(), expected); 390 | } 391 | 392 | #[test] 393 | fn int_decode() { 394 | let command = "ethabi decode params -t int256 fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" 395 | .split(' '); 396 | let expected = "int256 fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"; 397 | assert_eq!(execute(command).unwrap(), expected); 398 | } 399 | 400 | #[test] 401 | fn multi_decode() { 402 | let command = "ethabi decode params -t bool -t string -t bool 00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000096761766f66796f726b0000000000000000000000000000000000000000000000".split(' '); 403 | let expected = "bool true 404 | string gavofyork 405 | bool false"; 406 | assert_eq!(execute(command).unwrap(), expected); 407 | } 408 | 409 | #[test] 410 | fn array_decode() { 411 | let command = "ethabi decode params -t bool[] 00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".split(' '); 412 | let expected = "bool[] [true,false,false]"; 413 | assert_eq!(execute(command).unwrap(), expected); 414 | } 415 | 416 | #[test] 417 | fn abi_decode() { 418 | let command = "ethabi decode function ../res/foo.abi bar 0000000000000000000000000000000000000000000000000000000000000001".split(' '); 419 | let expected = "bool true"; 420 | assert_eq!(execute(command).unwrap(), expected); 421 | } 422 | 423 | #[test] 424 | fn log_decode() { 425 | let command = "ethabi decode log ../res/event.abi Event -l 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000004444444444444444444444444444444444444444".split(' '); 426 | let expected = "a true 427 | b 4444444444444444444444444444444444444444"; 428 | assert_eq!(execute(command).unwrap(), expected); 429 | } 430 | 431 | #[test] 432 | fn log_decode_signature() { 433 | let command = "ethabi decode log ../res/event.abi Event(bool,address) -l 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000004444444444444444444444444444444444444444".split(' '); 434 | let expected = "a true 435 | b 4444444444444444444444444444444444444444"; 436 | assert_eq!(execute(command).unwrap(), expected); 437 | } 438 | 439 | #[test] 440 | fn nonexistent_event() { 441 | // This should return an error because no event 'Nope(bool,address)' exists 442 | let command = "ethabi decode log ../res/event.abi Nope(bool,address) -l 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000004444444444444444444444444444444444444444".split(' '); 443 | assert!(execute(command).is_err()); 444 | } 445 | } 446 | --------------------------------------------------------------------------------