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
343 lines
10 KiB
Rust
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(())
|
|
}
|