WIP add contract abi

This commit is contained in:
Georgios Konstantopoulos 2020-05-25 13:05:00 +03:00
parent 548b24e518
commit 1dd3c3dc89
No known key found for this signature in database
GPG Key ID: FA607837CD26EDBC
8 changed files with 198 additions and 7 deletions

28
Cargo.lock generated
View File

@ -189,6 +189,20 @@ dependencies = [
"version_check",
]
[[package]]
name = "ethabi"
version = "12.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052a565e3de82944527d6d10a465697e6bb92476b772ca7141080c901f6a63c6"
dependencies = [
"ethereum-types",
"rustc-hex",
"serde",
"serde_json",
"tiny-keccak 1.5.0",
"uint",
]
[[package]]
name = "ethbloom"
version = "0.9.2"
@ -199,7 +213,7 @@ dependencies = [
"fixed-hash",
"impl-rlp",
"impl-serde",
"tiny-keccak",
"tiny-keccak 2.0.2",
]
[[package]]
@ -221,6 +235,7 @@ name = "ethers"
version = "0.1.0"
dependencies = [
"async-trait",
"ethabi",
"ethereum-types",
"failure",
"rand 0.5.6",
@ -232,7 +247,7 @@ dependencies = [
"serde_json",
"solc",
"thiserror",
"tiny-keccak",
"tiny-keccak 2.0.2",
"tokio",
"url",
"zeroize",
@ -1226,6 +1241,15 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "tiny-keccak"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d8a021c69bb74a44ccedb824a046447e2c84a01df9e5c20779750acb38e11b2"
dependencies = [
"crunchy",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"

View File

@ -20,6 +20,7 @@ tiny-keccak = { version = "2.0.2", default-features = false }
solc = { git = "https://github.com/paritytech/rust_solc "}
rlp = "0.4.5"
ethabi = "12.0.0"
[dev-dependencies]
tokio = { version = "0.2.21", features = ["macros"] }

25
examples/contract.rs Normal file
View File

@ -0,0 +1,25 @@
use ethers::{
types::{Address, Filter},
Contract, HttpProvider, MainnetWallet,
};
use std::convert::TryFrom;
#[tokio::main]
async fn main() -> Result<(), failure::Error> {
// connect to the network
let provider = HttpProvider::try_from("http://localhost:8545")?;
// create a wallet and connect it to the provider
let client = "15c42bf2987d5a8a73804a8ea72fb4149f88adf73e98fc3f8a8ce9f24fcb7774"
.parse::<MainnetWallet>()?
.connect(&provider);
// Contract should take both provider or a signer
let contract = Contract::new(
"f817796F60D268A36a57b8D2dF1B97B14C0D0E1d".parse::<Address>()?,
abi,
);
Ok(())
}

104
src/contract/abi.rs Normal file
View File

@ -0,0 +1,104 @@
//! This module implements extensions to the `ethabi` API.
//! Taken from: https://github.com/gnosis/ethcontract-rs/blob/master/common/src/abiext.rs
use ethabi::{Event, Function, ParamType};
use crate::{utils::id, types::Selector};
/// Extension trait for `ethabi::Function`.
pub trait FunctionExt {
/// Compute the method signature in the standard ABI format. This does not
/// include the output types.
fn abi_signature(&self) -> String;
/// Compute the Keccak256 function selector used by contract ABIs.
fn selector(&self) -> Selector;
}
impl FunctionExt for Function {
fn abi_signature(&self) -> String {
let mut full_signature = self.signature();
if let Some(colon) = full_signature.find(':') {
full_signature.truncate(colon);
}
full_signature
}
fn selector(&self) -> Selector {
id(self.abi_signature())
}
}
/// Extension trait for `ethabi::Event`.
pub trait EventExt {
/// Compute the event signature in human-readable format. The `keccak256`
/// hash of this value is the actual event signature that is used as topic0
/// in the transaction logs.
fn abi_signature(&self) -> String;
}
impl EventExt for Event {
fn abi_signature(&self) -> String {
format!(
"{}({}){}",
self.name,
self.inputs
.iter()
.map(|input| input.kind.to_string())
.collect::<Vec<_>>()
.join(","),
if self.anonymous { " anonymous" } else { "" },
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_function_signature() {
for (f, expected) in &[
(r#"{"name":"foo","inputs":[],"outputs":[]}"#, "foo()"),
(
r#"{"name":"bar","inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"bool"}],"outputs":[]}"#,
"bar(uint256,bool)",
),
(
r#"{"name":"baz","inputs":[{"name":"a","type":"uint256"}],"outputs":[{"name":"b","type":"bool"}]}"#,
"baz(uint256)",
),
(
r#"{"name":"bax","inputs":[],"outputs":[{"name":"a","type":"uint256"},{"name":"b","type":"bool"}]}"#,
"bax()",
),
] {
let function: Function = serde_json::from_str(f).expect("invalid function JSON");
let signature = function.abi_signature();
assert_eq!(signature, *expected);
}
}
#[test]
fn format_event_signature() {
for (e, expected) in &[
(r#"{"name":"foo","inputs":[],"anonymous":false}"#, "foo()"),
(
r#"{"name":"bar","inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"bool"}],"anonymous":false}"#,
"bar(uint256,bool)",
),
(
r#"{"name":"baz","inputs":[{"name":"a","type":"uint256"}],"anonymous":true}"#,
"baz(uint256) anonymous",
),
(
r#"{"name":"bax","inputs":[],"anonymous":true}"#,
"bax() anonymous",
),
] {
let event: Event = serde_json::from_str(e).expect("invalid event JSON");
let signature = event.abi_signature();
assert_eq!(signature, *expected);
}
}
}

17
src/contract/mod.rs Normal file
View File

@ -0,0 +1,17 @@
use crate::types::Address;
mod abi;
pub struct Contract<ABI> {
pub address: Address,
pub abi: ABI,
}
impl<ABI> Contract<ABI> {
pub fn new<A: Into<Address>>(address: A, abi: ABI) -> Self {
Self {
address: address.into(),
abi,
}
}
}

View File

@ -18,8 +18,10 @@
pub mod providers;
pub use providers::HttpProvider;
pub mod signers;
mod contract;
pub use contract::Contract;
mod signers;
pub use signers::{AnyWallet, MainnetWallet, Signer};
/// Ethereum related datatypes

View File

@ -1,5 +1,7 @@
//! Various Ethereum Related Datatypes
pub type Selector = [u8; 4];
// Re-export common ethereum datatypes with more specific names
pub use ethereum_types::H256 as TxHash;
pub use ethereum_types::{Address, Bloom, H256, U256, U64};
@ -21,3 +23,5 @@ pub use block::{Block, BlockId, BlockNumber};
mod log;
pub use log::{Filter, Log};

View File

@ -1,5 +1,5 @@
//! Various utilities for manipulating Ethereum related dat
use crate::types::H256;
use crate::types::{H256, Selector};
use tiny_keccak::{Hasher, Keccak};
const PREFIX: &str = "\x19Ethereum Signed Message:\n";
@ -31,12 +31,14 @@ pub fn keccak256(bytes: &[u8]) -> [u8; 32] {
output
}
/// Gets the first 4 bytes
pub fn id(name: &str) -> [u8; 4] {
/// Calculate the function selector as per the contract ABI specification. This
/// is defined as the first 4 bytes of the Keccak256 hash of the function
/// signature.
pub fn id<S: AsRef<str>>(signature: S) -> Selector {
let mut output = [0u8; 4];
let mut hasher = Keccak::v256();
hasher.update(name.as_bytes());
hasher.update(signature.as_ref().as_bytes());
hasher.finalize(&mut output);
output
@ -64,4 +66,16 @@ mod tests {
.unwrap()
);
}
#[test]
fn simple_function_signature() {
// test vector retrieved from
// https://web3js.readthedocs.io/en/v1.2.4/web3-eth-abi.html#encodefunctionsignature
assert_eq!(id("myMethod(uint256,string)"), [0x24, 0xee, 0x00, 0x97],);
}
#[test]
fn revert_function_signature() {
assert_eq!(id("Error(string)"), [0x08, 0xc3, 0x79, 0xa0]);
}
}