diff --git a/.circleci/config.yml b/.circleci/config.yml index 46eac582..584f100c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,4 +1,34 @@ version: 2 +etup +# https://medium.com/@edouard.oger/rust-caching-on-circleci-using-sccache-c996344f0115 +commands: + setup-sccache: + steps: + - run: + name: Install sccache + command: | + cargo install sccache + # This configures Rust to use sccache. + echo 'export "RUSTC_WRAPPER"="sccache"' >> $BASH_ENV + # This is the maximum space sccache cache will use on disk. + echo 'export "SCCACHE_CACHE_SIZE"="1G"' >> $BASH_ENV + sccache --version + restore-sccache-cache: + steps: + - restore_cache: + name: Restore sccache cache + key: sccache-cache-stable-{{ arch }}-{{ .Environment.CIRCLE_JOB }} + save-sccache-cache: + steps: + - save_cache: + name: Save sccache cache + # We use {{ epoch }} to always upload a fresh cache: + # Of course, restore_cache will not find this exact key, + # but it will fall back to the closest key (aka the most recent). + # See https://discuss.circleci.com/t/add-mechanism-to-update-existing-cache-key/9014/13 + key: sccache-cache-stable-{{ arch }}-{{ .Environment.CIRCLE_JOB }}-{{ epoch }} + paths: + - "~/.cache/sccache" jobs: build: @@ -6,11 +36,8 @@ jobs: - image: circleci/rust:latest steps: - checkout - - run: - name: Install Dependencies - command: | - cargo install cargo-audit - rustup component add clippy + - setup-sccache + - restore-sccache-cache - run: cargo test --all - run: name: Check style @@ -20,3 +47,4 @@ jobs: - run: name: Audit Dependencies command: cargo audit + - save-sccache-cache diff --git a/Cargo.lock b/Cargo.lock index 87431443..581b4a5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,12 +127,37 @@ dependencies = [ "bitflags", ] +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "ct-logs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113" +dependencies = [ + "sct", +] + [[package]] name = "dtoa" version = "0.4.5" @@ -290,6 +315,18 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" +[[package]] +name = "futures-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.5" @@ -301,6 +338,9 @@ name = "futures-task" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] [[package]] name = "futures-util" @@ -309,9 +349,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" dependencies = [ "futures-core", + "futures-macro", "futures-task", "pin-project", "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", ] [[package]] @@ -401,6 +445,24 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac965ea399ec3a25ac7d13b8affd4b8f39325cca00858ddf5eb29b79e6b14b08" +dependencies = [ + "bytes", + "ct-logs", + "futures-util", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "webpki", +] + [[package]] name = "idna" version = "0.2.0" @@ -579,6 +641,18 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" +[[package]] +name = "once_cell" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + [[package]] name = "parity-scale-codec" version = "1.3.0" @@ -648,6 +722,18 @@ dependencies = [ "uint", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" + +[[package]] +name = "proc-macro-nested" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" + [[package]] name = "proc-macro2" version = "1.0.15" @@ -849,6 +935,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "js-sys", "lazy_static", "log", @@ -856,18 +943,36 @@ dependencies = [ "mime_guess", "percent-encoding", "pin-project-lite", + "rustls", "serde", "serde_json", "serde_urlencoded", "time", "tokio", + "tokio-rustls", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] +[[package]] +name = "ring" +version = "0.16.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c" +dependencies = [ + "cc", + "lazy_static", + "libc", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.8", +] + [[package]] name = "rlp" version = "0.4.5" @@ -889,12 +994,57 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustls" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75ffeb84a6bd9d014713119542ce415db3a3e4748f0bfce1e1416cd224a23a5" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework", +] + [[package]] name = "ryu" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.8", +] + +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "secp256k1" version = "0.17.2" @@ -914,6 +1064,29 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.110" @@ -979,6 +1152,12 @@ dependencies = [ "regex", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "static_assertions" version = "1.1.0" @@ -1085,6 +1264,18 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4" +dependencies = [ + "futures-core", + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-util" version = "0.3.1" @@ -1156,6 +1347,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.1.1" @@ -1267,6 +1464,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4" +dependencies = [ + "webpki", +] + [[package]] name = "winapi" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index 9291dde2..9df3b546 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" ethereum-types = { version = "0.9.2", default-features = false, features = ["serialize"] } url = { version = "2.1.1", default-features = false } async-trait = { version = "0.1.31", default-features = false } -reqwest = { version = "0.10.4", default-features = false, features = ["json"] } +reqwest = { version = "0.10.4", default-features = false, features = ["json", "rustls-tls"] } serde = { version = "1.0.110", default-features = false, features = ["derive"] } serde_json = { version = "1.0.53", default-features = false } thiserror = { version = "1.0.19", default-features = false } diff --git a/examples/get_logs.rs b/examples/get_logs.rs new file mode 100644 index 00000000..0e1b25c5 --- /dev/null +++ b/examples/get_logs.rs @@ -0,0 +1,21 @@ +use ethers::{ + types::{Address, Filter}, + HttpProvider, +}; +use std::convert::TryFrom; + +#[tokio::main] +async fn main() -> Result<(), failure::Error> { + // connect to the network + let provider = HttpProvider::try_from("http://localhost:8545")?; + + let filter = Filter::new() + .address_str("f817796F60D268A36a57b8D2dF1B97B14C0D0E1d")? + .event("ValueChanged(address,string,string)") // event name + .topic("9729a6fbefefc8f6005933898b13dc45c3a2c8b7".parse::
()?); // indexed param + + let logs = provider.get_logs(&filter).await?; + println!("Got logs: {}", serde_json::to_string(&logs).unwrap()); + + Ok(()) +} diff --git a/src/providers/mod.rs b/src/providers/mod.rs index 4c745d85..82de8f01 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -8,8 +8,8 @@ mod http; use crate::{ signers::{Client, Signer}, types::{ - Address, Block, BlockId, BlockNumber, Transaction, TransactionReceipt, TransactionRequest, - TxHash, U256, + Address, Block, BlockId, BlockNumber, Filter, Log, Transaction, TransactionReceipt, + TransactionRequest, TxHash, U256, }, utils, }; @@ -72,6 +72,11 @@ impl Provider

