synor/apps/cli/src/commands/contract.rs
Gulshan Yadav 48949ebb3f Initial commit: Synor blockchain monorepo
A complete blockchain implementation featuring:
- synord: Full node with GHOSTDAG consensus
- explorer-web: Modern React blockchain explorer with 3D DAG visualization
- CLI wallet and tools
- Smart contract SDK and example contracts (DEX, NFT, token)
- WASM crypto library for browser/mobile
2026-01-08 05:22:17 +05:30

343 lines
10 KiB
Rust

//! Contract commands.
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use crate::client::RpcClient;
use crate::config::CliConfig;
use crate::output::{self, OutputFormat};
use crate::ContractCommands;
/// Handle contract commands.
pub async fn handle(
client: &RpcClient,
_config: &CliConfig,
cmd: ContractCommands,
format: OutputFormat,
) -> Result<()> {
match cmd {
ContractCommands::Deploy { wasm, deployer, args, gas } => {
deploy(client, wasm, &deployer, args.as_deref(), Some(gas), format).await
}
ContractCommands::Call { contract_id, method, caller, args, value, gas } => {
call(client, &contract_id, &method, &caller, args.as_deref(), value, Some(gas), format).await
}
ContractCommands::Code { contract_id } => {
code(client, &contract_id, format).await
}
ContractCommands::Storage { contract_id, key } => {
storage(client, &contract_id, &key, format).await
}
ContractCommands::EstimateGas { contract_id, method, caller, args, value } => {
estimate_gas(client, &contract_id, &method, &caller, args.as_deref(), value, format).await
}
ContractCommands::Info { contract_id } => {
info(client, &contract_id, format).await
}
}
}
/// Deploy a contract.
async fn deploy(
client: &RpcClient,
wasm_path: PathBuf,
deployer: &str,
args: Option<&str>,
gas_limit: Option<u64>,
format: OutputFormat,
) -> Result<()> {
// Read WASM file
let wasm_bytes = fs::read(&wasm_path)?;
let wasm_hex = hex::encode(&wasm_bytes);
output::print_info(&format!("Deploying contract ({} bytes)...", wasm_bytes.len()));
let args_hex = args.unwrap_or("");
let spinner = output::create_spinner("Deploying contract...");
let result = client.deploy_contract(&wasm_hex, args_hex, deployer, gas_limit).await?;
spinner.finish_and_clear();
if let Some(error) = &result.error {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"success": false,
"error": error
}))?);
}
OutputFormat::Text => {
output::print_error(&format!("Deployment failed: {}", error));
}
}
return Ok(());
}
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"success": true,
"contractId": result.contract_id,
"address": result.address,
"gasUsed": result.gas_used,
}))?);
}
OutputFormat::Text => {
output::print_success("Contract deployed!");
output::print_kv("Contract ID", &result.contract_id);
output::print_kv("Address", &result.address);
output::print_kv("Gas Used", &result.gas_used.to_string());
}
}
Ok(())
}
/// Call a contract method.
async fn call(
client: &RpcClient,
contract_id: &str,
method: &str,
caller: &str,
args: Option<&str>,
value: u64,
gas_limit: Option<u64>,
format: OutputFormat,
) -> Result<()> {
let args_hex = args.unwrap_or("");
let result = client.call_contract(contract_id, method, args_hex, caller, value, gas_limit).await?;
if let Some(error) = &result.error {
match format {
OutputFormat::Json => {
output::print_value(&serde_json::json!({
"success": false,
"error": error
}), format);
}
OutputFormat::Text => {
output::print_error(&format!("Contract call failed: {}", error));
}
}
return Ok(());
}
match format {
OutputFormat::Json => {
output::print_value(&serde_json::json!({
"success": result.success,
"data": result.data,
"gasUsed": result.gas_used,
"logs": result.logs
}), format);
}
OutputFormat::Text => {
output::print_header("Contract Call Result");
output::print_kv("Success", if result.success { "Yes" } else { "No" });
output::print_kv("Gas Used", &result.gas_used.to_string());
if !result.data.is_empty() {
output::print_kv("Return Data", &result.data);
}
if !result.logs.is_empty() {
println!("\nLogs:");
for (i, log) in result.logs.iter().enumerate() {
println!(" [{}] Contract: {}", i, log.contract_id);
for topic in &log.topics {
println!(" Topic: {}", topic);
}
if !log.data.is_empty() {
println!(" Data: {}", log.data);
}
}
}
}
}
Ok(())
}
/// Get contract code.
async fn code(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Result<()> {
let result = client.get_contract_code(contract_id).await?;
if let Some(error) = &result.error {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?);
}
OutputFormat::Text => {
output::print_error(&format!("Failed to get code: {}", error));
}
}
return Ok(());
}
let code_hex = result.code.unwrap_or_default();
match format {
OutputFormat::Json => {
let result = serde_json::json!({
"contractId": contract_id,
"code": code_hex,
"size": code_hex.len() / 2,
});
println!("{}", serde_json::to_string_pretty(&result)?);
}
OutputFormat::Text => {
output::print_kv("Contract ID", contract_id);
output::print_kv("Size", &format!("{} bytes", code_hex.len() / 2));
if code_hex.len() <= 256 {
println!("\nCode: {}", code_hex);
} else {
println!("\nCode (truncated): {}...", &code_hex[..256]);
}
}
}
Ok(())
}
/// Get contract storage.
async fn storage(client: &RpcClient, contract_id: &str, key: &str, format: OutputFormat) -> Result<()> {
let result = client.get_contract_storage(contract_id, key).await?;
if let Some(error) = &result.error {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?);
}
OutputFormat::Text => {
output::print_error(&format!("Failed to get storage: {}", error));
}
}
return Ok(());
}
let value = result.value.unwrap_or_else(|| "null".to_string());
match format {
OutputFormat::Json => {
let result = serde_json::json!({
"contractId": contract_id,
"key": key,
"value": value,
});
println!("{}", serde_json::to_string_pretty(&result)?);
}
OutputFormat::Text => {
output::print_kv("Contract ID", contract_id);
output::print_kv("Key", key);
output::print_kv("Value", &value);
}
}
Ok(())
}
/// Estimate gas for a call.
async fn estimate_gas(
client: &RpcClient,
contract_id: &str,
method: &str,
caller: &str,
args: Option<&str>,
value: u64,
format: OutputFormat,
) -> Result<()> {
let args_hex = args.unwrap_or("");
let result = client.estimate_gas(contract_id, method, args_hex, caller, value).await?;
if let Some(error) = &result.error {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?);
}
OutputFormat::Text => {
output::print_error(&format!("Failed to estimate gas: {}", error));
}
}
return Ok(());
}
match format {
OutputFormat::Json => {
let result = serde_json::json!({
"contractId": contract_id,
"method": method,
"estimatedGas": result.estimated_gas,
});
println!("{}", serde_json::to_string_pretty(&result)?);
}
OutputFormat::Text => {
output::print_kv("Contract ID", contract_id);
output::print_kv("Method", method);
output::print_kv("Estimated Gas", &result.estimated_gas.to_string());
}
}
Ok(())
}
/// Get contract metadata.
async fn info(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Result<()> {
let result = client.get_contract(contract_id).await?;
if let Some(error) = &result.error {
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"error": error
}))?);
}
OutputFormat::Text => {
output::print_error(&format!("Failed to get contract info: {}", error));
}
}
return Ok(());
}
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({
"contractId": contract_id,
"codeHash": result.code_hash,
"deployer": result.deployer,
"deployedAt": result.deployed_at,
"deployedHeight": result.deployed_height,
}))?);
}
OutputFormat::Text => {
output::print_header("Contract Info");
output::print_kv("Contract ID", contract_id);
if let Some(hash) = &result.code_hash {
output::print_kv("Code Hash", hash);
}
if let Some(deployer) = &result.deployer {
output::print_kv("Deployer", deployer);
}
if let Some(time) = result.deployed_at {
output::print_kv("Deployed At", &format!("{}", time));
}
if let Some(height) = result.deployed_height {
output::print_kv("Deployed Height", &format!("{}", height));
}
}
}
Ok(())
}