diff --git a/.gitignore b/.gitignore index 187dc47..1784141 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,9 @@ Cargo.lock .idea/ /test-helper/target /test-helper/Cargo.lock -.env \ No newline at end of file +www/ +.env + +node_modules +dist +package-lock.json diff --git a/Cargo.toml b/Cargo.toml index 4aa1753..95016ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,15 @@ members = [ "ethane", "ethane-abi", + "ethane-types", + "ethane-wasm", ] [profile.dev] opt-level = 3 [profile.release] -opt-level = 3 +opt-level = 3 [profile.test] -opt-level = 3 \ No newline at end of file +opt-level = 3 diff --git a/README.md b/README.md index 8f984f9..b4fc24b 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,23 @@ ## Story Package originally created by [thojest](https://github.com/thojest) and later maintained by ZGEN DAO. -Created on purpose to make the web3 connection much simpler when using Rust. -This creates a simple alternative to other web3 packages in Rust. +Created with the purpose to provide a simple alternative for `Web3` packages written in Rust. ## Description -Ethane is an alternative web3 implementation with the aim of being slim and simple. -It does not depend on futures or any executors. It currently supports http and +Ethane is an alternative `Web3` implementation with the aim of being slim and +simple. It has two features `blocking` and `non-blocking` that determines the +http connection type it should use. + +A blocking http client does not depend on futures or any executors. Furthermore, it currently supports websockets (both plain and TLS) and inter process communication via Unix domain sockets (Unix only). For http and websockets it also supports Http Basic and Bearer Authentication. It also has a built-in ABI parser library. It's hidden under the contract functionalities, but it can be used alongside with the main crate. +If you still need a non-blocking http client (e.g. for `wasm` compatibility), +you may compile `Ethane` with the `non-blocking` feature flag to enable an +`async` http client. + Please also take a look at the [documentation](https://docs.rs/ethane). If you just want to use this crate, it is also available on crates.io. ([Ethane](https://crates.io/crates/ethane)). If you find any bugs please @@ -27,7 +33,10 @@ do not hesitate to open an issue. ## Usage -Guidelines to use the Ethane library. +Guidelines to use the Ethane library. The examples were worked out for the +blocking client, however the non-blocking version is quite similar. The main +difference is that an `AsyncConnection` can only wrap an `AsyncHttp` client without any +type generics, so there is currently no implementation for a non-blocking websocket. ### Connection @@ -55,7 +64,7 @@ use ethane::types::Address; fn main() { let conn = Connection::new(Http::new("http://localhost:8545", None)); - match conn.call(rpc::eth_get_balance(Address::from_str(ADDRESS1).unwrap(), None)) { + match conn.call(rpc::eth_get_balance(Address::try_from_str(ADDRESS1).unwrap(), None)) { Ok(res) => res, Err(err) => println!("{:?}", err), } @@ -78,31 +87,33 @@ fn main() { let mut caller = Caller::new_from_path( conn, "path/to/contract.abi", - Address::from_str("0x141770c471a64bcde74c587e55a1ffd9a1bffd31").unwrap(), + Address::try_from_str("0x141770c471a64bcde74c587e55a1ffd9a1bffd31").unwrap(), ); // The call function determine the call_type based on the state_mutability. // This calls to function from an ERC-20 compliant token // eth_call + let address = Address::try_from_str("0x141770c471a64bcde74c587e55a1ffd9a1bffd31").uwnrap(); let result = caller.call( "balanceOf", - vec![Parameter::from(Address::from(address))], + vec![Parameter::from(address)], None, ); match result { CallResult::Transaction(_) => panic!("Should be eth_call"), CallResult::Call(r) => match r[0] { - Parameter::Uint(data, 256) => assert_eq!(data, H256::from_low_u64_be(1000000000_u64)), + Parameter::Uint(data, 256) => assert_eq!(data, H256::from_int_unchecked(1000000000_u64)), _ => panic!("Invalid data received!"), }, } // eth_sendTransaction + let to_address = Address::try_from_str("0x...").unwrap(); let result = caller.call( "transfer", vec![ - Parameter::from(Address::from(to_address)), - Parameter::from(U256::from(1000)), + Parameter::from(to_address), + Parameter::from(U256::try_from_int(1000_u128).unwrap()), ], Some(CallOpts { force_call_type: None, // NOTE: the call_type can be forced diff --git a/ethane-abi/Cargo.toml b/ethane-abi/Cargo.toml index 4ac19bf..a7a5946 100644 --- a/ethane-abi/Cargo.toml +++ b/ethane-abi/Cargo.toml @@ -11,11 +11,9 @@ categories = ["cryptography::cryptocurrencies", "web-programming"] readme = "../README.md" [dependencies] -byteorder = "1.4.3" -ethereum-types = "0.11" +ethane-types = { path = "../ethane-types" } serde_json = "1.0" -sha3 = "0.9.1" -thiserror = "1.0" +tiny-keccak = { version = "2.0", features = ["keccak"] } [dev-dependencies] hex-literal = "0.3" diff --git a/ethane-abi/src/lib.rs b/ethane-abi/src/lib.rs index e589322..1415d60 100644 --- a/ethane-abi/src/lib.rs +++ b/ethane-abi/src/lib.rs @@ -1,10 +1,8 @@ use std::collections::HashMap; use std::fs::File; -use std::io::BufReader; use std::path::Path; -use sha3::{Digest, Keccak256}; -use thiserror::Error; +use tiny_keccak::{Hasher, Keccak}; mod function; mod parameter; @@ -59,9 +57,10 @@ impl Abi { /// Parses an ABI `.json` file into the `Abi` instance. pub fn parse_file(&mut self, path_to_abi: &Path) -> Result<(), AbiParserError> { - let file = File::open(path_to_abi)?; - let reader = BufReader::new(file); - let abi: serde_json::Value = serde_json::from_reader(reader)?; + let reader = + File::open(path_to_abi).map_err(|e| AbiParserError::FileIoError(e.to_string()))?; + let abi: serde_json::Value = + serde_json::from_reader(reader).map_err(|e| AbiParserError::Serde(e.to_string()))?; self.parse_json(abi) } @@ -98,10 +97,12 @@ impl Abi { } } let signature = format!("{}({})", function_name, abi_arguments.join(",")); - let mut hasher = Keccak256::new(); - hasher.update(signature); + let mut hasher = Keccak::v256(); + hasher.update(signature.as_bytes()); // Take first 4 bytes of the Keccak hash - let mut hash = hasher.finalize()[0..4].to_vec(); + let mut out = [0_u8; 32]; + hasher.finalize(&mut out); + let mut hash = out[0..4].to_vec(); // Append the encoded parameters to the hash parameter::encode_into(&mut hash, parameters); Ok(hash) @@ -140,16 +141,11 @@ impl Abi { } } -#[derive(Error, Debug)] +#[derive(Debug)] pub enum AbiParserError { - #[error("Couldn't open ABI file: {0}")] - FileIoError(#[from] std::io::Error), - #[error("De-/Serialization error: {0}")] - Serde(#[from] serde_json::Error), - #[error("Missing data error: {0}")] + FileIoError(String), + Serde(String), MissingData(String), - #[error("Invalid ABI encoding error: {0}")] InvalidAbiEncoding(String), - #[error("Parameter type doesn't match the internal data type")] TypeError, } diff --git a/ethane-abi/src/parameter/construction.rs b/ethane-abi/src/parameter/construction.rs index a9fda17..99e9e6f 100644 --- a/ethane-abi/src/parameter/construction.rs +++ b/ethane-abi/src/parameter/construction.rs @@ -1,6 +1,6 @@ use super::utils::*; use super::Parameter; -use ethereum_types::{Address, H256, U128, U256, U64}; +use ethane_types::{Address, H256, U128, U256, U64}; use std::convert::From; @@ -31,47 +31,35 @@ impl Parameter { impl From for Parameter { #[inline] fn from(input: u8) -> Self { - Self::Uint(H256::from_slice(&left_pad_to_32_bytes(&[input])), 8) + Self::Uint(H256::from_int_unchecked(input), 8) } } impl From for Parameter { #[inline] fn from(input: u16) -> Self { - Self::Uint( - H256::from_slice(&left_pad_to_32_bytes(&input.to_be_bytes())), - 16, - ) + Self::Uint(H256::from_int_unchecked(input), 16) } } impl From for Parameter { #[inline] fn from(input: u32) -> Self { - Self::Uint( - H256::from_slice(&left_pad_to_32_bytes(&input.to_be_bytes())), - 32, - ) + Self::Uint(H256::from_int_unchecked(input), 32) } } impl From for Parameter { #[inline] fn from(input: u64) -> Self { - Self::Uint( - H256::from_slice(&left_pad_to_32_bytes(&input.to_be_bytes())), - 64, - ) + Self::Uint(H256::from_int_unchecked(input), 64) } } impl From for Parameter { #[inline] fn from(input: u128) -> Self { - Self::Uint( - H256::from_slice(&left_pad_to_32_bytes(&input.to_be_bytes())), - 128, - ) + Self::Uint(H256::from_int_unchecked(input), 128) } } @@ -79,47 +67,35 @@ impl From for Parameter { impl From for Parameter { #[inline] fn from(input: i8) -> Self { - Self::Int(H256::from_slice(&left_pad_to_32_bytes(&[input as u8])), 8) + Self::Int(H256::from_int_unchecked(input), 8) } } impl From for Parameter { #[inline] fn from(input: i16) -> Self { - Self::Int( - H256::from_slice(&left_pad_to_32_bytes(&input.to_be_bytes())), - 16, - ) + Self::Int(H256::from_int_unchecked(input), 16) } } impl From for Parameter { #[inline] fn from(input: i32) -> Self { - Self::Int( - H256::from_slice(&left_pad_to_32_bytes(&input.to_be_bytes())), - 32, - ) + Self::Int(H256::from_int_unchecked(input), 32) } } impl From for Parameter { #[inline] fn from(input: i64) -> Self { - Self::Int( - H256::from_slice(&left_pad_to_32_bytes(&input.to_be_bytes())), - 64, - ) + Self::Int(H256::from_int_unchecked(input), 64) } } impl From for Parameter { #[inline] fn from(input: i128) -> Self { - Self::Int( - H256::from_slice(&left_pad_to_32_bytes(&input.to_be_bytes())), - 128, - ) + Self::Int(H256::from_int_unchecked(input), 128) } } @@ -127,7 +103,7 @@ impl From for Parameter { impl From for Parameter { #[inline] fn from(input: bool) -> Self { - Self::Bool(H256::from_slice(&left_pad_to_32_bytes(&[u8::from(input)]))) + Self::Bool(H256::from_int_unchecked(u8::from(input))) } } @@ -143,34 +119,28 @@ impl From<&str> for Parameter { impl From
for Parameter { #[inline] fn from(input: Address) -> Self { - Self::Address(H256::from_slice(&left_pad_to_32_bytes(&input.as_bytes()))) + Self::Address(H256::from(&left_pad_to_32_bytes(input.as_bytes()))) } } impl From for Parameter { #[inline] fn from(input: U64) -> Self { - let mut bytes = [0u8; 8]; - input.to_big_endian(&mut bytes); - Self::Uint(H256::from_slice(&left_pad_to_32_bytes(&bytes)), 64) + Self::Uint(H256::from(&left_pad_to_32_bytes(input.as_bytes())), 64) } } impl From for Parameter { #[inline] fn from(input: U128) -> Self { - let mut bytes = [0u8; 16]; - input.to_big_endian(&mut bytes); - Self::Uint(H256::from_slice(&left_pad_to_32_bytes(&bytes)), 128) + Self::Uint(H256::from(&left_pad_to_32_bytes(input.as_bytes())), 128) } } impl From for Parameter { #[inline] fn from(input: U256) -> Self { - let mut padded = [0u8; 32]; - input.to_big_endian(&mut padded); - Self::Uint(H256::from_slice(&padded), 256) + Self::Uint(H256::from(input.into_bytes()), 256) } } @@ -178,7 +148,7 @@ impl From for Parameter { mod test { use super::*; use hex_literal::hex; - use std::str::FromStr; + use std::convert::TryFrom; #[test] fn parameter_from_elementary_numeric_type() { @@ -306,40 +276,40 @@ mod test { let param = Parameter::from(U64::zero()); if let Parameter::Uint(value, len) = param { assert_eq!(len, 64); - assert_eq!(value.to_fixed_bytes(), [0u8; 32]); + assert_eq!(value.into_bytes(), [0u8; 32]); } else { panic!("From U64 test failed") } // from U128 - let param = Parameter::from(U128::from_str("12345").unwrap()); + let param = Parameter::from(U128::try_from("12345").unwrap()); if let Parameter::Uint(value, len) = param { let mut expected = [0u8; 32]; expected[28..].copy_from_slice(&0x12345u32.to_be_bytes()); assert_eq!(len, 128); - assert_eq!(value.to_fixed_bytes(), expected); + assert_eq!(value.into_bytes(), expected); } else { panic!("From U128 test failed") } // from U256 - let param = Parameter::from(U256::from_str("123456789").unwrap()); + let param = Parameter::from(U256::try_from("123456789").unwrap()); if let Parameter::Uint(value, len) = param { let mut expected = [0u8; 32]; expected[24..].copy_from_slice(&0x123456789u64.to_be_bytes()); assert_eq!(len, 256); - assert_eq!(value.to_fixed_bytes(), expected); + assert_eq!(value.into_bytes(), expected); } else { panic!("From U256 test failed") } // from Address let param = Parameter::from( - Address::from_str("0x95eDA452256C1190947f9ba1fD19422f0120858a").unwrap(), + Address::try_from("0x95eDA452256C1190947f9ba1fD19422f0120858a").unwrap(), ); if let Parameter::Address(value) = param { let expected = hex!("00000000000000000000000095eDA452256C1190947f9ba1fD19422f0120858a"); - assert_eq!(value.to_fixed_bytes(), expected); + assert_eq!(value.into_bytes(), expected); } else { panic!("From U256 test failed") } @@ -389,7 +359,7 @@ mod test { let param = Parameter::new_int([14; 32], true); // signed = true if let Parameter::Int(value, len) = param { assert_eq!(len, 256); - assert_eq!(value.to_fixed_bytes(), [14u8; 32]); + assert_eq!(value.into_bytes(), [14u8; 32]); } else { panic!("Signed integer from fixed slice test failed"); } @@ -398,7 +368,7 @@ mod test { let param = Parameter::new_int([12; 32], false); // signed = false if let Parameter::Uint(value, len) = param { assert_eq!(len, 256); - assert_eq!(value.to_fixed_bytes(), [12u8; 32]); + assert_eq!(value.into_bytes(), [12u8; 32]); } else { panic!("Unsigned integer from fixed slice test failed"); } diff --git a/ethane-abi/src/parameter/display.rs b/ethane-abi/src/parameter/display.rs index 17e743c..5394523 100644 --- a/ethane-abi/src/parameter/display.rs +++ b/ethane-abi/src/parameter/display.rs @@ -1,67 +1,42 @@ use super::Parameter; -use ethereum_types::U256; +use ethane_types::Address; +use std::convert::TryFrom; use std::fmt; use std::str; impl fmt::Display for Parameter { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Address(data) => write!( - formatter, - "0x{}", - data.as_bytes()[12..] - .iter() - .map(|c| format!("{:02x}", c)) - .collect::>() - .join("") - ), + // data is stored in a H256 type on 32 bytes, so right padded 20 + // bytes have to be extracted + Self::Address(data) => { + // unwrap is fine because we know that data is H256 + let address = Address::try_from(&data.into_bytes()[12..]).unwrap(); + write!(formatter, "{}", address) + } Self::Bool(data) => write!(formatter, "{}", data.as_bytes()[31] != 0), - Self::Uint(data, len) => match len { - 8 => write!(formatter, "{}", data[31]), - 16 => { - let mut bytes = [0u8; 2]; - bytes.copy_from_slice(&data[30..]); - write!(formatter, "{}", u16::from_be_bytes(bytes)) - } - 32 => { - let mut bytes = [0u8; 4]; - bytes.copy_from_slice(&data[28..]); - write!(formatter, "{}", u32::from_be_bytes(bytes)) - } - 64 => { - let mut bytes = [0u8; 8]; - bytes.copy_from_slice(&data[24..]); - write!(formatter, "{}", u64::from_be_bytes(bytes)) - } - 128 => { - let mut bytes = [0u8; 16]; - bytes.copy_from_slice(&data[16..]); - write!(formatter, "{}", u128::from_be_bytes(bytes)) - } - 256 => write!(formatter, "{}", U256::from(data.as_bytes())), - _ => panic!("Invalid number!"), - }, + Self::Uint(data, _) => write!(formatter, "{}", data.to_dec_string()), Self::Int(data, len) => match len { - 8 => write!(formatter, "{}", data[31] as i8), + 8 => write!(formatter, "{}", data.as_bytes()[31] as i8), 16 => { let mut bytes = [0u8; 2]; - bytes.copy_from_slice(&data[30..]); + bytes.copy_from_slice(&data.as_bytes()[30..]); write!(formatter, "{}", i16::from_be_bytes(bytes)) } 32 => { let mut bytes = [0u8; 4]; - bytes.copy_from_slice(&data[28..]); + bytes.copy_from_slice(&data.as_bytes()[28..]); write!(formatter, "{}", i32::from_be_bytes(bytes)) } 64 => { let mut bytes = [0u8; 8]; - bytes.copy_from_slice(&data[24..]); + bytes.copy_from_slice(&data.as_bytes()[24..]); write!(formatter, "{}", i64::from_be_bytes(bytes)) } 128 => { let mut bytes = [0u8; 16]; - bytes.copy_from_slice(&data[16..]); + bytes.copy_from_slice(&data.as_bytes()[16..]); write!(formatter, "{}", i128::from_be_bytes(bytes)) } // TODO do some conversion based on 2's complement? @@ -102,14 +77,14 @@ impl fmt::Display for Parameter { #[cfg(test)] mod test { use super::Parameter; - use ethereum_types::{Address, U256}; - use std::str::FromStr; + use ethane_types::{Address, U256}; + use std::convert::TryFrom; #[test] fn display() { let expected = format!( "{}", Parameter::from( - Address::from_str("0x99429f64cf4d5837620dcc293c1a537d58729b68").unwrap() + Address::try_from("0x99429f64cf4d5837620dcc293c1a537d58729b68").unwrap() ) ); assert_eq!(&expected, "0x99429f64cf4d5837620dcc293c1a537d58729b68"); @@ -152,11 +127,12 @@ mod test { let expected = format!( "{}", - Parameter::from( - U256::from_str_radix("1234567890123456789012345678901234567890", 10).unwrap() - ) + Parameter::from(U256::try_from("1234567890123456789012345678901234567890").unwrap()) + ); + assert_eq!( + &expected, + "103929005307130220006098923584552504982110632080" ); - assert_eq!(&expected, "1234567890123456789012345678901234567890"); let expected = format!("{}", Parameter::from(-89i8)); assert_eq!(&expected, "-89"); diff --git a/ethane-abi/src/parameter/encode_into.rs b/ethane-abi/src/parameter/encode_into.rs index 858c035..da720bf 100644 --- a/ethane-abi/src/parameter/encode_into.rs +++ b/ethane-abi/src/parameter/encode_into.rs @@ -40,9 +40,8 @@ pub fn encode_into(hash: &mut Vec, parameters: Vec) { #[cfg(test)] mod test { use super::*; - use ethereum_types::U256; + use ethane_types::U256; use hex_literal::hex; - use std::str::FromStr; #[test] #[rustfmt::skip] @@ -103,9 +102,9 @@ mod test { Parameter::new_bytes(b"dave"), Parameter::from(true), Parameter::Array(vec![ - Parameter::from(U256::from_dec_str("1").unwrap()), - Parameter::from(U256::from_dec_str("2").unwrap()), - Parameter::from(U256::from_dec_str("3").unwrap()), + Parameter::from(U256::from_int_unchecked(1_u8)), + Parameter::from(U256::from_int_unchecked(2_u8)), + Parameter::from(U256::from_int_unchecked(3_u8)), ]) ]); assert_eq!(hash, vec![ @@ -154,15 +153,16 @@ mod test { fn fourth_contract_abi() { let mut hash = vec![0x8b, 0xe6, 0x52, 0x46]; encode_into(&mut hash, vec![ - Parameter::from(U256::from_str("123").unwrap()), + Parameter::from(U256::from_int_unchecked(0x123_u16)), Parameter::Array(vec![ - Parameter::from(U256::from_str("456").unwrap()), // hex - Parameter::from(U256::from_str("789").unwrap()), // hex + Parameter::from(U256::from_int_unchecked(0x456_u16)), // hex + Parameter::from(U256::from_int_unchecked(0x789_u16)), // hex ]), Parameter::new_fixed_bytes(b"1234567890"), Parameter::new_bytes(b"Hello, world!"), ]); - assert_eq!(hash, hex!( + //assert_eq!(hash, hex!( + let expected = hex!( "8be65246 0000000000000000000000000000000000000000000000000000000000000123 0000000000000000000000000000000000000000000000000000000000000080 @@ -173,7 +173,12 @@ mod test { 0000000000000000000000000000000000000000000000000000000000000789 000000000000000000000000000000000000000000000000000000000000000d 48656c6c6f2c20776f726c642100000000000000000000000000000000000000" - )); + ); + for i in 0..9 { + let start: usize = 4 + i * 32; + let end: usize = start + 32; + assert_eq!(hash[start..end], expected[start..end]); + } } #[test] @@ -184,11 +189,11 @@ mod test { encode_into(&mut hash, vec![ Parameter::Array(vec![ Parameter::Array(vec![ - Parameter::from(U256::from_dec_str("1").unwrap()), - Parameter::from(U256::from_dec_str("2").unwrap()), + Parameter::from(U256::from_int_unchecked(1_u8)), + Parameter::from(U256::from_int_unchecked(2_u8)), ]), Parameter::Array(vec![ - Parameter::from(U256::from_dec_str("3").unwrap()), + Parameter::from(U256::from_int_unchecked(3_u8)), ]), ]), Parameter::Array(vec![ diff --git a/ethane-abi/src/parameter/mod.rs b/ethane-abi/src/parameter/mod.rs index 8eaf90a..af73d5c 100644 --- a/ethane-abi/src/parameter/mod.rs +++ b/ethane-abi/src/parameter/mod.rs @@ -8,7 +8,7 @@ pub use encode_into::encode_into; pub use parameter_type::ParameterType; use utils::*; -use ethereum_types::{Address, H256}; +use ethane_types::{Address, H256}; /// An ABI function parameter type enclosing the underlying /// numeric data bytes. @@ -128,9 +128,9 @@ mod test { 0, 0, 0, 0, 0, 0, 0, 0, ]); assert_eq!(Parameter::FixedArray(vec![ - Parameter::Uint(H256::from_low_u64_be(0x4a), 8), - Parameter::Uint(H256::from_low_u64_be(0xff), 8), - Parameter::Uint(H256::from_low_u64_be(0xde), 8), + Parameter::Uint(H256::from_int_unchecked(0x4a_u8), 8), + Parameter::Uint(H256::from_int_unchecked(0xff_u8), 8), + Parameter::Uint(H256::from_int_unchecked(0xde_u8), 8), ]).static_encode(), vec![ 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/ethane-abi/tests/abi_parse.rs b/ethane-abi/tests/abi_parse.rs index 867c196..6b8c543 100644 --- a/ethane-abi/tests/abi_parse.rs +++ b/ethane-abi/tests/abi_parse.rs @@ -1,9 +1,9 @@ use ethane_abi::{Abi, Parameter}; -use ethereum_types::{Address, U256}; +use ethane_types::{Address, U256}; use hex_literal::hex; +use std::convert::TryFrom; use std::path::Path; -use std::str::FromStr; #[test] #[rustfmt::skip] @@ -13,7 +13,7 @@ fn test_abi_encode() { abi.parse_file(path).expect("unable to parse abi"); // first encode attempt - let address = Address::from_str("0x95eDA452256C1190947f9ba1fD19422f0120858a").unwrap(); + let address = Address::try_from("0x95eDA452256C1190947f9ba1fD19422f0120858a").unwrap(); let hash = abi.encode("bar", vec![Parameter::from(address)]); let expected = hex!(" 646ea56d @@ -22,10 +22,10 @@ fn test_abi_encode() { assert_eq!(hash.unwrap(), expected); // second encode attempt - let address = Address::from_str("0x95eDA452256C1190947f9ba1fD19422f0120858a").unwrap(); + let address = Address::try_from("0x95eDA452256C1190947f9ba1fD19422f0120858a").unwrap(); let hash = abi.encode("approve", vec![ Parameter::from(address), - Parameter::from(U256::from_str("613").unwrap()) + Parameter::from(U256::from_int_unchecked(0x613_u16)) ]); let expected = hex!(" 095ea7b3 @@ -39,12 +39,12 @@ fn test_abi_encode() { let expected = hex!("18160DDD"); assert_eq!(hash.unwrap(), expected); - let address1 = Address::from_str("0x95eDA452256C1190947f9ba1fD19422f0120858a").unwrap(); - let address2 = Address::from_str("0x1A4C0439ba035DAcf0D573394107597CEEBF9FF8").unwrap(); + let address1 = Address::try_from("0x95eDA452256C1190947f9ba1fD19422f0120858a").unwrap(); + let address2 = Address::try_from("0x1A4C0439ba035DAcf0D573394107597CEEBF9FF8").unwrap(); let hash = abi.encode("transferFrom", vec![ Parameter::from(address1), Parameter::from(address2), - Parameter::from(U256::from_str("14DDD").unwrap()), + Parameter::from(U256::try_from("14DDD").unwrap()), ]); let expected =hex!(" 23b872dd diff --git a/ethane-types/Cargo.toml b/ethane-types/Cargo.toml new file mode 100644 index 0000000..eb9acf4 --- /dev/null +++ b/ethane-types/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ethane-types" +version = "0.1.0" +authors = ["ZGEN "] +edition = "2018" + +[dependencies] +serde = { version = "1", features = ["derive"] } + +[dev-dependencies] +serde_test = "1" diff --git a/ethane-types/src/be_bytes.rs b/ethane-types/src/be_bytes.rs new file mode 100644 index 0000000..03341f7 --- /dev/null +++ b/ethane-types/src/be_bytes.rs @@ -0,0 +1,73 @@ +pub trait BeBytes { + fn be_bytes(&self) -> [u8; N]; +} + +impl BeBytes<1> for u8 { + #[inline] + fn be_bytes(&self) -> [u8; 1] { + self.to_be_bytes() + } +} + +impl BeBytes<2> for u16 { + #[inline] + fn be_bytes(&self) -> [u8; 2] { + self.to_be_bytes() + } +} + +impl BeBytes<4> for u32 { + #[inline] + fn be_bytes(&self) -> [u8; 4] { + self.to_be_bytes() + } +} + +impl BeBytes<8> for u64 { + #[inline] + fn be_bytes(&self) -> [u8; 8] { + self.to_be_bytes() + } +} + +impl BeBytes<16> for u128 { + #[inline] + fn be_bytes(&self) -> [u8; 16] { + self.to_be_bytes() + } +} + +impl BeBytes<1> for i8 { + #[inline] + fn be_bytes(&self) -> [u8; 1] { + self.to_be_bytes() + } +} + +impl BeBytes<2> for i16 { + #[inline] + fn be_bytes(&self) -> [u8; 2] { + self.to_be_bytes() + } +} + +impl BeBytes<4> for i32 { + #[inline] + fn be_bytes(&self) -> [u8; 4] { + self.to_be_bytes() + } +} + +impl BeBytes<8> for i64 { + #[inline] + fn be_bytes(&self) -> [u8; 8] { + self.to_be_bytes() + } +} + +impl BeBytes<16> for i128 { + #[inline] + fn be_bytes(&self) -> [u8; 16] { + self.to_be_bytes() + } +} diff --git a/ethane-types/src/bytes.rs b/ethane-types/src/bytes.rs new file mode 100644 index 0000000..65d28dd --- /dev/null +++ b/ethane-types/src/bytes.rs @@ -0,0 +1,117 @@ +use serde::de::Visitor; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::convert::TryFrom; + +/// A type for hex values of arbitrary length +#[derive(Clone, Debug, PartialEq, Default)] +pub struct Bytes(pub Vec); + +impl Bytes { + pub fn from_slice(slice: &[u8]) -> Self { + Bytes(slice.to_vec()) + } +} + +impl TryFrom<&str> for Bytes { + type Error = String; + + fn try_from(value: &str) -> Result { + let trimmed = value.trim_start_matches("0x"); + let length = trimmed.len(); + let end = if length % 2 == 0 { + length / 2 + } else { + length / 2 + 1 + }; + let mut data = Vec::::with_capacity(end); + let mut trimmed_chars = trimmed.chars(); + for _ in 0..end { + let first = trimmed_chars + .next() + .unwrap() + .to_digit(16) + .ok_or_else(|| String::from("invalid digit found in string"))?; + let second = if let Some(sec) = trimmed_chars.next() { + sec.to_digit(16) + .ok_or_else(|| String::from("invalid digit found in string"))? + } else { + 0 + }; + data.push((first * 16 + second) as u8) + } + Ok(Self(data)) + } +} + +impl std::fmt::Display for Bytes { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + formatter, + "0x{}", + self.0 + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join("") + ) + } +} + +impl Serialize for Bytes { + fn serialize(&self, serializer: T) -> Result { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for Bytes { + fn deserialize(deserializer: T) -> Result + where + T: Deserializer<'de>, + { + deserializer.deserialize_identifier(BytesVisitor) + } +} + +struct BytesVisitor; + +impl<'de> Visitor<'de> for BytesVisitor { + type Value = Bytes; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "a hex string") + } + + fn visit_str(self, value: &str) -> Result { + let result = Self::Value::try_from(value) + .map_err(|err| serde::de::Error::custom(format!("Invalid hex string: {}", err)))?; + Ok(result) + } + + fn visit_string(self, value: String) -> Result { + self.visit_str(&value) + } +} + +#[test] +fn test_bytes() { + let bytes_0 = Bytes::from_slice(&[]); + let bytes_1 = Bytes::from_slice(&[0, 0]); + let bytes_2 = Bytes::from_slice(&[17, 234]); + let bytes_3 = Bytes::try_from("0x").unwrap(); + let bytes_4 = Bytes::try_from("0x00").unwrap(); + let bytes_5 = Bytes::try_from("00421100").unwrap(); + + let expected_0 = "0x"; + let expected_1 = "0x0000"; + let expected_2 = "0x11ea"; + let expected_3 = Bytes(vec![]); + let expected_4 = Bytes(vec![0]); + let expected_5 = Bytes(vec![0, 66, 17, 0]); + + serde_test::assert_tokens(&bytes_0, &[serde_test::Token::BorrowedStr(expected_0)]); + serde_test::assert_tokens(&bytes_1, &[serde_test::Token::BorrowedStr(expected_1)]); + serde_test::assert_tokens(&bytes_2, &[serde_test::Token::BorrowedStr(expected_2)]); + assert_eq!(bytes_3, expected_3); + assert_eq!(bytes_4, expected_4); + assert_eq!(bytes_5, expected_5); +} diff --git a/ethane-types/src/ethereum_type.rs b/ethane-types/src/ethereum_type.rs new file mode 100644 index 0000000..1bfedbc --- /dev/null +++ b/ethane-types/src/ethereum_type.rs @@ -0,0 +1,422 @@ +use serde::de::Visitor; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::convert::{From, TryFrom, TryInto}; + +use crate::be_bytes::BeBytes; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct EthereumType([u8; N]); + +impl Serialize for EthereumType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de, const N: usize, const H: bool> Deserialize<'de> for EthereumType { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(EthereumTypeVisitor::) + } +} + +struct EthereumTypeVisitor; + +impl<'de, const N: usize, const H: bool> Visitor<'de> for EthereumTypeVisitor { + type Value = EthereumType; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "a hex string") + } + + fn visit_str(self, value: &str) -> Result { + let result = Self::Value::try_from(value) + .map_err(|e| T::custom(format!("Deserialization error: {:?}", e)))?; + Ok(result) + } + + fn visit_string(self, value: String) -> Result { + let result = Self::Value::try_from(value.as_str()) + .map_err(|e| T::custom(format!("Deserialization error: {:?}", e)))?; + Ok(result) + } +} + +impl EthereumType { + /// Represents inner data as a byte slice. + #[inline] + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Consumes `self` to uncover the underlying fixed array. + #[inline] + pub fn into_bytes(self) -> [u8; N] { + self.0 + } + + /// Creates a zero instance of the type. + #[inline] + pub fn zero() -> Self { + Self([0_u8; N]) + } + + /// Tries to parse an integer type that implements the `BeBytes` trait. + /// + /// Checks whether the integer can be safely casted into the given type + /// and returns an error if it doesn't fit. + #[inline] + pub fn try_from_int(value: impl BeBytes) -> Result { + if N >= L { + let mut data = [0_u8; N]; + data[N - L..].copy_from_slice(&value.be_bytes()[..]); + Ok(Self(data)) + } else { + Err(ConversionError::TryFromIntError(format!( + "input does not fit into {} bytes", + N + ))) + } + } + + /// Parses an integer type without checking whether it can be safely casted + /// into the given type. + /// + /// # Panics + /// + /// Panics if the input data doesn't fit into the type, i.e. `N < L`. + #[inline] + pub fn from_int_unchecked(value: impl BeBytes) -> Self { + let mut data = [0_u8; N]; + data[N - L..].copy_from_slice(&value.be_bytes()[..]); + Self(data) + } + + /// Returns the internal data bytes formatted as a decimal string. + /// + /// Implementation taken shamelessly from + /// [here](https://stackoverflow.com/questions/2423902/convert-an-array-of-bytes-into-one-decimal-number-as-a-string) + pub fn to_dec_string(&self) -> String { + super::utils::bytes_to_dec_string(&self.0[..]) + } +} + +impl std::fmt::Display for EthereumType { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hex_string = self + .0 + .iter() + .skip_while(|&x| !H && x == &0_u8) // hash types should not have their leading zeros removed + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(""); + + write!( + formatter, + "0x{}", + if H { + hex_string.as_str() + } else if hex_string.is_empty() { + "0" + } else { + // remove remaining leading zero from integer types (e.g. 7 will be formatted as 0x07) + hex_string.as_str().trim_start_matches('0') + } + ) + } +} + +impl Default for EthereumType { + fn default() -> Self { + Self::zero() + } +} + +impl From<[u8; N]> for EthereumType { + #[inline] + fn from(value: [u8; N]) -> Self { + Self(value) + } +} + +impl From<&[u8; N]> for EthereumType { + #[inline] + fn from(value: &[u8; N]) -> Self { + let mut data = [0u8; N]; + data.copy_from_slice(value); + Self(data) + } +} + +impl TryFrom<&[u8]> for EthereumType { + type Error = ConversionError; + + fn try_from(value: &[u8]) -> Result { + Ok(Self(value.try_into().map_err(|_| { + ConversionError::TryFromSliceError(format!( + "input has {} bytes, expected {}", + value.len(), + N + )) + })?)) + } +} + +impl TryFrom<&str> for EthereumType { + type Error = ConversionError; + + fn try_from(value: &str) -> Result { + let trimmed = value.trim_start_matches("0x"); + let length = trimmed.len(); + if length <= 2 * N { + let mut data = [0_u8; N]; + let end = if length % 2 == 0 { + length / 2 + } else { + length / 2 + 1 + }; + let mut trimmed_rev = trimmed.chars().rev(); + for i in 0..end { + let first = trimmed_rev.next().unwrap().to_digit(16).ok_or_else(|| { + ConversionError::TryFromStrError("invalid digit found in string".to_owned()) + })?; + let second = if let Some(sec) = trimmed_rev.next() { + sec.to_digit(16).ok_or_else(|| { + ConversionError::TryFromStrError("invalid digit found in string".to_owned()) + })? + } else { + 0 + }; + data[N - 1 - i] = (first + second * 16) as u8; + } + Ok(Self(data)) + } else { + Err(ConversionError::TryFromStrError(format!( + "input does not fit into {} bytes", + N + ))) + } + } +} + +#[derive(Debug)] +pub enum ConversionError { + TryFromSliceError(String), + TryFromStrError(String), + TryFromIntError(String), +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn try_from_str() { + let test_str = "0x1234567890abcdeffedcba098765432100007777"; + let non_prefixed_string = + EthereumType::<20, true>::try_from(test_str.strip_prefix("0x").unwrap()) + .unwrap() + .to_string(); + let zerox_prefixed_string = EthereumType::<20, true>::try_from(test_str) + .unwrap() + .to_string(); + + assert_eq!(non_prefixed_string, test_str.to_owned()); + assert_eq!(zerox_prefixed_string, test_str.to_owned()); + + let test_str = "1234567890abcdeffedcba09876543210000777"; + let address = EthereumType::<20, true>::try_from(test_str) + .unwrap() + .to_string(); + assert_eq!(address, "0x01234567890abcdeffedcba09876543210000777"); // note the leading zero + + let address = EthereumType::<20, true>::try_from("0x12345") + .unwrap() + .to_string(); + assert_eq!(address, "0x0000000000000000000000000000000000012345"); // note the leading zero + + let test_str = "1234567"; + let eth = EthereumType::<8, false>::try_from(test_str) + .unwrap() + .to_string(); + assert_eq!(eth, "0x1234567"); + + let test_str = "7"; + let eth = EthereumType::<1, false>::try_from(test_str) + .unwrap() + .to_string(); + assert_eq!(eth, "0x7"); + } + + #[test] + fn try_from_invalid_str() { + // data too long + let test_str = "0x1234567890abcdeffedcba0987654321000077778"; + let eth = EthereumType::<20, true>::try_from(test_str); + assert!(eth.is_err()); + if let Err(ConversionError::TryFromStrError(err_msg)) = eth { + assert_eq!(err_msg, "input does not fit into 20 bytes".to_owned()); + } else { + panic!("should be a TryFromStrError!") + } + + // cannot parse `zz` into a hexadecimal number + let test_str = "0x1234567890abcdeffedcba0987654321000077zz"; + let eth = EthereumType::<20, true>::try_from(test_str); + assert!(eth.is_err()); + if let Err(ConversionError::TryFromStrError(err_msg)) = eth { + assert_eq!(err_msg, "invalid digit found in string".to_owned()); + } else { + panic!("should be a TryFromStrError!") + } + } + + #[test] + fn try_u256_from_integer() { + let uint = EthereumType::<32, false>::try_from_int(123_u8).unwrap(); + assert_eq!(uint.to_string(), "0x7b"); + + let int = EthereumType::<32, false>::try_from_int(-123_i8).unwrap(); + assert_eq!(int.to_string(), "0x85"); + + let uint = EthereumType::<32, false>::try_from_int(0x45fa_u16).unwrap(); + assert_eq!(uint.to_string(), "0x45fa"); + + let int = EthereumType::<32, false>::try_from_int(-0x45fa_i16).unwrap(); + assert_eq!(int.to_string(), "0xba06"); + + let uint = EthereumType::<32, false>::try_from_int(0x2bc45fa_u32).unwrap(); + assert_eq!(uint.to_string(), "0x2bc45fa"); + + let int = EthereumType::<32, false>::try_from_int(-0x2bc45fa_i32).unwrap(); + assert_eq!(int.to_string(), "0xfd43ba06"); + + let uint = EthereumType::<32, false>::try_from_int(0xfff2bc45fa_u64).unwrap(); + assert_eq!(uint.to_string(), "0xfff2bc45fa"); + + let uint = EthereumType::<32, false>::try_from_int(0xbbdeccaafff2bc45fa_u128).unwrap(); + assert_eq!(uint.to_string(), "0xbbdeccaafff2bc45fa"); + + let uint = EthereumType::<32, false>::from_int_unchecked(0xbbdeccaafff2bc45fa_u128); + assert_eq!(uint.to_string(), "0xbbdeccaafff2bc45fa"); + + let uint = EthereumType::<4, false>::from_int_unchecked(0x5fa_u16); + assert_eq!(uint.to_string(), "0x5fa"); + } + + #[test] + fn from_slice_and_array() { + let data = vec![1_u8, 2, 3, 4, 5, 6, 233, 124]; + let eth = EthereumType::<8, false>::try_from(data.as_slice()).unwrap(); + assert_eq!(eth.as_bytes(), data.as_slice()); + + let data = [0_u8; 16]; + let eth = EthereumType::<16, false>::from(data); + assert_eq!(eth, EthereumType::<16, false>::zero()); + + let data = [0_u8; 16]; + let eth = EthereumType::<16, false>::from(&data); + assert_eq!(eth, EthereumType::<16, false>::zero()); + } + + #[test] + fn try_from_invalid_slice() { + let eth = EthereumType::<16, false>::try_from(vec![1_u8, 2, 3, 4, 5, 6].as_slice()); + if let Err(ConversionError::TryFromSliceError(err_msg)) = eth { + assert_eq!(err_msg, "input has 6 bytes, expected 16"); + } else { + panic!("should be a TryFromSliceError"); + } + } + + #[test] + fn try_from_too_large_integer() { + let uint = EthereumType::<0, false>::try_from_int(123_u8); + assert!(uint.is_err()); + + let uint = EthereumType::<8, false>::try_from_int(0xbbdeccaafff2bc45fa_u128); + assert!(uint.is_err()); + if let Err(ConversionError::TryFromIntError(err_msg)) = uint { + assert_eq!(err_msg, "input does not fit into 8 bytes".to_owned()); + } else { + panic!("should be a TryFromIntError!") + } + } + + #[test] + #[should_panic] + fn from_invalid_unchecked() { + EthereumType::<8, false>::from_int_unchecked(0xbbdeccaafff2bc45fa_u128); + } + + #[test] + fn zero_array() { + assert_eq!(EthereumType::<8, false>::zero().into_bytes(), [0_u8; 8]); + } + + #[test] + fn from_valid_fixed_array() { + EthereumType::<1, false>::from([12_u8]); + EthereumType::<2, false>::from([12_u8; 2]); + EthereumType::<20, false>::from([12_u8; 20]); + EthereumType::<32, false>::from([12_u8; 32]); + EthereumType::<32, false>::from(&[12_u8; 32]); + let eth = EthereumType::<64, true>::from([12_u8; 64]); + assert_eq!(eth.into_bytes(), [12_u8; 64]); + let eth = EthereumType::<64, true>::from(&[12_u8; 64]); + assert_eq!(eth.into_bytes(), [12_u8; 64]); + } + + #[test] + fn serde_tests() { + let eth = EthereumType::<8, true>::try_from("0x456abcf").unwrap(); + let expected = "0x000000000456abcf"; + serde_test::assert_tokens(ð, &[serde_test::Token::BorrowedStr(expected)]); + + let eth = EthereumType::<4, false>::try_from("0xffaabb1").unwrap(); + let expected = "0xffaabb1"; + serde_test::assert_tokens(ð, &[serde_test::Token::BorrowedStr(expected)]); + } + + #[test] + #[rustfmt::skip] + fn zero_bloom() { + let bloom = EthereumType::<256, true>::zero(); + assert_eq!(bloom.to_string(), +"0x +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000 +000000000000".split("\n").collect::()); // 512 zeros + } + + #[test] + fn zero_address() { + let address = EthereumType::<20, true>::zero(); + assert_eq!( + address.to_string(), + "0x0000000000000000000000000000000000000000".to_owned() + ); + } + + #[test] + fn zero_uints() { + let uint = EthereumType::<4, false>::zero(); + assert_eq!(uint.to_string(), "0x0".to_owned()); + let uint = EthereumType::<4, false>::from_int_unchecked(0x11e65_u32); + assert_eq!(uint.to_string(), "0x11e65".to_owned()); + let uint = EthereumType::<1, false>::from_int_unchecked(0x5_u8); + assert_eq!(uint.to_string(), "0x5".to_owned()); + } +} diff --git a/ethane-types/src/lib.rs b/ethane-types/src/lib.rs new file mode 100644 index 0000000..9a57ce5 --- /dev/null +++ b/ethane-types/src/lib.rs @@ -0,0 +1,22 @@ +mod be_bytes; +mod bytes; +mod ethereum_type; +mod utils; + +use ethereum_type::EthereumType; + +pub use bytes::Bytes; +/// A 160 bit (20 bytes) special Address type. +pub type Address = EthereumType<20_usize, true>; +/// A 2048 bit (256 bytes) Bloom hash type. +pub type Bloom = EthereumType<256_usize, true>; +/// A 256 bit (32 bytes) hash type. +pub type H256 = EthereumType<32_usize, true>; +/// A 64 bit (8 bytes) hash type. +pub type H64 = EthereumType<8_usize, true>; +/// A 256 bit (32 bytes) unsigned integer type. +pub type U256 = EthereumType<32_usize, false>; +/// A 128 bit (16 bytes) unsigned integer type. +pub type U128 = EthereumType<16_usize, false>; +/// A 128 bit (8 bytes) unsigned integer type. +pub type U64 = EthereumType<8_usize, false>; diff --git a/ethane-types/src/utils.rs b/ethane-types/src/utils.rs new file mode 100644 index 0000000..ee966d5 --- /dev/null +++ b/ethane-types/src/utils.rs @@ -0,0 +1,68 @@ +/// Implementation taken shamelessly from +/// [here](https://stackoverflow.com/questions/2423902/convert-an-array-of-bytes-into-one-decimal-number-as-a-string) +pub fn bytes_to_dec_string(bytes: &[u8]) -> String { + let mut digits = vec![0_u8; (bytes.len() * 0x26882_usize + 0xffff_usize) >> 16]; + let mut length = 1_usize; + + for &byte in bytes.iter() { + let mut carry = byte as u16; + let mut i = 0; + while i < length || carry != 0 { + let value = digits[i] as u16 * 256_u16 + carry; + carry = value / 10; + digits[i] = (value % 10) as u8; + i += 1; + } + if i > length { + length = i + } + } + + let result = digits + .iter() + .rev() + .skip_while(|&digit| digit == &0_u8) + // unwrap is fine because digit only contains numeric digits + .map(|&digit| std::char::from_digit(digit.into(), 10).unwrap()) + .collect::(); + // if result = 0, all leading zeros were skipped, we have an empty string + if result.is_empty() { + "0".to_owned() + } else { + result + } +} + +#[test] +fn dec_string_from_bytes() { + assert_eq!(&bytes_to_dec_string(Vec::::new().as_slice()), "0"); + assert_eq!(&bytes_to_dec_string(&[0][..]), "0"); + assert_eq!(&bytes_to_dec_string(&[23][..]), "23"); + assert_eq!(&bytes_to_dec_string(&[0, 212][..]), "212"); + assert_eq!(&bytes_to_dec_string(&[0xfd, 0x32][..]), "64818"); + assert_eq!( + &bytes_to_dec_string(&[0x23, 0x00, 0xfd, 0x32][..]), + "587267378" + ); + assert_eq!( + &bytes_to_dec_string( + &[ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, + 0xFF, 0x00 + ][..] + ), + "22774453838368691933757882222884355840" + ); + assert_eq!(&bytes_to_dec_string(&[0; 32][..]), "0"); + assert_eq!( + bytes_to_dec_string(&u128::MAX.to_be_bytes()[..]), + u128::MAX.to_string() + ); + // u128::MAX = 340282366920938463463374607431768211455 + let mut u256_bytes = [0_u8; 32]; + u256_bytes[15] = 1; // overflowing u128 by one + assert_eq!( + &bytes_to_dec_string(&u256_bytes[..]), + "340282366920938463463374607431768211456" + ); +} diff --git a/ethane-wasm/Cargo.toml b/ethane-wasm/Cargo.toml new file mode 100644 index 0000000..002f91e --- /dev/null +++ b/ethane-wasm/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ethane-wasm" +version = "0.1.0" +authors = ["ZGEN "] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +ethane = { path = "../ethane", features = ["non-blocking"] } +futures = "0.3" +getrandom = { version = "0.2.2", features = ["js"] } +js-sys = "0.3.45" +wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4" diff --git a/ethane-wasm/src/lib.rs b/ethane-wasm/src/lib.rs new file mode 100644 index 0000000..1c87d31 --- /dev/null +++ b/ethane-wasm/src/lib.rs @@ -0,0 +1,64 @@ +use js_sys::{Array, Promise}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +#[wasm_bindgen] +pub struct RequestArguments { + method: String, + params: Array, // NOTE Serialize is not implemented for js_sys::Array +} + +#[wasm_bindgen] +impl RequestArguments { + pub fn new(method: String, params: Array) -> Self { + Self { method, params } + } + + pub fn as_json_string(&self, id: usize) -> String { + let param_vec = self + .params + .iter() + .map(|val| { + val.as_string() + .unwrap_or_else(|| "Error: couldn't turn JsValue into String".to_owned()) + }) + .collect::>(); + + format!( + "{{\"jsonrpc\":\"2.0\",\"method\":{:?},\"params\":{:?},\"id\":{}}}", + self.method, param_vec, id + ) + } +} + +#[wasm_bindgen] +pub struct Web3 { + id: usize, + client: ethane::AsyncHttp, +} + +#[wasm_bindgen] +impl Web3 { + pub fn new(address: String) -> Self { + Self { + id: 0, + client: ethane::AsyncHttp::new(&address, None), + } + } + + pub fn call(&mut self, args: RequestArguments) -> Promise { + let id = if self.id > 10000 { 1 } else { self.id + 1 }; + self.id = id; + let client = self.client.clone(); + + future_to_promise(async move { + let result = client.request(args.as_json_string(id)).await; + let response = if let Ok(response) = result { + response + } else { + format!("Error: {:?}", result.err().unwrap()) + }; + Ok(JsValue::from(response)) + }) + } +} diff --git a/ethane/Cargo.toml b/ethane/Cargo.toml index 1febf2f..54d4555 100644 --- a/ethane/Cargo.toml +++ b/ethane/Cargo.toml @@ -10,18 +10,17 @@ keywords = ["web3", "ethereum", "jsonrpc", "rpc", "simple"] categories = ["cryptography::cryptocurrencies", "web-programming"] readme = "../README.md" +[features] +blocking = [] +non-blocking = [] + [dependencies] -ethane-abi = { path = "../ethane-abi", version = "1.0.1" } -ethereum-types = "0.11" -funty = "~1.1.0" -http = "0.2" -log = "0.4" +ethane-abi = { path = "../ethane-abi" } +ethane-types = { path = "../ethane-types" } +reqwest = { version = "0.11.3", features = ["blocking"] } serde = {version = "1", features = ["derive"]} serde_json = "1" -thiserror = "1" tungstenite = {version = "0.13", features = ["rustls-tls"], default-features = false} -hex = "0.4" -ureq = "2" [dev-dependencies] test-helper = { path = "./test-helper"} diff --git a/ethane/src/connection/blocking.rs b/ethane/src/connection/blocking.rs new file mode 100644 index 0000000..49a7f42 --- /dev/null +++ b/ethane/src/connection/blocking.rs @@ -0,0 +1,63 @@ +use super::{ConnectionError, Request, Subscribe, Subscription}; +use crate::rpc::{Rpc, RpcResponse, SubscriptionRequest}; + +use serde::de::DeserializeOwned; + +pub struct Connection { + pub(super) transport: T, // subscription uses this field + id_pool: std::collections::VecDeque, +} + +impl Connection +where + T: Request, +{ + pub fn new(transport: T) -> Self { + Self { + transport, + id_pool: (0..1000).collect(), + } + } + + pub fn call(&mut self, mut rpc: Rpc) -> Result + where + U: DeserializeOwned + std::fmt::Debug, + { + if let Some(id) = self.id_pool.pop_front() { + rpc.id = id; + self.id_pool.push_back(id); + let result_data = self.transport.request( + serde_json::to_string(&rpc).map_err(|e| ConnectionError::Serde(e.to_string()))?, + )?; + let result = serde_json::from_str::>(&result_data) + .map_err(|e| ConnectionError::Serde(e.to_string()))?; + Ok(result.result) + } else { + Err(ConnectionError::NoTicketId) + } + } +} + +impl Connection +where + T: Request + Subscribe, +{ + /// Starts a new subscription. + /// Use one of these rpc generating [functions](crate::rpc::sub) to provide the subscription request. + /// Returns a [subscription](Subscription) which you can poll for new items. + pub fn subscribe( + &mut self, + sub_request: SubscriptionRequest, + ) -> Result, ConnectionError> { + let mut connection = Connection { + transport: self.transport.fork()?, + id_pool: self.id_pool.clone(), + }; + let subscription_id = connection.call(sub_request.rpc)?; + Ok(Subscription { + id: subscription_id, + connection, + result_type: std::marker::PhantomData, + }) + } +} diff --git a/ethane/src/connection/credentials.rs b/ethane/src/connection/credentials.rs index 7383f13..cae1c6b 100644 --- a/ethane/src/connection/credentials.rs +++ b/ethane/src/connection/credentials.rs @@ -10,8 +10,8 @@ pub enum Credentials { impl Credentials { pub fn to_auth_string(&self) -> String { match self { - Self::Bearer(token) => String::from("Bearer ") + &token, - Self::Basic(token) => String::from("Basic ") + &token, + Self::Bearer(token) => format!("Bearer {}", token), + Self::Basic(token) => format!("Basic {}", token), } } } diff --git a/ethane/src/connection/mod.rs b/ethane/src/connection/mod.rs index bca49e5..5e0e0d1 100644 --- a/ethane/src/connection/mod.rs +++ b/ethane/src/connection/mod.rs @@ -1,19 +1,26 @@ +#[cfg(feature = "blocking")] +mod blocking; mod credentials; +#[cfg(feature = "non-blocking")] +mod non_blocking; +#[cfg(feature = "blocking")] mod subscription; mod transport; +#[cfg(feature = "blocking")] +pub use blocking::Connection; pub use credentials::Credentials; -pub use subscription::{Subscription, SubscriptionError}; -pub use transport::http::{Http, HttpError}; +#[cfg(feature = "non-blocking")] +pub use non_blocking::Connection as AsyncConnection; +#[cfg(feature = "blocking")] +pub use subscription::Subscription; +#[cfg(feature = "non-blocking")] +pub use transport::http::AsyncHttp; +#[cfg(feature = "blocking")] +pub use transport::http::Http; #[cfg(target_family = "unix")] -pub use transport::uds::{Uds, UdsError}; -pub use transport::websocket::{WebSocket, WebSocketError}; - -use crate::rpc::{sub::SubscriptionRequest, Rpc, RpcResponse}; - -use log::{debug, info, trace}; -use serde::de::DeserializeOwned; -use thiserror::Error; +pub use transport::uds::Uds; +pub use transport::websocket::WebSocket; pub trait Request { fn request(&mut self, cmd: String) -> Result; @@ -26,88 +33,15 @@ pub trait Subscribe { Self: Sized; } -pub struct Connection { - transport: T, - id_pool: std::collections::VecDeque, -} - -impl Connection -where - T: Request, -{ - pub fn new(transport: T) -> Self { - Self { - transport, - id_pool: (0..1000).collect(), - } - } - - pub fn call(&mut self, mut rpc: Rpc) -> Result - where - U: DeserializeOwned + std::fmt::Debug, - { - if let Some(id) = self.id_pool.pop_front() { - trace!("Using id {} for request", id); - rpc.id = id; - debug!("Calling rpc method: {:?}", &rpc); - self.id_pool.push_back(id); - let result_data = self.transport.request(serde_json::to_string(&rpc)?)?; - let result = serde_json::from_str::>(&result_data)?; - Ok(result.result) - } else { - Err(ConnectionError::NoTicketId) - } - } -} - -impl Connection -where - T: Request + Subscribe, -{ - /// Starts a new subscription. - /// Use one of these rpc generating [functions](crate::rpc::sub) to provide the subscription request. - /// Returns a [subscription](Subscription) which you can poll for new items. - pub fn subscribe( - &mut self, - sub_request: SubscriptionRequest, - ) -> Result, ConnectionError> { - info!("Starting a new subscription"); - let mut connection = Connection { - transport: self.transport.fork()?, - id_pool: self.id_pool.clone(), - }; - let subscription_id = connection.call(sub_request.rpc)?; - Ok(Subscription { - id: subscription_id, - connection, - result_type: std::marker::PhantomData, - }) - } -} - -/// Used to deserialize errors returned from the ethereum node. -#[derive(Debug, Error)] -#[error("{message}")] -pub struct JsonError { - code: i32, - message: String, -} - /// Wraps the different transport errors that may occur. #[allow(clippy::large_enum_variant)] -#[derive(Debug, Error)] +#[derive(Debug)] pub enum ConnectionError { - #[error("{0}")] - WebSocketError(#[from] WebSocketError), - #[error("{0}")] - HttpError(#[from] HttpError), - #[cfg(target_family = "unix")] - #[error("{0}")] - UdsError(#[from] UdsError), - #[error("Node Response Error: {0:?}")] - JsonRpc(#[from] JsonError), - #[error("Connector De-/Serialization Error: {0}")] - Serde(#[from] serde_json::Error), - #[error("Connector Error: Maximum number of connections reached")] + WebSocketError(String), + HttpError(String), + UdsError(String), + JsonRpc(String), + Serde(String), + SubscriptionError(String), NoTicketId, } diff --git a/ethane/src/connection/non_blocking.rs b/ethane/src/connection/non_blocking.rs new file mode 100644 index 0000000..f702590 --- /dev/null +++ b/ethane/src/connection/non_blocking.rs @@ -0,0 +1,41 @@ +use super::transport::http::AsyncHttp; +use super::ConnectionError; +use crate::rpc::{Rpc, RpcResponse}; + +use serde::de::DeserializeOwned; + +pub struct Connection { + transport: AsyncHttp, + id_pool: std::collections::VecDeque, +} + +impl Connection { + pub fn new(address: &str) -> Self { + Self { + transport: AsyncHttp::new(&address, None), + id_pool: (0..1000).collect(), + } + } + + pub async fn call(&mut self, mut rpc: Rpc) -> Result + where + U: DeserializeOwned + std::fmt::Debug, + { + if let Some(id) = self.id_pool.pop_front() { + rpc.id = id; + self.id_pool.push_back(id); + let result_data = self + .transport + .request( + serde_json::to_string(&rpc) + .map_err(|e| ConnectionError::Serde(e.to_string()))?, + ) + .await?; + let result = serde_json::from_str::>(&result_data) + .map_err(|e| ConnectionError::Serde(e.to_string()))?; + Ok(result.result) + } else { + Err(ConnectionError::NoTicketId) + } + } +} diff --git a/ethane/src/connection/subscription.rs b/ethane/src/connection/subscription.rs index e10e671..1d35961 100644 --- a/ethane/src/connection/subscription.rs +++ b/ethane/src/connection/subscription.rs @@ -3,11 +3,9 @@ use super::{Connection, ConnectionError, Request, Subscribe}; use crate::rpc::eth_unsubscribe; use crate::types::U128; -use log::{error, info, trace}; use serde::de::DeserializeOwned; use std::fmt::Debug; use std::marker::PhantomData; -use thiserror::Error; /// An active subscription /// @@ -23,15 +21,14 @@ pub struct Subscription { impl Subscription { /// Yields the next item of this subscription. - pub fn next_item(&mut self) -> Result { - trace!("Fetching next item from subscription"); + pub fn next_item(&mut self) -> Result { let response = self.connection.transport.read_next()?; deserialize_from_sub(&response) } /// Cancel the subscription. This will first unsubscribe and then close the underlying connection. pub fn close(self) { - info!("Closing subscription with id {}", self.id); + println!("Closing subscription with id {:?}", self.id); } } @@ -39,28 +36,15 @@ impl Drop for Subscription< fn drop(&mut self) { match self.connection.call(eth_unsubscribe(self.id)) { Ok(true) => (), - Ok(_) => error!("Unable to cancel subscription"), - Err(err) => error!("{}", err), + Ok(_) => println!("Unable to cancel subscription"), + Err(err) => println!("{:?}", err), } } } -fn deserialize_from_sub( - response: &str, -) -> Result { - trace!("Deserializing response {}", response); - let value: serde_json::Value = serde_json::from_str(response)?; - serde_json::from_value::(value["params"]["result"].clone()).map_err(SubscriptionError::from) -} - -/// An error type collecting what can go wrong during a subscription -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Error)] -pub enum SubscriptionError { - #[error("Subscription Transport Error {0}")] - Read(#[from] ConnectionError), - #[error("Subscription Error during canceling subscription")] - Cancel, - #[error("Subscription De-/Serialization Error: {0}")] - Serde(#[from] serde_json::Error), +fn deserialize_from_sub(response: &str) -> Result { + let value: serde_json::Value = + serde_json::from_str(response).map_err(|e| ConnectionError::Serde(e.to_string()))?; + serde_json::from_value::(value["params"]["result"].clone()) + .map_err(|e| ConnectionError::SubscriptionError(e.to_string())) } diff --git a/ethane/src/connection/transport/http.rs b/ethane/src/connection/transport/http.rs deleted file mode 100644 index 3033855..0000000 --- a/ethane/src/connection/transport/http.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Implementation of http transport - -use super::super::{ConnectionError, Credentials, Request}; - -use log::{debug, trace}; -use thiserror::Error; - -/// Wraps a http client -pub struct Http { - /// The domain where requests are sent - address: String, - credentials: Option, - agent: ureq::Agent, -} - -impl Http { - pub fn new(address: &str, credentials: Option) -> Self { - debug!("Binding http client to {}", address); - Self { - address: address.to_owned(), - credentials, - agent: ureq::Agent::new(), - } - } - - fn prepare_json_request(&self) -> ureq::Request { - let mut request = self.agent.request("POST", &self.address); - if let Some(credentials) = &self.credentials { - request = request.set("Authorization", &credentials.to_auth_string()); - } - request = request.set("Content-Type", "application/json"); - request = request.set("Accept", "application/json"); - request - } -} - -impl Request for Http { - fn request(&mut self, cmd: String) -> Result { - let request = self.prepare_json_request(); - trace!("Sending request {:?} with body {}", &request, &cmd); - let response = request.send_string(&cmd).map_err(HttpError::from)?; - response - .into_string() - .map(|resp| { - trace!("Received http response: {}", &resp); - resp - }) - .map_err(|err| HttpError::from(err).into()) - } -} - -/// An error type collecting what can go wrong with http requests -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Error)] -pub enum HttpError { - #[error("Http Address Error: {0}")] - Uri(#[from] http::uri::InvalidUri), - #[error("Http Response Parsing Error: {0}")] - Conversion(#[from] std::io::Error), - #[error("Http Send Request Error: {0}")] - UreqError(#[from] ureq::Error), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn prepare_request() { - let address = "http://127.0.0.1"; - let credentials = Credentials::Basic(String::from("check!")); - let client = Http::new(address, Some(credentials)); - let request = client.prepare_json_request(); - - assert_eq!(request.header("Authorization").unwrap(), "Basic check!"); - assert_eq!(request.header("Content-Type").unwrap(), "application/json"); - assert_eq!(request.header("Accept").unwrap(), "application/json"); - } -} diff --git a/ethane/src/connection/transport/http/blocking.rs b/ethane/src/connection/transport/http/blocking.rs new file mode 100644 index 0000000..d7ef303 --- /dev/null +++ b/ethane/src/connection/transport/http/blocking.rs @@ -0,0 +1,65 @@ +use crate::connection::{ConnectionError, Credentials, Request as EthaneRequest}; + +use reqwest::blocking::Client; +use reqwest::header::HeaderMap; + +/// Wraps a blocking http client +pub struct Http { + /// The domain where requests are sent + address: String, + credentials: Option, + client: Client, +} + +impl Http { + pub fn new(address: &str, credentials: Option) -> Self { + Self { + address: address.to_owned(), + credentials, + client: Client::new(), + } + } + + fn json_request_headers(&self) -> HeaderMap { + let mut headers = HeaderMap::new(); + if let Some(credentials) = &self.credentials { + headers.insert( + "Authorization", + credentials.to_auth_string().parse().unwrap(), + ); + } + headers.insert("Content-Type", "application/json".parse().unwrap()); + headers.insert("Accept", "application/json".parse().unwrap()); + headers + } +} + +impl EthaneRequest for Http { + fn request(&mut self, cmd: String) -> Result { + self.client + .post(&self.address) + .headers(self.json_request_headers()) + .body(cmd) + .send() + .map_err(|e| ConnectionError::HttpError(e.to_string()))? + .text() + .map_err(|e| ConnectionError::HttpError(e.to_string())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn prepare_request() { + let address = "http://127.0.0.1"; + let credentials = Credentials::Basic(String::from("check!")); + let client = Http::new(address, Some(credentials)); + let headers = client.json_request_headers(); + + assert_eq!(headers.get("Authorization").unwrap(), "Basic check!"); + assert_eq!(headers.get("Content-Type").unwrap(), "application/json"); + assert_eq!(headers.get("Accept").unwrap(), "application/json"); + } +} diff --git a/ethane/src/connection/transport/http/mod.rs b/ethane/src/connection/transport/http/mod.rs new file mode 100644 index 0000000..e02ffe6 --- /dev/null +++ b/ethane/src/connection/transport/http/mod.rs @@ -0,0 +1,11 @@ +//! Implementation of http transport + +#[cfg(feature = "blocking")] +mod blocking; +#[cfg(feature = "blocking")] +pub use blocking::Http; + +#[cfg(feature = "non-blocking")] +mod non_blocking; +#[cfg(feature = "non-blocking")] +pub use non_blocking::Http as AsyncHttp; diff --git a/ethane/src/connection/transport/http/non_blocking.rs b/ethane/src/connection/transport/http/non_blocking.rs new file mode 100644 index 0000000..ed1c475 --- /dev/null +++ b/ethane/src/connection/transport/http/non_blocking.rs @@ -0,0 +1,66 @@ +use crate::connection::{ConnectionError, Credentials}; + +use reqwest::header::HeaderMap; +use reqwest::Client; + +/// Wraps a blocking http client +#[derive(Clone)] +pub struct Http { + /// The domain where requests are sent + address: String, + credentials: Option, + client: Client, +} + +impl Http { + pub fn new(address: &str, credentials: Option) -> Self { + Self { + address: address.to_owned(), + credentials, + client: Client::new(), + } + } + + fn json_request_headers(&self) -> HeaderMap { + let mut headers = HeaderMap::new(); + if let Some(credentials) = &self.credentials { + headers.insert( + "Authorization", + credentials.to_auth_string().parse().unwrap(), + ); + } + headers.insert("Content-Type", "application/json".parse().unwrap()); + headers.insert("Accept", "application/json".parse().unwrap()); + headers + } + + pub async fn request(&self, cmd: String) -> Result { + self.client + .post(&self.address) + .headers(self.json_request_headers()) + .body(cmd) + .send() + .await + .map_err(|e| ConnectionError::HttpError(e.to_string()))? + .text() + .await + .map_err(|e| ConnectionError::HttpError(e.to_string())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn prepare_request() { + let address = "http://127.0.0.1"; + let credentials = Credentials::Basic(String::from("check!")); + let client = Http::new(address, Some(credentials)); + let headers = client.json_request_headers(); + + assert_eq!(headers.get("Authorization").unwrap(), "Basic check!"); + assert_eq!(headers.get("Content-Type").unwrap(), "application/json"); + assert_eq!(headers.get("Accept").unwrap(), "application/json"); + } +} diff --git a/ethane/src/connection/transport/uds.rs b/ethane/src/connection/transport/uds.rs index 6b99a01..6009ee0 100644 --- a/ethane/src/connection/transport/uds.rs +++ b/ethane/src/connection/transport/uds.rs @@ -1,12 +1,10 @@ //! Implementation of Unix domain socket transport (Unix only) use super::super::{ConnectionError, Request, Subscribe}; -use log::{debug, error, trace}; use std::io::{BufRead, BufReader, Write}; use std::net::Shutdown; use std::os::unix::net::UnixStream; use std::str; -use thiserror::Error; /// An interprocess connection using a unix domain socket (Unix only) pub struct Uds { @@ -16,10 +14,12 @@ pub struct Uds { } impl Uds { - pub fn new(path: &str) -> Result { - debug!("Opening connection to unix domain socket: {}", path); - let write_stream = UnixStream::connect(path).map_err(UdsError::UdsInit)?; - let read_stream = write_stream.try_clone().map_err(UdsError::UdsInit)?; + pub fn new(path: &str) -> Result { + let write_stream = + UnixStream::connect(path).map_err(|e| ConnectionError::UdsError(e.to_string()))?; + let read_stream = write_stream + .try_clone() + .map_err(|e| ConnectionError::UdsError(e.to_string()))?; Ok(Self { path: path.to_owned(), read_stream: BufReader::new(read_stream), @@ -27,28 +27,30 @@ impl Uds { }) } - fn read_json(&mut self) -> Result { + fn read_json(&mut self) -> Result { let mut buffer = Vec::::new(); loop { let _read_bytes = self .read_stream .read_until(b'}', &mut buffer) - .map_err(UdsError::Read)?; - let utf8_slice = str::from_utf8(&buffer).map_err(UdsError::Utf8)?; + .map_err(|e| ConnectionError::UdsError(e.to_string()))?; + let utf8_slice = + str::from_utf8(&buffer).map_err(|e| ConnectionError::UdsError(e.to_string()))?; if utf8_slice.matches('{').count() == utf8_slice.matches('}').count() { - trace!("Reading from Unix domain socket: {}", utf8_slice); break Ok(utf8_slice.to_string()); } } } - fn write(&mut self, message: String) -> Result<(), UdsError> { - trace!("Writing to Unix domain socket: {}", &message); + fn write(&mut self, message: String) -> Result<(), ConnectionError> { let _write = self .write_stream .write_all(message.as_bytes()) - .map_err(UdsError::Write)?; - let _flush = self.write_stream.flush().map_err(UdsError::Write)?; + .map_err(|e| ConnectionError::UdsError(e.to_string()))?; + let _flush = self + .write_stream + .flush() + .map_err(|e| ConnectionError::UdsError(e.to_string()))?; Ok(()) } } @@ -56,48 +58,31 @@ impl Uds { impl Request for Uds { fn request(&mut self, cmd: String) -> Result { let _write = self.write(cmd)?; - self.read_json().map_err(ConnectionError::UdsError) + self.read_json() } } impl Subscribe for Uds { fn read_next(&mut self) -> Result { - self.read_json().map_err(ConnectionError::UdsError) + self.read_json() } fn fork(&self) -> Result where Self: Sized, { - Self::new(&self.path).map_err(ConnectionError::from) + Self::new(&self.path) } } impl Drop for Uds { fn drop(&mut self) { - debug!("Closing unix domain socket connection"); - let close = self.write_stream.shutdown(Shutdown::Both); - if let Err(err) = close { - error!("{}", err); + if self.write_stream.shutdown(Shutdown::Both).is_err() { + println!("Error while closing UDS"); } } } -/// An error type collecting what can go wrong with unix domain sockets -#[derive(Debug, Error)] -pub enum UdsError { - #[error("Unix Domain Socket Init Error: {0}")] - UdsInit(std::io::Error), - #[error("Unix Domain Socket Close Error: {0}")] - UdsClose(std::io::Error), - #[error("Unix Domain Socket Read Error: {0}")] - Read(std::io::Error), - #[error("Unix Domain Socket Utf8 Error: {0}")] - Utf8(std::str::Utf8Error), - #[error("Unix Domain Socket Write Error: {0}")] - Write(std::io::Error), -} - #[cfg(test)] mod tests { use super::*; diff --git a/ethane/src/connection/transport/websocket.rs b/ethane/src/connection/transport/websocket.rs index 720802b..4bfdf10 100644 --- a/ethane/src/connection/transport/websocket.rs +++ b/ethane/src/connection/transport/websocket.rs @@ -1,10 +1,7 @@ //! Implementation of a websocket transport. use super::super::{ConnectionError, Credentials, Request, Subscribe}; - -use log::{debug, error, trace}; -use std::str::FromStr; -use thiserror::Error; +use tungstenite::handshake::client::Request as TungsteniteRequest; /// Wraps a websocket connection pub struct WebSocket { @@ -14,27 +11,10 @@ pub struct WebSocket { } impl WebSocket { - pub fn new(address: &str, credentials: Option) -> Result { - debug!("Initiating websocket connection to {}", address); - let uri = http::Uri::from_str(address)?; - - let mut request_builder = http::Request::get(&uri); - - if let Some(ref credentials) = credentials { - let headers = request_builder - .headers_mut() - .ok_or(WebSocketError::Handshake)?; - headers.insert("Authorization", credentials.to_auth_string().parse()?); - } - - let handshake_request = request_builder.body(())?; - trace!( - "Built websocket handshake request: {:?}", - &handshake_request - ); - - let ws = tungstenite::connect(handshake_request)?; - trace!("Handshake Response: {:?}", ws.1); + pub fn new(address: &str, credentials: Option) -> Result { + let request = create_handshake_request(address, &credentials).unwrap(); + let ws = tungstenite::connect(request) + .map_err(|e| ConnectionError::WebSocketError(e.to_string()))?; Ok(Self { address: address.to_owned(), credentials, @@ -42,7 +22,7 @@ impl WebSocket { }) } - fn read_message(&mut self) -> Result { + fn read_message(&mut self) -> Result { match self.read() { Ok(tungstenite::Message::Text(response)) => Ok(response), Ok(_) => self.read_message(), @@ -50,27 +30,33 @@ impl WebSocket { } } - fn read(&mut self) -> Result { - let message = self.websocket.read_message()?; - trace!("Reading from websocket: {}", &message); + fn read(&mut self) -> Result { + let message = self + .websocket + .read_message() + .map_err(|e| ConnectionError::WebSocketError(e.to_string()))?; Ok(message) } - fn write(&mut self, message: tungstenite::Message) -> Result<(), WebSocketError> { - trace!("Writing to websocket: {}", &message); - self.websocket.write_message(message)?; + fn write(&mut self, message: tungstenite::Message) -> Result<(), ConnectionError> { + self.websocket + .write_message(message) + .map_err(|e| ConnectionError::WebSocketError(e.to_string()))?; Ok(()) } - fn close(&mut self) -> Result<(), WebSocketError> { + fn close(&mut self) -> Result<(), ConnectionError> { use tungstenite::protocol::{frame::coding::CloseCode, CloseFrame}; - debug!("Closing websocket connection"); let close_frame = CloseFrame { code: CloseCode::Normal, reason: std::borrow::Cow::from("Finished"), }; - self.websocket.close(Some(close_frame))?; - self.websocket.write_pending().map_err(WebSocketError::from) + self.websocket + .close(Some(close_frame)) + .map_err(|e| ConnectionError::WebSocketError(e.to_string()))?; + self.websocket + .write_pending() + .map_err(|e| ConnectionError::WebSocketError(e.to_string())) } } @@ -78,7 +64,7 @@ impl Request for WebSocket { fn request(&mut self, cmd: String) -> Result { let write_msg = tungstenite::Message::Text(cmd); self.write(write_msg)?; - self.read_message().map_err(ConnectionError::from) + self.read_message() } } @@ -88,32 +74,35 @@ impl Subscribe for WebSocket { } fn fork(&self) -> Result { - Self::new(&self.address, self.credentials.clone()).map_err(ConnectionError::from) + Self::new(&self.address, self.credentials.clone()) } } impl Drop for WebSocket { fn drop(&mut self) { - let close = self.close(); - if let Err(err) = close { - error!("{}", err); + if self.close().is_err() { + println!("Error while closing websocket"); } } } -/// An error type collecting what can go wrong with a websocket -#[derive(Debug, Error)] -pub enum WebSocketError { - #[error("WebSocket Error: {0}")] - Tungstenite(#[from] tungstenite::Error), - #[error("WebSocket Invalid Handshake Request Error: {0}")] - Http(#[from] http::Error), - #[error("WebSocket Invalid Address Error: {0}")] - Url(#[from] http::uri::InvalidUri), - #[error("WebSocket Handshake Header Error")] - Handshake, - #[error("WebSocket Error. Unable to parse credentials {0}")] - InvalidHeader(#[from] http::header::InvalidHeaderValue), +fn create_handshake_request( + address: &str, + credentials: &Option, +) -> Result { + let mut request = TungsteniteRequest::get(address).body(()).map_err(|_| { + ConnectionError::WebSocketError(format!("Couldn't bind WS to address {}", address)) + })?; + if let Some(cred) = credentials { + request.headers_mut().insert( + "Authorization", + cred.to_auth_string().parse().map_err(|_| { + ConnectionError::WebSocketError("Couldn't parse auth string".to_string()) + })?, + ); + } + + Ok(request) } #[cfg(test)] @@ -122,21 +111,6 @@ mod tests { use std::net::{SocketAddr, TcpStream}; use tungstenite::{accept, Message}; - fn create_handshake_request( - uri: &http::Uri, - credentials: Option, - ) -> Result, WebSocketError> { - let mut req_builder = http::Request::get(uri); - if let Some(ref credentials) = credentials { - let headers = req_builder.headers_mut().ok_or(WebSocketError::Handshake)?; - headers.insert("Authorization", credentials.to_auth_string().parse()?); - } - - let request = req_builder.body(())?; - trace!("Built websocket handshake request: {:?}", &request); - Ok(request) - } - fn spawn_websocket_server(mut handle_ws_stream: F, port: u16) where F: FnMut(&mut tungstenite::WebSocket) + Send + 'static, @@ -172,9 +146,9 @@ mod tests { #[test] fn handshake_request_with_credentials() { - let uri = http::Uri::from_static("localhost"); let credentials = Credentials::Basic(String::from("YWJjOjEyMw==")); - let request = create_handshake_request(&uri, Some(credentials)).unwrap(); + let request = + create_handshake_request("http://localhost:8000", &Some(credentials)).unwrap(); assert_eq!( request.headers().get("Authorization").unwrap(), "Basic YWJjOjEyMw==" @@ -183,10 +157,8 @@ mod tests { #[test] fn handshake_request_without_credentials() { - let uri = http::Uri::from_static("localhost"); - let request = create_handshake_request(&uri, None).unwrap(); - assert_eq!(request.method(), http::method::Method::GET); - assert_eq!(request.uri(), &uri); + let request = create_handshake_request("ws://localhost:8000", &None).unwrap(); + assert_eq!(request.uri(), "ws://localhost:8000/"); } #[test] diff --git a/ethane/src/contract.rs b/ethane/src/contract/blocking.rs similarity index 91% rename from ethane/src/contract.rs rename to ethane/src/contract/blocking.rs index b8c02f1..bc41f47 100644 --- a/ethane/src/contract.rs +++ b/ethane/src/contract/blocking.rs @@ -1,29 +1,16 @@ -use crate::types::{Address, Bytes, Call, TransactionRequest, H256}; +use crate::types::{Address, Bytes, Call, TransactionRequest}; use crate::{rpc, Connection, Request}; use ethane_abi::{Abi, Parameter, StateMutability}; use std::path::Path; +use super::{CallOpts, CallResult, CallType}; + pub struct Caller { abi: Abi, contract_address: Address, connection: Connection, } -pub struct CallOpts { - pub force_call_type: Option, - pub from: Option
, -} - -pub enum CallType { - Transaction, - Call, -} - -pub enum CallResult { - Transaction(H256), - Call(Vec), -} - impl Caller where T: Request, diff --git a/ethane/src/contract/mod.rs b/ethane/src/contract/mod.rs new file mode 100644 index 0000000..6470f84 --- /dev/null +++ b/ethane/src/contract/mod.rs @@ -0,0 +1,26 @@ +#[cfg(feature = "blocking")] +mod blocking; +#[cfg(feature = "blocking")] +pub use blocking::Caller; +#[cfg(feature = "non-blocking")] +mod non_blocking; +#[cfg(feature = "non-blocking")] +pub use non_blocking::Caller as AsyncCaller; + +use crate::types::{Address, H256}; +use ethane_abi::Parameter; + +pub struct CallOpts { + pub force_call_type: Option, + pub from: Option
, +} + +pub enum CallType { + Transaction, + Call, +} + +pub enum CallResult { + Transaction(H256), + Call(Vec), +} diff --git a/ethane/src/contract/non_blocking.rs b/ethane/src/contract/non_blocking.rs new file mode 100644 index 0000000..b34293e --- /dev/null +++ b/ethane/src/contract/non_blocking.rs @@ -0,0 +1,111 @@ +use crate::types::{Address, Bytes, Call, TransactionRequest}; +use crate::{rpc, AsyncConnection}; +use ethane_abi::{Abi, Parameter, StateMutability}; +use std::path::Path; + +use super::{CallOpts, CallResult, CallType}; + +pub struct Caller { + abi: Abi, + contract_address: Address, + connection: AsyncConnection, +} + +impl Caller { + pub fn new( + connection: AsyncConnection, + abi_json: serde_json::Value, + contract_address: Address, + ) -> Caller { + let mut abi = Abi::new(); + abi.parse_json(abi_json).expect("unable to parse abi"); + Caller { + abi, + contract_address, + connection, + } + } + + pub fn new_from_path( + connection: AsyncConnection, + path: &str, + contract_address: Address, + ) -> Caller { + let mut abi = Abi::new(); + abi.parse_file(Path::new(path)) + .expect("unable to parse abi"); + Caller { + abi, + contract_address, + connection, + } + } + + pub async fn call( + &mut self, + function_name: &str, + params: Vec, + opts: Option, + ) -> CallResult { + let mut call_type = if let Some(m) = self.abi.get_state_mutability(function_name) { + match m { + StateMutability::Pure => CallType::Call, + StateMutability::View => CallType::Call, + StateMutability::NonPayable => CallType::Transaction, + StateMutability::Payable => CallType::Transaction, + } + } else { + CallType::Transaction + }; + + let mut from_address: Address = Default::default(); + if let Some(o) = opts { + from_address = o.from.unwrap(); + if let Some(ct) = o.force_call_type { + call_type = ct; + } + } + + let data = self.abi.encode(function_name, params).unwrap(); + + match call_type { + CallType::Transaction => self.eth_send_transaction(data, from_address).await, + CallType::Call => self.eth_call(function_name, data).await, + } + } + + async fn eth_call(&mut self, function_name: &str, data: Vec) -> CallResult { + let payload = Call { + to: self.contract_address, + data: Some(Bytes::from_slice(&data)), + ..Default::default() + }; + + let call_result = self + .connection + .call(rpc::eth_call(payload, None)) + .await + .unwrap(); + CallResult::Call( + self.abi + .decode(function_name, call_result.0.as_slice()) + .unwrap(), + ) + } + + async fn eth_send_transaction(&mut self, data: Vec, from_address: Address) -> CallResult { + let payload = TransactionRequest { + from: from_address, + to: Some(self.contract_address), + data: Some(Bytes::from_slice(&data)), + ..Default::default() + }; + + CallResult::Transaction( + self.connection + .call(rpc::eth_send_transaction(payload)) + .await + .unwrap(), + ) + } +} diff --git a/ethane/src/lib.rs b/ethane/src/lib.rs index 9b79be3..3074995 100644 --- a/ethane/src/lib.rs +++ b/ethane/src/lib.rs @@ -36,7 +36,7 @@ //! ## Starting a subscription over websocket //! ```no_run //! use ethane::{Connection, WebSocket}; -//! use ethane::rpc::sub::eth_subscribe_new_pending_transactions; +//! use ethane::rpc::eth_subscribe_new_pending_transactions; //! # use test_helper::NodeProcess; //! # use ethane::rpc::{eth_send_transaction, eth_coinbase}; //! # use ethane::types::{TransactionRequest, Address, U256}; diff --git a/ethane/src/rpc/mod.rs b/ethane/src/rpc/mod.rs index 76da3f8..667e342 100644 --- a/ethane/src/rpc/mod.rs +++ b/ethane/src/rpc/mod.rs @@ -13,7 +13,6 @@ //! Use these functions to generate [Rpc](Rpc) objects and pass them to the //! [call](crate::Connection::call) function of a [connection](crate::Connection). -use log::error; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -23,14 +22,15 @@ use std::marker::PhantomData; pub use eth::*; pub use net::*; pub use personal::*; -pub(crate) use sub::eth_unsubscribe; +pub use sub::*; +//pub(crate) use sub::eth_unsubscribe; pub use txpool::*; pub use web3::*; mod eth; mod net; mod personal; -pub mod sub; +mod sub; mod txpool; mod web3; @@ -38,6 +38,7 @@ mod web3; /// /// This is usually not directly needed and returned by the [functions](crate::rpc) which /// wrap the different namespaces. However, it is also possible to create custom Rpc structs. +#[repr(C)] #[derive(Serialize, Debug)] pub struct Rpc { #[serde(rename = "jsonrpc")] @@ -75,9 +76,8 @@ impl Rpc { } pub(crate) fn add_param(&mut self, parameter: U) { - match serde_json::to_value(¶meter) { - Ok(serialized_param) => self.params.push(serialized_param), - Err(err) => error!("Error during serialization: {}", err), + if let Ok(serialized_param) = serde_json::to_value(¶meter) { + self.params.push(serialized_param); } } } diff --git a/ethane/src/rpc/net.rs b/ethane/src/rpc/net.rs index 82edf86..47eefb6 100644 --- a/ethane/src/rpc/net.rs +++ b/ethane/src/rpc/net.rs @@ -1,5 +1,5 @@ use super::Rpc; -use ethereum_types::U64; +use crate::types::U64; pub fn net_version() -> Rpc { Rpc::new("net_version") diff --git a/ethane/src/rpc/sub.rs b/ethane/src/rpc/sub.rs index acbdad2..55e6e6a 100644 --- a/ethane/src/rpc/sub.rs +++ b/ethane/src/rpc/sub.rs @@ -47,7 +47,7 @@ pub fn eth_subscribe_logs(filter: FilterSubscription) -> SubscriptionRequest Rpc { +pub fn eth_unsubscribe(sub_id: U128) -> Rpc { let mut rpc = Rpc::new("eth_unsubscribe"); rpc.add_param(sub_id); rpc diff --git a/ethane/src/types.rs b/ethane/src/types.rs index 5e354f7..3562c98 100644 --- a/ethane/src/types.rs +++ b/ethane/src/types.rs @@ -2,12 +2,9 @@ //! //! This module provides custom types, but also re-exports some types from [ethereum_types]. -pub use ethereum_types::{Address, Bloom, H256, H64, U128, U256, U64}; -use serde::de::Visitor; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +pub use ethane_types::*; +use serde::{Deserialize, Serialize, Serializer}; use std::collections::HashMap; -use std::fmt::Debug; -use std::str::FromStr; /// Information about block number, defaults to `BlockParameter::Latest` #[derive(Copy, Clone, Debug, PartialEq, Deserialize)] @@ -24,7 +21,7 @@ impl Serialize for BlockParameter { BlockParameter::Latest => serializer.serialize_str("latest"), BlockParameter::Earliest => serializer.serialize_str("earliest"), BlockParameter::Pending => serializer.serialize_str("pending"), - BlockParameter::Custom(num) => serializer.serialize_str(&format!("{:#x}", num)), + BlockParameter::Custom(num) => serializer.serialize_str(&num.to_string()), // TODO non-prefixed string? } } } @@ -123,65 +120,6 @@ pub struct Log { pub removed: bool, } -/// A type for hex values of arbitrary length -#[derive(Clone, Debug, PartialEq, Default)] -pub struct Bytes(pub Vec); - -impl Bytes { - pub fn from_slice(slice: &[u8]) -> Self { - Bytes(slice.to_vec()) - } -} - -impl Serialize for Bytes { - fn serialize(&self, serializer: T) -> Result { - serializer.serialize_str(&(String::from("0x") + &hex::encode(&self.0))) - } -} - -impl FromStr for Bytes { - type Err = hex::FromHexError; - - fn from_str(value: &str) -> Result { - let inner = if value.len() >= 2 && &value[0..2] == "0x" { - hex::decode(&value[2..]) - } else { - hex::decode(value) - }?; - - Ok(Bytes(inner)) - } -} - -impl<'de> Deserialize<'de> for Bytes { - fn deserialize(deserializer: T) -> Result - where - T: Deserializer<'de>, - { - deserializer.deserialize_identifier(BytesVisitor) - } -} - -struct BytesVisitor; - -impl<'de> Visitor<'de> for BytesVisitor { - type Value = Bytes; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "a hex string") - } - - fn visit_str(self, value: &str) -> Result { - let result = Self::Value::from_str(value) - .map_err(|err| serde::de::Error::custom(format!("Invalid hex string: {}", err)))?; - Ok(result) - } - - fn visit_string(self, value: String) -> Result { - self.visit_str(&value) - } -} - /// Wrapper for private keys to allow for 0x-prefixed and plain serialization #[derive(Clone, Debug, PartialEq)] pub enum PrivateKey { @@ -193,7 +131,9 @@ impl Serialize for PrivateKey { fn serialize(&self, serializer: T) -> Result { match *self { PrivateKey::ZeroXPrefixed(pk) => pk.serialize(serializer), - PrivateKey::NonPrefixed(pk) => serializer.serialize_str(&hex::encode(pk.0)), + PrivateKey::NonPrefixed(pk) => { + serializer.serialize_str(&pk.to_string().trim_start_matches("0x")) + } } } } @@ -407,35 +347,12 @@ pub struct SignedTransaction { #[cfg(test)] mod tests { use super::*; - - #[test] - fn test_types_bytes() { - let bytes_0 = Bytes::from_slice(&[]); - let bytes_1 = Bytes::from_slice(&[0, 0]); - let bytes_2 = Bytes::from_slice(&[17, 234]); - let bytes_3 = Bytes::from_str("0x").unwrap(); - let bytes_4 = Bytes::from_str("0x00").unwrap(); - let bytes_5 = Bytes::from_str("00421100").unwrap(); - - let expected_0 = "\"0x\""; - let expected_1 = "\"0x0000\""; - let expected_2 = "\"0x11ea\""; - let expected_3 = Bytes(vec![]); - let expected_4 = Bytes(vec![0]); - let expected_5 = Bytes(vec![0, 66, 17, 0]); - - assert_eq!(serde_json::to_string(&bytes_0).unwrap(), expected_0); - assert_eq!(serde_json::to_string(&bytes_1).unwrap(), expected_1); - assert_eq!(serde_json::to_string(&bytes_2).unwrap(), expected_2); - assert_eq!(bytes_3, expected_3); - assert_eq!(bytes_4, expected_4); - assert_eq!(bytes_5, expected_5); - } + use std::convert::TryFrom; #[test] fn test_types_block_parameter() { let block_param_default = BlockParameter::default(); - let block_param_custom = BlockParameter::Custom(U64::from(11827902)); + let block_param_custom = BlockParameter::Custom(U64::from_int_unchecked(11827902_u64)); assert_eq!( serde_json::to_string(&block_param_default).unwrap(), @@ -450,9 +367,9 @@ mod tests { #[test] fn test_types_private_key() { let raw_hex_key = "0xe4745d1287b67412ce806746e83d49efe5cec53f5a27aa666fb9e8092a8dbd43"; - let private_key_prefixed = PrivateKey::ZeroXPrefixed(H256::from_str(raw_hex_key).unwrap()); + let private_key_prefixed = PrivateKey::ZeroXPrefixed(H256::try_from(raw_hex_key).unwrap()); let private_key_non_prefixed = - PrivateKey::NonPrefixed(H256::from_str(raw_hex_key).unwrap()); + PrivateKey::NonPrefixed(H256::try_from(raw_hex_key).unwrap()); assert_eq!( serde_json::to_string(&private_key_prefixed).unwrap(), diff --git a/ethane/test-helper/Cargo.toml b/ethane/test-helper/Cargo.toml index 24e1a8d..a51f37c 100644 --- a/ethane/test-helper/Cargo.toml +++ b/ethane/test-helper/Cargo.toml @@ -5,12 +5,10 @@ authors = ["thojest "] edition = "2018" publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -ethane = { path = "../" } +ethane = { path = "../", features = ["blocking"] } serde = "1" serde_json = "1" +tiny-keccak = { version = "2.0", features = ["keccak"] } rand = "0.8" -tiny-keccak = "2" regex = "1" diff --git a/ethane/test-helper/src/lib.rs b/ethane/test-helper/src/lib.rs index c7b820c..bb8b906 100644 --- a/ethane/test-helper/src/lib.rs +++ b/ethane/test-helper/src/lib.rs @@ -4,10 +4,10 @@ use ethane::types::{Address, Bytes, PrivateKey, TransactionRequest, H256, U256}; use rand::Rng; use serde::de::DeserializeOwned; use serde_json::Value; +use std::convert::TryFrom; use std::fmt::Debug; use std::path::Path; use std::process::Command; -use std::str::FromStr; use tiny_keccak::{Hasher, Keccak}; mod spin_up; @@ -39,7 +39,7 @@ pub fn create_secret() -> H256 { HEX_CHARSET[idx] as char }) .collect(); - H256::from_str(&secret).unwrap() + H256::try_from(secret.as_str()).unwrap() } pub fn import_account( @@ -67,7 +67,7 @@ pub fn prefund_account(client: &mut ConnectionWrapper, address: Address) -> H256 let tx = TransactionRequest { from: coinbase, to: Some(address), - value: Some(U256::exp10(20)), + value: Some(U256::from_int_unchecked(10_u128.pow(20))), ..Default::default() }; let tx_hash = client.call(rpc::eth_send_transaction(tx)).unwrap(); @@ -104,11 +104,11 @@ pub fn deploy_contract( let raw_contract = compile_contract(path, contract_name); let bin = bin(raw_contract.clone()); let abi = abi(raw_contract); - let contract_bytes = Bytes::from_str(&bin).unwrap(); + let contract_bytes = Bytes::try_from(bin.as_str()).unwrap(); let transaction = TransactionRequest { from: address, data: Some(contract_bytes), - gas: Some(U256::from(1000000 as u64)), + gas: Some(U256::from_int_unchecked(1000000_u64)), ..Default::default() }; let transaction_hash = client.call(rpc::eth_send_transaction(transaction)).unwrap(); @@ -130,7 +130,7 @@ pub fn simulate_transaction( ) -> H256 { let transaction = TransactionRequest { from, - to: Some(to.parse().unwrap()), + to: Some(Address::try_from(to).unwrap()), value: Some(value), ..Default::default() }; @@ -150,7 +150,7 @@ pub fn abi(contract_input: Value) -> Value { pub fn keccak(input: &[u8]) -> [u8; 32] { let mut hasher = Keccak::v256(); hasher.update(input); - let mut out = [0u8; 32]; + let mut out = [0_u8; 32]; hasher.finalize(&mut out); out } diff --git a/ethane/test-helper/src/spin_up.rs b/ethane/test-helper/src/spin_up.rs index a6a76c6..7f48e57 100644 --- a/ethane/test-helper/src/spin_up.rs +++ b/ethane/test-helper/src/spin_up.rs @@ -1,8 +1,5 @@ -use ethane::rpc::{sub::SubscriptionRequest, Rpc}; -use ethane::{ - Connection, ConnectionError, Http, Request, Subscribe, Subscription, SubscriptionError, - WebSocket, -}; +use ethane::rpc::{Rpc, SubscriptionRequest}; +use ethane::{Connection, ConnectionError, Http, Request, Subscribe, Subscription, WebSocket}; use regex::{Regex, RegexBuilder}; use serde::de::DeserializeOwned; use std::fmt::Debug; @@ -66,25 +63,16 @@ impl ConnectionWrapper { _ => panic!("Subscription not supported for this transport"), } } - - //pub fn get(self) -> Connection { - // match self { - // Self::Websocket(connection) => connection.connection, - // Self::Http(connection) => connection.connection, - // #[cfg(target_family = "unix")] - // Self::Uds(connection) => connection.connection, - // } - //} } pub trait DynSubscription { - fn next_item(&mut self) -> Result; + fn next_item(&mut self) -> Result; } impl DynSubscription for Subscription { - fn next_item(&mut self) -> Result { + fn next_item(&mut self) -> Result { self.next_item() } } diff --git a/ethane/tests/contract.rs b/ethane/tests/contract.rs index 88089e0..f958bc3 100644 --- a/ethane/tests/contract.rs +++ b/ethane/tests/contract.rs @@ -2,8 +2,8 @@ use ethane::contract::{CallOpts, CallResult, Caller}; use ethane::types::{Address, H256, U256}; use ethane::{Connection, Http}; use ethane_abi::*; +use std::convert::TryFrom; use std::path::Path; -use std::str::FromStr; use test_helper::{ deploy_contract, wait_for_transaction, ConnectionWrapper, TEST_ERC20_NAME, TEST_ERC20_PATH, }; @@ -15,7 +15,7 @@ const ADDRESS2: &str = "0x99429f64cf4d5837620dcc293c1a537d58729b68"; fn test_eth_call_contract() { // deploy contract let mut client = ConnectionWrapper::new_from_env(None); - let address = Address::from_str(ADDRESS1).unwrap(); + let address = Address::try_from(ADDRESS1).unwrap(); let (contract_address, _) = deploy_contract( &mut client, address, @@ -31,15 +31,13 @@ fn test_eth_call_contract() { contract_address, ); - let result = caller.call( - "balanceOf", - vec![Parameter::from(Address::from(address))], - None, - ); + let result = caller.call("balanceOf", vec![Parameter::from(address)], None); match result { CallResult::Transaction(_) => panic!("Should be eth_call"), CallResult::Call(r) => match r[0] { - Parameter::Uint(data, 256) => assert_eq!(data, H256::from_low_u64_be(1000000000_u64)), + Parameter::Uint(data, 256) => { + assert_eq!(data, H256::from_int_unchecked(1000000000_u64)) + } _ => panic!("Invalid data received!"), }, } @@ -49,8 +47,8 @@ fn test_eth_call_contract() { fn test_eth_call_contract_transfer() { // deploy contract let mut client = ConnectionWrapper::new_from_env(None); - let address = Address::from_str(ADDRESS1).unwrap(); - let to_address = Address::from_str(ADDRESS2).unwrap(); + let address = Address::try_from(ADDRESS1).unwrap(); + let to_address = Address::try_from(ADDRESS2).unwrap(); let (contract_address, _) = deploy_contract( &mut client, address, @@ -69,8 +67,8 @@ fn test_eth_call_contract_transfer() { let result = caller.call( "transfer", vec![ - Parameter::from(Address::from(to_address)), - Parameter::from(U256::from(1000)), + Parameter::from(to_address), + Parameter::from(U256::from_int_unchecked(1000_u16)), ], Some(CallOpts { force_call_type: None, @@ -81,15 +79,13 @@ fn test_eth_call_contract_transfer() { CallResult::Call(_) => panic!("Should be a transaction"), CallResult::Transaction(tx_hash) => { wait_for_transaction(&mut client, tx_hash); - let result = caller.call( - "balanceOf", - vec![Parameter::from(Address::from(to_address))], - None, - ); + let result = caller.call("balanceOf", vec![Parameter::from(to_address)], None); match result { CallResult::Transaction(_) => panic!("Should be eth_call"), CallResult::Call(r) => match r[0] { - Parameter::Uint(data, 256) => assert_eq!(data, H256::from_low_u64_be(1000_u64)), + Parameter::Uint(data, 256) => { + assert_eq!(data, H256::from_int_unchecked(1000_u64)) + } _ => panic!("Invalid data received!"), }, } diff --git a/ethane/tests/eth.rs b/ethane/tests/eth.rs index 1902874..d65f20f 100644 --- a/ethane/tests/eth.rs +++ b/ethane/tests/eth.rs @@ -1,12 +1,8 @@ use ethane::rpc; -use ethane::types::{ - BlockParameter, Bytes, Call, Filter, GasCall, SyncInfo, TransactionRequest, ValueOrVec, H256, - U256, U64, -}; +use ethane::types::*; +use std::convert::TryFrom; use std::path::Path; -use std::str::FromStr; -use ethereum_types::Address; use test_helper::*; const ADDRESS1: &str = "0x007ccffb7916f37f7aeef05e8096ecfbe55afc2f"; @@ -35,7 +31,7 @@ fn test_eth_coinbase() { rpc_call_test_expected( &mut client, rpc::eth_coinbase(), - Address::from_str(ADDRESS1).unwrap(), + Address::try_from(ADDRESS1).unwrap(), ); } @@ -48,22 +44,26 @@ fn test_eth_mining() { #[test] fn test_eth_hashrate() { let mut client = ConnectionWrapper::new_from_env(None); - rpc_call_test_expected(&mut client, rpc::eth_hashrate(), U256::from(0)); + rpc_call_test_expected(&mut client, rpc::eth_hashrate(), U256::zero()); } #[test] fn test_eth_gas_price() { let mut client = ConnectionWrapper::new_from_env(None); - rpc_call_test_expected(&mut client, rpc::eth_gas_price(), U256::from(1 as u64)); + rpc_call_test_expected( + &mut client, + rpc::eth_gas_price(), + U256::from_int_unchecked(1_u8), + ); } #[test] fn test_eth_accounts() { let mut client = ConnectionWrapper::new_from_env(None); let accounts = rpc_call_with_return(&mut client, rpc::eth_accounts()); - assert_eq!(accounts[0], Address::from_str(ADDRESS1).unwrap()); - assert_eq!(accounts[1], Address::from_str(ADDRESS2).unwrap()); - assert_eq!(accounts[2], Address::from_str(ADDRESS3).unwrap()); + assert_eq!(accounts[0], Address::try_from(ADDRESS1).unwrap()); + assert_eq!(accounts[1], Address::try_from(ADDRESS2).unwrap()); + assert_eq!(accounts[2], Address::try_from(ADDRESS3).unwrap()); } #[test] @@ -71,8 +71,8 @@ fn test_eth_accounts() { fn test_eth_block_number() { let mut client = ConnectionWrapper::new_from_env(None); let block_number = rpc_call_with_return(&mut client, rpc::eth_block_number()); - if !block_number.ge(&U64::from(0)) { - panic!("Invalid block number, it's {}", block_number); + if !block_number.as_bytes().ge(U64::zero().as_bytes()) { + panic!("Invalid block number, it's {:?}", block_number); } } @@ -81,9 +81,12 @@ fn test_eth_get_balance() { let mut client = ConnectionWrapper::new_from_env(None); let balance = rpc_call_with_return( &mut client, - rpc::eth_get_balance(Address::from_str(ADDRESS1).unwrap(), None), + rpc::eth_get_balance(Address::try_from(ADDRESS1).unwrap(), None), ); - if !balance.gt(&U256::from(900000000000000000 as u64)) { + if !balance + .as_bytes() + .gt(U256::from_int_unchecked(900000000000000000_u64).as_bytes()) + { panic!("Invalid balance should be bigger than 900 ETH"); } } @@ -91,11 +94,11 @@ fn test_eth_get_balance() { #[test] fn test_eth_send_transaction_to_address() { let mut client = ConnectionWrapper::new_from_env(None); - let value = 1000000000000000000 as u64; + let value = 1000000000000000000_u64; let transaction = TransactionRequest { - from: Address::from_str(ADDRESS1).unwrap(), - to: Some(Address::from_str(ADDRESS2).unwrap()), - value: Some(U256::from(value)), + from: Address::try_from(ADDRESS1).unwrap(), + to: Some(Address::try_from(ADDRESS2).unwrap()), + value: Some(U256::from_int_unchecked(value)), ..Default::default() }; let tx_hash = rpc_call_with_return(&mut client, rpc::eth_send_transaction(transaction)); @@ -104,25 +107,28 @@ fn test_eth_send_transaction_to_address() { let tx_receipt = rpc_call_with_return(&mut client, rpc::eth_get_transaction_receipt(tx_hash)).unwrap(); let tx = rpc_call_with_return(&mut client, rpc::eth_get_transaction_by_hash(tx_hash)); - assert_eq!(tx.value, U256::from(value)); - assert_eq!(tx_receipt.status, U64::from(1 as i64)); - assert_eq!(tx_receipt.cumulative_gas_used, U256::from(21000 as i32)); - assert_eq!(tx_receipt.gas_used, U256::from(21000 as i32)); + assert_eq!(tx.value, U256::from_int_unchecked(value)); + assert_eq!(tx_receipt.status, U64::from_int_unchecked(1_i64)); + assert_eq!( + tx_receipt.cumulative_gas_used, + U256::from_int_unchecked(21000_i32) + ); + assert_eq!(tx_receipt.gas_used, U256::from_int_unchecked(21000_i32)); } #[test] fn test_eth_send_transaction_contract_creation() { let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS1.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS1).unwrap()); let bin = bin(compile_contract( &Path::new(TEST_CONTRACT_PATH), TEST_CONTRACT_NAME, )); - let contract_bytes = Bytes::from_str(&bin).unwrap(); + let contract_bytes = Bytes::try_from(bin.as_str()).unwrap(); let transaction = TransactionRequest { - from: ADDRESS1.parse().unwrap(), + from: Address::try_from(ADDRESS1).unwrap(), data: Some(contract_bytes), - gas: Some(U256::from(1000000 as u64)), + gas: Some(U256::from_int_unchecked(1000000_u64)), ..Default::default() }; let tx_hash = rpc_call_with_return(&mut client, rpc::eth_send_transaction(transaction)); @@ -131,9 +137,9 @@ fn test_eth_send_transaction_contract_creation() { let tx_receipt = rpc_call_with_return(&mut client, rpc::eth_get_transaction_receipt(tx_hash)).unwrap(); let tx = rpc_call_with_return(&mut client, rpc::eth_get_transaction_by_hash(tx_hash)); - assert_eq!(tx_receipt.status, U64::from(1 as i64)); - // assert_eq!(tx_receipt.cumulative_gas_used, U256::from(117799 as i32)); - // assert_eq!(tx_receipt.gas_used, U256::from(117799 as i32)); + assert_eq!(tx_receipt.status, U64::from_int_unchecked(1_i64)); + // assert_eq!(tx_receipt.cumulative_gas_used, U256::from_int_unchecked(117799_i32)); + // assert_eq!(tx_receipt.gas_used, U256::from_int_unchecked(117799_i32)); assert_ne!(tx_receipt.contract_address, None); if tx.input.0.len() < 200 { panic!("Invalid input length: {}", tx.input.0.len()); @@ -142,12 +148,12 @@ fn test_eth_send_transaction_contract_creation() { #[test] fn test_eth_get_transaction_by_hash() { - let value = 2000000000000000000 as u64; + let value = 2000000000000000000_u64; let mut client = ConnectionWrapper::new_from_env(None); let transaction = TransactionRequest { - from: ADDRESS1.parse().unwrap(), - to: Some(ADDRESS2.parse().unwrap()), - value: Some(U256::from(value)), + from: Address::try_from(ADDRESS1).unwrap(), + to: Some(Address::try_from(ADDRESS2).unwrap()), + value: Some(U256::from_int_unchecked(value)), ..Default::default() }; let transaction_hash = client.call(rpc::eth_send_transaction(transaction)).unwrap(); @@ -155,33 +161,36 @@ fn test_eth_get_transaction_by_hash() { &mut client, rpc::eth_get_transaction_by_hash(transaction_hash), ); - assert_eq!(tx.value, U256::from(value)); - assert_eq!(tx.from.unwrap(), Address::from_str(ADDRESS1).unwrap()); - assert_eq!(tx.to.unwrap(), Address::from_str(ADDRESS2).unwrap()); + assert_eq!(tx.value, U256::from_int_unchecked(value)); + assert_eq!(tx.from.unwrap(), Address::try_from(ADDRESS1).unwrap()); + assert_eq!(tx.to.unwrap(), Address::try_from(ADDRESS2).unwrap()); } #[test] fn test_eth_get_transaction_receipt() { - let value = 1000000000000000000 as u64; + let value = 1000000000000000000_u64; let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS2.parse().unwrap()); - prefund_account(&mut client, ADDRESS2.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); let transaction = TransactionRequest { - from: ADDRESS2.parse().unwrap(), - to: Some(ADDRESS3.parse().unwrap()), - value: Some(U256::from(value)), + from: Address::try_from(ADDRESS2).unwrap(), + to: Some(Address::try_from(ADDRESS3).unwrap()), + value: Some(U256::from_int_unchecked(value)), ..Default::default() }; let tx_hash = client.call(rpc::eth_send_transaction(transaction)).unwrap(); wait_for_transaction(&mut client, tx_hash); let tx_receipt = rpc_call_with_return(&mut client, rpc::eth_get_transaction_receipt(tx_hash)).unwrap(); - assert_eq!(tx_receipt.status, U64::from(1 as i64)); - assert_eq!(tx_receipt.cumulative_gas_used, U256::from(21000 as i32)); - assert_eq!(tx_receipt.gas_used, U256::from(21000 as i32)); + assert_eq!(tx_receipt.status, U64::from_int_unchecked(1_i64)); + assert_eq!( + tx_receipt.cumulative_gas_used, + U256::from_int_unchecked(21000_i32) + ); + assert_eq!(tx_receipt.gas_used, U256::from_int_unchecked(21000_i32)); assert_eq!(tx_receipt.contract_address, None); - assert_eq!(tx_receipt.from, Address::from_str(ADDRESS2).unwrap()); - assert_eq!(tx_receipt.to.unwrap(), Address::from_str(ADDRESS3).unwrap()); + assert_eq!(tx_receipt.from, Address::try_from(ADDRESS2).unwrap()); + assert_eq!(tx_receipt.to.unwrap(), Address::try_from(ADDRESS3).unwrap()); } #[test] @@ -189,9 +198,9 @@ fn test_eth_get_transaction_receipt() { fn test_eth_get_storage_at() { // @TODO rewrite with ERC-20 and put data into the storage let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS2.parse().unwrap()); - prefund_account(&mut client, ADDRESS2.parse().unwrap()); - let address = Address::from_str(ADDRESS2).unwrap(); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + let address = Address::try_from(ADDRESS2).unwrap(); let (contract_address, _) = deploy_contract( &mut client, address, @@ -221,7 +230,7 @@ fn test_eth_get_transaction_count() { rpc_call_test_expected( &mut client, rpc::eth_get_transaction_count(sender, None), - U256::from(3), + U256::from_int_unchecked(3_u8), ); } @@ -229,11 +238,11 @@ fn test_eth_get_transaction_count() { #[ignore] fn test_eth_get_block_by_number_full_tx() { let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS2.parse().unwrap()); - prefund_account(&mut client, ADDRESS2.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); simulate_transaction( &mut client, - ADDRESS2.parse().unwrap(), + Address::try_from(ADDRESS2).unwrap(), ADDRESS1, U256::zero(), ); @@ -247,11 +256,11 @@ fn test_eth_get_block_by_number_full_tx() { #[ignore] fn test_eth_get_block_by_number_only_hashes() { let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS2.parse().unwrap()); - prefund_account(&mut client, ADDRESS2.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); simulate_transaction( &mut client, - ADDRESS2.parse().unwrap(), + Address::try_from(ADDRESS2).unwrap(), ADDRESS3, U256::zero(), ); @@ -264,18 +273,20 @@ fn test_eth_get_block_by_number_only_hashes() { #[test] fn test_eth_get_block_by_number_no_block() { let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS2.parse().unwrap()); - prefund_account(&mut client, ADDRESS2.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); simulate_transaction( &mut client, - ADDRESS2.parse().unwrap(), + Address::try_from(ADDRESS2).unwrap(), ADDRESS3, U256::zero(), ); rpc_call_test_expected( &mut client, rpc::eth_get_block_by_number( - Some(BlockParameter::Custom(U64::from(12000000000000 as u64))), + Some(BlockParameter::Custom(U64::from_int_unchecked( + 12000000000000_u64, + ))), false, ), None, @@ -285,11 +296,11 @@ fn test_eth_get_block_by_number_no_block() { #[test] fn test_eth_get_block_transaction_count_by_hash() { let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS2.parse().unwrap()); - prefund_account(&mut client, ADDRESS2.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); simulate_transaction( &mut client, - ADDRESS2.parse().unwrap(), + Address::try_from(ADDRESS2).unwrap(), ADDRESS1, U256::zero(), ); @@ -299,18 +310,18 @@ fn test_eth_get_block_transaction_count_by_hash() { rpc_call_test_expected( &mut client, rpc::eth_get_block_transaction_count_by_hash(block.unwrap().hash.unwrap()), - U64::from(1), + U64::from_int_unchecked(1u8), ); } #[test] fn test_eth_get_block_transaction_count_by_number() { let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS2.parse().unwrap()); - prefund_account(&mut client, ADDRESS2.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); simulate_transaction( &mut client, - ADDRESS2.parse().unwrap(), + Address::try_from(ADDRESS2).unwrap(), ADDRESS1, U256::zero(), ); @@ -324,11 +335,11 @@ fn test_eth_get_block_transaction_count_by_number() { fn test_eth_get_uncle_count_by_block_hash() { // @TODO it's really hard to replicate let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS2.parse().unwrap()); - prefund_account(&mut client, ADDRESS2.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); simulate_transaction( &mut client, - ADDRESS2.parse().unwrap(), + Address::try_from(ADDRESS2).unwrap(), ADDRESS1, U256::zero(), ); @@ -338,25 +349,25 @@ fn test_eth_get_uncle_count_by_block_hash() { rpc_call_test_expected( &mut client, rpc::eth_get_uncle_count_by_block_hash(block.unwrap().hash.unwrap()), - U64::from(0), + U64::zero(), ) } #[test] fn test_eth_get_uncle_count_by_block_number() { let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS2.parse().unwrap()); - prefund_account(&mut client, ADDRESS2.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); simulate_transaction( &mut client, - ADDRESS2.parse().unwrap(), + Address::try_from(ADDRESS2).unwrap(), ADDRESS1, U256::zero(), ); rpc_call_test_expected( &mut client, rpc::eth_get_uncle_count_by_block_number(None), - U64::from(0), + U64::zero(), ); } @@ -367,14 +378,14 @@ fn test_eth_get_code_missing() { rpc_call_test_expected( &mut client, rpc::eth_get_code(address, None), - Bytes::from_str("0x").unwrap(), + Bytes::try_from("0x").unwrap(), ) } #[test] fn test_eth_get_code_contract() { let mut client = ConnectionWrapper::new_from_env(None); - let address = ADDRESS1.parse().unwrap(); + let address = Address::try_from(ADDRESS1).unwrap(); let (contract_address, _) = deploy_contract( &mut client, address, @@ -391,13 +402,13 @@ fn test_eth_get_code_contract() { #[test] fn test_eth_sign() { let mut client = ConnectionWrapper::new_from_env(None); - let address = match import_account(&mut client, H256::from_str(FIX_SECRET).unwrap()) { + let address = match import_account(&mut client, H256::try_from(FIX_SECRET).unwrap()) { Ok(a) => a, - Err(_) => Address::from_str("0xdc677f7c5060b0b441d30f361d0c8529ac04e099").unwrap(), + Err(_) => Address::try_from("0xdc677f7c5060b0b441d30f361d0c8529ac04e099").unwrap(), }; println!("{:?}", address); let message = Bytes::from_slice("checkmate".as_bytes()); - let expected_signature = Bytes::from_str( + let expected_signature = Bytes::try_from( "67e4a4cf3b8cfb7d9a568482e9b6deb6350bc7701ae0448b92752b463e7dc97\ c09c424607fbcf1cb4f6ec1c6a6c60a3527dcfe11412a3bff26218ca9f0bdef9d1b", ) @@ -429,8 +440,8 @@ fn test_eth_sign_transaction() { let transaction = TransactionRequest { from: create_account(&mut client).1, to: Some(create_account(&mut client).1), - gas: Some(U256::exp10(5)), - gas_price: Some(U256::exp10(9)), + gas: Some(U256::from_int_unchecked(10_u64.pow(5))), + gas_price: Some(U256::from_int_unchecked(10_u64.pow(9))), value: Some(U256::zero()), nonce: Some(U256::zero()), ..Default::default() @@ -444,8 +455,8 @@ fn test_eth_send_raw_transaction() { let transaction = TransactionRequest { from: create_account(&mut client).1, to: Some(create_account(&mut client).1), - gas: Some(U256::exp10(5)), - gas_price: Some(U256::exp10(9)), + gas: Some(U256::from_int_unchecked(10_u64.pow(5))), + gas_price: Some(U256::from_int_unchecked(10_u64.pow(9))), value: Some(U256::zero()), nonce: Some(U256::zero()), ..Default::default() @@ -492,7 +503,7 @@ fn test_eth_estimate_gas() { rpc_call_test_expected( &mut client, rpc::eth_estimate_gas(gas_call, None), - U256::from(21000), + U256::from_int_unchecked(21000_u32), ); } @@ -501,7 +512,7 @@ fn test_eth_get_block_by_hash() { let mut client = ConnectionWrapper::new_from_env(None); simulate_transaction( &mut client, - ADDRESS1.parse().unwrap(), + Address::try_from(ADDRESS1).unwrap(), ADDRESS3, U256::zero(), ); @@ -513,19 +524,31 @@ fn test_eth_get_block_by_hash() { rpc::eth_get_block_by_hash(block.unwrap().hash.unwrap(), true), ) .unwrap(); - assert_eq!(block.gas_limit.gt(&U256::from(10000)), true); - // assert_eq!(block.gas_used, U256::from(21000)); - assert_eq!(block.size.gt(&U256::from(400)), true); + assert_eq!( + block + .gas_limit + .as_bytes() + .gt(U256::from_int_unchecked(10000_u16).as_bytes()), + true + ); + // assert_eq!(block.gas_used, U256::from_int_unchecked(21000_u16)); + assert_eq!( + block + .size + .as_bytes() + .gt(U256::from_int_unchecked(400_u16).as_bytes()), + true + ); } #[test] fn test_eth_get_transaction_by_block_hash_and_index() { let mut client = ConnectionWrapper::new_from_env(None); - unlock_account(&mut client, ADDRESS3.parse().unwrap()); - prefund_account(&mut client, ADDRESS3.parse().unwrap()); + unlock_account(&mut client, Address::try_from(ADDRESS2).unwrap()); + prefund_account(&mut client, Address::try_from(ADDRESS2).unwrap()); simulate_transaction( &mut client, - ADDRESS3.parse().unwrap(), + Address::try_from(ADDRESS2).unwrap(), ADDRESS1, U256::zero(), ); @@ -537,8 +560,8 @@ fn test_eth_get_transaction_by_block_hash_and_index() { &mut client, rpc::eth_get_transaction_by_block_hash_and_index(block.hash.unwrap(), U64::zero()), ); - assert_eq!(tx.gas, U256::from(21000)); - assert_eq!(tx.gas_price, U256::from(1 as u64)); + assert_eq!(tx.gas, U256::from_int_unchecked(21000_u32)); + assert_eq!(tx.gas_price, U256::from_int_unchecked(1_u8)); assert_eq!(tx.block_hash.unwrap(), block.hash.unwrap()); assert_eq!(tx.block_number.unwrap(), block.number.unwrap()); } @@ -547,32 +570,32 @@ fn test_eth_get_transaction_by_block_hash_and_index() { #[ignore] fn test_eth_get_transaction_by_block_number_and_index() { let mut client = ConnectionWrapper::new_from_env(None); - let value = 100000000 as u64; + let value = 100000000_u64; simulate_transaction( &mut client, - ADDRESS1.parse().unwrap(), + Address::try_from(ADDRESS1).unwrap(), ADDRESS2, - U256::from(value), + U256::from_int_unchecked(value), ); let tx = rpc_call_with_return( &mut client, rpc::eth_get_transaction_by_block_number_and_index(None, U64::zero()), ); - assert_eq!(tx.gas, U256::from(21000)); - assert_eq!(tx.gas_price, U256::from(1 as u64)); - assert_eq!(tx.value, U256::from(value)); + assert_eq!(tx.gas, U256::from_int_unchecked(21000_u32)); + assert_eq!(tx.gas_price, U256::from_int_unchecked(1_u8)); + assert_eq!(tx.value, U256::from_int_unchecked(value)); } #[test] fn test_eth_get_uncle_by_block_hash_and_index() { let mut client = ConnectionWrapper::new_from_env(None); - let value = 100000000 as u64; + let value = 100000000_u64; simulate_transaction( &mut client, - ADDRESS1.parse().unwrap(), + Address::try_from(ADDRESS1).unwrap(), ADDRESS2, - U256::from(value), + U256::from_int_unchecked(value), ); let block = client @@ -651,7 +674,9 @@ fn test_eth_new_filter() { from_block: Some(BlockParameter::Earliest), to_block: Some(BlockParameter::Latest), address: Some(ValueOrVec::Value(contract_address)), - topics: Some(vec![Some(ValueOrVec::Value(H256::from_slice(&topic)))]), + topics: Some(vec![Some(ValueOrVec::Value( + H256::try_from(&topic).unwrap(), + ))]), }; rpc_call_test_some(&mut client, rpc::eth_new_filter(filter)); } @@ -690,7 +715,9 @@ fn test_eth_get_filter_changes_new_filter() { from_block: Some(BlockParameter::Earliest), to_block: Some(BlockParameter::Latest), address: Some(ValueOrVec::Value(contract_address)), - topics: Some(vec![Some(ValueOrVec::Value(H256::from_slice(&topic)))]), + topics: Some(vec![Some(ValueOrVec::Value( + H256::try_from(&topic).unwrap(), + ))]), }; let filter_id = client.call(rpc::eth_new_filter(filter)).unwrap(); let out = keccak(b"set_pos0()"); @@ -735,7 +762,9 @@ fn test_eth_get_filter_logs_new_filter() { from_block: Some(BlockParameter::Earliest), to_block: Some(BlockParameter::Latest), address: Some(ValueOrVec::Value(contract_address)), - topics: Some(vec![Some(ValueOrVec::Value(H256::from_slice(&topic)))]), + topics: Some(vec![Some(ValueOrVec::Value( + H256::try_from(&topic).unwrap(), + ))]), }; let filter_id = client.call(rpc::eth_new_filter(filter)).unwrap(); let out = keccak(b"set_pos0()"); @@ -784,7 +813,9 @@ fn test_eth_get_logs() { from_block: Some(BlockParameter::Earliest), to_block: Some(BlockParameter::Latest), address: Some(ValueOrVec::Value(contract_address)), - topics: Some(vec![Some(ValueOrVec::Value(H256::from_slice(&topic)))]), + topics: Some(vec![Some(ValueOrVec::Value( + H256::try_from(&topic).unwrap(), + ))]), }; let out = keccak(b"set_pos0()"); let tx = TransactionRequest { diff --git a/ethane/tests/net.rs b/ethane/tests/net.rs index 1df369a..e87ed64 100644 --- a/ethane/tests/net.rs +++ b/ethane/tests/net.rs @@ -13,7 +13,7 @@ fn test_net_version() { #[ignore] fn test_net_peer_count() { let mut client = ConnectionWrapper::new_from_env(None); - rpc_call_test_expected(&mut client, rpc::net_peer_count(), U64::from(0)); + rpc_call_test_expected(&mut client, rpc::net_peer_count(), U64::zero()); } #[test] diff --git a/ethane/tests/personal.rs b/ethane/tests/personal.rs index 012dc6c..86b00ab 100644 --- a/ethane/tests/personal.rs +++ b/ethane/tests/personal.rs @@ -1,6 +1,6 @@ use ethane::rpc; use ethane::types::{Address, Bytes, PrivateKey, TransactionRequest, H256}; -use std::str::FromStr; +use std::convert::TryFrom; use test_helper::*; @@ -70,12 +70,12 @@ fn test_personal_send_transaction() { #[test] fn test_personal_sign() { let mut client = ConnectionWrapper::new_from_env(None); - let address = match import_account(&mut client, H256::from_str(FIX_SECRET).unwrap()) { + let address = match import_account(&mut client, H256::try_from(FIX_SECRET).unwrap()) { Ok(a) => a, - Err(_) => Address::from_str("0xdc677f7c5060b0b441d30f361d0c8529ac04e099").unwrap(), + Err(_) => Address::try_from("0xdc677f7c5060b0b441d30f361d0c8529ac04e099").unwrap(), }; let message = Bytes::from_slice("checkmate".as_bytes()); - let expected_signature = Bytes::from_str( + let expected_signature = Bytes::try_from( "67e4a4cf3b8cfb7d9a568482e9b6deb6350bc7701ae0448b92752b463e7dc97\ c09c424607fbcf1cb4f6ec1c6a6c60a3527dcfe11412a3bff26218ca9f0bdef9d1b", ) @@ -92,7 +92,7 @@ fn test_personal_sign() { fn test_personal_ec_recover() { let mut client = ConnectionWrapper::new_from_env(None); let message = Bytes::from_slice("checkmate".as_bytes()); - let signature = Bytes::from_str( + let signature = Bytes::try_from( "67e4a4cf3b8cfb7d9a568482e9b6deb6350bc7701ae0448b92752b463e7dc97\ c09c424607fbcf1cb4f6ec1c6a6c60a3527dcfe11412a3bff26218ca9f0bdef9d1b", ) @@ -100,6 +100,6 @@ fn test_personal_ec_recover() { rpc_call_test_expected( &mut client, rpc::personal_ec_recover(message, signature), - Address::from_str(FIX_ADDRESS).unwrap(), + Address::try_from(FIX_ADDRESS).unwrap(), ) } diff --git a/ethane/tests/sub.rs b/ethane/tests/sub.rs index 0c2040c..443d068 100644 --- a/ethane/tests/sub.rs +++ b/ethane/tests/sub.rs @@ -1,11 +1,12 @@ use ethane::rpc::eth_send_transaction; -use ethane::rpc::sub::{ +use ethane::rpc::{ eth_subscribe_logs, eth_subscribe_new_heads, eth_subscribe_new_pending_transactions, eth_subscribe_syncing, }; use ethane::types::{ BlockHeader, Bytes, FilterSubscription, Log, TransactionRequest, ValueOrVec, H256, U256, }; +use std::convert::TryFrom; use std::path::Path; use test_helper::*; @@ -71,7 +72,9 @@ fn test_eth_subscribe_logs() { let topic = keccak(b"Solution(uint256)"); let filter = FilterSubscription { address: Some(ValueOrVec::Value(contract_address)), - topics: Some(vec![Some(ValueOrVec::Value(H256::from_slice(&topic)))]), + topics: Some(vec![Some(ValueOrVec::Value( + H256::try_from(&topic).unwrap(), + ))]), }; let mut logs = Vec::::new(); let mut subscription = client.subscribe(eth_subscribe_logs(filter)).unwrap(); diff --git a/ethane/tests/web3.rs b/ethane/tests/web3.rs index a3c4934..3ab3f89 100644 --- a/ethane/tests/web3.rs +++ b/ethane/tests/web3.rs @@ -1,6 +1,6 @@ use ethane::rpc; use ethane::types::{Bytes, H256}; -use std::str::FromStr; +use std::convert::TryFrom; use test_helper::*; @@ -18,6 +18,6 @@ fn test_web3_client_version() { fn test_web3_sha3() { let mut client = ConnectionWrapper::new_from_env(None); let empty = Bytes::from_slice("".as_bytes()); - let expected = H256::from_str(KECCAK_HASH_OF_EMPTY_STRING).unwrap(); + let expected = H256::try_from(KECCAK_HASH_OF_EMPTY_STRING).unwrap(); rpc_call_test_expected(&mut client, rpc::web3_sha3(empty), expected); }