ci: add IDL freshness check and consolidate artifacts

Move IDL files to artifacts/ and add a convention-based CI check that
discovers all programs via */methods/guest/src/bin/*.rs and fails if
any program is missing its IDL or has one that is out of date.
This commit is contained in:
r4bbit 2026-04-09 19:29:03 +02:00
parent 9824cd8f90
commit 94f14ae305
10 changed files with 163 additions and 17 deletions

View File

@ -116,3 +116,48 @@ jobs:
run: cargo test -p integration_tests
env:
RISC0_DEV_MODE: 1
check-idl:
name: Check IDL
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.94.0"
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Build idl-gen
run: cargo build -p idl-gen --release
- name: Check IDL files are present and up to date
run: |
set -e
failed=0
for src in $(find . -path '*/methods/guest/src/bin/*.rs' | sort); do
program=$(basename "$src" .rs)
idl="artifacts/${program}-idl.json"
if [ ! -f "$idl" ]; then
echo "Missing IDL for '${program}' — generate with: cargo run -p idl-gen -- ${src#./} > ${idl}"
failed=1
continue
fi
./target/release/idl-gen "$src" > /tmp/${program}-idl.json
if ! diff "$idl" /tmp/${program}-idl.json > /dev/null 2>&1; then
echo "IDL for '${program}' is out of date — regenerate with: cargo run -p idl-gen -- ${src#./} > ${idl}"
failed=1
fi
done
exit $failed

View File

@ -35,11 +35,24 @@ Built binaries output to: `<program>/methods/guest/target/riscv32im-risc0-zkvm-e
## IDL Generation
Using the `idl-gen` crate (no external toolchain required — this is what CI uses):
```bash
spel generate-idl token/methods/guest/src/bin/token.rs > token/token-idl.json
spel generate-idl amm/methods/guest/src/bin/amm.rs > amm/amm-idl.json
cargo run -p idl-gen -- token/methods/guest/src/bin/token.rs > artifacts/token-idl.json
cargo run -p idl-gen -- amm/methods/guest/src/bin/amm.rs > artifacts/amm-idl.json
cargo run -p idl-gen -- ata/methods/guest/src/bin/ata.rs > artifacts/ata-idl.json
```
Alternatively, using the `spel` CLI (requires the SPEL toolchain):
```bash
spel generate-idl token/methods/guest/src/bin/token.rs > artifacts/token-idl.json
spel generate-idl amm/methods/guest/src/bin/amm.rs > artifacts/amm-idl.json
spel generate-idl ata/methods/guest/src/bin/ata.rs > artifacts/ata-idl.json
```
Generated IDL files live in `artifacts/`. CI checks that every program under `*/methods/guest/src/bin/` has a corresponding `artifacts/<program>-idl.json` that matches the source.
## Deployment
`wallet` and `spel` are CLI tools that ship with the [SPEL](https://github.com/logos-co/spel.git) toolchain. `wallet` requires `NSSA_WALLET_HOME_DIR` to point to a directory containing the wallet config.

62
Cargo.lock generated
View File

@ -433,7 +433,7 @@ dependencies = [
"maybe-async",
"reqwest",
"serde",
"thiserror",
"thiserror 2.0.18",
]
[[package]]
@ -539,7 +539,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
"thiserror",
"thiserror 2.0.18",
]
[[package]]
@ -604,7 +604,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
"thiserror 2.0.18",
]
[[package]]
@ -1510,6 +1510,14 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idl-gen"
version = "0.1.0"
dependencies = [
"serde_json",
"spel-framework-core",
]
[[package]]
name = "idna"
version = "1.1.0"
@ -1828,7 +1836,7 @@ dependencies = [
"serde",
"serde_with",
"sha2",
"thiserror",
"thiserror 2.0.18",
]
[[package]]
@ -1845,7 +1853,7 @@ dependencies = [
"risc0-zkvm",
"serde",
"serde_with",
"thiserror",
"thiserror 2.0.18",
]
[[package]]
@ -2133,7 +2141,7 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2",
"thiserror",
"thiserror 2.0.18",
"tokio",
"tracing",
"web-time",
@ -2154,7 +2162,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
"thiserror",
"thiserror 2.0.18",
"tinyvec",
"tracing",
"web-time",
@ -2271,7 +2279,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.17",
"libredox",
"thiserror",
"thiserror 2.0.18",
]
[[package]]
@ -2744,7 +2752,7 @@ dependencies = [
"sha2",
"strum",
"tempfile",
"thiserror",
"thiserror 2.0.18",
"toml",
"yaml-rust2",
]
@ -2952,6 +2960,20 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "spel-framework-core"
version = "0.1.0"
source = "git+https://github.com/logos-co/spel.git?rev=57201f64b4542bb3f592bc9a0aa654c47aa908a6#57201f64b4542bb3f592bc9a0aa654c47aa908a6"
dependencies = [
"borsh",
"nssa_core",
"serde",
"serde_json",
"sha2",
"syn 2.0.117",
"thiserror 1.0.69",
]
[[package]]
name = "spin"
version = "0.9.8"
@ -3072,13 +3094,33 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
"thiserror-impl 2.0.18",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]

View File

@ -10,6 +10,7 @@ members = [
"ata",
"ata/methods",
"integration_tests",
"tools/idl-gen",
]
exclude = [
"token/methods/guest",

View File

@ -76,17 +76,29 @@ spel inspect <path-to-binary>
The IDL describes the program's instructions and can be used to interact with a deployed program.
**Using the `idl-gen` crate** (no external toolchain required — this is what CI uses):
```bash
# Example
spel generate-idl token/methods/guest/src/bin/token.rs > token/token-idl.json
spel generate-idl amm/methods/guest/src/bin/amm.rs > amm/amm-idl.json
cargo run -p idl-gen -- token/methods/guest/src/bin/token.rs > artifacts/token-idl.json
cargo run -p idl-gen -- amm/methods/guest/src/bin/amm.rs > artifacts/amm-idl.json
cargo run -p idl-gen -- ata/methods/guest/src/bin/ata.rs > artifacts/ata-idl.json
```
**Using the `spel` CLI** (requires the SPEL toolchain):
```bash
spel generate-idl token/methods/guest/src/bin/token.rs > artifacts/token-idl.json
spel generate-idl amm/methods/guest/src/bin/amm.rs > artifacts/amm-idl.json
spel generate-idl ata/methods/guest/src/bin/ata.rs > artifacts/ata-idl.json
```
Generated IDL files are committed under `artifacts/`. CI will fail if a program's IDL is missing or out of date.
### Invoke Instructions
Use `spel --idl <IDL> <INSTRUCTION> [ARGS...]` to call a deployed program instruction:
```bash
spel --idl token/token-idl.json <instruction> [args...]
spel --idl amm/amm-idl.json <instruction> [args...]
spel --idl artifacts/token-idl.json <instruction> [args...]
spel --idl artifacts/amm-idl.json <instruction> [args...]
```

14
tools/idl-gen/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "idl-gen"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "idl-gen"
path = "src/main.rs"
[dependencies]
spel-framework-core = { git = "https://github.com/logos-co/spel.git", rev = "57201f64b4542bb3f592bc9a0aa654c47aa908a6", features = [
"idl-gen",
] }
serde_json = "1.0"

19
tools/idl-gen/src/main.rs Normal file
View File

@ -0,0 +1,19 @@
use std::{path::PathBuf, process};
fn main() {
let path: PathBuf = match std::env::args().nth(1) {
Some(p) => PathBuf::from(p),
None => {
eprintln!("Usage: idl-gen <source-file>");
process::exit(1);
}
};
match spel_framework_core::idl_gen::generate_idl_from_file(&path) {
Ok(idl) => println!("{}", serde_json::to_string_pretty(&idl).unwrap()),
Err(e) => {
eprintln!("Error: {e}");
process::exit(1);
}
}
}