diff --git a/Cargo.lock b/Cargo.lock index 1499da4f..781e7480 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,6 +169,67 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloy-primitives" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ca2c09d5911548a5cb620382ea0e1af99d3c898ce0efecbbd274a4676cf53e" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "proptest", + "rand 0.8.5", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0fac0fc16baf1f63f78b47c3d24718f3619b0714076f6a02957d808d52cbef" +dependencies = [ + "arrayvec 0.7.4", + "bytes", + "smol_str", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40cea54ac58080a1b88ea6556866eac1902b321186c77d53ad2b5ebbbf0e038" +dependencies = [ + "const-hex", + "dunce", + "heck", + "indexmap 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.38", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81aa34725607be118c395d62c1d8d97c8a343dd1ada5370ed508ed5232eab6a" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -199,6 +260,130 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.0", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "array-bytes" version = "4.2.0" @@ -2205,6 +2390,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec 0.7.4", + "auto_impl", + "bytes", +] + [[package]] name = "femme" version = "2.2.1" @@ -2703,6 +2899,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hkdf" version = "0.10.0" @@ -2946,6 +3148,17 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-num-traits" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint", +] + [[package]] name = "impl-rlp" version = "0.3.0" @@ -4041,6 +4254,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + [[package]] name = "petgraph" version = "0.6.4" @@ -4286,6 +4510,7 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", + "impl-num-traits", "impl-rlp", "impl-serde", "scale-info", @@ -4356,6 +4581,8 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" dependencies = [ + "bit-set", + "bit-vec", "bitflags 2.4.1", "lazy_static", "num-traits", @@ -4363,6 +4590,8 @@ dependencies = [ "rand_chacha 0.3.1", "rand_xorshift", "regex-syntax 0.7.5", + "rusty-fork", + "tempfile", "unarray", ] @@ -4375,6 +4604,12 @@ dependencies = [ "cc", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.33" @@ -4732,6 +4967,7 @@ dependencies = [ "rosetta-tx-polkadot", "serde", "serde_json", + "void", "wasm-bindgen", "web-sys", ] @@ -4758,6 +4994,7 @@ name = "rosetta-config-ethereum" version = "0.5.0" dependencies = [ "anyhow", + "ethereum-types", "rosetta-config-astar", "rosetta-core", "serde", @@ -4882,6 +5119,8 @@ dependencies = [ name = "rosetta-server-astar" version = "0.5.0" dependencies = [ + "alloy-primitives", + "alloy-sol-types", "anyhow", "async-trait", "ethers", @@ -4919,12 +5158,15 @@ dependencies = [ "rosetta-docker", "serde_json", "tokio", + "void", ] [[package]] name = "rosetta-server-ethereum" version = "0.5.0" dependencies = [ + "alloy-primitives", + "alloy-sol-types", "anyhow", "async-trait", "ethabi", @@ -5002,6 +5244,36 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ruint" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608a5726529f2f0ef81b8fde9873c4bb829d6b5b5ca6be4d97345ddf0749c825" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + [[package]] name = "rust-bip39" version = "1.0.0" @@ -5041,6 +5313,15 @@ dependencies = [ "semver 0.9.0", ] +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -5140,6 +5421,18 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ruzstd" version = "0.4.0" @@ -5472,7 +5765,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", ] [[package]] @@ -5490,6 +5792,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "send_wrapper" version = "0.4.0" @@ -5789,6 +6100,15 @@ dependencies = [ "futures-lite", ] +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", +] + [[package]] name = "smoldot" version = "0.8.0" @@ -6697,6 +7017,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c7ad08db24862d5b787a94714ff6b047935c3e3f60af944ac969404debd7ff" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -7256,6 +7588,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uint" version = "0.9.5" @@ -7425,6 +7763,21 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "waker-fn" version = "1.1.1" diff --git a/chains/astar/server/Cargo.toml b/chains/astar/server/Cargo.toml index b44c7a81..7cb81334 100644 --- a/chains/astar/server/Cargo.toml +++ b/chains/astar/server/Cargo.toml @@ -27,6 +27,8 @@ subxt = { workspace = true, features = ["substrate-compat"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } [dev-dependencies] +alloy-primitives = { version = "0.5" } +alloy-sol-types = { version = "0.5" } ethers-solc = "2.0" rosetta-client.workspace = true rosetta-docker = { workspace = true, features = ["tests"] } diff --git a/chains/astar/server/src/lib.rs b/chains/astar/server/src/lib.rs index 29b645eb..dd0694a0 100644 --- a/chains/astar/server/src/lib.rs +++ b/chains/astar/server/src/lib.rs @@ -5,22 +5,22 @@ use rosetta_config_astar::metadata::{ dev as astar_metadata, dev::runtime_types::{frame_system::AccountInfo, pallet_balances::types::AccountData}, }; -use rosetta_config_ethereum::{EthereumMetadata, EthereumMetadataParams}; +use rosetta_config_ethereum::{ + EthereumMetadata, EthereumMetadataParams, Query as EthQuery, QueryResult as EthQueryResult, +}; use rosetta_core::{ crypto::{ address::{Address, AddressFormat}, PublicKey, }, types::{ - Block, BlockIdentifier, CallRequest, Coin, PartialBlockIdentifier, Transaction, - TransactionIdentifier, + Block, BlockIdentifier, Coin, PartialBlockIdentifier, Transaction, TransactionIdentifier, }, BlockchainClient, BlockchainConfig, }; use rosetta_server::ws::default_client; use rosetta_server_ethereum::MaybeWsEthereumClient; use serde::{Deserialize, Serialize}; -use serde_json::Value; use sp_core::crypto::Ss58AddressFormat; use std::sync::Arc; use subxt::{ @@ -124,6 +124,8 @@ impl BlockchainClient for AstarClient { type MetadataParams = AstarMetadataParams; type Metadata = AstarMetadata; type EventStream<'a> = ::EventStream<'a>; + type Call = EthQuery; + type CallResult = EthQueryResult; fn config(&self) -> &BlockchainConfig { self.client.config() @@ -221,7 +223,7 @@ impl BlockchainClient for AstarClient { self.client.block_transaction(block_identifier, tx).await } - async fn call(&self, req: &CallRequest) -> Result { + async fn call(&self, req: &EthQuery) -> Result { self.client.call(req).await } @@ -230,14 +232,26 @@ impl BlockchainClient for AstarClient { } } +#[allow(clippy::ignored_unit_patterns)] #[cfg(test)] mod tests { use super::*; + use alloy_sol_types::{sol, SolCall}; use ethers_solc::{artifacts::Source, CompilerInput, EvmVersion, Solc}; + use rosetta_config_ethereum::{AtBlock, CallResult}; use rosetta_docker::Env; use sha3::Digest; use std::{collections::BTreeMap, path::Path}; + sol! { + interface TestContract { + event AnEvent(); + function emitEvent() external; + + function identity(bool a) external view returns (bool); + } + } + pub async fn client_from_config(config: BlockchainConfig) -> Result { let url = config.node_uri.to_string(); AstarClient::from_config(config, url.as_str()).await @@ -297,24 +311,25 @@ mod tests { wallet.faucet(faucet).await?; let bytes = compile_snippet( - r#" + r" event AnEvent(); function emitEvent() public { emit AnEvent(); } - "#, + ", )?; let tx_hash = wallet.eth_deploy_contract(bytes).await?; - - let receipt = wallet.eth_transaction_receipt(&tx_hash).await?; - let contract_address = receipt.get("contractAddress").and_then(Value::as_str).unwrap(); - let tx_hash = - wallet.eth_send_call(contract_address, "function emitEvent()", &[], 0).await?; - let receipt = wallet.eth_transaction_receipt(&tx_hash).await?; - let logs = receipt.get("logs").and_then(Value::as_array).unwrap(); + let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); + let contract_address = receipt.contract_address.unwrap(); + let tx_hash = { + let data = TestContract::emitEventCall::SELECTOR.to_vec(); + wallet.eth_send_call(contract_address.0, data, 0).await? + }; + let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); + let logs = receipt.logs; assert_eq!(logs.len(), 1); - let topic = logs[0]["topics"][0].as_str().unwrap(); - let expected = format!("0x{}", hex::encode(sha3::Keccak256::digest("AnEvent()"))); + let topic = logs[0].topics[0]; + let expected = H256::from_slice(sha3::Keccak256::digest("AnEvent()").as_ref()); assert_eq!(topic, expected); env.shutdown().await?; Ok(()) @@ -331,26 +346,32 @@ mod tests { wallet.faucet(faucet).await?; let bytes = compile_snippet( - r#" + r" function identity(bool a) public view returns (bool) { return a; } - "#, + ", )?; let tx_hash = wallet.eth_deploy_contract(bytes).await?; - let receipt = wallet.eth_transaction_receipt(&tx_hash).await?; - let contract_address = receipt["contractAddress"].as_str().unwrap(); - - let response = wallet - .eth_view_call( - contract_address, - "function identity(bool a) returns (bool)", - &["true".into()], - None, + let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); + let contract_address = receipt.contract_address.unwrap(); + + let response = { + let call = TestContract::identityCall { a: true }; + wallet + .eth_view_call(contract_address.0, call.abi_encode(), AtBlock::Latest) + .await? + }; + assert_eq!( + response, + CallResult::Success( + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1 + ] + .to_vec() ) - .await?; - let result: Vec = serde_json::from_value(response)?; - assert_eq!(result[0], "true"); + ); env.shutdown().await?; Ok(()) } diff --git a/chains/bitcoin/server/Cargo.toml b/chains/bitcoin/server/Cargo.toml index 8361cfdc..ec1ae79d 100644 --- a/chains/bitcoin/server/Cargo.toml +++ b/chains/bitcoin/server/Cargo.toml @@ -15,6 +15,7 @@ rosetta-config-bitcoin.workspace = true rosetta-core.workspace = true serde_json = "1.0" tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +void = "1.0" [dev-dependencies] rosetta-docker = { workspace = true, features = ["tests"] } diff --git a/chains/bitcoin/server/src/lib.rs b/chains/bitcoin/server/src/lib.rs index 59d73564..72634ed9 100644 --- a/chains/bitcoin/server/src/lib.rs +++ b/chains/bitcoin/server/src/lib.rs @@ -3,13 +3,12 @@ use bitcoincore_rpc_async::{bitcoin::BlockHash, Auth, Client, RpcApi}; use rosetta_core::{ crypto::{address::Address, PublicKey}, types::{ - Block, BlockIdentifier, CallRequest, Coin, PartialBlockIdentifier, Transaction, - TransactionIdentifier, + Block, BlockIdentifier, Coin, PartialBlockIdentifier, Transaction, TransactionIdentifier, }, BlockchainClient, BlockchainConfig, }; -use serde_json::Value; use std::str::FromStr; +use void::{unreachable, Void}; pub type BitcoinMetadataParams = (); pub type BitcoinMetadata = (); @@ -57,6 +56,8 @@ impl BlockchainClient for BitcoinClient { type MetadataParams = BitcoinMetadataParams; type Metadata = BitcoinMetadata; type EventStream<'a> = rosetta_core::EmptyEventStream; + type Call = Void; + type CallResult = (); fn config(&self) -> &BlockchainConfig { &self.config @@ -170,8 +171,8 @@ impl BlockchainClient for BitcoinClient { anyhow::bail!("not implemented") } - async fn call(&self, _req: &CallRequest) -> Result { - anyhow::bail!("not implemented") + async fn call(&self, req: &Void) -> Result<()> { + unreachable(*req) } } diff --git a/chains/ethereum/config/Cargo.toml b/chains/ethereum/config/Cargo.toml index 06254933..6085a4f4 100644 --- a/chains/ethereum/config/Cargo.toml +++ b/chains/ethereum/config/Cargo.toml @@ -8,6 +8,11 @@ description = "Ethereum configuration." [dependencies] anyhow = "1.0" +ethereum-types = { version = "0.14", default-features = false, features = ["rlp", "ethbloom", "codec", "num-traits"] } rosetta-config-astar = { workspace = true } rosetta-core.workspace = true serde.workspace = true + +[features] +default = ["std"] +std = ["ethereum-types/std", "ethereum-types/serialize"] diff --git a/chains/ethereum/config/src/lib.rs b/chains/ethereum/config/src/lib.rs index 368342d9..622e55cb 100644 --- a/chains/ethereum/config/src/lib.rs +++ b/chains/ethereum/config/src/lib.rs @@ -1,11 +1,15 @@ +mod types; + use anyhow::Result; +pub use ethereum_types; + use rosetta_config_astar::config as astar_config; use rosetta_core::{ crypto::{address::AddressFormat, Algorithm}, BlockchainConfig, NodeUri, }; -use serde::{Deserialize, Serialize}; use std::sync::Arc; +pub use types::*; /// Retrieve the [`BlockchainConfig`] from the provided polygon `network` /// @@ -116,19 +120,3 @@ fn evm_config( testnet: is_dev, } } - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct EthereumMetadataParams { - pub destination: Vec, - pub amount: [u64; 4], - pub data: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct EthereumMetadata { - pub chain_id: u64, - pub nonce: u64, - pub max_priority_fee_per_gas: [u64; 4], - pub max_fee_per_gas: [u64; 4], - pub gas_limit: [u64; 4], -} diff --git a/chains/ethereum/config/src/types.rs b/chains/ethereum/config/src/types.rs new file mode 100644 index 00000000..956ceadf --- /dev/null +++ b/chains/ethereum/config/src/types.rs @@ -0,0 +1,236 @@ +use ethereum_types::{Address, Bloom, H256, U256}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EthereumMetadataParams { + pub destination: Vec, + pub amount: [u64; 4], + pub data: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct EthereumMetadata { + pub chain_id: u64, + pub nonce: u64, + pub max_priority_fee_per_gas: [u64; 4], + pub max_fee_per_gas: [u64; 4], + pub gas_limit: [u64; 4], +} + +#[derive(Default, Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum AtBlock { + #[default] + Latest, + Hash(H256), + Number(u64), +} + +///·Returns·the·balance·of·the·account·of·given·address. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct GetBalance { + /// Account address + pub address: Address, + /// Balance at the block + pub block: AtBlock, +} + +/// Executes a new message call immediately without creating a transaction on the blockchain. +#[derive(Clone, Default, PartialEq, Eq, Debug, Hash)] +pub struct CallContract { + /// The address the transaction is sent from. + pub from: Option
, + /// The address the transaction is directed to. + pub to: Address, + /// Integer of the value sent with this transaction. + pub value: U256, + /// Hash of the method signature and encoded parameters. + pub data: Vec, + /// Call at block + pub block: AtBlock, +} + +/// Returns the account and storage values of the specified account including the Merkle-proof. +/// This call can be used to verify that the data you are pulling from is not tampered with. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct GetTransactionReceipt { + pub tx_hash: H256, +} + +/// Returns the value from a storage position at a given address. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct GetStorageAt { + /// Account address + pub address: Address, + /// integer of the position in the storage. + pub at: H256, + /// Storage at the block + pub block: AtBlock, +} + +/// Returns the account and storage values, including the Merkle proof, of the specified +/// account. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct GetProof { + pub account: Address, + pub storage_keys: Vec, + pub block: AtBlock, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Query { + /// Returns the balance of the account of given address. + GetBalance(GetBalance), + /// Returns the value from a storage position at a given address. + GetStorageAt(GetStorageAt), + /// Returns the receipt of a transaction by transaction hash. + GetTransactionReceipt(GetTransactionReceipt), + /// Executes a new message call immediately without creating a transaction on the block + /// chain. + CallContract(CallContract), + /// Returns the account and storage values of the specified account including the + /// Merkle-proof. This call can be used to verify that the data you are pulling + /// from is not tampered with. + GetProof(GetProof), +} + +/// The result of contract call execution +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CallResult { + /// Call executed succesfully + Success(Vec), + /// Call reverted with message + Revert(Vec), + /// normal EVM error. + Error, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum QueryResult { + /// Returns the balance of the account of given address. + GetBalance(U256), + /// Returns the value from a storage position at a given address. + GetStorageAt(H256), + /// Returns the receipt of a transaction by transaction hash. + GetTransactionReceipt(Option), + /// Executes a new message call immediately without creating a transaction on the block + /// chain. + CallContract(CallResult), + /// Returns the account and storage values of the specified account including the + /// Merkle-proof. This call can be used to verify that the data you are pulling + /// from is not tampered with. + GetProof(EIP1186ProofResponse), +} + +/// A log produced by a transaction. +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] +pub struct Log { + /// H160. the contract that emitted the log + pub address: Address, + + /// topics: Array of 0 to 4 32 Bytes of indexed log arguments. + /// (In solidity: The first topic is the hash of the signature of the event + /// (e.g. `Deposit(address,bytes32,uint256)`), except you declared the event + /// with the anonymous specifier.) + pub topics: Vec, + + /// Data + pub data: Vec, + + /// Block Hash + pub block_hash: Option, + + /// Block Number + pub block_number: Option, + + /// Transaction Hash + pub transaction_hash: Option, + + /// Transaction Index + pub transaction_index: Option, + + /// Integer of the log index position in the block. None if it's a pending log. + pub log_index: Option, + + /// Integer of the transactions index position log was created from. + /// None when it's a pending log. + pub transaction_log_index: Option, + + /// Log Type + pub log_type: Option, + + /// True when the log was removed, due to a chain reorganization. + /// false if it's a valid log. + pub removed: Option, +} + +/// "Receipt" of an executed transaction: details of its execution. +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +pub struct TransactionReceipt { + /// Transaction hash. + pub transaction_hash: H256, + + /// Index within the block. + pub transaction_index: u64, + + /// Hash of the block this transaction was included within. + pub block_hash: Option, + + /// Number of the block this transaction was included within. + pub block_number: Option, + + /// address of the sender. + pub from: Address, + + // address of the receiver. null when its a contract creation transaction. + pub to: Option
, + + /// Cumulative gas used within the block after this was executed. + pub cumulative_gas_used: U256, + + /// Gas used by this transaction alone. + /// + /// Gas used is `None` if the the client is running in light client mode. + pub gas_used: Option, + + /// Contract address created, or `None` if not a deployment. + pub contract_address: Option
, + + /// Logs generated within this transaction. + pub logs: Vec, + + /// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) + pub status_code: Option, + + /// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) + pub state_root: Option, + + /// Logs bloom + pub logs_bloom: Bloom, + + /// The price paid post-execution by the transaction (i.e. base fee + priority fee). + /// Both fields in 1559-style transactions are *maximums* (max fee + max priority fee), the + /// amount that's actually paid by users can only be determined post-execution + pub effective_gas_price: Option, + + /// EIP-2718 transaction type + pub transaction_type: Option, +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +pub struct StorageProof { + pub key: H256, + pub proof: Vec>, + pub value: U256, +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +pub struct EIP1186ProofResponse { + pub address: Address, + pub balance: U256, + pub code_hash: H256, + pub nonce: u64, + pub storage_hash: H256, + pub account_proof: Vec>, + pub storage_proof: Vec, +} diff --git a/chains/ethereum/server/Cargo.toml b/chains/ethereum/server/Cargo.toml index fed5b16d..75c929c5 100644 --- a/chains/ethereum/server/Cargo.toml +++ b/chains/ethereum/server/Cargo.toml @@ -25,6 +25,8 @@ tracing = "0.1" url = "2.4" [dev-dependencies] +alloy-primitives = { version = "0.5" } +alloy-sol-types = { version = "0.5" } ethers-solc = "2.0" rosetta-client.workspace = true rosetta-docker = { workspace = true, features = ["tests"] } diff --git a/chains/ethereum/server/src/client.rs b/chains/ethereum/server/src/client.rs index b5a9f012..bdb62b35 100644 --- a/chains/ethereum/server/src/client.rs +++ b/chains/ethereum/server/src/client.rs @@ -3,36 +3,27 @@ use crate::{ proof::verify_proof, utils::{get_non_pending_block, NonPendingBlock}, }; -use anyhow::{bail, Context, Result}; -use ethabi::token::{LenientTokenizer, Tokenizer}; +use anyhow::{Context, Result}; use ethers::{ - abi::{Detokenize, HumanReadableParser, InvalidOutputType, Token}, prelude::*, providers::{JsonRpcClient, Middleware, Provider}, + types::Bytes, utils::{keccak256, rlp::Encodable}, }; -use rosetta_config_ethereum::{EthereumMetadata, EthereumMetadataParams}; +use rosetta_config_ethereum::{ + AtBlock, CallContract, CallResult, EIP1186ProofResponse, EthereumMetadata, + EthereumMetadataParams, GetBalance, GetProof, GetStorageAt, GetTransactionReceipt, Log, + Query as EthQuery, QueryResult as EthQueryResult, StorageProof, TransactionReceipt, +}; use rosetta_core::{ crypto::{address::Address, PublicKey}, types::{ - Block, BlockIdentifier, CallRequest, Coin, PartialBlockIdentifier, Transaction, - TransactionIdentifier, + Block, BlockIdentifier, Coin, PartialBlockIdentifier, Transaction, TransactionIdentifier, }, BlockchainConfig, }; -use serde_json::{json, Value}; use std::{str::FromStr, sync::Arc}; -struct Detokenizer { - tokens: Vec, -} - -impl Detokenize for Detokenizer { - fn from_tokens(tokens: Vec) -> Result { - Ok(Self { tokens }) - } -} - /// Strategy used to determine the finalized block #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum BlockFinalityStrategy { @@ -286,88 +277,89 @@ where } #[allow(clippy::too_many_lines)] - pub async fn call(&self, req: &CallRequest) -> Result { - let call_details = req.method.split('-').collect::>(); - if call_details.len() != 3 { - anyhow::bail!("Invalid length of call request params"); - } - let contract_address = call_details[0]; - let method_or_position = call_details[1]; - let call_type = call_details[2]; - - let block_id = req - .block_identifier - .as_ref() - .map(|block_identifier| -> Result { - if let Some(block_hash) = block_identifier.hash.as_ref() { - return BlockId::from_str(block_hash).map_err(|e| anyhow::anyhow!("{e}")); - } else if let Some(block_number) = block_identifier.index { - return Ok(BlockId::Number(BlockNumber::Number(U64::from(block_number)))); + pub async fn call(&self, req: &EthQuery) -> Result { + let result = match req { + EthQuery::GetBalance(GetBalance { address, block }) => { + let block_id = match *block { + AtBlock::Latest => BlockId::Number(BlockNumber::Latest), + AtBlock::Number(number) => BlockId::Number(number.into()), + AtBlock::Hash(hash) => BlockId::Hash(hash), + }; + let balance = self.client.get_balance(*address, Some(block_id)).await?; + EthQueryResult::GetBalance(balance) + }, + EthQuery::GetStorageAt(GetStorageAt { address, at, block }) => { + let block_id = match *block { + AtBlock::Latest => BlockId::Number(BlockNumber::Latest), + AtBlock::Number(number) => BlockId::Number(BlockNumber::Number(number.into())), + AtBlock::Hash(hash) => BlockId::Hash(hash), + }; + let value = self.client.get_storage_at(*address, *at, Some(block_id)).await?; + EthQueryResult::GetStorageAt(value) + }, + EthQuery::GetTransactionReceipt(GetTransactionReceipt { tx_hash }) => { + let receipt = self.client.get_transaction_receipt(*tx_hash).await?.map(|receipt| { + TransactionReceipt { + transaction_hash: receipt.transaction_hash, + transaction_index: receipt.transaction_index.as_u64(), + block_hash: receipt.block_hash, + block_number: receipt.block_number.map(|number| number.as_u64()), + from: receipt.from, + to: receipt.to, + cumulative_gas_used: receipt.cumulative_gas_used, + gas_used: receipt.gas_used, + contract_address: receipt.contract_address, + status_code: receipt.status.map(|number| number.as_u64()), + state_root: receipt.root, + logs: receipt + .logs + .into_iter() + .map(|log| Log { + address: log.address, + topics: log.topics, + data: log.data.to_vec(), + block_hash: log.block_hash, + block_number: log.block_number.map(|n| n.as_u64()), + transaction_hash: log.transaction_hash, + transaction_index: log.transaction_index.map(|n| n.as_u64()), + log_index: log.log_index, + transaction_log_index: log.transaction_log_index, + log_type: log.log_type, + removed: log.removed, + }) + .collect(), + logs_bloom: receipt.logs_bloom, + effective_gas_price: receipt.effective_gas_price, + transaction_type: receipt.transaction_type.map(|number| number.as_u64()), + } + }); + EthQueryResult::GetTransactionReceipt(receipt) + }, + EthQuery::CallContract(CallContract { from, to, data, value, block }) => { + let block_id = match *block { + AtBlock::Latest => BlockId::Number(BlockNumber::Latest), + AtBlock::Number(number) => BlockId::Number(BlockNumber::Number(number.into())), + AtBlock::Hash(hash) => BlockId::Hash(hash), }; - bail!("invalid block identifier") - }) - .transpose()?; - - let params = &req.parameters; - match call_type.to_lowercase().as_str() { - "call" => { - //process constant call - let contract_address = H160::from_str(contract_address)?; - - let function = HumanReadableParser::parse_function(method_or_position)?; - let params: Vec = serde_json::from_value(params.clone())?; - let mut tokens = Vec::with_capacity(params.len()); - for (ty, arg) in function.inputs.iter().zip(params) { - tokens.push(LenientTokenizer::tokenize(&ty.kind, &arg)?); - } - let data = function.encode_input(&tokens)?; - let tx = Eip1559TransactionRequest { - to: Some(contract_address.into()), - data: Some(data.into()), + from: *from, + to: Some((*to).into()), + data: Some(data.clone().into()), + value: Some(*value), ..Default::default() }; - let tx = &tx.into(); - let received_data = self.client.call(tx, block_id).await?; - - let detokenizer: Detokenizer = - decode_function_data(&function, received_data, false)?; - let mut result = Vec::with_capacity(tokens.len()); - for token in detokenizer.tokens { - result.push(token.to_string()); - } - Ok(serde_json::to_value(result)?) + let received_data = self.client.call(tx, Some(block_id)).await?; + EthQueryResult::CallContract(CallResult::Success(received_data.to_vec())) }, - "storage" => { - //process storage call - let from = H160::from_str(contract_address)?; - - let location = H256::from_str(method_or_position)?; - - // TODO: remove the params["block_number"], use block_identifier instead, leaving it - // here for compatibility - let block_num = params["block_number"] - .as_u64() - .map(|block_num| BlockId::Number(block_num.into())) - .or(block_id); - - let storage_check = self.client.get_storage_at(from, location, block_num).await?; - Ok(Value::String(format!("{storage_check:#?}",))) - }, - "storage_proof" => { - let from = H160::from_str(contract_address)?; - - let location = H256::from_str(method_or_position)?; - - // TODO: remove the params["block_number"], use block_identifier instead, leaving it - // here for compatibility - let block_num = params["block_number"] - .as_u64() - .map(|block_num| BlockId::Number(block_num.into())) - .or(block_id); - - let proof_data = self.client.get_proof(from, vec![location], block_num).await?; + EthQuery::GetProof(GetProof { account, storage_keys, block }) => { + let block_id = match *block { + AtBlock::Latest => BlockId::Number(BlockNumber::Latest), + AtBlock::Number(number) => BlockId::Number(BlockNumber::Number(number.into())), + AtBlock::Hash(hash) => BlockId::Hash(hash), + }; + let proof_data = + self.client.get_proof(*account, storage_keys.clone(), Some(block_id)).await?; //process verfiicatin of proof let storage_hash = proof_data.storage_hash; @@ -377,33 +369,40 @@ where let key_hash = keccak256(key); let encoded_val = storage_proof.value.rlp_bytes().to_vec(); - let is_valid = verify_proof( + let _is_valid = verify_proof( &storage_proof.proof, storage_hash.as_bytes(), &key_hash.to_vec(), &encoded_val, ); - - let result = serde_json::to_value(&proof_data)?; - - Ok(json!({ - "proof": result, - "isValid": is_valid - })) - }, - "transaction_receipt" => { - let tx_hash = H256::from_str(contract_address)?; - let receipt = self.client.get_transaction_receipt(tx_hash).await?; - let result = serde_json::to_value(&receipt)?; - if block_id.is_some() { - bail!("block identifier is ignored for transaction receipt"); - } - Ok(result) - }, - _ => { - bail!("request type not supported") + EthQueryResult::GetProof(EIP1186ProofResponse { + address: proof_data.address, + balance: proof_data.balance, + code_hash: proof_data.code_hash, + nonce: proof_data.nonce.as_u64(), + storage_hash: proof_data.storage_hash, + account_proof: proof_data + .account_proof + .into_iter() + .map(|bytes| bytes.0.to_vec()) + .collect(), + storage_proof: proof_data + .storage_proof + .into_iter() + .map(|storage_proof| StorageProof { + key: storage_proof.key, + proof: storage_proof + .proof + .into_iter() + .map(|proof| proof.0.to_vec()) + .collect(), + value: storage_proof.value, + }) + .collect(), + }) }, - } + }; + Ok(result) } } diff --git a/chains/ethereum/server/src/lib.rs b/chains/ethereum/server/src/lib.rs index e2c08cb8..ab200c43 100644 --- a/chains/ethereum/server/src/lib.rs +++ b/chains/ethereum/server/src/lib.rs @@ -1,17 +1,17 @@ use anyhow::Result; use client::EthereumClient; use ethers::providers::Http; -pub use rosetta_config_ethereum::{EthereumMetadata, EthereumMetadataParams}; +pub use rosetta_config_ethereum::{ + EthereumMetadata, EthereumMetadataParams, Query as EthQuery, QueryResult as EthQueryResult, +}; use rosetta_core::{ crypto::{address::Address, PublicKey}, types::{ - Block, BlockIdentifier, CallRequest, Coin, PartialBlockIdentifier, Transaction, - TransactionIdentifier, + Block, BlockIdentifier, Coin, PartialBlockIdentifier, Transaction, TransactionIdentifier, }, BlockchainClient, BlockchainConfig, }; use rosetta_server::ws::{default_client, DefaultClient}; -use serde_json::Value; use url::Url; mod client; @@ -24,6 +24,10 @@ use rosetta_ethereum_rpc_client::EthPubsubAdapter; pub use event_stream::EthereumEventStream; +pub mod config { + pub use rosetta_config_ethereum::*; +} + #[derive(Clone)] pub enum MaybeWsEthereumClient { Http(EthereumClient), @@ -86,6 +90,8 @@ impl BlockchainClient for MaybeWsEthereumClient { type MetadataParams = EthereumMetadataParams; type Metadata = EthereumMetadata; type EventStream<'a> = EthereumEventStream<'a, EthPubsubAdapter>; + type Call = EthQuery; + type CallResult = EthQueryResult; fn config(&self) -> &BlockchainConfig { match self { @@ -180,7 +186,7 @@ impl BlockchainClient for MaybeWsEthereumClient { } } - async fn call(&self, req: &CallRequest) -> Result { + async fn call(&self, req: &EthQuery) -> Result { match self { Self::Http(http_client) => http_client.call(req).await, Self::Ws(ws_client) => ws_client.call(req).await, @@ -198,14 +204,27 @@ impl BlockchainClient for MaybeWsEthereumClient { } } +#[allow(clippy::ignored_unit_patterns)] #[cfg(test)] mod tests { use super::*; + use alloy_sol_types::{sol, SolCall}; + use ethabi::ethereum_types::H256; use ethers_solc::{artifacts::Source, CompilerInput, EvmVersion, Solc}; + use rosetta_config_ethereum::{AtBlock, CallResult}; use rosetta_docker::Env; use sha3::Digest; use std::{collections::BTreeMap, path::Path}; + sol! { + interface TestContract { + event AnEvent(); + function emitEvent() external; + + function identity(bool a) external view returns (bool); + } + } + pub async fn client_from_config(config: BlockchainConfig) -> Result { let url = config.node_uri.to_string(); MaybeWsEthereumClient::from_config(config, url.as_str()).await @@ -274,25 +293,24 @@ mod tests { wallet.faucet(faucet).await?; let bytes = compile_snippet( - r#" + r" event AnEvent(); function emitEvent() public { emit AnEvent(); } - "#, + ", )?; let tx_hash = wallet.eth_deploy_contract(bytes).await?; - - let receipt = wallet.eth_transaction_receipt(&tx_hash).await?; - let contract_address = - receipt.get("contractAddress").and_then(serde_json::Value::as_str).unwrap(); - let tx_hash = - wallet.eth_send_call(contract_address, "function emitEvent()", &[], 0).await?; - let receipt = wallet.eth_transaction_receipt(&tx_hash).await?; - let logs = receipt.get("logs").and_then(serde_json::Value::as_array).unwrap(); - assert_eq!(logs.len(), 1); - let topic = logs[0]["topics"][0].as_str().unwrap(); - let expected = format!("0x{}", hex::encode(sha3::Keccak256::digest("AnEvent()"))); + let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); + let contract_address = receipt.contract_address.unwrap(); + let tx_hash = { + let call = TestContract::emitEventCall {}; + wallet.eth_send_call(contract_address.0, call.abi_encode(), 0).await? + }; + let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); + assert_eq!(receipt.logs.len(), 1); + let topic = receipt.logs[0].topics[0]; + let expected = H256(sha3::Keccak256::digest("AnEvent()").into()); assert_eq!(topic, expected); env.shutdown().await?; Ok(()) @@ -310,26 +328,32 @@ mod tests { wallet.faucet(faucet).await?; let bytes = compile_snippet( - r#" + r" function identity(bool a) public view returns (bool) { return a; } - "#, + ", )?; let tx_hash = wallet.eth_deploy_contract(bytes).await?; - let receipt = wallet.eth_transaction_receipt(&tx_hash).await?; - let contract_address = receipt["contractAddress"].as_str().unwrap(); - - let response = wallet - .eth_view_call( - contract_address, - "function identity(bool a) returns (bool)", - &["true".into()], - None, + let receipt = wallet.eth_transaction_receipt(tx_hash).await?.unwrap(); + let contract_address = receipt.contract_address.unwrap(); + + let response = { + let call = TestContract::identityCall { a: true }; + wallet + .eth_view_call(contract_address.0, call.abi_encode(), AtBlock::Latest) + .await? + }; + assert_eq!( + response, + CallResult::Success( + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1 + ] + .to_vec() ) - .await?; - let result: Vec = serde_json::from_value(response)?; - assert_eq!(result[0], "true"); + ); env.shutdown().await?; Ok(()) } diff --git a/chains/ethereum/tx/src/lib.rs b/chains/ethereum/tx/src/lib.rs index 489a395f..2794838b 100644 --- a/chains/ethereum/tx/src/lib.rs +++ b/chains/ethereum/tx/src/lib.rs @@ -1,10 +1,6 @@ use anyhow::Result; -use ethabi::token::{LenientTokenizer, Tokenizer}; -use ethers_core::{ - abi::HumanReadableParser, - types::{ - transaction::eip2930::AccessList, Eip1559TransactionRequest, NameOrAddress, Signature, H160, - }, +use ethers_core::types::{ + transaction::eip2930::AccessList, Eip1559TransactionRequest, NameOrAddress, Signature, H160, }; use rosetta_config_ethereum::{EthereumMetadata, EthereumMetadataParams}; use rosetta_core::{ @@ -34,23 +30,16 @@ impl TransactionBuilder for EthereumTransactionBuilder { fn method_call( &self, - contract: &str, - method: &str, - params: &[String], + contract: &[u8; 20], + data: &[u8], amount: u128, ) -> Result { - let destination: H160 = contract.parse()?; + let destination = H160::from_slice(contract); let amount: U256 = amount.into(); - let function = HumanReadableParser::parse_function(method)?; - let mut tokens = Vec::with_capacity(params.len()); - for (ty, arg) in function.inputs.iter().zip(params) { - tokens.push(LenientTokenizer::tokenize(&ty.kind, arg)?); - } - let bytes = function.encode_input(&tokens)?; Ok(EthereumMetadataParams { destination: destination.0.to_vec(), amount: amount.0, - data: bytes, + data: data.to_vec(), }) } diff --git a/chains/polkadot/server/src/lib.rs b/chains/polkadot/server/src/lib.rs index 58bee157..ea9edd18 100644 --- a/chains/polkadot/server/src/lib.rs +++ b/chains/polkadot/server/src/lib.rs @@ -113,6 +113,8 @@ impl BlockchainClient for PolkadotClient { type MetadataParams = PolkadotMetadataParams; type Metadata = PolkadotMetadata; type EventStream<'a> = EmptyEventStream; + type Call = CallRequest; + type CallResult = Value; fn config(&self) -> &BlockchainConfig { &self.config diff --git a/chains/polkadot/tx/src/lib.rs b/chains/polkadot/tx/src/lib.rs index 90685cde..490e46f8 100644 --- a/chains/polkadot/tx/src/lib.rs +++ b/chains/polkadot/tx/src/lib.rs @@ -100,9 +100,8 @@ impl TransactionBuilder for PolkadotTransactionBuilder { fn method_call( &self, - _module: &str, - _method: &str, - _params: &[String], + _contract: &[u8; 20], + _data: &[u8], _amount: u128, ) -> Result { bail!("Not Implemented") diff --git a/rosetta-client/Cargo.toml b/rosetta-client/Cargo.toml index 88b79699..17ec72e4 100644 --- a/rosetta-client/Cargo.toml +++ b/rosetta-client/Cargo.toml @@ -26,6 +26,7 @@ rosetta-tx-ethereum.workspace = true rosetta-tx-polkadot.workspace = true serde.workspace = true serde_json.workspace = true +void = "1.0" [target.'cfg(target_family = "wasm")'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/rosetta-client/src/client.rs b/rosetta-client/src/client.rs index 340637b9..f41a95b6 100644 --- a/rosetta-client/src/client.rs +++ b/rosetta-client/src/client.rs @@ -14,12 +14,14 @@ use rosetta_core::{BlockchainClient, ClientEvent}; use rosetta_server_astar::{AstarClient, AstarMetadata, AstarMetadataParams}; use rosetta_server_bitcoin::{BitcoinClient, BitcoinMetadata, BitcoinMetadataParams}; use rosetta_server_ethereum::{ + config::{Query as EthQuery, QueryResult as EthQueryResult}, EthereumMetadata, EthereumMetadataParams, MaybeWsEthereumClient as EthereumClient, }; use rosetta_server_polkadot::{PolkadotClient, PolkadotMetadata, PolkadotMetadataParams}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::{pin::Pin, str::FromStr}; +use void::Void; // TODO: Use #[allow(clippy::large_enum_variant)] @@ -103,6 +105,19 @@ pub enum GenericMetadata { Polkadot(PolkadotMetadata), } +pub enum GenericCall { + Bitcoin(Void), + Ethereum(EthQuery), + Polkadot(CallRequest), +} + +#[allow(clippy::large_enum_variant)] +pub enum GenericCallResult { + Bitcoin(()), + Ethereum(EthQueryResult), + Polkadot(Value), +} + macro_rules! dispatch { ($self:tt$($method:tt)+) => { match $self { @@ -119,6 +134,8 @@ impl BlockchainClient for GenericClient { type MetadataParams = GenericMetadataParams; type Metadata = GenericMetadata; type EventStream<'a> = Pin + Send + Unpin + 'a>>; + type Call = GenericCall; + type CallResult = GenericCallResult; fn config(&self) -> &BlockchainConfig { dispatch!(self.config()) @@ -190,8 +207,32 @@ impl BlockchainClient for GenericClient { dispatch!(self.block_transaction(block, tx).await) } - async fn call(&self, req: &CallRequest) -> Result { - dispatch!(self.call(req).await) + async fn call(&self, req: &GenericCall) -> Result { + let result = match self { + Self::Bitcoin(client) => match req { + GenericCall::Bitcoin(args) => GenericCallResult::Bitcoin(client.call(args).await?), + _ => anyhow::bail!("invalid call"), + }, + Self::Ethereum(client) => match req { + GenericCall::Ethereum(args) => { + GenericCallResult::Ethereum(client.call(args).await?) + }, + _ => anyhow::bail!("invalid call"), + }, + Self::Astar(client) => match req { + GenericCall::Ethereum(args) => { + GenericCallResult::Ethereum(client.call(args).await?) + }, + _ => anyhow::bail!("invalid call"), + }, + Self::Polkadot(client) => match req { + GenericCall::Polkadot(args) => { + GenericCallResult::Polkadot(client.call(args).await?) + }, + _ => anyhow::bail!("invalid call"), + }, + }; + Ok(result) } /// Return a stream of events, return None if the blockchain doesn't support events. diff --git a/rosetta-client/src/tx_builder.rs b/rosetta-client/src/tx_builder.rs index 646f33c2..721d501f 100644 --- a/rosetta-client/src/tx_builder.rs +++ b/rosetta-client/src/tx_builder.rs @@ -17,7 +17,9 @@ impl GenericTransactionBuilder { pub fn new(config: &BlockchainConfig) -> Result { Ok(match config.blockchain { "astar" => Self::Astar(rosetta_tx_ethereum::EthereumTransactionBuilder), - "ethereum" => Self::Ethereum(rosetta_tx_ethereum::EthereumTransactionBuilder), + "ethereum" | "polygon" | "arbitrum" => { + Self::Ethereum(rosetta_tx_ethereum::EthereumTransactionBuilder) + }, "polkadot" => Self::Polkadot(rosetta_tx_polkadot::PolkadotTransactionBuilder), _ => anyhow::bail!("unsupported blockchain"), }) @@ -33,17 +35,14 @@ impl GenericTransactionBuilder { pub fn method_call( &self, - contract: &str, - method: &str, - params: &[String], + contract: &[u8; 20], + data: &[u8], amount: u128, ) -> Result { Ok(match self { - Self::Astar(tx) => { - AstarMetadataParams(tx.method_call(contract, method, params, amount)?).into() - }, - Self::Ethereum(tx) => tx.method_call(contract, method, params, amount)?.into(), - Self::Polkadot(tx) => tx.method_call(contract, method, params, amount)?.into(), + Self::Astar(tx) => AstarMetadataParams(tx.method_call(contract, data, amount)?).into(), + Self::Ethereum(tx) => tx.method_call(contract, data, amount)?.into(), + Self::Polkadot(tx) => tx.method_call(contract, data, amount)?.into(), }) } diff --git a/rosetta-client/src/wallet.rs b/rosetta-client/src/wallet.rs index 7773e042..aefdf8c7 100644 --- a/rosetta-client/src/wallet.rs +++ b/rosetta-client/src/wallet.rs @@ -9,10 +9,14 @@ use crate::{ }; use anyhow::Result; use rosetta_core::{ - types::{Block, CallRequest, PartialBlockIdentifier, Transaction}, + types::{Block, PartialBlockIdentifier, Transaction}, BlockchainClient, RosettaAlgorithm, }; -use serde_json::json; +use rosetta_server_ethereum::config::{ + ethereum_types::{self, Address as EthAddress, H256, U256}, + AtBlock, CallContract, CallResult, EIP1186ProofResponse, GetProof, GetStorageAt, + GetTransactionReceipt, Query as EthQuery, QueryResult as EthQueryResult, TransactionReceipt, +}; use std::path::Path; /// The wallet provides the main entry point to this crate. @@ -140,25 +144,6 @@ impl Wallet { self.client.block_transaction(&block_identifer, &tx_identifier).await } - /// Extension of rosetta-api does multiple things - /// 1. fetching storage - /// 2. calling extrinsic/contract - #[allow(clippy::missing_errors_doc)] - async fn call( - &self, - method: String, - params: &serde_json::Value, - block_identifier: Option, - ) -> Result { - let req = CallRequest { - network_identifier: self.client.config().network(), - method, - parameters: params.clone(), - block_identifier, - }; - self.client.call(&req).await - } - /// Returns the coins of the wallet. #[allow(clippy::missing_errors_doc)] pub async fn coins(&self) -> Result> { @@ -228,41 +213,44 @@ impl Wallet { /// deploys contract to chain #[allow(clippy::missing_errors_doc)] - pub async fn eth_deploy_contract(&self, bytecode: Vec) -> Result> { + pub async fn eth_deploy_contract(&self, bytecode: Vec) -> Result<[u8; 32]> { let metadata_params = self.tx.deploy_contract(bytecode)?; - self.construct(&metadata_params).await + let bytes = self.construct(&metadata_params).await?; + let mut tx_hash = [0u8; 32]; + tx_hash.copy_from_slice(&bytes[0..32]); + Ok(tx_hash) } /// calls contract send call function #[allow(clippy::missing_errors_doc)] pub async fn eth_send_call( &self, - contract_address: &str, - method_signature: &str, - params: &[String], + contract_address: [u8; 20], + data: Vec, amount: u128, - ) -> Result> { - let metadata_params = - self.tx.method_call(contract_address, method_signature, params, amount)?; - self.construct(&metadata_params).await + ) -> Result<[u8; 32]> { + let metadata_params = self.tx.method_call(&contract_address, data.as_ref(), amount)?; + let bytes = self.construct(&metadata_params).await?; + let mut tx_hash = [0u8; 32]; + tx_hash.copy_from_slice(&bytes[0..32]); + Ok(tx_hash) } /// estimates gas of send call #[allow(clippy::missing_errors_doc)] pub async fn eth_send_call_estimate_gas( &self, - contract_address: &str, - method_signature: &str, - params: &[String], + contract_address: [u8; 20], + data: Vec, amount: u128, ) -> Result { - let metadata_params = - self.tx.method_call(contract_address, method_signature, params, amount)?; - let metadata = match self.metadata(&metadata_params).await? { - GenericMetadata::Ethereum(metadata) => metadata, - GenericMetadata::Astar(metadata) => metadata.0, - _ => anyhow::bail!("unsupported op"), - }; + let metadata_params = self.tx.method_call(&contract_address, data.as_ref(), amount)?; + let metadata: rosetta_server_ethereum::EthereumMetadata = + match self.metadata(&metadata_params).await? { + GenericMetadata::Ethereum(metadata) => metadata, + GenericMetadata::Astar(metadata) => metadata.0, + _ => anyhow::bail!("unsupported op"), + }; Ok(rosetta_tx_ethereum::U256(metadata.gas_limit).as_u128()) } @@ -270,43 +258,106 @@ impl Wallet { #[allow(clippy::missing_errors_doc)] pub async fn eth_view_call( &self, - contract_address: &str, - method_signature: &str, - params: &[String], - block_identifier: Option, - ) -> Result { - let method = format!("{contract_address}-{method_signature}-call"); - self.call(method, &json!(params), block_identifier).await + contract_address: [u8; 20], + data: Vec, + block_identifier: AtBlock, + ) -> Result { + let contract_address = EthAddress::from(contract_address); + let call = CallContract { + from: None, + to: contract_address, + value: U256::zero(), + data, + block: block_identifier, + }; + let result = match &self.client { + GenericClient::Ethereum(client) => client.call(&EthQuery::CallContract(call)).await?, + GenericClient::Astar(client) => client.call(&EthQuery::CallContract(call)).await?, + GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_view_call"), + GenericClient::Bitcoin(_) => anyhow::bail!("bitcoin doesn't support eth_view_call"), + }; + let EthQueryResult::CallContract(exit_reason) = result else { + anyhow::bail!("[this is a bug] invalid result type"); + }; + Ok(exit_reason) } /// gets storage from ethereum contract #[allow(clippy::missing_errors_doc)] pub async fn eth_storage( &self, - contract_address: &str, - storage_slot: &str, - block_identifier: Option, - ) -> Result { - let method = format!("{contract_address}-{storage_slot}-storage"); - self.call(method, &json!({}), block_identifier).await + contract_address: [u8; 20], + storage_slot: [u8; 32], + block_identifier: AtBlock, + ) -> Result { + let contract_address = EthAddress::from(contract_address); + let storage_slot = H256(storage_slot); + let get_storage = + GetStorageAt { address: contract_address, at: storage_slot, block: block_identifier }; + let result = match &self.client { + GenericClient::Ethereum(client) => { + client.call(&EthQuery::GetStorageAt(get_storage)).await? + }, + GenericClient::Astar(client) => { + client.call(&EthQuery::GetStorageAt(get_storage)).await? + }, + GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_storage"), + GenericClient::Bitcoin(_) => anyhow::bail!("bitcoin doesn't support eth_storage"), + }; + let EthQueryResult::GetStorageAt(value) = result else { + anyhow::bail!("[this is a bug] invalid result type"); + }; + Ok(value) } /// gets storage proof from ethereum contract #[allow(clippy::missing_errors_doc)] - pub async fn eth_storage_proof( + pub async fn eth_storage_proof + Send + Sync>( &self, - contract_address: &str, - storage_slot: &str, - block_identifier: Option, - ) -> Result { - let method = format!("{contract_address}-{storage_slot}-storage_proof"); - self.call(method, &json!({}), block_identifier).await + contract_address: [u8; 20], + storage_keys: I, + block_identifier: AtBlock, + ) -> Result { + use ethereum_types::Address; + let contract_address = Address::from(contract_address); + let get_proof = GetProof { + account: contract_address, + storage_keys: storage_keys.collect(), + block: block_identifier, + }; + let result = match &self.client { + GenericClient::Ethereum(client) => client.call(&EthQuery::GetProof(get_proof)).await?, + GenericClient::Astar(client) => client.call(&EthQuery::GetProof(get_proof)).await?, + GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_storage"), + GenericClient::Bitcoin(_) => anyhow::bail!("bitcoin doesn't support eth_storage"), + }; + let EthQueryResult::GetProof(proof) = result else { + anyhow::bail!("[this is a bug] invalid result type"); + }; + Ok(proof) } /// gets transaction receipt of specific hash #[allow(clippy::missing_errors_doc)] - pub async fn eth_transaction_receipt(&self, tx_hash: &[u8]) -> Result { - let call_method = format!("{}--transaction_receipt", hex::encode(tx_hash)); - self.call(call_method, &json!({}), None).await + pub async fn eth_transaction_receipt( + &self, + tx_hash: [u8; 32], + ) -> Result> { + let tx_hash = H256(tx_hash); + let get_tx_receipt = GetTransactionReceipt { tx_hash }; + let result = match &self.client { + GenericClient::Ethereum(client) => { + client.call(&EthQuery::GetTransactionReceipt(get_tx_receipt)).await? + }, + GenericClient::Astar(client) => { + client.call(&EthQuery::GetTransactionReceipt(get_tx_receipt)).await? + }, + GenericClient::Polkadot(_) => anyhow::bail!("polkadot doesn't support eth_storage"), + GenericClient::Bitcoin(_) => anyhow::bail!("bitcoin doesn't support eth_storage"), + }; + let EthQueryResult::GetTransactionReceipt(maybe_receipt) = result else { + anyhow::bail!("[this is a bug] invalid result type"); + }; + Ok(maybe_receipt) } } diff --git a/rosetta-core/src/lib.rs b/rosetta-core/src/lib.rs index f76802ad..e9e69b00 100644 --- a/rosetta-core/src/lib.rs +++ b/rosetta-core/src/lib.rs @@ -6,7 +6,7 @@ use crate::{ Algorithm, PublicKey, SecretKey, }, types::{ - Block, BlockIdentifier, CallRequest, Coin, Currency, CurveType, NetworkIdentifier, + Block, BlockIdentifier, Coin, Currency, CurveType, NetworkIdentifier, PartialBlockIdentifier, SignatureType, Transaction, TransactionIdentifier, }, }; @@ -14,7 +14,6 @@ use anyhow::Result; use async_trait::async_trait; pub use futures_util::{future, stream}; use serde::{de::DeserializeOwned, Serialize}; -use serde_json::Value; use std::sync::Arc; use futures_util::stream::Empty; @@ -114,6 +113,8 @@ pub trait BlockchainClient: Sized + Send + Sync + 'static { type MetadataParams: DeserializeOwned + Serialize + Send + Sync + 'static; type Metadata: DeserializeOwned + Serialize + Send + Sync + 'static; type EventStream<'a>: stream::Stream + Send + Unpin + 'a; + type Call: Send + Sync + Sized + 'static; + type CallResult: Send + Sync + Sized + 'static; fn config(&self) -> &BlockchainConfig; fn genesis_block(&self) -> &BlockIdentifier; @@ -135,7 +136,7 @@ pub trait BlockchainClient: Sized + Send + Sync + 'static { block: &BlockIdentifier, tx: &TransactionIdentifier, ) -> Result; - async fn call(&self, req: &CallRequest) -> Result; + async fn call(&self, req: &Self::Call) -> Result; /// Return a stream of events, return None if the blockchain doesn't support events. async fn listen<'a>(&'a self) -> Result>> { @@ -151,6 +152,8 @@ where type MetadataParams = ::MetadataParams; type Metadata = ::Metadata; type EventStream<'a> = ::EventStream<'a>; + type Call = ::Call; + type CallResult = ::CallResult; fn config(&self) -> &BlockchainConfig { BlockchainClient::config(Self::as_ref(self)) @@ -196,10 +199,9 @@ where ) -> Result { BlockchainClient::block_transaction(Self::as_ref(self), block, tx).await } - async fn call(&self, req: &CallRequest) -> Result { + async fn call(&self, req: &Self::Call) -> Result { BlockchainClient::call(Self::as_ref(self), req).await } - /// Return a stream of events, return None if the blockchain doesn't support events. async fn listen<'a>(&'a self) -> Result>> { BlockchainClient::listen(Self::as_ref(self)).await @@ -247,9 +249,8 @@ pub trait TransactionBuilder: Default + Sized { /// Returns `Err` if for some reason it cannot construct the metadata parameters. fn method_call( &self, - contract: &str, - method: &str, - values: &[String], + contract: &[u8; 20], + data: &[u8], amount: u128, ) -> Result; diff --git a/rosetta-server/src/ws/reconnect_impl.rs b/rosetta-server/src/ws/reconnect_impl.rs index 429e0bd9..339b419f 100644 --- a/rosetta-server/src/ws/reconnect_impl.rs +++ b/rosetta-server/src/ws/reconnect_impl.rs @@ -217,34 +217,27 @@ impl Future for ReadyOrWaitFuture { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - loop { - match this.state.take() { - Some(ReadyOrWaitState::Ready(result)) => return Poll::Ready(result), - Some(ReadyOrWaitState::Waiting(mut future)) => { - match future.poll_unpin(cx) { - // The request delay timeout - Poll::Ready(Either::Left(_)) => { - return Poll::Ready(Err(Error::Custom( - "Timeout: cannot process request, client reconnecting..." - .to_string(), - ))) - }, - // The client was reconnected! - Poll::Ready(Either::Right((Ok(client), _))) => { - return Poll::Ready(Ok(client)) - }, - // Failed to reconnect - Poll::Ready(Either::Right((Err(result), _))) => { - return Poll::Ready(Err(result.into_inner())) - }, - Poll::Pending => { - *this.state = Some(ReadyOrWaitState::Waiting(future)); - return Poll::Pending; - }, - } - }, - None => panic!("ClientReadyFuture polled after completion"), - } + match this.state.take() { + Some(ReadyOrWaitState::Ready(result)) => Poll::Ready(result), + Some(ReadyOrWaitState::Waiting(mut future)) => { + match future.poll_unpin(cx) { + // The request delay timeout + Poll::Ready(Either::Left(_)) => Poll::Ready(Err(Error::Custom( + "Timeout: cannot process request, client reconnecting...".to_string(), + ))), + // The client was reconnected! + Poll::Ready(Either::Right((Ok(client), _))) => Poll::Ready(Ok(client)), + // Failed to reconnect + Poll::Ready(Either::Right((Err(result), _))) => { + Poll::Ready(Err(result.into_inner())) + }, + Poll::Pending => { + *this.state = Some(ReadyOrWaitState::Waiting(future)); + Poll::Pending + }, + } + }, + None => panic!("ClientReadyFuture polled after completion"), } } }