wasm_host_simulator/
main.rs

1extern crate core;
2
3mod data_provider;
4mod decoding;
5mod hashing;
6mod host_functions_wamr;
7mod mock_data;
8mod vm_wamr;
9
10use crate::mock_data::MockData;
11use clap::Parser;
12use env_logger::Builder;
13use log::LevelFilter;
14use log::{debug, error, info};
15use std::fs;
16use std::io::Write;
17use std::path::PathBuf;
18
19/// Wasm WASM testing utility
20#[derive(Parser, Debug)]
21#[command(version, about, long_about = None)]
22struct Args {
23    /// Path to the WASM file
24    #[arg(long)]
25    dir: Option<String>,
26
27    /// Test case to run (success/failure)
28    #[arg(short, long, default_value = "success")]
29    test_case: String,
30
31    /// Project name
32    #[arg(short, long)]
33    project: String,
34
35    /// Verbose logging
36    #[arg(short, long)]
37    verbose: bool,
38
39    /// Function to run in the WASM module
40    #[arg(long, default_value = "finish")]
41    function: String,
42
43    /// Gas cap can be changed for debugging.
44    /// Note that default value is the default used in rippled.
45    #[arg(long, default_value = "1000000")]
46    gas_cap: u32,
47}
48
49#[allow(clippy::type_complexity)]
50fn load_test_data(
51    dir: Option<String>,
52    project: &str,
53    test_case: &str,
54) -> Result<(String, String, String, String, String), Box<dyn std::error::Error>> {
55    // Convention: fixtures must be in projects/<project>/fixtures/<test_case>/
56    let base_path = if let Some(dir) = dir {
57        PathBuf::from(dir).join("fixtures").join(test_case)
58    } else {
59        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
60            .parent()
61            .unwrap()
62            .join("projects")
63            .join(project)
64            .join("fixtures")
65            .join(test_case)
66    };
67
68    if !base_path.exists() {
69        return Err(format!(
70            "Test case '{}' not found at expected location: {}",
71            test_case,
72            base_path.display()
73        )
74        .into());
75    }
76
77    let tx_path = base_path.join("tx.json");
78    let lo_path = base_path.join("ledger_object.json");
79    let lh_path = base_path.join("ledger_header.json");
80    let l_path = base_path.join("ledger.json");
81    let nfts_path = base_path.join("nfts.json");
82
83    let tx_json = fs::read_to_string(tx_path)?;
84    let lo_json = fs::read_to_string(lo_path)?;
85    let lh_json = fs::read_to_string(lh_path)?;
86    let l_json = fs::read_to_string(l_path)?;
87    let nft_json = fs::read_to_string(nfts_path)?;
88
89    Ok((tx_json, lo_json, lh_json, l_json, nft_json))
90}
91
92fn main() -> Result<(), Box<dyn std::error::Error>> {
93    let args = Args::parse();
94
95    let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
96        .parent()
97        .unwrap()
98        .join("projects");
99
100    if !base_path.exists() {
101        eprintln!(
102            "Error: Could not find projects directory at expected location: {}",
103            base_path.display()
104        );
105        std::process::exit(1);
106    }
107
108    let wasm_file = base_path
109        .join("target/wasm32v1-none/debug")
110        .join(format!("{}.wasm", args.project))
111        .to_string_lossy()
112        .to_string();
113
114    // Initialize logger with appropriate level
115    let log_level = if args.verbose {
116        LevelFilter::Debug
117    } else {
118        LevelFilter::Info
119    };
120
121    Builder::new()
122        .format(|buf, record| {
123            writeln!(
124                buf,
125                "[{} {}] {}",
126                record.level(),
127                record.target(),
128                record.args()
129            )
130        })
131        .filter(None, log_level)
132        .init();
133
134    info!("Starting Wasm host application {:?}", args);
135    info!("Target function: {} (default is 'finish')", args.function);
136    info!("Using test case: {}", args.test_case);
137    info!("Project: {}", args.project);
138    info!(
139        "Source Directory: {}",
140        args.dir.as_deref().unwrap_or("default")
141    );
142    info!("Loading test data from fixtures");
143    let (tx_json, lo_json, lh_json, l_json, nft_json) =
144        match load_test_data(args.dir, &args.project, &args.test_case) {
145            Ok((tx, lo, lh, l, nft)) => {
146                debug!("Test data loaded successfully");
147                (tx, lo, lh, l, nft)
148            }
149            Err(e) => {
150                error!("Failed to load test data: {}", e);
151                return Err(e);
152            }
153        };
154
155    let data_source = MockData::new(&tx_json, &lo_json, &lh_json, &l_json, &nft_json);
156    info!("Executing function: {}", args.function);
157    // TODO: Make Gas Cap optional via https://github.com/ripple/craft/issues/141
158    match vm_wamr::run_func(wasm_file, &args.function, Some(args.gas_cap), data_source) {
159        Ok(result) => {
160            if (result && args.test_case == "success") || (!result && args.test_case == "failure") {
161                println!("-------------------------------------------------");
162                println!("| WASM FUNCTION EXECUTION RESULT                |");
163                println!("-------------------------------------------------");
164                println!("| Function:   {:<33} |", args.function);
165                println!("| Test Case:  {:<33} |", args.test_case);
166                println!("| Result:     {:<33} |", result);
167                println!("-------------------------------------------------");
168                info!("Function completed with result: {}", result);
169            } else {
170                println!("-------------------------------------------------");
171                println!("| WASM FUNCTION EXECUTION RESULT                |");
172                println!("-------------------------------------------------");
173                println!("| Function:   {:<33} |", args.function);
174                println!("| Test Case:  {:<33} |", args.test_case);
175                println!("| Result:     {:<33} |", result);
176                println!("-------------------------------------------------");
177                info!("Function completed with result: {}", result);
178                return Err("Function result did not match expected outcome".into());
179            }
180        }
181        Err(e) => {
182            println!("-------------------------------------------------");
183            println!("| WASM FUNCTION EXECUTION ERROR                 |");
184            println!("-------------------------------------------------");
185            println!("| Function:   {:<33} |", args.function);
186            println!("| Test Case:  {:<33} |", args.test_case);
187            println!("| Error:      {:<33} |", e);
188            println!("-------------------------------------------------");
189            error!("Function execution failed: {}", e);
190            return Err(Box::new(e));
191        }
192    }
193
194    info!("Wasm host application execution completed");
195    Ok(())
196}