From 16b50f8e52f11cecb3b03e0971a7d7b2ec682ef5 Mon Sep 17 00:00:00 2001 From: Ricky Saechao Date: Tue, 29 Oct 2024 12:35:55 -0700 Subject: [PATCH] feat(tck): add AccountCreateTransaction Signed-off-by: Ricky Saechao --- Cargo.lock | 3 + tck/Cargo.toml | 4 ++ tck/src/helpers.rs | 47 ++++++++++++ tck/src/main.rs | 1 + tck/src/sdk_client.rs | 164 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 tck/src/helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 22e24afb..7a5c8447 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2849,6 +2849,9 @@ dependencies = [ "hyper 0.14.30", "jsonrpsee", "once_cell", + "serde", + "serde_json", + "time", "tokio", "tower", "tower-http", diff --git a/tck/Cargo.toml b/tck/Cargo.toml index c11c7267..5d31d976 100644 --- a/tck/Cargo.toml +++ b/tck/Cargo.toml @@ -17,6 +17,10 @@ tower-http = { version = "0.5.2", features = ["full"] } hedera = { path = "../." } once_cell = "1.19.0" futures-util = "0.3.30" +serde_json = "1.0.1" +serde = { version = "1.0.181", features = ["derive"] } +time = "0.3.9" + [dependencies.jsonrpsee] version = "0.23.2" diff --git a/tck/src/helpers.rs b/tck/src/helpers.rs new file mode 100644 index 00000000..60fc494a --- /dev/null +++ b/tck/src/helpers.rs @@ -0,0 +1,47 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +#[derive(Debug, Clone)] +#[derive(Serialize, Deserialize)] +pub enum TransactionParamValue { + String(String), + I64(i64), + Bool(bool), + Array(Vec), + Map(HashMap) +} + +impl FromStr for TransactionParamValue { + type Err = String; + + fn from_str(s: &str) -> Result { + // Try parsing as i64 + if let Ok(i) = s.parse::() { + return Ok(TransactionParamValue::I64(i)); + } + + // Try parsing as bool + if let Ok(b) = s.parse::() { + return Ok(TransactionParamValue::Bool(b)); + } + + // Try parsing as JSON array + if s.starts_with('[') && s.ends_with(']') { + if let Ok(array) = serde_json::from_str::>(s) { + return Ok(TransactionParamValue::Array(array)); + } + } + + // Try parsing as JSON map + if s.starts_with('{') && s.ends_with('}') { + if let Ok(map) = serde_json::from_str::>(s) { + return Ok(TransactionParamValue::Map(map)); + } + } + + // Default to String + Ok(TransactionParamValue::String(s.to_string())) + } +} diff --git a/tck/src/main.rs b/tck/src/main.rs index bd727536..5b7da29d 100644 --- a/tck/src/main.rs +++ b/tck/src/main.rs @@ -24,6 +24,7 @@ use sdk_client::{ use tokio::signal; pub(crate) mod sdk_client; +mod helpers; #[tokio::main] async fn main() -> anyhow::Result<()> { diff --git a/tck/src/sdk_client.rs b/tck/src/sdk_client.rs index d622932d..6344b33e 100644 --- a/tck/src/sdk_client.rs +++ b/tck/src/sdk_client.rs @@ -6,17 +6,20 @@ use std::sync::{ }; use hedera::{ - AccountId, - Client, - PrivateKey, + AccountCreateTransaction, AccountId, EntityId, Client, EvmAddress, Hbar, PrivateKey, Transaction, TransactionId }; + use jsonrpsee::core::async_trait; use jsonrpsee::proc_macros::rpc; +use jsonrpsee::types::error::PARSE_ERROR_CODE; use jsonrpsee::types::{ ErrorObject, ErrorObjectOwned, }; use once_cell::sync::Lazy; +use time::Duration; + +use crate::helpers::TransactionParamValue; static GLOBAL_SDK_CLIENT: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); @@ -41,6 +44,22 @@ pub trait Rpc { #[method(name = "reset")] fn reset(&self) -> Result, ErrorObjectOwned>; + + #[method(name = "createAccount")] + fn create_account( + &self, + key: Option, + initial_balance: Option, + receiver_signature_required: Option, + auto_renew_period: Option, + memo: Option, + max_automatic_token_associations: Option, + staked_account_id: Option, + alias: Option, + staked_node_id: Option, + declining_staking_reward: Option, + common_transaction_params: Option>, + ) -> Result; } pub struct RpcServerImpl; @@ -134,4 +153,143 @@ impl RpcServer for RpcServerImpl { *global_client = None; Ok(HashMap::from([("status".to_string(), "SUCCESS".to_string())].to_owned())) } + + fn create_account( + &self, + key: Option, + initial_balance: Option, + receiver_signature_required: Option, + auto_renew_period: Option, + memo: Option, + max_automatic_token_associations: Option, + staked_account_id: Option, + alias: Option, + staked_node_id: Option, + decline_staking_reward: Option, + common_transaction_params: Option>, + ) -> Result { + let client = GLOBAL_SDK_CLIENT.lock().unwrap(); + let client = client.as_ref().ok_or_else(|| { + ErrorObject::owned(-32603, "Client not initialized".to_string(), None::<()>) + })?; + + let mut account_create_tx = AccountCreateTransaction::new(); + + if let Some(key) = key { + let private_key = PrivateKey::from_str(&key) + .map_err(|e| ErrorObject::owned(-32603, e.to_string(), None::<()>))?; + let public_key = private_key.public_key(); + account_create_tx.key(public_key); + } + + if let Some(initial_balance) = initial_balance { + account_create_tx.initial_balance(Hbar::new(initial_balance)); + } + + if let Some(receiver_signature_required) = receiver_signature_required { + account_create_tx.receiver_signature_required(receiver_signature_required); + } + + if let Some(auto_renew_period) = auto_renew_period { + account_create_tx.auto_renew_period(Duration::seconds(auto_renew_period)); + } + if let Some(memo) = memo { + account_create_tx.account_memo(memo); + } + + if let Some(max_automatic_token_associations) = max_automatic_token_associations { + account_create_tx.max_automatic_token_associations(max_automatic_token_associations as i32); + } + + if let Some(staked_account_id) = staked_account_id { + account_create_tx.staked_account_id(AccountId::from_str(&staked_account_id).unwrap()); + } + + if let Some(alias) = alias { + account_create_tx.alias(EvmAddress::from_str(&alias).map_err(|e| ErrorObject::owned(PARSE_ERROR_CODE, e.to_string(), None::<()>))?); + } + + if let Some(staked_node_id) = staked_node_id { + account_create_tx.staked_node_id(staked_node_id as u64); + } + + if let Some(decline_staking_reward) = decline_staking_reward { + account_create_tx.decline_staking_reward(decline_staking_reward); + } + + if let Some(common_transaction_params) = common_transaction_params { + fill_common_transaction_params(client, &mut account_create_tx, &common_transaction_params); + + account_create_tx.freeze_with(client).unwrap(); + + if let Some(signers) = common_transaction_params.get("signers") { + match signers { + TransactionParamValue::Array(signers) => { + for signer in signers { + account_create_tx.sign(PrivateKey::from_str_der(signer).unwrap()); + } + }, + _ => {} + } + } + } + + + + + Ok("SUCCESS".to_owned()) + } + } + +fn fill_common_transaction_params( + client: &Client, + transaction: &mut Transaction, + common_transaction_params: &HashMap, +) -> Result +{ + if let Some(transaction_id) = common_transaction_params.get("transactionId") { + match transaction_id { + TransactionParamValue::String(transaction_id) => { + transaction.transaction_id(TransactionId::from_str(transaction_id.as_str()).unwrap()); + } + _ => {} + } + } + + if let Some(node_id) = common_transaction_params.get("nodeId") { + match node_id { + TransactionParamValue::String(node_id) => { + transaction.node_account_ids([AccountId::from_str(&node_id.as_str()).unwrap()]); + } + _ => {} + } + } + + if let Some(max_fee) = common_transaction_params.get("maxTransactionFee") { + match max_fee { + TransactionParamValue::String(max_fee) => { + transaction.max_transaction_fee(Hbar::from_tinybars(max_fee.as_str().parse::().unwrap())); + } + _ => {} + } + } + + if let Some(transaction_valid_duration) = common_transaction_params.get("transactionValidDuration") { + match transaction_valid_duration { + TransactionParamValue::String(transaction_valid_duration) => { + transaction.transaction_valid_duration(Duration::seconds(transaction_valid_duration.as_str().parse::().unwrap())); + } + _ => {} + } + } + + if let Some(memo) = common_transaction_params.get("memo") { + match memo { + TransactionParamValue::String(memo) => { + transaction.transaction_memo(memo.as_str()); + } + _ => {} + } + } +} \ No newline at end of file