mod action;
mod trace;
mod transaction;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use serde_json;
use tracing::{debug, trace, warn};
use transaction::TransactionError;
use crate::{
contract, ABISerializable, APIClient, AccountName, ActionName, Asset, Bytes, JsonValue, Name, PrivateKey
};
extern crate self as kudu;
pub type Map<K, V> = BTreeMap<K, V>;
pub type Set<T> = BTreeSet<T>;
pub trait Contract: ABISerializable {
fn account() -> AccountName;
fn name() -> ActionName;
}
pub use action::{Action, ActionError, IntoPermissionVec, PermissionLevel};
pub use trace::{
AccountAuthSequence, AccountDelta,
ActionReceipt, ActionReceiptV0,
ActionTrace, ActionTraceV0, ActionTraceV1,
PackedTransactionV0,
Trace,
TransactionTrace, TransactionTraceV0, TransactionTraceException, TransactionTraceMsg,
};
pub use transaction::{SignedTransaction, Transaction};
#[derive(Clone, Debug, PartialEq, Eq, ABISerializable, Serialize, Deserialize)]
#[contract(account="eosio.token", name="transfer")]
pub struct Transfer {
pub from: Name,
pub to: Name,
pub quantity: Asset,
pub memo: String,
}
pub const DEBUG: usize = 0;
pub const WARN: usize = 1;
pub fn nodeos_log<const N: usize>(response: &JsonValue, console_output: &[String]) {
if console_output.is_empty() { return; }
let tx_id = response["transaction_id"].as_str().unwrap();
if N == DEBUG { debug!("Console output for tx: {}", tx_id); }
else if N == WARN { warn!("Console output for tx: {}", tx_id); }
for output in console_output {
for line in output.lines() {
if N == DEBUG { debug!(line); }
else if N == WARN { warn!(line); }
}
}
}
pub fn parse_trace(response: &JsonValue) -> Result<Vec<String>, Vec<String>> {
trace!("parse trace: {}", serde_json::to_string_pretty(response).unwrap());
let mut lines = vec![];
if let Some(processed) = response.get("processed") {
for trace in processed["action_traces"].as_array().unwrap().iter() {
if let Some(output) = trace.get("console") {
if !output.as_str().unwrap().is_empty() {
lines.push(output.to_string());
}
}
for inline_trace in trace["inline_traces"].as_array().unwrap().iter() {
if let Some(output) = inline_trace.get("console") {
if !output.as_str().unwrap().is_empty() {
lines.push(output.to_string());
}
}
}
}
Ok(lines)
}
else if let Some(error) = response.get("error") {
let msg = &error["details"][0]["message"];
lines.push(error["what"].to_string());
lines.push(msg.to_string());
Err(lines)
}
else {
lines.push(format!("Unhandled case!! {}", serde_json::to_string_pretty(response).unwrap()));
Err(lines)
}
}
pub fn log_tx_trace(response: &JsonValue) -> Result<(), TransactionError> {
match parse_trace(response) {
Ok(lines) => {
nodeos_log::<DEBUG>(response, &lines);
Ok(())
},
Err(lines) => {
nodeos_log::<WARN>(response, &lines);
Err(TransactionError::NodeosError { message: lines.join("\n") })
},
}
}
pub fn push_action(
client: Arc<APIClient>,
actor: Name,
signing_key: &PrivateKey,
contract: Name,
action: Name,
args: &JsonValue,
) -> Result<(), TransactionError>
{
debug!("PUSH ACTION: {actor} {contract} {action} {args}");
let action = Action {
account: contract,
name: action,
authorization: vec![PermissionLevel { actor, permission: Name::constant("active") }],
data: Bytes::new(),
}
.with_data(args)?;
let mut tx = Transaction::new(vec![action]);
tx.link(client)?;
let signed_tx = tx.sign(signing_key)?;
let result = signed_tx.send_unchecked()?;
log_tx_trace(&result)
}
#[cfg(test)]
mod tests {
use super::*;
use kudu::{PrivateKey, json};
#[allow(non_snake_case)]
fn N(name: &str) -> Name { Name::constant(name) }
#[test]
#[ignore] fn test_push_action() -> Result<(), TransactionError> {
kudu::tracing_init();
let client = APIClient::local();
let key = PrivateKey::eosio_dev();
push_action(client, N("eosio"), &key, N("eosio.token"), N("transfer"), &json!({
"from": "eosio",
"to": "eosio.token",
"quantity": "1.0000 SYS",
"memo": "here's a sys for you!"
}))?;
Ok(())
}
}