diff --git a/Cargo.lock b/Cargo.lock index 0441527..9dba891 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -759,6 +759,16 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "cargo_toml" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88da5a13c620b4ca0078845707ea9c3faf11edbc3ffd8497d11d686211cd1ac0" +dependencies = [ + "serde", + "toml", +] + [[package]] name = "cc" version = "1.2.16" @@ -830,6 +840,160 @@ dependencies = [ "inout", ] +[[package]] +name = "codama" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c46db268f2c816f7a6834c0fff1fc0320450ef5d81975bbf0a6eeb52ef9c93ca" +dependencies = [ + "codama-errors", + "codama-korok-plugins", + "codama-korok-visitors", + "codama-koroks", + "codama-macros", + "codama-nodes", + "codama-stores", + "proc-macro2", +] + +[[package]] +name = "codama-attributes" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404f1560d84ee21be65bedc9a25ab1e8a41a862aaaa9e31b3f7e909cd0f8bbdf" +dependencies = [ + "codama-errors", + "codama-nodes", + "codama-syn-helpers", + "derive_more", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "codama-errors" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e56c7fdd1cfabadd8f2535b3901dc5bce44dffd2299ac4249285a95d9922840" +dependencies = [ + "cargo_toml", + "proc-macro2", + "serde_json", + "syn 2.0.90", + "thiserror 2.0.17", +] + +[[package]] +name = "codama-korok-plugins" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7463a0931cfd90ae69f681aca23e7e93e0e6bc2dad1028e11c9051803ad6567f" +dependencies = [ + "codama-errors", + "codama-korok-visitors", + "codama-koroks", +] + +[[package]] +name = "codama-korok-visitors" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad5f926bb3ea0ebe38992f4395dfb87363323083939b7a09560878503322e3c" +dependencies = [ + "cargo_toml", + "codama-attributes", + "codama-errors", + "codama-koroks", + "codama-nodes", + "codama-syn-helpers", + "serde_json", + "syn 2.0.90", +] + +[[package]] +name = "codama-koroks" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8816dac083eeafabfec24b23553a504fe349f6eedf4c387679f73a501cfe8710" +dependencies = [ + "codama-attributes", + "codama-errors", + "codama-nodes", + "codama-stores", + "codama-syn-helpers", + "derive_more", + "proc-macro2", + "syn 2.0.90", +] + +[[package]] +name = "codama-macros" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2545c465cfe9f0ff96f33c8b7ef58db212e307afaff56e5274379e2b9b3f9e7e" +dependencies = [ + "codama-attributes", + "codama-errors", + "codama-koroks", + "codama-stores", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "codama-nodes" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f6df08db9a0a16da729816c8de1ad670efb300683955abd7b0de07899c3486d" +dependencies = [ + "codama-errors", + "codama-nodes-derive", + "derive_more", + "serde", + "serde_json", +] + +[[package]] +name = "codama-nodes-derive" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238404cfc7c05c8f62207a909be22e4840f27f69cce61ee940ce1e27a346c797" +dependencies = [ + "codama-errors", + "codama-syn-helpers", + "derive_more", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "codama-stores" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f60573db8f7b599b4028ecfeebf86cfc3291d157c05e43872e0dc80a5fad009" +dependencies = [ + "cargo_toml", + "codama-errors", + "proc-macro2", + "syn 2.0.90", +] + +[[package]] +name = "codama-syn-helpers" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cf58f8fea7cc924b4c91e55536a5c77a1fce2059905c8f05d167cb4ec3bced" +dependencies = [ + "codama-errors", + "derive_more", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -1156,6 +1320,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "difflib" version = "0.4.0" @@ -3787,6 +3971,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6665,8 +6858,11 @@ name = "spl-record" version = "0.4.0" dependencies = [ "bytemuck", + "codama", + "codama-korok-plugins", "num-derive", "num-traits", + "serde_json", "solana-account-info", "solana-instruction", "solana-keypair", @@ -7079,11 +7275,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -7092,6 +7303,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] diff --git a/program/Cargo.toml b/program/Cargo.toml index a51283f..2f82b95 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -9,9 +9,11 @@ edition = "2021" [features] no-entrypoint = [] +codama = ["dep:codama"] [dependencies] bytemuck = { version = "1.23.1", features = ["derive"] } +codama = { version = "0.6.4", optional = true } num-derive = "0.4" num-traits = "0.2" solana-account-info = "3.0.0" @@ -32,6 +34,11 @@ solana-system-interface = "2" solana-transaction = "3.0.0" solana-transaction-error = "3.0.0" +[build-dependencies] +codama = "0.6.4" +codama-korok-plugins = "0.6.4" +serde_json = "1.0" + [lib] crate-type = ["cdylib", "lib"] diff --git a/program/build.rs b/program/build.rs new file mode 100644 index 0000000..c85923f --- /dev/null +++ b/program/build.rs @@ -0,0 +1,55 @@ +//! Codama IDL build script. + +use { + codama::Codama, + codama_korok_plugins::DefaultPlugin, + std::{env, fs, path::Path}, +}; + +fn main() { + // Run the build script if the source files have changed, or if the + // developer provides the GENERATE_IDL environment variable. + // + // ``` + // `GENERATE_IDL=1 cargo build` + // ``` + // + // The environment variable approach is useful if the local Codama has been + // updated. + println!("cargo:rerun-if-changed=src/"); + println!("cargo:rerun-if-env-changed=GENERATE_IDL"); + + if let Err(e) = generate_idl() { + println!("cargo:warning=Failed to generate IDL: {}", e); + } +} + +fn generate_idl() -> Result<(), Box> { + let manifest_dir = env::var("CARGO_MANIFEST_DIR")?; + let crate_path = Path::new(&manifest_dir); + + let codama = Codama::load(crate_path)? + .without_default_plugin() + .add_plugin(DefaultPlugin); // Standard parsing + + let idl_json = codama.get_json_idl()?; + + // Parse and format the JSON with pretty printing. + let parsed: serde_json::Value = serde_json::from_str(&idl_json)?; + let mut formatted_json = serde_json::to_string_pretty(&parsed)?; + + // Add newline at the end to match standard formatting. + formatted_json.push('\n'); + + // Define output directory + let out_dir = Path::new(&manifest_dir).join("idl"); + fs::create_dir_all(&out_dir)?; + + // Write to `spl_record.json` + let idl_path = out_dir.join("spl_record.json"); + fs::write(&idl_path, formatted_json)?; + + println!("cargo:warning=IDL written to: {}", idl_path.display()); + + Ok(()) +} diff --git a/program/idl.json b/program/idl/spl_record.json similarity index 76% rename from program/idl.json rename to program/idl/spl_record.json index 912989f..2df838b 100644 --- a/program/idl.json +++ b/program/idl/spl_record.json @@ -1,30 +1,22 @@ { + "additionalPrograms": [], "kind": "rootNode", - "standard": "codama", - "version": "1.0.0", "program": { - "kind": "programNode", - "name": "splRecord", - "publicKey": "recr1L3PCGKLbckBqMNcJhuuyU1zgo8nBhfLVsJNwr5", - "version": "0.4.0", "accounts": [ { - "kind": "accountNode", - "name": "recordData", "data": { - "kind": "structTypeNode", "fields": [ { + "defaultValue": { + "kind": "numberValueNode", + "number": 1 + }, "kind": "structFieldTypeNode", "name": "version", "type": { - "kind": "numberTypeNode", + "endian": "le", "format": "u8", - "endian": "le" - }, - "defaultValue": { - "kind": "numberValueNode", - "number": 1 + "kind": "numberTypeNode" } }, { @@ -34,7 +26,8 @@ "kind": "publicKeyTypeNode" } } - ] + ], + "kind": "structTypeNode" }, "discriminators": [ { @@ -42,40 +35,55 @@ "name": "version", "offset": 0 } - ] + ], + "kind": "accountNode", + "name": "recordData" + } + ], + "definedTypes": [], + "errors": [ + { + "code": 0, + "kind": "errorNode", + "message": "Incorrect authority provided on update or delete", + "name": "incorrectAuthority" + }, + { + "code": 1, + "kind": "errorNode", + "message": "Calculation overflow", + "name": "overflow" } ], "instructions": [ { - "kind": "instructionNode", - "name": "initialize", "accounts": [ { - "kind": "instructionAccountNode", - "name": "recordAccount", + "isSigner": false, "isWritable": true, - "isSigner": false + "kind": "instructionAccountNode", + "name": "recordAccount" }, { - "kind": "instructionAccountNode", - "name": "authority", + "isSigner": false, "isWritable": false, - "isSigner": false + "kind": "instructionAccountNode", + "name": "authority" } ], "arguments": [ { + "defaultValue": { + "kind": "numberValueNode", + "number": 0 + }, + "defaultValueStrategy": "omitted", "kind": "instructionArgumentNode", "name": "discriminator", - "defaultValueStrategy": "omitted", "type": { - "kind": "numberTypeNode", + "endian": "le", "format": "u8", - "endian": "le" - }, - "defaultValue": { - "kind": "numberValueNode", - "number": 0 + "kind": "numberTypeNode" } } ], @@ -85,47 +93,47 @@ "name": "discriminator", "offset": 0 } - ] + ], + "kind": "instructionNode", + "name": "initialize" }, { - "kind": "instructionNode", - "name": "write", "accounts": [ { - "kind": "instructionAccountNode", - "name": "recordAccount", + "isSigner": false, "isWritable": true, - "isSigner": false + "kind": "instructionAccountNode", + "name": "recordAccount" }, { - "kind": "instructionAccountNode", - "name": "authority", + "isSigner": true, "isWritable": false, - "isSigner": true + "kind": "instructionAccountNode", + "name": "authority" } ], "arguments": [ { + "defaultValue": { + "kind": "numberValueNode", + "number": 1 + }, + "defaultValueStrategy": "omitted", "kind": "instructionArgumentNode", "name": "discriminator", - "defaultValueStrategy": "omitted", "type": { - "kind": "numberTypeNode", + "endian": "le", "format": "u8", - "endian": "le" - }, - "defaultValue": { - "kind": "numberValueNode", - "number": 1 + "kind": "numberTypeNode" } }, { "kind": "instructionArgumentNode", "name": "offset", "type": { - "kind": "numberTypeNode", + "endian": "le", "format": "u64", - "endian": "le" + "kind": "numberTypeNode" } }, { @@ -133,13 +141,13 @@ "name": "data", "type": { "kind": "sizePrefixTypeNode", - "type": { - "kind": "bytesTypeNode" - }, "prefix": { - "kind": "numberTypeNode", + "endian": "le", "format": "u32", - "endian": "le" + "kind": "numberTypeNode" + }, + "type": { + "kind": "bytesTypeNode" } } } @@ -150,44 +158,44 @@ "name": "discriminator", "offset": 0 } - ] + ], + "kind": "instructionNode", + "name": "write" }, { - "kind": "instructionNode", - "name": "setAuthority", "accounts": [ { - "kind": "instructionAccountNode", - "name": "recordAccount", + "isSigner": false, "isWritable": true, - "isSigner": false + "kind": "instructionAccountNode", + "name": "recordAccount" }, { - "kind": "instructionAccountNode", - "name": "authority", + "isSigner": true, "isWritable": false, - "isSigner": true + "kind": "instructionAccountNode", + "name": "authority" }, { - "kind": "instructionAccountNode", - "name": "newAuthority", + "isSigner": false, "isWritable": false, - "isSigner": false + "kind": "instructionAccountNode", + "name": "newAuthority" } ], "arguments": [ { + "defaultValue": { + "kind": "numberValueNode", + "number": 2 + }, + "defaultValueStrategy": "omitted", "kind": "instructionArgumentNode", "name": "discriminator", - "defaultValueStrategy": "omitted", "type": { - "kind": "numberTypeNode", + "endian": "le", "format": "u8", - "endian": "le" - }, - "defaultValue": { - "kind": "numberValueNode", - "number": 2 + "kind": "numberTypeNode" } } ], @@ -197,44 +205,44 @@ "name": "discriminator", "offset": 0 } - ] + ], + "kind": "instructionNode", + "name": "setAuthority" }, { - "kind": "instructionNode", - "name": "closeAccount", "accounts": [ { - "kind": "instructionAccountNode", - "name": "recordAccount", + "isSigner": false, "isWritable": true, - "isSigner": false + "kind": "instructionAccountNode", + "name": "recordAccount" }, { - "kind": "instructionAccountNode", - "name": "authority", + "isSigner": true, "isWritable": false, - "isSigner": true + "kind": "instructionAccountNode", + "name": "authority" }, { - "kind": "instructionAccountNode", - "name": "receiver", + "isSigner": false, "isWritable": true, - "isSigner": false + "kind": "instructionAccountNode", + "name": "receiver" } ], "arguments": [ { + "defaultValue": { + "kind": "numberValueNode", + "number": 3 + }, + "defaultValueStrategy": "omitted", "kind": "instructionArgumentNode", "name": "discriminator", - "defaultValueStrategy": "omitted", "type": { - "kind": "numberTypeNode", + "endian": "le", "format": "u8", - "endian": "le" - }, - "defaultValue": { - "kind": "numberValueNode", - "number": 3 + "kind": "numberTypeNode" } } ], @@ -244,47 +252,47 @@ "name": "discriminator", "offset": 0 } - ] + ], + "kind": "instructionNode", + "name": "closeAccount" }, { - "kind": "instructionNode", - "name": "reallocate", "accounts": [ { - "kind": "instructionAccountNode", - "name": "recordAccount", + "isSigner": false, "isWritable": true, - "isSigner": false + "kind": "instructionAccountNode", + "name": "recordAccount" }, { - "kind": "instructionAccountNode", - "name": "authority", + "isSigner": true, "isWritable": false, - "isSigner": true + "kind": "instructionAccountNode", + "name": "authority" } ], "arguments": [ { + "defaultValue": { + "kind": "numberValueNode", + "number": 4 + }, + "defaultValueStrategy": "omitted", "kind": "instructionArgumentNode", "name": "discriminator", - "defaultValueStrategy": "omitted", "type": { - "kind": "numberTypeNode", + "endian": "le", "format": "u8", - "endian": "le" - }, - "defaultValue": { - "kind": "numberValueNode", - "number": 4 + "kind": "numberTypeNode" } }, { "kind": "instructionArgumentNode", "name": "dataLength", "type": { - "kind": "numberTypeNode", + "endian": "le", "format": "u64", - "endian": "le" + "kind": "numberTypeNode" } } ], @@ -294,25 +302,17 @@ "name": "discriminator", "offset": 0 } - ] + ], + "kind": "instructionNode", + "name": "reallocate" } ], - "definedTypes": [], + "kind": "programNode", + "name": "splRecord", "pdas": [], - "errors": [ - { - "kind": "errorNode", - "name": "incorrectAuthority", - "code": 0, - "message": "Incorrect authority provided on update or delete" - }, - { - "kind": "errorNode", - "name": "overflow", - "code": 1, - "message": "Calculation overflow" - } - ] + "publicKey": "recr1L3PCGKLbckBqMNcJhuuyU1zgo8nBhfLVsJNwr5", + "version": "0.4.0" }, - "additionalPrograms": [] + "standard": "codama", + "version": "1.0.0" } diff --git a/program/src/error.rs b/program/src/error.rs index 800a72b..eb50534 100644 --- a/program/src/error.rs +++ b/program/src/error.rs @@ -1,9 +1,12 @@ //! Error types +#[cfg(feature = "codama")] +use codama::CodamaErrors; use {num_derive::FromPrimitive, solana_program_error::ProgramError, thiserror::Error}; /// Errors that may be returned by the program. #[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)] +#[cfg_attr(feature = "codama", derive(CodamaErrors))] pub enum RecordError { /// Incorrect authority provided on update or delete #[error("Incorrect authority provided on update or delete")] diff --git a/program/src/instruction.rs b/program/src/instruction.rs index c8ffd48..59ba272 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -1,5 +1,7 @@ //! Program instructions +#[cfg(feature = "codama")] +use codama::{codama, CodamaInstructions}; use { crate::id, solana_instruction::{AccountMeta, Instruction}, @@ -10,6 +12,7 @@ use { /// Instructions supported by the program #[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "codama", derive(CodamaInstructions))] pub enum RecordInstruction<'a> { /// Create a new record /// @@ -17,6 +20,8 @@ pub enum RecordInstruction<'a> { /// /// 0. `[writable]` Record account, must be uninitialized /// 1. `[]` Record authority + #[cfg_attr(feature = "codama", codama(account(name = "record_account", writable)))] + #[cfg_attr(feature = "codama", codama(account(name = "authority")))] Initialize, /// Write to the provided record account @@ -25,10 +30,14 @@ pub enum RecordInstruction<'a> { /// /// 0. `[writable]` Record account, must be previously initialized /// 1. `[signer]` Current record authority + #[cfg_attr(feature = "codama", codama(account(name = "record_account", writable)))] + #[cfg_attr(feature = "codama", codama(account(name = "authority", signer)))] Write { /// Offset to start writing record, expressed as `u64`. offset: u64, /// Data to replace the existing record data + #[cfg_attr(feature = "codama", codama(type = bytes))] + #[cfg_attr(feature = "codama", codama(size_prefix = number(u32)))] data: &'a [u8], }, @@ -39,6 +48,9 @@ pub enum RecordInstruction<'a> { /// 0. `[writable]` Record account, must be previously initialized /// 1. `[signer]` Current record authority /// 2. `[]` New record authority + #[cfg_attr(feature = "codama", codama(account(name = "record_account", writable)))] + #[cfg_attr(feature = "codama", codama(account(name = "authority", signer)))] + #[cfg_attr(feature = "codama", codama(account(name = "new_authority")))] SetAuthority, /// Close the provided record account, draining lamports to recipient @@ -49,6 +61,9 @@ pub enum RecordInstruction<'a> { /// 0. `[writable]` Record account, must be previously initialized /// 1. `[signer]` Record authority /// 2. `[]` Receiver of account lamports + #[cfg_attr(feature = "codama", codama(account(name = "record_account", writable)))] + #[cfg_attr(feature = "codama", codama(account(name = "authority", signer)))] + #[cfg_attr(feature = "codama", codama(account(name = "receiver", writable)))] CloseAccount, /// Reallocate additional space in a record account @@ -60,6 +75,8 @@ pub enum RecordInstruction<'a> { /// /// 0. `[writable]` The record account to reallocate /// 1. `[signer]` The account's owner + #[cfg_attr(feature = "codama", codama(account(name = "record_account", writable)))] + #[cfg_attr(feature = "codama", codama(account(name = "authority", signer)))] Reallocate { /// The length of the data to hold in the record account excluding meta /// data diff --git a/program/src/state.rs b/program/src/state.rs index 64b55de..a09d691 100644 --- a/program/src/state.rs +++ b/program/src/state.rs @@ -1,4 +1,6 @@ //! Program state +#[cfg(feature = "codama")] +use codama::{codama, CodamaAccount}; use { bytemuck::{Pod, Zeroable}, solana_program_pack::IsInitialized, @@ -8,8 +10,11 @@ use { /// Header type for recorded account data #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +#[cfg_attr(feature = "codama", derive(CodamaAccount))] +#[cfg_attr(feature = "codama", codama(discriminator(field = "version")))] pub struct RecordData { /// Struct version, allows for upgrades to the program + #[cfg_attr(feature = "codama", codama(default_value = 1))] pub version: u8, /// The account allowed to update the data