newton_cli/commands/
utils.rs

1use eyre::Context;
2use newton_data_provider::{DataProviderConfig, WasmExecutor, WasmExecutorConfig};
3use newton_prover_core::config::{NewtonAvsConfig, NewtonAvsConfigBuilder};
4use std::path::PathBuf;
5
6use crate::config::NewtonCliConfig;
7
8/// Execute WASM with the given file path and input JSON
9pub async fn execute_wasm(
10    wasm_file: PathBuf,
11    input_json: String,
12    config: NewtonAvsConfig<NewtonCliConfig>,
13) -> eyre::Result<String> {
14    // Read WASM file
15    let wasm_bytes = std::fs::read(&wasm_file).with_context(|| format!("Failed to read WASM file: {:?}", wasm_file))?;
16
17    // Load DataProviderConfig from data-provider.toml
18    let data_provider_config = NewtonAvsConfigBuilder::new(config.chain_id)
19        .build::<DataProviderConfig>()
20        .map_err(|e| eyre::eyre!("Failed to load data-provider config: {e}"))?;
21
22    // Create executor config from the loaded DataProviderConfig
23    let executor_config = WasmExecutorConfig::from_data_provider_config(&data_provider_config);
24    let executor =
25        WasmExecutor::new(executor_config).map_err(|e| eyre::eyre!("Failed to create WASM executor: {e}"))?;
26
27    // Execute WASM with the input
28    let result = executor
29        .execute_wasm_bytes(&wasm_bytes, &input_json)
30        .await
31        .map_err(|e| eyre::eyre!("WASM execution failed: {e}"))?;
32
33    match result {
34        Ok(output) => Ok(output),
35        Err(error) => {
36            eyre::bail!("WASM execution error: {}", error);
37        }
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use std::{fs, io::Write};
45    use tempfile::TempDir;
46
47    /// Helper to create a test config using the builder
48    /// This will attempt to load from actual config files, which may fail in test environment
49    fn create_test_config(chain_id: u64) -> eyre::Result<NewtonAvsConfig<NewtonCliConfig>> {
50        NewtonAvsConfigBuilder::new(chain_id)
51            .build::<NewtonCliConfig>()
52            .map_err(|e| eyre::eyre!("Failed to build test config: {}", e))
53    }
54
55    /// Helper to create a temporary WASM file with invalid content (for error testing)
56    fn create_invalid_wasm_file(dir: &TempDir) -> PathBuf {
57        let wasm_path = dir.path().join("test.wasm");
58        let mut file = fs::File::create(&wasm_path).unwrap();
59        // Write invalid WASM bytes (not a valid WASM module)
60        file.write_all(b"invalid wasm content").unwrap();
61        wasm_path
62    }
63
64    /// Helper to create a non-existent file path
65    fn create_nonexistent_path() -> PathBuf {
66        PathBuf::from("/nonexistent/path/to/file.wasm")
67    }
68
69    #[tokio::test]
70    async fn test_execute_wasm_file_not_found() {
71        let config = match create_test_config(11155111) {
72            Ok(c) => c,
73            Err(_) => {
74                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
75                return;
76            }
77        };
78        let nonexistent_file = create_nonexistent_path();
79        let input_json = r#"{}"#.to_string();
80
81        let result = execute_wasm(nonexistent_file, input_json, config).await;
82
83        assert!(result.is_err(), "Should fail when WASM file doesn't exist");
84        let error_msg = result.unwrap_err().to_string();
85        assert!(
86            error_msg.contains("Failed to read WASM file") || error_msg.contains("No such file"),
87            "Error message should mention file reading failure. Got: {}",
88            error_msg
89        );
90    }
91
92    #[tokio::test]
93    async fn test_execute_wasm_invalid_wasm_content() {
94        let config = match create_test_config(11155111) {
95            Ok(c) => c,
96            Err(_) => {
97                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
98                return;
99            }
100        };
101        let temp_dir = TempDir::new().unwrap();
102        let wasm_file = create_invalid_wasm_file(&temp_dir);
103        let input_json = r#"{}"#.to_string();
104
105        let result = execute_wasm(wasm_file, input_json, config).await;
106
107        // This should fail either at executor creation or WASM execution
108        assert!(result.is_err(), "Should fail with invalid WASM content");
109        let error_msg = result.unwrap_err().to_string();
110        // The error could be about WASM compilation or execution
111        assert!(
112            error_msg.contains("WASM")
113                || error_msg.contains("wasm")
114                || error_msg.contains("executor")
115                || error_msg.contains("compile"),
116            "Error message should mention WASM-related failure. Got: {}",
117            error_msg
118        );
119    }
120
121    #[tokio::test]
122    async fn test_execute_wasm_empty_input_json() {
123        let config = match create_test_config(11155111) {
124            Ok(c) => c,
125            Err(_) => {
126                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
127                return;
128            }
129        };
130        let temp_dir = TempDir::new().unwrap();
131        let wasm_file = create_invalid_wasm_file(&temp_dir);
132        let input_json = String::new();
133
134        let result = execute_wasm(wasm_file, input_json, config).await;
135
136        // Should still fail due to invalid WASM, but input_json being empty shouldn't cause a different error
137        assert!(result.is_err(), "Should fail with invalid WASM even with empty input");
138    }
139
140    #[tokio::test]
141    async fn test_execute_wasm_valid_json_input() {
142        let config = match create_test_config(11155111) {
143            Ok(c) => c,
144            Err(_) => {
145                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
146                return;
147            }
148        };
149        let temp_dir = TempDir::new().unwrap();
150        let wasm_file = create_invalid_wasm_file(&temp_dir);
151        let input_json = r#"{"key": "value", "number": 42}"#.to_string();
152
153        let result = execute_wasm(wasm_file, input_json, config).await;
154
155        // Should fail due to invalid WASM, but JSON parsing should work
156        assert!(
157            result.is_err(),
158            "Should fail with invalid WASM even with valid JSON input"
159        );
160    }
161
162    #[tokio::test]
163    async fn test_execute_wasm_different_chain_ids() {
164        let temp_dir = TempDir::new().unwrap();
165        let wasm_file = create_invalid_wasm_file(&temp_dir);
166        let input_json = r#"{}"#.to_string();
167
168        // Test with different chain IDs
169        // Note: Some chain IDs may not have complete config files, which is expected
170        for chain_id in [1, 11155111, 17000] {
171            // Use catch_unwind to handle panics from config loading (some chain IDs may not have all required files)
172            let config_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| create_test_config(chain_id)));
173
174            let config = match config_result {
175                Ok(Ok(c)) => c,
176                Ok(Err(e)) => {
177                    eprintln!("Skipping chain_id {}: config loading failed: {}", chain_id, e);
178                    continue;
179                }
180                Err(_) => {
181                    eprintln!(
182                        "Skipping chain_id {}: config loading panicked (missing deployment files)",
183                        chain_id
184                    );
185                    continue;
186                }
187            };
188
189            let result = execute_wasm(wasm_file.clone(), input_json.clone(), config).await;
190
191            // Should fail due to invalid WASM, but config loading should work for supported chain IDs
192            assert!(
193                result.is_err(),
194                "Should fail with invalid WASM for chain_id {}",
195                chain_id
196            );
197        }
198    }
199
200    #[tokio::test]
201    async fn test_execute_wasm_error_context_preserved() {
202        let config = match create_test_config(11155111) {
203            Ok(c) => c,
204            Err(_) => {
205                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
206                return;
207            }
208        };
209        let nonexistent_file = create_nonexistent_path();
210        let input_json = r#"{}"#.to_string();
211
212        let result = execute_wasm(nonexistent_file.clone(), input_json, config).await;
213
214        assert!(result.is_err(), "Should fail when file doesn't exist");
215        let error_msg = result.unwrap_err().to_string();
216        // Verify the error context includes the file path
217        let file_str = nonexistent_file.to_string_lossy();
218        assert!(
219            error_msg.contains("Failed to read WASM file") || error_msg.contains(&*file_str),
220            "Error should include context about the file. Got: {}",
221            error_msg
222        );
223    }
224
225    // Note: Testing successful execution would require a valid WASM component file,
226    // which is complex to create. Integration tests would be better suited for that.
227    // These unit tests focus on error paths and input validation.
228}