From 646da2f436707e674fd6270a4594aaf60af70f75 Mon Sep 17 00:00:00 2001 From: Dimitris Frangiadakis Date: Mon, 11 Mar 2024 21:14:22 +0100 Subject: [PATCH] Serializer --- Cargo.lock | 81 ++++++++++++++++++++++++++++ Cargo.toml | 2 + src/icon_service.rs | 17 ++++++ src/transaction_builder.rs | 107 +++++++++++++++++++++++++++++++++++++ tests/test.rs | 24 +++++++++ 5 files changed, 231 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 854cb82..f517276 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "borsh" version = "1.3.1" @@ -177,6 +186,35 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -283,6 +321,16 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -340,6 +388,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.12" @@ -415,12 +469,14 @@ dependencies = [ name = "icon-sdk-rust" version = "0.1.0" dependencies = [ + "hex", "num-bigint", "num-traits", "reqwest", "rust_decimal", "serde", "serde_json", + "sha3", "tokio", ] @@ -465,6 +521,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -983,6 +1048,16 @@ dependencies = [ "serde", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "simdutf8" version = "0.1.4" @@ -1202,6 +1277,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index 0d9c7ca..b200c6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,5 @@ serde = { version = "1.0.197", features = ["derive"] } num-bigint = "0.4.4" num-traits = "0.2.18" rust_decimal = "1.34.3" +sha3 = "0.10.8" +hex = "0.4.3" diff --git a/src/icon_service.rs b/src/icon_service.rs index faa0b2e..7566e7a 100644 --- a/src/icon_service.rs +++ b/src/icon_service.rs @@ -37,3 +37,20 @@ pub async fn get_balance(address: &str) -> Result> { Ok(response) } + +pub async fn send_transaction(from: &str, to: &str, value: &str, version: &str, nid: &str, nonce: &str, step_limit: &str) -> Result> { + let transaction_builder = TransactionBuilder::new() + .method("icx_sendTransaction") + .from(from) + .to(to) + .value(value) + .version(version) + .nid(nid) + .nonce(nonce) + .step_limit(step_limit) + .serialize(true); + Ok(transaction_builder) + // let response: Value = transaction_builder.map_err(|e| Box::new(e) as Box)?; + // + // Ok(response) +} diff --git a/src/transaction_builder.rs b/src/transaction_builder.rs index 429e3d0..d3bac7b 100644 --- a/src/transaction_builder.rs +++ b/src/transaction_builder.rs @@ -1,6 +1,8 @@ use reqwest::{Client, Error}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value, Map}; +use sha3::{Digest, Sha3_256}; +use std::collections::BTreeMap; #[derive(Default, Serialize, Deserialize)] pub struct TransactionBuilder { @@ -68,6 +70,111 @@ impl TransactionBuilder { self.set_params(¶ms) } + pub fn from(self, from: &str) -> Self { + let mut params = Map::new(); + params.insert("from".to_string(), json!(from)); + + self.set_params(¶ms) + } + + pub fn to(self, to: &str) -> Self { + let mut params = Map::new(); + params.insert("to".to_string(), json!(to)); + + self.set_params(¶ms) + } + + pub fn value(self, value: &str) -> Self { + let mut params = Map::new(); + params.insert("value".to_string(), json!(value)); + + self.set_params(¶ms) + } + + pub fn version(self, version: &str) -> Self { + let mut params = Map::new(); + params.insert("version".to_string(), json!(version)); + + self.set_params(¶ms) + } + + pub fn nid(self, nid: &str) -> Self { + let mut params = Map::new(); + params.insert("nid".to_string(), json!(nid)); + + self.set_params(¶ms) + } + + pub fn nonce(self, nonce: &str) -> Self { + let mut params = Map::new(); + params.insert("nonce".to_string(), json!(nonce)); + + self.set_params(¶ms) + } + + pub fn step_limit(self, step_limit: &str) -> Self { + let mut params = Map::new(); + params.insert("stepLimit".to_string(), json!(step_limit)); + + self.set_params(¶ms) + } + + pub fn serialize(&self, hashed: bool) -> String { + let result_str = Self::value_traverse(&self.data["params"]); + let result_string_replaced = &result_str[1..result_str.len() - 1]; + let result = format!("icx_sendTransaction.{}", result_string_replaced); + + if hashed { + format!("{}", hex::encode(sha3::Sha3_256::digest(result.as_bytes()))) + } else { + result + } + } + + fn value_traverse(value: &Value) -> String { + match value { + Value::Object(obj) => { + let mut result = "{".to_string(); + let sorted: BTreeMap<_, _> = obj.iter().collect(); + for (key, val) in &sorted { + result.push_str(&format!("{}.", key)); + result.push_str(&Self::value_traverse(val)); + result.push('.'); + } + if result.ends_with('.') { + result.pop(); + } + result.push('}'); + result + }, + Value::Array(arr) => { + let mut result = "[".to_string(); + for val in arr { + result.push_str(&Self::value_traverse(val)); + result.push('.'); + } + if result.ends_with('.') { + result.pop(); + } + result.push(']'); + result + }, + Value::String(s) => Self::escape_string(s), + Value::Number(n) => n.to_string(), + Value::Bool(b) => b.to_string(), + Value::Null => "\\0".to_string(), + } + } + + fn escape_string(value: &str) -> String { + value.replace("\\", "\\\\") + .replace(".", "\\.") + .replace("{", "\\{") + .replace("}", "\\}") + .replace("[", "\\[") + .replace("]", "\\]") + } + pub async fn send(self) -> Result { let client = Client::new(); let url = self.icon_service_url.unwrap_or_else(|| "https://api.icon.community/api/v3".to_string()); diff --git a/tests/test.rs b/tests/test.rs index 8e4b5ee..35a819e 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -88,3 +88,27 @@ async fn test_icx_to_hex() -> Result<(), ()> { Ok(()) } + +#[tokio::test] +async fn test_serialize_transaction() -> Result<(), ()> { + let res = icon_service::send_transaction( + "hx8dc6ae3d93e60a2dddf80bfc5fb1cd16a2bf6160", + "hxf8689d6c4c8f333651469fdea2ac59a18f6c242d", + "0x2386f26fc10000", + "0x2", + "0x1", + "0x1", + "0x186a0" + ).await; + + match res { + Ok(response) => { + assert_eq!(response, "308167c8113b6e6f3f9e7ba28f495af468ed636a72e411d99823a78c61d104b3") + // assert_eq!(response["jsonrpc"], "2.0"); + // assert!(!response.as_object().unwrap().contains_key("error")); + }, + Err(e) => println!("Error: {:?}", e), + } + + Ok(()) +}