{ self.0.request("eth_estimateGas", Some(args)).await } + /// Gets the logs matching a given filter + pub async fn get_logs(&self, filter: &Filter) -> Result, P::Error> { + self.0.request("eth_getLogs", Some(filter)).await + } + /// Gets the accounts on the node pub async fn get_accounts(&self) -> Result, P::Error> { self.0.request("eth_accounts", None::<()>).await diff --git a/src/types/log.rs b/src/types/log.rs index c2c15b12..0876fc29 100644 --- a/src/types/log.rs +++ b/src/types/log.rs @@ -1,5 +1,9 @@ -use crate::types::{Address, BlockNumber, Bytes, H256, U256, U64}; +use crate::{ + types::{Address, BlockNumber, Bytes, H256, U256, U64}, + utils::keccak256, +}; use serde::{Deserialize, Serialize}; +use std::str::FromStr; /// A log produced by a transaction. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -18,35 +22,43 @@ pub struct Log { /// Block Hash #[serde(rename = "blockHash")] + #[serde(skip_serializing_if = "Option::is_none")] pub block_hash: Option, /// Block Number #[serde(rename = "blockNumber")] + #[serde(skip_serializing_if = "Option::is_none")] pub block_number: Option, /// Transaction Hash #[serde(rename = "transactionHash")] + #[serde(skip_serializing_if = "Option::is_none")] pub transaction_hash: Option, /// Transaction Index #[serde(rename = "transactionIndex")] + #[serde(skip_serializing_if = "Option::is_none")] pub transaction_index: Option, /// Integer of the log index position in the block. Noe if it's a pending log. #[serde(rename = "logIndex")] + #[serde(skip_serializing_if = "Option::is_none")] pub log_index: Option, /// Integer of the transactions index position log was created from. /// None when it's a pending log. #[serde(rename = "transactionLogIndex")] + #[serde(skip_serializing_if = "Option::is_none")] pub transaction_log_index: Option, /// Log Type #[serde(rename = "logType")] + #[serde(skip_serializing_if = "Option::is_none")] pub log_type: Option, /// True when the log was removed, due to a chain reorganization. /// false if its a valid log. + #[serde(skip_serializing_if = "Option::is_none")] pub removed: Option, } @@ -56,35 +68,68 @@ pub struct Filter { /// From Block #[serde(rename = "fromBlock", skip_serializing_if = "Option::is_none")] from_block: Option, + /// To Block #[serde(rename = "toBlock", skip_serializing_if = "Option::is_none")] to_block: Option, + /// Address #[serde(skip_serializing_if = "Option::is_none")] - address: Option>, + // TODO: The spec says that this can also be an array, do we really want to + // monitor for the same event for multiple contracts? + address: Option

, + /// Topics - #[serde(skip_serializing_if = "Option::is_none")] - topics: Option>>>, + #[serde(skip_serializing_if = "Vec::is_empty")] + // TODO: Split in an event name + 3 topics + topics: Vec>, + /// Limit #[serde(skip_serializing_if = "Option::is_none")] limit: Option, } impl Filter { - pub fn from_block>(mut self, block: BlockNumber) -> Self { + pub fn new() -> Self { + Self::default() + } + + pub fn from_block>(mut self, block: T) -> Self { self.from_block = Some(block.into()); self } - pub fn to_block>(mut self, block: BlockNumber) -> Self { + pub fn to_block>(mut self, block: T) -> Self { self.to_block = Some(block.into()); self } - // pub fn address>(mut self, block: BlockNumber) -> Self { - // self.to_block = Some(block.into()); - // self - // } + pub fn address>(mut self, address: T) -> Self { + self.address = Some(address.into()); + self + } + + pub fn address_str(mut self, address: &str) -> Result { + self.address = Some(Address::from_str(address)?); + Ok(self) + } + + /// given the event in string form, it hashes it and adds it to the topics to monitor + pub fn event(self, event_name: &str) -> Self { + let hash = H256::from(keccak256(event_name.as_bytes())); + self.topic(hash) + } + + pub fn topic>>(mut self, topic: T) -> Self { + self.topics.push(topic.into()); + self + } + + pub fn topics(mut self, topics: &[ValueOrArray]) -> Self { + self.topics.extend_from_slice(topics); + self + } + pub fn limit(mut self, limit: usize) -> Self { self.limit = Some(limit); self @@ -97,6 +142,20 @@ pub enum ValueOrArray { Array(Vec), } +impl From for ValueOrArray { + fn from(src: H256) -> Self { + ValueOrArray::Value(src) + } +} + +impl From
for ValueOrArray { + fn from(src: Address) -> Self { + let mut bytes = [0; 32]; + bytes[12..32].copy_from_slice(src.as_bytes()); + ValueOrArray::Value(H256::from(bytes)) + } +} + impl Serialize for ValueOrArray where T: Serialize,