diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 94df670..3c0c667 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -6,7 +6,7 @@ on: pull_request: branches: [main] schedule: - - cron: '0 2 * * *' # Daily at 2 AM UTC (nightly) + - cron: "0 2 * * *" # Daily at 2 AM UTC (nightly) env: CARGO_TERM_COLOR: always @@ -15,7 +15,7 @@ jobs: integration-tests: name: Run Integration Tests runs-on: ubuntu-latest - timeout-minutes: 15 # Generous timeout for test completion + timeout-minutes: 15 # Generous timeout for test completion steps: - name: Checkout repository @@ -33,6 +33,22 @@ jobs: ~/.cargo/git target + - name: Install system deps for tx3up + run: sudo apt-get update && sudo apt-get install -y xz-utils + + - name: Install tx3 toolchain + run: | + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/tx3-lang/up/releases/latest/download/tx3up-installer.sh | sh + tx3up + tx3up show + if [ -x "$HOME/.tx3/bin/tx3c" ]; then + "$HOME/.tx3/bin/tx3c" --version + echo "$HOME/.tx3/bin" >> "$GITHUB_PATH" + else + echo "tx3c not found at $HOME/.tx3/bin/tx3c" >&2 + exit 1 + fi + - name: Run error case tests working-directory: ./sdk env: @@ -49,10 +65,6 @@ jobs: TRP_API_KEY_PREPROD: ${{ secrets.TRP_API_KEY_PREPROD }} run: cargo test --test happy_path -- --nocapture - - name: Test summary - if: always() - run: | - echo "=== Integration Tests Summary ===" - echo "Error case tests: Complete" - echo "Happy path test: Complete" - echo "All TRP integration tests executed successfully" + - name: Run tx3c codegen test + working-directory: ./sdk + run: cargo test --test codegen diff --git a/.trix/client-lib/Cargo.toml.hbs b/.trix/client-lib/Cargo.toml.hbs index fa3247c..fce89f3 100644 --- a/.trix/client-lib/Cargo.toml.hbs +++ b/.trix/client-lib/Cargo.toml.hbs @@ -1,13 +1,12 @@ [package] -name = "{{protocolName}}" -version = "{{protocolVersion}}" -edition = "2024" +name = "{{tii.protocol.name}}" +version = "{{tii.protocol.version}}" +edition = "2021" [dependencies] -tx3-sdk = { version = "^0.9" } +tx3-sdk = { version = "^0.9.2" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -once_cell = "1.17" [lib] -path = "lib.rs" \ No newline at end of file +path = "lib.rs" diff --git a/.trix/client-lib/lib.rs.hbs b/.trix/client-lib/lib.rs.hbs index c8ebdf5..44f6668 100644 --- a/.trix/client-lib/lib.rs.hbs +++ b/.trix/client-lib/lib.rs.hbs @@ -1,84 +1,50 @@ // This file is auto-generated. -use std::collections::HashMap; -use serde::{Serialize, Deserialize}; - -pub use tx3_sdk::trp::ClientOptions; -use tx3_sdk::core::{TirEnvelope, BytesEncoding}; -use tx3_sdk::trp::{ResolveParams, TxEnvelope, SubmitParams, SubmitResponse}; - -pub const DEFAULT_TRP_ENDPOINT: &str = "{{trpEndpoint}}"; - -pub const DEFAULT_HEADERS: &[(&str, &str)] = &[ -{{#each headers}} - ("{{@key}}", "{{this}}"), -{{/each}} -]; - -{{#each transactions}} -pub const {{constantCase constant_name}}: &str = "{{ir_bytes}}"; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct {{pascalCase params_name}} { -{{#each parameters}} - pub {{snakeCase name}}: String, -{{/each}} -} -impl {{pascalCase params_name}} { - fn to_map(&self) -> serde_json::Map { - let mut map = serde_json::Map::new(); - - {{#each parameters}} - map.insert("{{snakeCase name}}".to_string(), serde_json::json!(&self.{{snakeCase name}})); +use serde::Serialize; +use serde_json::{json, Value}; +use tx3_sdk::core::{Address, ArgMap, TirEncoding, TirEnvelope, UtxoRef}; + +pub const PROTOCOL_NAME: &str = "{{tii.protocol.name}}"; +pub const PROTOCOL_VERSION: &str = "{{tii.protocol.version}}"; + +pub fn profiles() -> Value { + json!({ + {{#each tii.profiles}} + "{{@key}}": { + "environment": {{{environment}}}, + "parties": {{{parties}}} + }{{#unless @last}},{{/unless}} {{/each}} - - map.into() - } + }) } +{{#each tii.transactions}} +pub const {{constantCase @key}}_TIR: TirEnvelope = TirEnvelope { + content: "{{tir.content}}".to_string(), + encoding: TirEncoding::Hex, + version: "{{tir.version}}".to_string(), +}; + +#[derive(Debug, Clone, Serialize)] +pub struct {{pascalCase @key}}Params { +{{#each params.properties}} + pub {{snakeCase @key}}: {{schemaTypeFor this "rust"}}, {{/each}} -pub struct Client { - client: tx3_sdk::trp::Client, } -impl Client { - pub fn new(options: ClientOptions) -> Self { - Self { - client: tx3_sdk::trp::Client::new(options), - } - } - - pub fn with_default_options() -> Self { - let mut headers = HashMap::new(); - for (key, value) in DEFAULT_HEADERS { - headers.insert(key.to_string(), value.to_string()); - } - - Self::new(ClientOptions { - endpoint: DEFAULT_TRP_ENDPOINT.to_string(), - headers: Some(headers), - }) - } -{{#each transactions}} - - pub async fn {{snakeCase function_name}}(&self, args: {{pascalCase params_name}}) -> Result { - let tir_info = TirEnvelope { - content: {{constantCase constant_name}}.to_string(), - encoding: BytesEncoding::Hex, - version: "{{ir_version}}".to_string(), - }; +impl From<{{pascalCase @key}}Params> for ArgMap { + fn from(args: {{pascalCase @key}}Params) -> Self { + let mut map = ArgMap::new(); - self.client.resolve(ResolveParams { - tir: tir_info, - args: args.to_map(), - }).await - } -{{/each}} + {{#each params.properties}} + map.insert( + "{{@key}}".to_string(), + serde_json::to_value(&args.{{snakeCase @key}}) + .expect("failed to serialize tx arg"), + ); + {{/each}} - pub async fn submit(&self, params: SubmitParams) -> Result { - self.client.submit(params).await + map } } - -// Create a default client instance -pub static PROTOCOL: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| Client::with_default_options()); +{{/each}} diff --git a/bindgen/Cargo.toml.hbs b/bindgen/Cargo.toml.hbs deleted file mode 100644 index 7c1b15b..0000000 --- a/bindgen/Cargo.toml.hbs +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "{{protocolName}}" -version = "{{protocolVersion}}" -edition = "2024" - -[dependencies] -tx3-sdk = { version = "^0" } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -once_cell = "1.17" - -[lib] -path = "lib.rs" diff --git a/bindgen/lib.rs.hbs b/bindgen/lib.rs.hbs deleted file mode 100644 index f3cdedc..0000000 --- a/bindgen/lib.rs.hbs +++ /dev/null @@ -1,95 +0,0 @@ -// This file is auto-generated. - -use std::collections::HashMap; -use serde::{Serialize, Deserialize}; - -pub use tx3_sdk::trp::{ClientOptions,ArgValue}; -use tx3_sdk::trp::{ProtoTxRequest, TirInfo, TxEnvelope, SubmitParams, SubmitResponse, SubmitWitness}; - -pub const DEFAULT_TRP_ENDPOINT: &str = "{{trpEndpoint}}"; - -pub const DEFAULT_HEADERS: &[(&str, &str)] = &[ -{{#each headers}} - ("{{@key}}", "{{this}}"), -{{/each}} -]; - -pub const DEFAULT_ENV_ARGS: &[(&str, &str)] = &[ -{{#each envArgs}} - ("{{@key}}", "{{this}}"), -{{/each}} -]; - -{{#each transactions}} -pub const {{constantCase constant_name}}: &str = "{{ir_bytes}}"; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct {{pascalCase params_name}} { -{{#each parameters}} - pub {{snakeCase name}}: ArgValue, -{{/each}} -} -impl {{pascalCase params_name}} { - fn to_map(&self) -> HashMap { - let mut map = HashMap::new(); - - {{#each parameters}} - map.insert("{{snakeCase name}}".to_string(), self.{{snakeCase name}}.clone()); - {{/each}} - - map - } -} - -{{/each}} -pub struct Client { - client: tx3_sdk::trp::Client, -} - -impl Client { - pub fn new(options: ClientOptions) -> Self { - Self { - client: tx3_sdk::trp::Client::new(options), - } - } - - pub fn with_default_options() -> Self { - let mut headers = HashMap::new(); - for (key, value) in DEFAULT_HEADERS { - headers.insert(key.to_string(), value.to_string()); - } - - let mut env_args: HashMap = HashMap::new(); - for (key, value) in DEFAULT_ENV_ARGS { - env_args.insert(key.to_string(), ArgValue::String(value.to_string())); - } - - Self::new(ClientOptions { - endpoint: DEFAULT_TRP_ENDPOINT.to_string(), - headers: Some(headers), - env_args: Some(env_args), - }) - } -{{#each transactions}} - - pub async fn {{snakeCase function_name}}(&self, args: {{pascalCase params_name}}) -> Result { - let tir_info = TirInfo { - bytecode: {{constantCase constant_name}}.to_string(), - encoding: "hex".to_string(), - version: "{{ir_version}}".to_string(), - }; - - self.client.resolve(ProtoTxRequest { - tir: tir_info, - args: args.to_map(), - }).await - } -{{/each}} - - pub async fn submit(&self, tx: TxEnvelope, witnesses: Vec) -> Result { - self.client.submit(tx, witnesses).await - } -} - -// Create a default client instance -pub static PROTOCOL: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| Client::with_default_options()); diff --git a/sdk/tests/codegen.rs b/sdk/tests/codegen.rs new file mode 100644 index 0000000..1cca749 --- /dev/null +++ b/sdk/tests/codegen.rs @@ -0,0 +1,79 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; + +fn resolve_tx3c_path() -> Option { + if let Ok(path) = env::var("TX3_TX3C_PATH") { + let path = PathBuf::from(path); + if path.is_file() { + return Some(path); + } + } + + None +} + +fn unique_output_dir() -> PathBuf { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be available") + .as_nanos(); + env::temp_dir().join(format!("tx3c_codegen_test_{now}")) +} + +fn assert_file_exists(path: &Path) { + assert!(path.is_file(), "Expected file to exist: {}", path.display()); +} + +#[test] +fn test_tx3c_codegen_client_lib_template() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let tii_path = PathBuf::from(manifest_dir).join("../examples/transfer.tii"); + let template_dir = PathBuf::from(manifest_dir).join("../.trix/client-lib"); + + assert!( + tii_path.is_file(), + "Missing TII file: {}", + tii_path.display() + ); + assert!( + template_dir.is_dir(), + "Missing template directory: {}", + template_dir.display() + ); + + let output_dir = unique_output_dir(); + + let mut cmd = if let Some(tx3c_path) = resolve_tx3c_path() { + Command::new(tx3c_path) + } else { + Command::new("tx3c") + }; + + let output = cmd + .arg("codegen") + .arg("--tii") + .arg(&tii_path) + .arg("--template") + .arg(&template_dir) + .arg("--output") + .arg(&output_dir) + .output() + .expect("Failed to execute tx3c. Ensure tx3c is available."); + + if !output.status.success() { + panic!( + "tx3c codegen failed.\nSTDOUT:\n{}\nSTDERR:\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + + assert!(output_dir.is_dir(), "Output directory not created"); + assert_file_exists(&output_dir.join("Cargo.toml")); + assert_file_exists(&output_dir.join("lib.rs")); + + let _ = fs::remove_dir_all(&output_dir); +}