diff --git a/.clippy.toml b/.clippy.toml deleted file mode 100644 index bf931020d7..0000000000 --- a/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -large-error-threshold = 1_000_000 \ No newline at end of file diff --git a/.github/workflows/reusable-tests.yaml b/.github/workflows/reusable-tests.yaml index 8861fcf305..8941170d30 100644 --- a/.github/workflows/reusable-tests.yaml +++ b/.github/workflows/reusable-tests.yaml @@ -443,6 +443,8 @@ jobs: path: tests/anchor-cli-account - cmd: cd tests/bench && anchor test --skip-lint path: tests/bench + - cmd: cd tests/idl && ./test.sh + path: tests/idl steps: - uses: actions/checkout@v3 - uses: ./.github/actions/setup/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b455038d71..dbd1606180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,33 @@ The minor version will be incremented upon a breaking change and the patch versi ### Features +- lang: Add `get_lamports`, `add_lamports` and `sub_lamports` methods for all account types ([#2552](https://github.com/coral-xyz/anchor/pull/2552)). +- client: Add a helper struct `DynSigner` to simplify use of `Client where >` with Solana clap CLI utils that loads `Signer` as `Box` ([#2550](https://github.com/coral-xyz/anchor/pull/2550)). +- lang: Allow CPI calls matching an interface without pinning program ID ([#2559](https://github.com/coral-xyz/anchor/pull/2559)). +- cli, lang: Add IDL generation through compilation. `anchor build` still uses parsing method to generate IDLs, use `anchor idl build` to generate IDLs with the build method ([#2011](https://github.com/coral-xyz/anchor/pull/2011)). +- avm: Add support for the `.anchorversion` file to facilitate switching between different versions of the `anchor-cli` ([#2553](https://github.com/coral-xyz/anchor/pull/2553)). +- ts: Add ability to access workspace programs independent of the casing used, e.g. `anchor.workspace.myProgram`, `anchor.workspace.MyProgram`... ([#2579](https://github.com/coral-xyz/anchor/pull/2579)). +- spl: Export `mpl-token-metadata` crate ([#2583](https://github.com/coral-xyz/anchor/pull/2583)). +- spl: Add `TokenRecordAccount` for pNFTs ([#2597](https://github.com/coral-xyz/anchor/pull/2597)). +- ts: Add support for unnamed(tuple) enum in accounts ([#2601](https://github.com/coral-xyz/anchor/pull/2601)). +- cli: Add program template with multiple files for instructions, state... ([#2602](https://github.com/coral-xyz/anchor/pull/2602)). +- lang: `Box` the inner enums of `anchor_lang::error::Error` to optimize `anchor_lang::Result` ([#2600](https://github.com/coral-xyz/anchor/pull/2600)). + ### Fixes - ts: Packages no longer depend on `assert` ([#2535](https://github.com/coral-xyz/anchor/pull/2535)). +- lang: Support for `const` in the `InitSpace` macro ([#2555](https://github.com/coral-xyz/anchor/pull/2555)). +- cli: Support workspace inheritence ([#2570](https://github.com/coral-xyz/anchor/pull/2570)). +- client: Compile with Solana `1.14` ([#2572](https://github.com/coral-xyz/anchor/pull/2572)). +- cli: Fix `anchor build --no-docs` adding docs to the IDL ([#2575](https://github.com/coral-xyz/anchor/pull/2575)). +- ts: Load workspace programs on-demand rather than loading all of them at once ([#2579](https://github.com/coral-xyz/anchor/pull/2579)). +- lang: Fix `associated_token::token_program` constraint ([#2603](https://github.com/coral-xyz/anchor/pull/2603)). +- cli: Fix `anchor account` command panicking outside of workspace ([#2620](https://github.com/coral-xyz/anchor/pull/2620)). ### Breaking -- lang: Switch to type safe bumps in context ([#2542](https://github.com/coral-xyz/anchor/pull/2542)). +- syn: `idl` feature has been replaced with `idl-build`, `idl-parse` and `idl-types` features ([#2011](https://github.com/coral-xyz/anchor/pull/2011)). +- syn: IDL `parse` method now returns `Result` instead of `Result>` ([#2582](https://github.com/coral-xyz/anchor/pull/2582)). ## [0.28.0] - 2023-06-09 diff --git a/Cargo.lock b/Cargo.lock index 88b5768d6e..4c2a2935c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,10 +112,8 @@ name = "anchor-attribute-access-control" version = "0.28.0" dependencies = [ "anchor-syn", - "anyhow", "proc-macro2 1.0.60", "quote 1.0.28", - "regex", "syn 1.0.109", ] @@ -124,11 +122,9 @@ name = "anchor-attribute-account" version = "0.28.0" dependencies = [ "anchor-syn", - "anyhow", "bs58 0.5.0", "proc-macro2 1.0.60", "quote 1.0.28", - "rustversion", "syn 1.0.109", ] @@ -137,7 +133,7 @@ name = "anchor-attribute-constant" version = "0.28.0" dependencies = [ "anchor-syn", - "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -146,7 +142,6 @@ name = "anchor-attribute-error" version = "0.28.0" dependencies = [ "anchor-syn", - "proc-macro2 1.0.60", "quote 1.0.28", "syn 1.0.109", ] @@ -156,7 +151,6 @@ name = "anchor-attribute-event" version = "0.28.0" dependencies = [ "anchor-syn", - "anyhow", "proc-macro2 1.0.60", "quote 1.0.28", "syn 1.0.109", @@ -167,8 +161,6 @@ name = "anchor-attribute-program" version = "0.28.0" dependencies = [ "anchor-syn", - "anyhow", - "proc-macro2 1.0.60", "quote 1.0.28", "syn 1.0.109", ] @@ -205,8 +197,7 @@ dependencies = [ "solang-parser", "syn 1.0.109", "tar", - "tokio", - "toml", + "toml 0.7.6", "walkdir", ] @@ -232,7 +223,16 @@ name = "anchor-derive-accounts" version = "0.28.0" dependencies = [ "anchor-syn", - "anyhow", + "quote 1.0.28", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-serde" +version = "0.28.0" +dependencies = [ + "anchor-syn", + "borsh-derive-internal 0.10.3", "proc-macro2 1.0.60", "quote 1.0.28", "syn 1.0.109", @@ -258,7 +258,9 @@ dependencies = [ "anchor-attribute-event", "anchor-attribute-program", "anchor-derive-accounts", + "anchor-derive-serde", "anchor-derive-space", + "anchor-syn", "arrayref", "base64 0.13.1", "bincode", @@ -647,9 +649,7 @@ dependencies = [ "reqwest", "semver", "serde", - "serde_json", "tempfile", - "thiserror", ] [[package]] @@ -940,12 +940,12 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.13.3" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497049e9477329f8f6a559972ee42e117487d01d1e8c2cc9f836ea6fa23a9e1a" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" dependencies = [ "serde", - "toml", + "toml 0.7.6", ] [[package]] @@ -1012,7 +1012,7 @@ dependencies = [ "atty", "bitflags", "clap_lex 0.2.4", - "indexmap", + "indexmap 1.9.3", "once_cell", "strsim 0.10.0", "termcolor", @@ -1587,6 +1587,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.1" @@ -1841,7 +1847,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -1881,6 +1887,12 @@ dependencies = [ "ahash 0.8.3", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.3.3" @@ -2098,6 +2110,16 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "indicatif" version = "0.17.5" @@ -2217,9 +2239,9 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.19.12" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" +checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" dependencies = [ "ascii-canvas", "bit-set", @@ -2230,7 +2252,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.6.29", + "regex-syntax", "string_cache", "term", "tiny-keccak", @@ -2239,12 +2261,9 @@ dependencies = [ [[package]] name = "lalrpop-util" -version = "0.19.12" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" -dependencies = [ - "regex", -] +checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" [[package]] name = "lazy_static" @@ -2833,7 +2852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.3", ] [[package]] @@ -2967,7 +2986,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] @@ -3226,15 +3245,9 @@ checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.2", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.7.2" @@ -3559,6 +3572,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3599,7 +3621,7 @@ version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itoa", "ryu", "serde", @@ -3880,7 +3902,7 @@ dependencies = [ "bincode", "futures", "futures-util", - "indexmap", + "indexmap 1.9.3", "indicatif", "log", "quinn", @@ -3926,7 +3948,7 @@ dependencies = [ "async-trait", "bincode", "futures-util", - "indexmap", + "indexmap 1.9.3", "log", "rand 0.7.3", "rayon", @@ -4393,7 +4415,7 @@ dependencies = [ "crossbeam-channel", "futures-util", "histogram", - "indexmap", + "indexmap 1.9.3", "itertools 0.10.5", "libc", "log", @@ -4439,7 +4461,7 @@ dependencies = [ "async-trait", "bincode", "futures-util", - "indexmap", + "indexmap 1.9.3", "indicatif", "log", "rand 0.7.3", @@ -4585,14 +4607,15 @@ dependencies = [ [[package]] name = "solang-parser" -version = "0.2.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff87dae6cdccacdbf3b19e99b271083556e808de0f59c74a01482f64fdbc61fc" +checksum = "9c792fe9fae2a2f716846f214ca10d5a1e21133e0bf36cef34bcc4a852467b21" dependencies = [ "itertools 0.10.5", "lalrpop", "lalrpop-util", "phf", + "thiserror", "unicode-xid 0.2.4", ] @@ -5005,19 +5028,36 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" -version = "0.19.10" +version = "0.19.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +checksum = "5f8751d9c1b03c6500c387e96f81f815a4f8e72d142d2d4a9ffa6fedd51ddee7" dependencies = [ - "indexmap", + "indexmap 2.0.0", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -5562,9 +5602,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" dependencies = [ "memchr", ] diff --git a/avm/Cargo.toml b/avm/Cargo.toml index 53faaabbed..e8dbcf9103 100644 --- a/avm/Cargo.toml +++ b/avm/Cargo.toml @@ -15,12 +15,10 @@ path = "src/anchor/main.rs" [dependencies] anyhow = "1.0.32" cfg-if = "1.0.0" -clap = { version = "4.2.4", features = ["derive"]} +clap = { version = "4.2.4", features = ["derive"] } dirs = "4.0.0" -once_cell = { version = "1.8.0" } +once_cell = "1.8.0" reqwest = { version = "0.11.9", default-features = false, features = ["blocking", "json", "rustls-tls"] } semver = "1.0.4" serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.78" tempfile = "3.3.0" -thiserror = "1.0.30" diff --git a/avm/src/lib.rs b/avm/src/lib.rs index b26239b740..25d03ac168 100644 --- a/avm/src/lib.rs +++ b/avm/src/lib.rs @@ -45,10 +45,15 @@ pub fn version_binary_path(version: &Version) -> PathBuf { } /// Update the current version to a new version -pub fn use_version(version: &Version) -> Result<()> { +pub fn use_version(opt_version: Option) -> Result<()> { + let version = match opt_version { + Some(version) => version, + None => read_anchorversion_file()?, + }; + let installed_versions = read_installed_versions(); // Make sure the requested version is installed - if !installed_versions.contains(version) { + if !installed_versions.contains(&version) { if let Ok(current) = current_version() { println!("Version {version} is not installed, staying on version {current}."); } else { @@ -118,7 +123,7 @@ pub fn install_version(version: &Version, force: bool) -> Result<()> { current_version_file.write_all(version.to_string().as_bytes())?; } - use_version(version) + use_version(Some(version.clone())) } /// Remove an installed version of anchor-cli @@ -134,6 +139,14 @@ pub fn uninstall_version(version: &Version) -> Result<()> { Ok(()) } +/// Read version from .anchorversion +pub fn read_anchorversion_file() -> Result { + fs::read_to_string(".anchorversion") + .map_err(|e| anyhow!(".anchorversion file not found: {e}")) + .map(|content| Version::parse(content.trim()))? + .map_err(|e| anyhow!("Unable to parse version: {e}")) +} + /// Ensure the users home directory is setup with the paths required by AVM. pub fn ensure_paths() { let home_dir = AVM_HOME.to_path_buf(); @@ -237,9 +250,26 @@ pub fn read_installed_versions() -> Vec { mod tests { use crate::*; use semver::Version; + use std::env; use std::fs; use std::io::Write; + #[test] + fn test_read_anchorversion() { + ensure_paths(); + let mut dir = env::current_dir().unwrap(); + dir.push(".anchorversion"); + let mut file_created = fs::File::create(&dir).unwrap(); + let test_version = "0.26.0"; + file_created.write_all(test_version.as_bytes()).unwrap(); + + let version = read_anchorversion_file().unwrap(); + + assert_eq!(version.to_string(), test_version); + + fs::remove_file(&dir).unwrap(); + } + #[test] fn test_ensure_paths() { ensure_paths(); diff --git a/avm/src/main.rs b/avm/src/main.rs index 37e2906305..f0d8cb682e 100644 --- a/avm/src/main.rs +++ b/avm/src/main.rs @@ -15,8 +15,8 @@ pub struct Cli { pub enum Commands { #[clap(about = "Use a specific version of Anchor")] Use { - #[clap(value_parser = parse_version)] - version: Version, + #[clap(value_parser = parse_version, required = false)] + version: Option, }, #[clap(about = "Install a version of Anchor")] Install { @@ -48,7 +48,7 @@ fn parse_version(version: &str) -> Result { } pub fn entry(opts: Cli) -> Result<()> { match opts.command { - Commands::Use { version } => avm::use_version(&version), + Commands::Use { version } => avm::use_version(version), Commands::Install { version, force } => avm::install_version(&version, force), Commands::Uninstall { version } => avm::uninstall_version(&version), Commands::List {} => avm::list_versions(), diff --git a/bench/BINARY_SIZE.md b/bench/BINARY_SIZE.md new file mode 100644 index 0000000000..4fffa24ee2 --- /dev/null +++ b/bench/BINARY_SIZE.md @@ -0,0 +1,51 @@ +# Binary Size + +All notable changes in program binary size will be documented in this file. + +The changes are calculated by comparing the current results with the last version's results. Increase in size is shown with 🔴 and decrease is shown with 🟢. + +The programs and their tests are located in [/tests/bench](https://github.com/coral-xyz/anchor/tree/master/tests/bench). + +> **Note** +> Results documented in this file are autogenerated. Running the tests will update the current results when necessary, manually editing the results should be avoided. + +> **Warning** +> Results may vary depending on Solana version. + +## [Unreleased] + +Solana version: 1.16.0 + +| Program | Binary Size | +/- | +| ------- | ----------- | ----------------------- | +| bench | 1 049 608 | 🟢 **-104 128 (9.03%)** | + +### Notable changes + +- `Box` the `anchor_lang::Result` error variants ([#2600](https://github.com/coral-xyz/anchor/pull/2600)). + +--- + +## [0.28.0] + +Solana version: 1.16.0 + +| Program | Binary Size | +/- | +| ------- | ----------- | ---------------------- | +| bench | 1 153 736 | 🔴 **+35 000 (3.13%)** | + +### Notable changes + +- Upgrading Solana to `1.16`. The difference in binary size between `0.27.0` and `0.28.0` is the direct result of upgrading Solana version(both build tools and crates) ([#2512](https://github.com/coral-xyz/anchor/pull/2512)). + +--- + +## [0.27.0] + +Solana version: 1.14.16 + +| Program | Binary Size | +/- | +| ------- | ----------- | --- | +| bench | 1 118 736 | N/A | + +--- diff --git a/bench/COMPUTE_UNITS.md b/bench/COMPUTE_UNITS.md index 971c29f805..cbe442c8dd 100644 --- a/bench/COMPUTE_UNITS.md +++ b/bench/COMPUTE_UNITS.md @@ -7,205 +7,209 @@ The changes are calculated by comparing the current results with the last versio The programs and their tests are located in [/tests/bench](https://github.com/coral-xyz/anchor/tree/master/tests/bench). > **Note** -> The results documented in this file are autogenerated. Running the tests will update the current results when necessary, manually editing the results should be avoided. +> Results documented in this file are autogenerated. Running the tests will update the current results when necessary, manually editing the results should be avoided. > **Warning** -> The results may vary depending on Solana version. +> Results may vary depending on Solana version. ## [Unreleased] Solana version: 1.16.0 -| Instruction | Compute Units | +/- | -| --------------------------- | ------------- | -------------- | -| accountInfo1 | 810 | 🟢 **-20.20%** | -| accountInfo2 | 1400 | 🟢 **-5.08%** | -| accountInfo4 | 1855 | 🟢 **-5.55%** | -| accountInfo8 | 3674 | 🟢 **-4.35%** | -| accountEmptyInit1 | 5817 | - | -| accountEmpty1 | 980 | 🟢 **-14.71%** | -| accountEmptyInit2 | 10402 | - | -| accountEmpty2 | 1754 | - | -| accountEmptyInit4 | 19508 | - | -| accountEmpty4 | 2540 | - | -| accountEmptyInit8 | 37265 | - | -| accountEmpty8 | 5016 | - | -| accountSizedInit1 | 5924 | - | -| accountSized1 | 1027 | 🟢 **-15.40%** | -| accountSizedInit2 | 10680 | - | -| accountSized2 | 1873 | - | -| accountSizedInit4 | 19970 | - | -| accountSized4 | 2762 | - | -| accountSizedInit8 | 38122 | - | -| accountSized8 | 5353 | - | -| accountUnsizedInit1 | 6052 | - | -| accountUnsized1 | 1116 | 🟢 **-16.59%** | -| accountUnsizedInit2 | 10929 | - | -| accountUnsized2 | 1778 | - | -| accountUnsizedInit4 | 20339 | - | -| accountUnsized4 | 3136 | - | -| accountUnsizedInit8 | 39096 | - | -| accountUnsized8 | 5952 | - | -| boxedAccountEmptyInit1 | 6034 | - | -| boxedAccountEmpty1 | 866 | 🟢 **-2.48%** | -| boxedAccountEmptyInit2 | 10633 | - | -| boxedAccountEmpty2 | 1386 | 🟢 **-1.07%** | -| boxedAccountEmptyInit4 | 19311 | - | -| boxedAccountEmpty4 | 2424 | - | -| boxedAccountEmptyInit8 | 37136 | - | -| boxedAccountEmpty8 | 4659 | - | -| boxedAccountSizedInit1 | 6130 | - | -| boxedAccountSized1 | 895 | 🟢 **-2.40%** | -| boxedAccountSizedInit2 | 10828 | - | -| boxedAccountSized2 | 1448 | 🟢 **-1.03%** | -| boxedAccountSizedInit4 | 19703 | - | -| boxedAccountSized4 | 2543 | - | -| boxedAccountSizedInit8 | 37919 | - | -| boxedAccountSized8 | 4898 | - | -| boxedAccountUnsizedInit1 | 6240 | - | -| boxedAccountUnsized1 | 953 | 🟢 **-1.95%** | -| boxedAccountUnsizedInit2 | 11048 | - | -| boxedAccountUnsized2 | 1570 | - | -| boxedAccountUnsizedInit4 | 20138 | - | -| boxedAccountUnsized4 | 2768 | - | -| boxedAccountUnsizedInit8 | 38791 | - | -| boxedAccountUnsized8 | 5347 | - | -| boxedInterfaceAccountMint1 | 2296 | - | -| boxedInterfaceAccountMint2 | 4129 | - | -| boxedInterfaceAccountMint4 | 7783 | - | -| boxedInterfaceAccountMint8 | 15281 | - | -| boxedInterfaceAccountToken1 | 2001 | 🟢 **-1.09%** | -| boxedInterfaceAccountToken2 | 3582 | - | -| boxedInterfaceAccountToken4 | 6692 | - | -| boxedInterfaceAccountToken8 | 13098 | - | -| interfaceAccountMint1 | 2291 | 🟢 **-3.09%** | -| interfaceAccountMint2 | 5030 | - | -| interfaceAccountMint4 | 9803 | - | -| interfaceAccountMint8 | 18400 | - | -| interfaceAccountToken1 | 2018 | 🟢 **-3.49%** | -| interfaceAccountToken2 | 3948 | - | -| interfaceAccountToken4 | 7547 | - | -| interface1 | 892 | 🟢 **-15.77%** | -| interface2 | 1479 | - | -| interface4 | 1900 | - | -| interface8 | 3646 | - | -| program1 | 887 | 🟢 **-15.76%** | -| program2 | 1467 | - | -| program4 | 1878 | - | -| program8 | 3598 | - | -| signer1 | 822 | 🟢 **-19.25%** | -| signer2 | 1424 | 🟢 **-4.04%** | -| signer4 | 1907 | 🟢 **-3.88%** | -| signer8 | 3770 | 🟢 **-2.84%** | -| systemAccount1 | 876 | 🟢 **-18.28%** | -| systemAccount2 | 1530 | 🟢 **-3.77%** | -| systemAccount4 | 2118 | 🟢 **-3.51%** | -| systemAccount8 | 4195 | 🟢 **-2.56%** | -| uncheckedAccount1 | 809 | 🟢 **-20.22%** | -| uncheckedAccount2 | 1400 | 🟢 **-5.08%** | -| uncheckedAccount4 | 1856 | 🟢 **-5.55%** | -| uncheckedAccount8 | 3674 | 🟢 **-4.35%** | +| Instruction | Compute Units | +/- | +| --------------------------- | ------------- | ---------------------- | +| accountInfo1 | 584 | 🟢 **-431 (42.46%)** | +| accountInfo2 | 824 | 🟢 **-651 (44.14%)** | +| accountInfo4 | 1 319 | 🟢 **-645 (32.84%)** | +| accountInfo8 | 2 531 | 🟢 **-1 310 (34.11%)** | +| accountEmptyInit1 | 5 521 | 🟢 **-296 (5.09%)** | +| accountEmpty1 | 777 | 🟢 **-372 (32.38%)** | +| accountEmptyInit2 | 10 111 | 🟢 **-291 (2.80%)** | +| accountEmpty2 | 1 207 | 🟢 **-547 (31.19%)** | +| accountEmptyInit4 | 19 044 | 🟢 **-464 (2.38%)** | +| accountEmpty4 | 2 074 | 🟢 **-466 (18.35%)** | +| accountEmptyInit8 | 37 265 | - | +| accountEmpty8 | 3 967 | 🟢 **-1 049 (20.91%)** | +| accountSizedInit1 | 5 626 | 🟢 **-298 (5.03%)** | +| accountSized1 | 786 | 🟢 **-428 (35.26%)** | +| accountSizedInit2 | 10 322 | 🟢 **-358 (3.35%)** | +| accountSized2 | 1 234 | 🟢 **-639 (34.12%)** | +| accountSizedInit4 | 19 462 | 🟢 **-508 (2.54%)** | +| accountSized4 | 2 135 | 🟢 **-627 (22.70%)** | +| accountSizedInit8 | 38 122 | - | +| accountSized8 | 4 104 | 🟢 **-1 249 (23.33%)** | +| accountUnsizedInit1 | 5 742 | 🟢 **-310 (5.12%)** | +| accountUnsized1 | 821 | 🟢 **-517 (38.64%)** | +| accountUnsizedInit2 | 10 551 | 🟢 **-378 (3.46%)** | +| accountUnsized2 | 1 312 | 🟢 **-466 (26.21%)** | +| accountUnsizedInit4 | 19 927 | 🟢 **-412 (2.03%)** | +| accountUnsized4 | 2 315 | 🟢 **-821 (26.18%)** | +| accountUnsizedInit8 | 38 699 | 🟢 **-397 (1.02%)** | +| accountUnsized8 | 4 456 | 🟢 **-1 496 (25.13%)** | +| boxedAccountEmptyInit1 | 5 452 | 🟢 **-582 (9.65%)** | +| boxedAccountEmpty1 | 866 | 🟢 **-22 (2.48%)** | +| boxedAccountEmptyInit2 | 10 051 | 🟢 **-582 (5.47%)** | +| boxedAccountEmpty2 | 1 377 | 🟢 **-24 (1.71%)** | +| boxedAccountEmptyInit4 | 19 030 | 🟢 **-281 (1.46%)** | +| boxedAccountEmpty4 | 2 396 | 🟢 **-28 (1.16%)** | +| boxedAccountEmptyInit8 | 37 136 | - | +| boxedAccountEmpty8 | 4 472 | 🟢 **-187 (4.01%)** | +| boxedAccountSizedInit1 | 5 546 | 🟢 **-584 (9.53%)** | +| boxedAccountSized1 | 895 | 🟢 **-22 (2.40%)** | +| boxedAccountSizedInit2 | 10 242 | 🟢 **-586 (5.41%)** | +| boxedAccountSized2 | 1 439 | 🟢 **-24 (1.64%)** | +| boxedAccountSizedInit4 | 19 414 | 🟢 **-289 (1.47%)** | +| boxedAccountSized4 | 2 515 | 🟢 **-28 (1.10%)** | +| boxedAccountSizedInit8 | 37 919 | - | +| boxedAccountSized8 | 4 711 | 🟢 **-187 (3.82%)** | +| boxedAccountUnsizedInit1 | 5 823 | 🟢 **-417 (6.68%)** | +| boxedAccountUnsized1 | 950 | 🟢 **-22 (2.26%)** | +| boxedAccountUnsizedInit2 | 10 621 | 🟢 **-427 (3.86%)** | +| boxedAccountUnsized2 | 1 549 | 🟢 **-21 (1.34%)** | +| boxedAccountUnsizedInit4 | 19 825 | 🟢 **-313 (1.55%)** | +| boxedAccountUnsized4 | 2 737 | 🟢 **-31 (1.12%)** | +| boxedAccountUnsizedInit8 | 38 791 | 🟢 **-9 (0.02%)** | +| boxedAccountUnsized8 | 5 207 | 🟢 **-140 (2.62%)** | +| boxedInterfaceAccountMint1 | 2 137 | 🟢 **-159 (6.93%)** | +| boxedInterfaceAccountMint2 | 3 849 | 🟢 **-280 (6.78%)** | +| boxedInterfaceAccountMint4 | 7 215 | 🟢 **-568 (7.30%)** | +| boxedInterfaceAccountMint8 | 14 044 | 🟢 **-1 237 (8.10%)** | +| boxedInterfaceAccountToken1 | 2 066 | 🔴 **+43 (2.13%)** | +| boxedInterfaceAccountToken2 | 3 706 | 🔴 **+124 (3.46%)** | +| boxedInterfaceAccountToken4 | 6 932 | 🔴 **+240 (3.59%)** | +| boxedInterfaceAccountToken8 | 13 477 | 🔴 **+379 (2.89%)** | +| interfaceAccountMint1 | 2 313 | 🟢 **-51 (2.16%)** | +| interfaceAccountMint2 | 4 270 | 🟢 **-760 (15.11%)** | +| interfaceAccountMint4 | 8 185 | 🟢 **-1 618 (16.51%)** | +| interfaceAccountMint8 | 16 007 | 🟢 **-2 393 (13.01%)** | +| interfaceAccountToken1 | 2 059 | 🟢 **-32 (1.53%)** | +| interfaceAccountToken2 | 3 958 | 🔴 **+10 (0.25%)** | +| interfaceAccountToken4 | 7 816 | 🔴 **+269 (3.56%)** | +| interface1 | 691 | 🟢 **-368 (34.75%)** | +| interface2 | 940 | 🟢 **-539 (36.44%)** | +| interface4 | 1 450 | 🟢 **-450 (23.68%)** | +| interface8 | 2 605 | 🟢 **-1 041 (28.55%)** | +| program1 | 685 | 🟢 **-368 (34.95%)** | +| program2 | 928 | 🟢 **-539 (36.74%)** | +| program4 | 1 428 | 🟢 **-450 (23.96%)** | +| program8 | 2 557 | 🟢 **-1 041 (28.93%)** | +| signer1 | 621 | 🟢 **-397 (39.00%)** | +| signer2 | 895 | 🟢 **-589 (39.69%)** | +| signer4 | 1 455 | 🟢 **-529 (26.66%)** | +| signer8 | 2 721 | 🟢 **-1 159 (29.87%)** | +| systemAccount1 | 675 | 🟢 **-397 (37.03%)** | +| systemAccount2 | 1 001 | 🟢 **-589 (37.04%)** | +| systemAccount4 | 1 666 | 🟢 **-529 (24.10%)** | +| systemAccount8 | 3 146 | 🟢 **-1 159 (26.92%)** | +| uncheckedAccount1 | 583 | 🟢 **-431 (42.50%)** | +| uncheckedAccount2 | 824 | 🟢 **-651 (44.14%)** | +| uncheckedAccount4 | 1 320 | 🟢 **-645 (32.82%)** | +| uncheckedAccount8 | 2 531 | 🟢 **-1 310 (34.11%)** | ### Notable changes +- `Box` the `anchor_lang::Result` error variants ([#2600](https://github.com/coral-xyz/anchor/pull/2600)). + --- ## [0.28.0] Solana version: 1.16.0 -| Instruction | Compute Units | +/- | -| --------------------------- | ------------- | -------------- | -| accountInfo1 | 1015 | 🔴 **+6.39%** | -| accountInfo2 | 1475 | 🟢 **-5.87%** | -| accountInfo4 | 1964 | 🟢 **-4.61%** | -| accountInfo8 | 3841 | - | -| accountEmptyInit1 | 5817 | 🟢 **-2.37%** | -| accountEmpty1 | 1149 | 🔴 **+5.41%** | -| accountEmptyInit2 | 10402 | 🟢 **-1.63%** | -| accountEmpty2 | 1754 | 🟢 **-5.29%** | -| accountEmptyInit4 | 19508 | - | -| accountEmpty4 | 2540 | 🟢 **-4.01%** | -| accountEmptyInit8 | 37265 | - | -| accountEmpty8 | 5016 | - | -| accountSizedInit1 | 5924 | 🟢 **-2.29%** | -| accountSized1 | 1214 | 🔴 **+6.96%** | -| accountSizedInit2 | 10680 | - | -| accountSized2 | 1873 | 🟢 **-4.73%** | -| accountSizedInit4 | 19970 | - | -| accountSized4 | 2762 | - | -| accountSizedInit8 | 38122 | - | -| accountSized8 | 5353 | - | -| accountUnsizedInit1 | 6052 | 🟢 **-2.28%** | -| accountUnsized1 | 1338 | 🔴 **+7.64%** | -| accountUnsizedInit2 | 10929 | 🟢 **-1.02%** | -| accountUnsized2 | 1778 | 🟢 **-6.08%** | -| accountUnsizedInit4 | 20339 | - | -| accountUnsized4 | 3136 | 🔴 **+1.03%** | -| accountUnsizedInit8 | 39096 | - | -| accountUnsized8 | 5952 | 🟢 **-1.64%** | -| boxedAccountEmptyInit1 | 6034 | 🟢 **-2.05%** | -| boxedAccountEmpty1 | 888 | 🟢 **-9.02%** | -| boxedAccountEmptyInit2 | 10633 | 🟢 **-1.40%** | -| boxedAccountEmpty2 | 1401 | 🟢 **-6.54%** | -| boxedAccountEmptyInit4 | 19311 | - | -| boxedAccountEmpty4 | 2424 | 🟢 **-4.19%** | -| boxedAccountEmptyInit8 | 37136 | - | -| boxedAccountEmpty8 | 4659 | 🟢 **-2.53%** | -| boxedAccountSizedInit1 | 6130 | 🟢 **-2.01%** | -| boxedAccountSized1 | 917 | 🟢 **-8.57%** | -| boxedAccountSizedInit2 | 10828 | 🟢 **-1.34%** | -| boxedAccountSized2 | 1463 | 🟢 **-5.86%** | -| boxedAccountSizedInit4 | 19703 | - | -| boxedAccountSized4 | 2543 | 🟢 **-3.75%** | -| boxedAccountSizedInit8 | 37919 | - | -| boxedAccountSized8 | 4898 | 🟢 **-2.10%** | -| boxedAccountUnsizedInit1 | 6240 | 🟢 **-2.10%** | -| boxedAccountUnsized1 | 972 | 🟢 **-9.07%** | -| boxedAccountUnsizedInit2 | 11048 | 🟢 **-1.45%** | -| boxedAccountUnsized2 | 1570 | 🟢 **-6.49%** | -| boxedAccountUnsizedInit4 | 20138 | 🟢 **-1.05%** | -| boxedAccountUnsized4 | 2768 | 🟢 **-4.52%** | -| boxedAccountUnsizedInit8 | 38800 | - | -| boxedAccountUnsized8 | 5347 | 🟢 **-3.08%** | -| boxedInterfaceAccountMint1 | 2296 | - | -| boxedInterfaceAccountMint2 | 4129 | 🔴 **+1.88%** | -| boxedInterfaceAccountMint4 | 7783 | 🔴 **+3.25%** | -| boxedInterfaceAccountMint8 | 15281 | 🔴 **+3.96%** | -| boxedInterfaceAccountToken1 | 2023 | 🔴 **+16.47%** | -| boxedInterfaceAccountToken2 | 3582 | 🔴 **+22.34%** | -| boxedInterfaceAccountToken4 | 6692 | 🔴 **+26.48%** | -| boxedInterfaceAccountToken8 | 13098 | 🔴 **+28.35%** | -| interfaceAccountMint1 | 2364 | 🟢 **-6.56%** | -| interfaceAccountMint2 | 5030 | 🔴 **+6.43%** | -| interfaceAccountMint4 | 9803 | 🔴 **+3.94%** | -| interfaceAccountMint8 | 18400 | 🔴 **+3.90%** | -| interfaceAccountToken1 | 2091 | 🔴 **+19.15%** | -| interfaceAccountToken2 | 3948 | 🔴 **+22.95%** | -| interfaceAccountToken4 | 7547 | 🔴 **+25.66%** | -| interface1 | 1059 | 🔴 **+6.01%** | -| interface2 | 1479 | 🟢 **-6.04%** | -| interface4 | 1900 | 🟢 **-4.81%** | -| interface8 | 3646 | - | -| program1 | 1053 | 🔴 **+5.41%** | -| program2 | 1467 | 🟢 **-6.74%** | -| program4 | 1878 | 🟢 **-6.01%** | -| program8 | 3598 | 🟢 **-1.45%** | -| signer1 | 1018 | 🔴 **+6.26%** | -| signer2 | 1484 | 🟢 **-5.84%** | -| signer4 | 1984 | 🟢 **-4.57%** | -| signer8 | 3880 | - | -| systemAccount1 | 1072 | 🔴 **+5.82%** | -| systemAccount2 | 1590 | 🟢 **-5.69%** | -| systemAccount4 | 2195 | 🟢 **-4.48%** | -| systemAccount8 | 4305 | - | -| uncheckedAccount1 | 1014 | 🔴 **+6.40%** | -| uncheckedAccount2 | 1475 | 🟢 **-5.87%** | -| uncheckedAccount4 | 1965 | 🟢 **-4.61%** | -| uncheckedAccount8 | 3841 | - | +| Instruction | Compute Units | +/- | +| --------------------------- | ------------- | ---------------------- | +| accountInfo1 | 1 015 | 🔴 **+61 (6.39%)** | +| accountInfo2 | 1 475 | 🟢 **-92 (5.87%)** | +| accountInfo4 | 1 964 | 🟢 **-95 (4.61%)** | +| accountInfo8 | 3 841 | 🟢 **-15 (0.39%)** | +| accountEmptyInit1 | 5 817 | 🟢 **-141 (2.37%)** | +| accountEmpty1 | 1 149 | 🔴 **+59 (5.41%)** | +| accountEmptyInit2 | 10 402 | 🟢 **-172 (1.63%)** | +| accountEmpty2 | 1 754 | 🟢 **-98 (5.29%)** | +| accountEmptyInit4 | 19 508 | 🟢 **-49 (0.25%)** | +| accountEmpty4 | 2 540 | 🟢 **-106 (4.01%)** | +| accountEmptyInit8 | 37 265 | 🟢 **-276 (0.74%)** | +| accountEmpty8 | 5 016 | 🟢 **-27 (0.54%)** | +| accountSizedInit1 | 5 924 | 🟢 **-139 (2.29%)** | +| accountSized1 | 1 214 | 🔴 **+79 (6.96%)** | +| accountSizedInit2 | 10 680 | 🟢 **-103 (0.96%)** | +| accountSized2 | 1 873 | 🟢 **-93 (4.73%)** | +| accountSizedInit4 | 19 970 | 🟢 **-5 (0.03%)** | +| accountSized4 | 2 762 | 🟢 **-25 (0.90%)** | +| accountSizedInit8 | 38 122 | 🟢 **-259 (0.67%)** | +| accountSized8 | 5 353 | 🟢 **-6 (0.11%)** | +| accountUnsizedInit1 | 6 052 | 🟢 **-141 (2.28%)** | +| accountUnsized1 | 1 338 | 🔴 **+95 (7.64%)** | +| accountUnsizedInit2 | 10 929 | 🟢 **-113 (1.02%)** | +| accountUnsized2 | 1 778 | 🟢 **-115 (6.08%)** | +| accountUnsizedInit4 | 20 339 | 🟢 **-156 (0.76%)** | +| accountUnsized4 | 3 136 | 🔴 **+32 (1.03%)** | +| accountUnsizedInit8 | 39 096 | 🟢 **-323 (0.82%)** | +| accountUnsized8 | 5 952 | 🟢 **-99 (1.64%)** | +| boxedAccountEmptyInit1 | 6 034 | 🟢 **-126 (2.05%)** | +| boxedAccountEmpty1 | 888 | 🟢 **-88 (9.02%)** | +| boxedAccountEmptyInit2 | 10 633 | 🟢 **-151 (1.40%)** | +| boxedAccountEmpty2 | 1 401 | 🟢 **-98 (6.54%)** | +| boxedAccountEmptyInit4 | 19 311 | 🟢 **-189 (0.97%)** | +| boxedAccountEmpty4 | 2 424 | 🟢 **-106 (4.19%)** | +| boxedAccountEmptyInit8 | 37 136 | 🟢 **-279 (0.75%)** | +| boxedAccountEmpty8 | 4 659 | 🟢 **-121 (2.53%)** | +| boxedAccountSizedInit1 | 6 130 | 🟢 **-126 (2.01%)** | +| boxedAccountSized1 | 917 | 🟢 **-86 (8.57%)** | +| boxedAccountSizedInit2 | 10 828 | 🟢 **-147 (1.34%)** | +| boxedAccountSized2 | 1 463 | 🟢 **-91 (5.86%)** | +| boxedAccountSizedInit4 | 19 703 | 🟢 **-181 (0.91%)** | +| boxedAccountSized4 | 2 543 | 🟢 **-99 (3.75%)** | +| boxedAccountSizedInit8 | 37 919 | 🟢 **-263 (0.69%)** | +| boxedAccountSized8 | 4 898 | 🟢 **-105 (2.10%)** | +| boxedAccountUnsizedInit1 | 6 240 | 🟢 **-134 (2.10%)** | +| boxedAccountUnsized1 | 972 | 🟢 **-97 (9.07%)** | +| boxedAccountUnsizedInit2 | 11 048 | 🟢 **-163 (1.45%)** | +| boxedAccountUnsized2 | 1 570 | 🟢 **-109 (6.49%)** | +| boxedAccountUnsizedInit4 | 20 138 | 🟢 **-213 (1.05%)** | +| boxedAccountUnsized4 | 2 768 | 🟢 **-131 (4.52%)** | +| boxedAccountUnsizedInit8 | 38 800 | 🟢 **-318 (0.81%)** | +| boxedAccountUnsized8 | 5 347 | 🟢 **-170 (3.08%)** | +| boxedInterfaceAccountMint1 | 2 296 | 🟢 **-3 (0.13%)** | +| boxedInterfaceAccountMint2 | 4 129 | 🔴 **+76 (1.88%)** | +| boxedInterfaceAccountMint4 | 7 783 | 🔴 **+245 (3.25%)** | +| boxedInterfaceAccountMint8 | 15 281 | 🔴 **+582 (3.96%)** | +| boxedInterfaceAccountToken1 | 2 023 | 🔴 **+286 (16.47%)** | +| boxedInterfaceAccountToken2 | 3 582 | 🔴 **+654 (22.34%)** | +| boxedInterfaceAccountToken4 | 6 692 | 🔴 **+1 401 (26.48%)** | +| boxedInterfaceAccountToken8 | 13 098 | 🔴 **+2 893 (28.35%)** | +| interfaceAccountMint1 | 2 364 | 🟢 **-166 (6.56%)** | +| interfaceAccountMint2 | 5 030 | 🔴 **+304 (6.43%)** | +| interfaceAccountMint4 | 9 803 | 🔴 **+372 (3.94%)** | +| interfaceAccountMint8 | 18 400 | 🔴 **+691 (3.90%)** | +| interfaceAccountToken1 | 2 091 | 🔴 **+336 (19.15%)** | +| interfaceAccountToken2 | 3 948 | 🔴 **+737 (22.95%)** | +| interfaceAccountToken4 | 7 547 | 🔴 **+1 541 (25.66%)** | +| interface1 | 1 059 | 🔴 **+60 (6.01%)** | +| interface2 | 1 479 | 🟢 **-95 (6.04%)** | +| interface4 | 1 900 | 🟢 **-96 (4.81%)** | +| interface8 | 3 646 | 🟢 **-5 (0.14%)** | +| program1 | 1 053 | 🔴 **+54 (5.41%)** | +| program2 | 1 467 | 🟢 **-106 (6.74%)** | +| program4 | 1 878 | 🟢 **-120 (6.01%)** | +| program8 | 3 598 | 🟢 **-53 (1.45%)** | +| signer1 | 1 018 | 🔴 **+60 (6.26%)** | +| signer2 | 1 484 | 🟢 **-92 (5.84%)** | +| signer4 | 1 984 | 🟢 **-95 (4.57%)** | +| signer8 | 3 880 | 🟢 **-15 (0.39%)** | +| systemAccount1 | 1 072 | 🔴 **+59 (5.82%)** | +| systemAccount2 | 1 590 | 🟢 **-96 (5.69%)** | +| systemAccount4 | 2 195 | 🟢 **-103 (4.48%)** | +| systemAccount8 | 4 305 | 🟢 **-31 (0.71%)** | +| uncheckedAccount1 | 1 014 | 🔴 **+61 (6.40%)** | +| uncheckedAccount2 | 1 475 | 🟢 **-92 (5.87%)** | +| uncheckedAccount4 | 1 965 | 🟢 **-95 (4.61%)** | +| uncheckedAccount8 | 3 841 | 🟢 **-14 (0.36%)** | ### Notable changes +- Upgrading Solana to `1.16`. The difference in compute units usage between `0.27.0` and `0.28.0` is the direct result of upgrading Solana version(both build tools and crates) ([#2512](https://github.com/coral-xyz/anchor/pull/2512)). + --- ## [0.27.0] @@ -215,91 +219,91 @@ Solana version: 1.14.16 | Instruction | Compute Units | +/- | | --------------------------- | ------------- | --- | | accountInfo1 | 954 | N/A | -| accountInfo2 | 1567 | N/A | -| accountInfo4 | 2059 | N/A | -| accountInfo8 | 3856 | N/A | -| accountEmptyInit1 | 5958 | N/A | -| accountEmpty1 | 1090 | N/A | -| accountEmptyInit2 | 10574 | N/A | -| accountEmpty2 | 1852 | N/A | -| accountEmptyInit4 | 19557 | N/A | -| accountEmpty4 | 2646 | N/A | -| accountEmptyInit8 | 37541 | N/A | -| accountEmpty8 | 5043 | N/A | -| accountSizedInit1 | 6063 | N/A | -| accountSized1 | 1135 | N/A | -| accountSizedInit2 | 10783 | N/A | -| accountSized2 | 1966 | N/A | -| accountSizedInit4 | 19975 | N/A | -| accountSized4 | 2787 | N/A | -| accountSizedInit8 | 38381 | N/A | -| accountSized8 | 5359 | N/A | -| accountUnsizedInit1 | 6193 | N/A | -| accountUnsized1 | 1243 | N/A | -| accountUnsizedInit2 | 11042 | N/A | -| accountUnsized2 | 1893 | N/A | -| accountUnsizedInit4 | 20495 | N/A | -| accountUnsized4 | 3104 | N/A | -| accountUnsizedInit8 | 39419 | N/A | -| accountUnsized8 | 6051 | N/A | -| boxedAccountEmptyInit1 | 6160 | N/A | +| accountInfo2 | 1 567 | N/A | +| accountInfo4 | 2 059 | N/A | +| accountInfo8 | 3 856 | N/A | +| accountEmptyInit1 | 5 958 | N/A | +| accountEmpty1 | 1 090 | N/A | +| accountEmptyInit2 | 10 574 | N/A | +| accountEmpty2 | 1 852 | N/A | +| accountEmptyInit4 | 19 557 | N/A | +| accountEmpty4 | 2 646 | N/A | +| accountEmptyInit8 | 37 541 | N/A | +| accountEmpty8 | 5 043 | N/A | +| accountSizedInit1 | 6 063 | N/A | +| accountSized1 | 1 135 | N/A | +| accountSizedInit2 | 10 783 | N/A | +| accountSized2 | 1 966 | N/A | +| accountSizedInit4 | 19 975 | N/A | +| accountSized4 | 2 787 | N/A | +| accountSizedInit8 | 38 381 | N/A | +| accountSized8 | 5 359 | N/A | +| accountUnsizedInit1 | 6 193 | N/A | +| accountUnsized1 | 1 243 | N/A | +| accountUnsizedInit2 | 11 042 | N/A | +| accountUnsized2 | 1 893 | N/A | +| accountUnsizedInit4 | 20 495 | N/A | +| accountUnsized4 | 3 104 | N/A | +| accountUnsizedInit8 | 39 419 | N/A | +| accountUnsized8 | 6 051 | N/A | +| boxedAccountEmptyInit1 | 6 160 | N/A | | boxedAccountEmpty1 | 976 | N/A | -| boxedAccountEmptyInit2 | 10784 | N/A | -| boxedAccountEmpty2 | 1499 | N/A | -| boxedAccountEmptyInit4 | 19500 | N/A | -| boxedAccountEmpty4 | 2530 | N/A | -| boxedAccountEmptyInit8 | 37415 | N/A | -| boxedAccountEmpty8 | 4780 | N/A | -| boxedAccountSizedInit1 | 6256 | N/A | -| boxedAccountSized1 | 1003 | N/A | -| boxedAccountSizedInit2 | 10975 | N/A | -| boxedAccountSized2 | 1554 | N/A | -| boxedAccountSizedInit4 | 19884 | N/A | -| boxedAccountSized4 | 2642 | N/A | -| boxedAccountSizedInit8 | 38182 | N/A | -| boxedAccountSized8 | 5003 | N/A | -| boxedAccountUnsizedInit1 | 6374 | N/A | -| boxedAccountUnsized1 | 1069 | N/A | -| boxedAccountUnsizedInit2 | 11211 | N/A | -| boxedAccountUnsized2 | 1679 | N/A | -| boxedAccountUnsizedInit4 | 20351 | N/A | -| boxedAccountUnsized4 | 2899 | N/A | -| boxedAccountUnsizedInit8 | 39118 | N/A | -| boxedAccountUnsized8 | 5517 | N/A | -| boxedInterfaceAccountMint1 | 2299 | N/A | -| boxedInterfaceAccountMint2 | 4053 | N/A | -| boxedInterfaceAccountMint4 | 7538 | N/A | -| boxedInterfaceAccountMint8 | 14699 | N/A | -| boxedInterfaceAccountToken1 | 1737 | N/A | -| boxedInterfaceAccountToken2 | 2928 | N/A | -| boxedInterfaceAccountToken4 | 5291 | N/A | -| boxedInterfaceAccountToken8 | 10205 | N/A | -| interfaceAccountMint1 | 2530 | N/A | -| interfaceAccountMint2 | 4726 | N/A | -| interfaceAccountMint4 | 9431 | N/A | -| interfaceAccountMint8 | 17709 | N/A | -| interfaceAccountToken1 | 1755 | N/A | -| interfaceAccountToken2 | 3211 | N/A | -| interfaceAccountToken4 | 6006 | N/A | +| boxedAccountEmptyInit2 | 10 784 | N/A | +| boxedAccountEmpty2 | 1 499 | N/A | +| boxedAccountEmptyInit4 | 19 500 | N/A | +| boxedAccountEmpty4 | 2 530 | N/A | +| boxedAccountEmptyInit8 | 37 415 | N/A | +| boxedAccountEmpty8 | 4 780 | N/A | +| boxedAccountSizedInit1 | 6 256 | N/A | +| boxedAccountSized1 | 1 003 | N/A | +| boxedAccountSizedInit2 | 10 975 | N/A | +| boxedAccountSized2 | 1 554 | N/A | +| boxedAccountSizedInit4 | 19 884 | N/A | +| boxedAccountSized4 | 2 642 | N/A | +| boxedAccountSizedInit8 | 38 182 | N/A | +| boxedAccountSized8 | 5 003 | N/A | +| boxedAccountUnsizedInit1 | 6 374 | N/A | +| boxedAccountUnsized1 | 1 069 | N/A | +| boxedAccountUnsizedInit2 | 11 211 | N/A | +| boxedAccountUnsized2 | 1 679 | N/A | +| boxedAccountUnsizedInit4 | 20 351 | N/A | +| boxedAccountUnsized4 | 2 899 | N/A | +| boxedAccountUnsizedInit8 | 39 118 | N/A | +| boxedAccountUnsized8 | 5 517 | N/A | +| boxedInterfaceAccountMint1 | 2 299 | N/A | +| boxedInterfaceAccountMint2 | 4 053 | N/A | +| boxedInterfaceAccountMint4 | 7 538 | N/A | +| boxedInterfaceAccountMint8 | 14 699 | N/A | +| boxedInterfaceAccountToken1 | 1 737 | N/A | +| boxedInterfaceAccountToken2 | 2 928 | N/A | +| boxedInterfaceAccountToken4 | 5 291 | N/A | +| boxedInterfaceAccountToken8 | 10 205 | N/A | +| interfaceAccountMint1 | 2 530 | N/A | +| interfaceAccountMint2 | 4 726 | N/A | +| interfaceAccountMint4 | 9 431 | N/A | +| interfaceAccountMint8 | 17 709 | N/A | +| interfaceAccountToken1 | 1 755 | N/A | +| interfaceAccountToken2 | 3 211 | N/A | +| interfaceAccountToken4 | 6 006 | N/A | | interface1 | 999 | N/A | -| interface2 | 1574 | N/A | -| interface4 | 1996 | N/A | -| interface8 | 3651 | N/A | +| interface2 | 1 574 | N/A | +| interface4 | 1 996 | N/A | +| interface8 | 3 651 | N/A | | program1 | 999 | N/A | -| program2 | 1573 | N/A | -| program4 | 1998 | N/A | -| program8 | 3651 | N/A | +| program2 | 1 573 | N/A | +| program4 | 1 998 | N/A | +| program8 | 3 651 | N/A | | signer1 | 958 | N/A | -| signer2 | 1576 | N/A | -| signer4 | 2079 | N/A | -| signer8 | 3895 | N/A | -| systemAccount1 | 1013 | N/A | -| systemAccount2 | 1686 | N/A | -| systemAccount4 | 2298 | N/A | -| systemAccount8 | 4336 | N/A | +| signer2 | 1 576 | N/A | +| signer4 | 2 079 | N/A | +| signer8 | 3 895 | N/A | +| systemAccount1 | 1 013 | N/A | +| systemAccount2 | 1 686 | N/A | +| systemAccount4 | 2 298 | N/A | +| systemAccount8 | 4 336 | N/A | | uncheckedAccount1 | 953 | N/A | -| uncheckedAccount2 | 1567 | N/A | -| uncheckedAccount4 | 2060 | N/A | -| uncheckedAccount8 | 3855 | N/A | +| uncheckedAccount2 | 1 567 | N/A | +| uncheckedAccount4 | 2 060 | N/A | +| uncheckedAccount8 | 3 855 | N/A | --- diff --git a/bench/STACK_MEMORY.md b/bench/STACK_MEMORY.md new file mode 100644 index 0000000000..96c70ca4ec --- /dev/null +++ b/bench/STACK_MEMORY.md @@ -0,0 +1,309 @@ +# Stack Memory + +All notable changes in stack memory usage will be documented in this file. + +The changes are calculated by comparing the current results with the last version's results. Increase in usage is shown with 🔴 and decrease is shown with 🟢. + +The programs and their tests are located in [/tests/bench](https://github.com/coral-xyz/anchor/tree/master/tests/bench). + +> **Note** +> Results documented in this file are autogenerated. Running the tests will update the current results when necessary, manually editing the results should be avoided. + +> **Warning** +> Results may vary depending on Solana version. + +## [Unreleased] + +Solana version: 1.16.0 + +| Instruction | Stack Memory | +/- | +| ------------------------------ | ------------ | ---------------------- | +| account_info1 | 128 | 🟢 **-200 (60.98%)** | +| account_info2 | 128 | 🟢 **-248 (65.96%)** | +| account_info4 | 128 | 🟢 **-432 (77.14%)** | +| account_info8 | 128 | 🟢 **-600 (82.42%)** | +| account_empty_init1 | 320 | 🟢 **-272 (45.95%)** | +| account_empty_init2 | 400 | 🟢 **-160 (28.57%)** | +| account_empty_init4 | 448 | 🟢 **-184 (29.11%)** | +| account_empty_init8 | 640 | 🟢 **-184 (22.33%)** | +| account_empty1 | 128 | 🟢 **-192 (60.00%)** | +| account_empty2 | 128 | 🟢 **-240 (65.22%)** | +| account_empty4 | 128 | 🟢 **-424 (76.81%)** | +| account_empty8 | 128 | 🟢 **-600 (82.42%)** | +| account_sized_init1 | 328 | 🟢 **-272 (45.33%)** | +| account_sized_init2 | 416 | 🟢 **-136 (24.64%)** | +| account_sized_init4 | 480 | 🟢 **-184 (27.71%)** | +| account_sized_init8 | 704 | 🟢 **-184 (20.72%)** | +| account_sized1 | 128 | 🟢 **-200 (60.98%)** | +| account_sized2 | 128 | 🟢 **-264 (67.35%)** | +| account_sized4 | 128 | 🟢 **-440 (77.46%)** | +| account_sized8 | 128 | 🟢 **-664 (83.84%)** | +| account_unsized_init1 | 344 | 🟢 **-280 (44.87%)** | +| account_unsized_init2 | 448 | 🟢 **-136 (23.29%)** | +| account_unsized_init4 | 544 | 🟢 **-184 (25.27%)** | +| account_unsized_init8 | 832 | 🟢 **-184 (18.11%)** | +| account_unsized1 | 128 | 🟢 **-216 (62.79%)** | +| account_unsized2 | 128 | 🟢 **-328 (71.93%)** | +| account_unsized4 | 128 | 🟢 **-504 (79.75%)** | +| account_unsized8 | 128 | 🟢 **-792 (86.09%)** | +| boxed_account_empty_init1 | 176 | 🟢 **-376 (68.12%)** | +| boxed_account_empty_init2 | 208 | 🟢 **-192 (48.00%)** | +| boxed_account_empty_init4 | 288 | 🟢 **-144 (33.33%)** | +| boxed_account_empty_init8 | 320 | 🟢 **-176 (35.48%)** | +| boxed_account_empty1 | 128 | 🟢 **-192 (60.00%)** | +| boxed_account_empty2 | 144 | 🟢 **-176 (55.00%)** | +| boxed_account_empty4 | 144 | 🟢 **-176 (55.00%)** | +| boxed_account_empty8 | 128 | 🟢 **-208 (61.90%)** | +| boxed_account_sized_init1 | 176 | 🟢 **-376 (68.12%)** | +| boxed_account_sized_init2 | 208 | 🟢 **-192 (48.00%)** | +| boxed_account_sized_init4 | 288 | 🟢 **-144 (33.33%)** | +| boxed_account_sized_init8 | 320 | 🟢 **-176 (35.48%)** | +| boxed_account_sized1 | 128 | 🟢 **-192 (60.00%)** | +| boxed_account_sized2 | 144 | 🟢 **-176 (55.00%)** | +| boxed_account_sized4 | 144 | 🟢 **-176 (55.00%)** | +| boxed_account_sized8 | 128 | 🟢 **-208 (61.90%)** | +| boxed_account_unsized_init1 | 280 | 🟢 **-272 (49.28%)** | +| boxed_account_unsized_init2 | 320 | 🟢 **-80 (20.00%)** | +| boxed_account_unsized_init4 | 288 | 🟢 **-144 (33.33%)** | +| boxed_account_unsized_init8 | 320 | 🟢 **-176 (35.48%)** | +| boxed_account_unsized1 | 152 | 🟢 **-168 (52.50%)** | +| boxed_account_unsized2 | 144 | 🟢 **-176 (55.00%)** | +| boxed_account_unsized4 | 176 | 🟢 **-144 (45.00%)** | +| boxed_account_unsized8 | 192 | 🟢 **-144 (42.86%)** | +| boxed_interface_account_mint1 | 128 | 🟢 **-192 (60.00%)** | +| boxed_interface_account_mint2 | 144 | 🟢 **-176 (55.00%)** | +| boxed_interface_account_mint4 | 144 | 🟢 **-176 (55.00%)** | +| boxed_interface_account_mint8 | 128 | 🟢 **-208 (61.90%)** | +| boxed_interface_account_token1 | 128 | 🟢 **-192 (60.00%)** | +| boxed_interface_account_token2 | 144 | 🟢 **-176 (55.00%)** | +| boxed_interface_account_token4 | 144 | 🟢 **-176 (55.00%)** | +| boxed_interface_account_token8 | 128 | 🟢 **-208 (61.90%)** | +| interface_account_mint1 | 128 | 🟢 **-376 (74.60%)** | +| interface_account_mint2 | 128 | 🟢 **-552 (81.18%)** | +| interface_account_mint4 | 128 | 🟢 **-888 (87.40%)** | +| interface_account_mint8 | 128 | 🟢 **-1 560 (92.42%)** | +| interface_account_token1 | 128 | 🟢 **-552 (81.18%)** | +| interface_account_token2 | 128 | 🟢 **-728 (85.05%)** | +| interface_account_token4 | 128 | 🟢 **-1 240 (90.64%)** | +| interface1 | 128 | 🟢 **-192 (60.00%)** | +| interface2 | 128 | 🟢 **-240 (65.22%)** | +| interface4 | 128 | 🟢 **-424 (76.81%)** | +| interface8 | 128 | 🟢 **-600 (82.42%)** | +| program1 | 128 | 🟢 **-192 (60.00%)** | +| program2 | 128 | 🟢 **-240 (65.22%)** | +| program4 | 128 | 🟢 **-424 (76.81%)** | +| program8 | 128 | 🟢 **-600 (82.42%)** | +| signer1 | 128 | 🟢 **-200 (60.98%)** | +| signer2 | 128 | 🟢 **-248 (65.96%)** | +| signer4 | 128 | 🟢 **-432 (77.14%)** | +| signer8 | 128 | 🟢 **-600 (82.42%)** | +| system_account1 | 128 | 🟢 **-200 (60.98%)** | +| system_account2 | 128 | 🟢 **-248 (65.96%)** | +| system_account4 | 128 | 🟢 **-432 (77.14%)** | +| system_account8 | 128 | 🟢 **-600 (82.42%)** | +| unchecked_account1 | 128 | 🟢 **-200 (60.98%)** | +| unchecked_account2 | 128 | 🟢 **-248 (65.96%)** | +| unchecked_account4 | 128 | 🟢 **-432 (77.14%)** | +| unchecked_account8 | 128 | 🟢 **-600 (82.42%)** | + +### Notable changes + +- `Box` the `anchor_lang::Result` error variants ([#2600](https://github.com/coral-xyz/anchor/pull/2600)). + +--- + +## [0.28.0] + +Solana version: 1.16.0 + +| Instruction | Stack Memory | +/- | +| ------------------------------ | ------------ | ---------------------- | +| account_info1 | 328 | 🔴 **+80 (32.26%)** | +| account_info2 | 376 | 🟢 **-16 (4.08%)** | +| account_info4 | 560 | 🔴 **+48 (9.38%)** | +| account_info8 | 728 | 🟢 **-168 (18.75%)** | +| account_empty_init1 | 592 | 🔴 **+8 (1.37%)** | +| account_empty_init2 | 560 | 🔴 **+48 (9.38%)** | +| account_empty_init4 | 632 | 🟢 **-72 (10.23%)** | +| account_empty_init8 | 824 | 🟢 **-264 (24.26%)** | +| account_empty1 | 320 | 🔴 **+120 (60.00%)** | +| account_empty2 | 368 | 🔴 **+24 (6.98%)** | +| account_empty4 | 552 | 🔴 **+88 (18.97%)** | +| account_empty8 | 728 | 🟢 **-120 (14.15%)** | +| account_sized_init1 | 600 | 🔴 **+8 (1.35%)** | +| account_sized_init2 | 552 | 🔴 **+8 (1.47%)** | +| account_sized_init4 | 664 | 🟢 **-104 (13.54%)** | +| account_sized_init8 | 888 | 🟢 **-328 (26.97%)** | +| account_sized1 | 328 | 🔴 **+128 (64.00%)** | +| account_sized2 | 392 | 🔴 **+32 (8.89%)** | +| account_sized4 | 568 | 🔴 **+40 (7.58%)** | +| account_sized8 | 792 | 🟢 **-184 (18.85%)** | +| account_unsized_init1 | 624 | 🔴 **+16 (2.63%)** | +| account_unsized_init2 | 584 | 🟢 **-24 (3.95%)** | +| account_unsized_init4 | 728 | 🟢 **-168 (18.75%)** | +| account_unsized_init8 | 1 016 | 🟢 **-456 (30.98%)** | +| account_unsized1 | 344 | 🔴 **+168 (95.45%)** | +| account_unsized2 | 456 | 🔴 **+64 (16.33%)** | +| account_unsized4 | 632 | 🟢 **-24 (3.66%)** | +| account_unsized8 | 920 | 🟢 **-312 (25.32%)** | +| boxed_account_empty_init1 | 552 | 🔴 **+8 (1.47%)** | +| boxed_account_empty_init2 | 400 | 🟢 **-8 (1.96%)** | +| boxed_account_empty_init4 | 432 | 🔴 **+8 (1.89%)** | +| boxed_account_empty_init8 | 496 | 🔴 **+40 (8.77%)** | +| boxed_account_empty1 | 320 | 🔴 **+80 (33.33%)** | +| boxed_account_empty2 | 320 | 🔴 **+72 (29.03%)** | +| boxed_account_empty4 | 320 | 🔴 **+40 (14.29%)** | +| boxed_account_empty8 | 336 | 🔴 **+24 (7.69%)** | +| boxed_account_sized_init1 | 552 | 🔴 **+8 (1.47%)** | +| boxed_account_sized_init2 | 400 | 🟢 **-8 (1.96%)** | +| boxed_account_sized_init4 | 432 | 🔴 **+8 (1.89%)** | +| boxed_account_sized_init8 | 496 | 🔴 **+40 (8.77%)** | +| boxed_account_sized1 | 320 | 🔴 **+80 (33.33%)** | +| boxed_account_sized2 | 320 | 🔴 **+72 (29.03%)** | +| boxed_account_sized4 | 320 | 🔴 **+40 (14.29%)** | +| boxed_account_sized8 | 336 | 🔴 **+24 (7.69%)** | +| boxed_account_unsized_init1 | 552 | 🔴 **+8 (1.47%)** | +| boxed_account_unsized_init2 | 400 | 🟢 **-8 (1.96%)** | +| boxed_account_unsized_init4 | 432 | 🔴 **+8 (1.89%)** | +| boxed_account_unsized_init8 | 496 | 🔴 **+40 (8.77%)** | +| boxed_account_unsized1 | 320 | 🔴 **+72 (29.03%)** | +| boxed_account_unsized2 | 320 | 🔴 **+72 (29.03%)** | +| boxed_account_unsized4 | 320 | 🔴 **+40 (14.29%)** | +| boxed_account_unsized8 | 336 | 🔴 **+24 (7.69%)** | +| boxed_interface_account_mint1 | 320 | 🔴 **+80 (33.33%)** | +| boxed_interface_account_mint2 | 320 | 🔴 **+72 (29.03%)** | +| boxed_interface_account_mint4 | 320 | 🔴 **+40 (14.29%)** | +| boxed_interface_account_mint8 | 336 | 🔴 **+24 (7.69%)** | +| boxed_interface_account_token1 | 320 | 🔴 **+80 (33.33%)** | +| boxed_interface_account_token2 | 320 | 🔴 **+72 (29.03%)** | +| boxed_interface_account_token4 | 320 | 🔴 **+40 (14.29%)** | +| boxed_interface_account_token8 | 336 | 🔴 **+24 (7.69%)** | +| interface_account_mint1 | 504 | 🔴 **+296 (142.31%)** | +| interface_account_mint2 | 680 | 🟢 **-72 (9.57%)** | +| interface_account_mint4 | 1 016 | 🟢 **-408 (28.65%)** | +| interface_account_mint8 | 1 688 | 🟢 **-1 080 (39.02%)** | +| interface_account_token1 | 680 | 🔴 **+416 (157.58%)** | +| interface_account_token2 | 856 | 🟢 **-248 (22.46%)** | +| interface_account_token4 | 1 368 | 🟢 **-760 (35.71%)** | +| interface1 | 320 | 🔴 **+120 (60.00%)** | +| interface2 | 368 | 🔴 **+24 (6.98%)** | +| interface4 | 552 | 🔴 **+88 (18.97%)** | +| interface8 | 728 | 🟢 **-120 (14.15%)** | +| program1 | 320 | 🔴 **+120 (60.00%)** | +| program2 | 368 | 🔴 **+24 (6.98%)** | +| program4 | 552 | 🔴 **+88 (18.97%)** | +| program8 | 728 | 🟢 **-120 (14.15%)** | +| signer1 | 328 | 🔴 **+80 (32.26%)** | +| signer2 | 376 | 🟢 **-16 (4.08%)** | +| signer4 | 560 | 🔴 **+48 (9.38%)** | +| signer8 | 728 | 🟢 **-168 (18.75%)** | +| system_account1 | 328 | 🔴 **+80 (32.26%)** | +| system_account2 | 376 | 🟢 **-16 (4.08%)** | +| system_account4 | 560 | 🔴 **+48 (9.38%)** | +| system_account8 | 728 | 🟢 **-168 (18.75%)** | +| unchecked_account1 | 328 | 🔴 **+80 (32.26%)** | +| unchecked_account2 | 376 | 🟢 **-16 (4.08%)** | +| unchecked_account4 | 560 | 🔴 **+48 (9.38%)** | +| unchecked_account8 | 728 | 🟢 **-168 (18.75%)** | + +### Notable changes + +- Upgrading Solana to `1.16`. The difference in stack memory usage between `0.27.0` and `0.28.0` is the direct result of upgrading Solana version(both build tools and crates) ([#2512](https://github.com/coral-xyz/anchor/pull/2512)). + +--- + +## [0.27.0] + +Solana version: 1.14.16 + +| Instruction | Stack Memory | +/- | +| ------------------------------ | ------------ | --- | +| account_info1 | 248 | N/A | +| account_info2 | 392 | N/A | +| account_info4 | 512 | N/A | +| account_info8 | 896 | N/A | +| account_empty_init1 | 584 | N/A | +| account_empty_init2 | 512 | N/A | +| account_empty_init4 | 704 | N/A | +| account_empty_init8 | 1 088 | N/A | +| account_empty1 | 200 | N/A | +| account_empty2 | 344 | N/A | +| account_empty4 | 464 | N/A | +| account_empty8 | 848 | N/A | +| account_sized_init1 | 592 | N/A | +| account_sized_init2 | 544 | N/A | +| account_sized_init4 | 768 | N/A | +| account_sized_init8 | 1 216 | N/A | +| account_sized1 | 200 | N/A | +| account_sized2 | 360 | N/A | +| account_sized4 | 528 | N/A | +| account_sized8 | 976 | N/A | +| account_unsized_init1 | 608 | N/A | +| account_unsized_init2 | 608 | N/A | +| account_unsized_init4 | 896 | N/A | +| account_unsized_init8 | 1 472 | N/A | +| account_unsized1 | 176 | N/A | +| account_unsized2 | 392 | N/A | +| account_unsized4 | 656 | N/A | +| account_unsized8 | 1 232 | N/A | +| boxed_account_empty_init1 | 544 | N/A | +| boxed_account_empty_init2 | 408 | N/A | +| boxed_account_empty_init4 | 424 | N/A | +| boxed_account_empty_init8 | 456 | N/A | +| boxed_account_empty1 | 240 | N/A | +| boxed_account_empty2 | 248 | N/A | +| boxed_account_empty4 | 280 | N/A | +| boxed_account_empty8 | 312 | N/A | +| boxed_account_sized_init1 | 544 | N/A | +| boxed_account_sized_init2 | 408 | N/A | +| boxed_account_sized_init4 | 424 | N/A | +| boxed_account_sized_init8 | 456 | N/A | +| boxed_account_sized1 | 240 | N/A | +| boxed_account_sized2 | 248 | N/A | +| boxed_account_sized4 | 280 | N/A | +| boxed_account_sized8 | 312 | N/A | +| boxed_account_unsized_init1 | 544 | N/A | +| boxed_account_unsized_init2 | 408 | N/A | +| boxed_account_unsized_init4 | 424 | N/A | +| boxed_account_unsized_init8 | 456 | N/A | +| boxed_account_unsized1 | 248 | N/A | +| boxed_account_unsized2 | 248 | N/A | +| boxed_account_unsized4 | 280 | N/A | +| boxed_account_unsized8 | 312 | N/A | +| boxed_interface_account_mint1 | 240 | N/A | +| boxed_interface_account_mint2 | 248 | N/A | +| boxed_interface_account_mint4 | 280 | N/A | +| boxed_interface_account_mint8 | 312 | N/A | +| boxed_interface_account_token1 | 240 | N/A | +| boxed_interface_account_token2 | 248 | N/A | +| boxed_interface_account_token4 | 280 | N/A | +| boxed_interface_account_token8 | 312 | N/A | +| interface_account_mint1 | 208 | N/A | +| interface_account_mint2 | 752 | N/A | +| interface_account_mint4 | 1 424 | N/A | +| interface_account_mint8 | 2 768 | N/A | +| interface_account_token1 | 264 | N/A | +| interface_account_token2 | 1 104 | N/A | +| interface_account_token4 | 2 128 | N/A | +| interface1 | 200 | N/A | +| interface2 | 344 | N/A | +| interface4 | 464 | N/A | +| interface8 | 848 | N/A | +| program1 | 200 | N/A | +| program2 | 344 | N/A | +| program4 | 464 | N/A | +| program8 | 848 | N/A | +| signer1 | 248 | N/A | +| signer2 | 392 | N/A | +| signer4 | 512 | N/A | +| signer8 | 896 | N/A | +| system_account1 | 248 | N/A | +| system_account2 | 392 | N/A | +| system_account4 | 512 | N/A | +| system_account8 | 896 | N/A | +| unchecked_account1 | 248 | N/A | +| unchecked_account2 | 392 | N/A | +| unchecked_account4 | 512 | N/A | +| unchecked_account8 | 896 | N/A | + +--- diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 76b26fb5e3..b265f72eae 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -14,16 +14,15 @@ path = "src/bin/main.rs" [features] dev = [] -default = [] [dependencies] anchor-client = { path = "../client", version = "0.28.0" } anchor-lang = { path = "../lang", version = "0.28.0" } -anchor-syn = { path = "../lang/syn", features = ["event-cpi", "idl", "init-if-needed"], version = "0.28.0" } +anchor-syn = { path = "../lang/syn", features = ["event-cpi", "idl-parse", "init-if-needed"], version = "0.28.0" } anyhow = "1.0.32" base64 = "0.13.1" bincode = "1.3.3" -cargo_toml = "0.13.0" +cargo_toml = "0.15.3" chrono = "0.4.19" clap = { version = "4.2.4", features = ["derive"] } dirs = "4.0" @@ -42,9 +41,8 @@ solana-cli-config = ">=1.14, <1.17" solana-faucet = ">=1.14, <1.17" solana-program = ">=1.14, <1.17" solana-sdk = ">=1.14, <1.17" -solang-parser = "=0.2.3" +solang-parser = "=0.3.1" syn = { version = "1.0.60", features = ["full", "extra-traits"] } tar = "0.4.35" -tokio = "~1.14.1" -toml = "0.5.8" +toml = "0.7.6" walkdir = "2.3.2" diff --git a/cli/src/config.rs b/cli/src/config.rs index 6328416106..fcbdae23f1 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -1,6 +1,6 @@ use crate::is_hidden; use anchor_client::Cluster; -use anchor_syn::idl::Idl; +use anchor_syn::idl::types::Idl; use anyhow::{anyhow, bail, Context, Error, Result}; use clap::{Parser, ValueEnum}; use heck::ToSnakeCase; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index b03a57733f..282d5e5add 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -6,7 +6,10 @@ use crate::config::{ use anchor_client::Cluster; use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY}; use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize}; -use anchor_syn::idl::{EnumFields, Idl, IdlType, IdlTypeDefinitionTy}; +use anchor_syn::idl::types::{ + EnumFields, Idl, IdlConst, IdlErrorCode, IdlEvent, IdlType, IdlTypeDefinition, + IdlTypeDefinitionTy, +}; use anyhow::{anyhow, Context, Result}; use clap::Parser; use flate2::read::GzDecoder; @@ -14,9 +17,10 @@ use flate2::read::ZlibDecoder; use flate2::write::{GzEncoder, ZlibEncoder}; use flate2::Compression; use heck::{ToKebabCase, ToSnakeCase}; -use regex::RegexBuilder; +use regex::{Regex, RegexBuilder}; use reqwest::blocking::multipart::{Form, Part}; use reqwest::blocking::Client; +use rust_template::ProgramTemplate; use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; use serde_json::{json, Map, Value as JsonValue}; @@ -66,15 +70,23 @@ pub struct Opts { pub enum Command { /// Initializes a workspace. Init { + /// Workspace name name: String, + /// Use JavaScript instead of TypeScript #[clap(short, long)] javascript: bool, + /// Use Solidity instead of Rust #[clap(short, long)] solidity: bool, + /// Don't initialize git #[clap(long)] no_git: bool, + /// Use `jest` instead of `mocha` for tests #[clap(long)] jest: bool, + /// Rust program template to use + #[clap(value_enum, short, long, default_value = "single")] + template: ProgramTemplate, }, /// Builds the workspace. #[clap(name = "build", alias = "b")] @@ -204,9 +216,14 @@ pub enum Command { }, /// Creates a new program. New { + /// Program name + name: String, + /// Use Solidity instead of Rust #[clap(short, long)] solidity: bool, - name: String, + /// Rust program template to use + #[clap(value_enum, short, long, default_value = "single")] + template: ProgramTemplate, }, /// Commands for interacting with interface definitions. Idl { @@ -417,6 +434,18 @@ pub enum IdlCommand { #[clap(long)] no_docs: bool, }, + /// Generates the IDL for the program using the compilation method. + Build { + /// Output file for the IDL (stdout if not specified) + #[clap(short, long)] + out: Option, + /// Output file for the TypeScript IDL + #[clap(short = 't', long)] + out_ts: Option, + /// Suppress doc strings in output + #[clap(long)] + no_docs: bool, + }, /// Fetches an IDL for the given address from a cluster. /// The address can be a program, IDL account, or IDL buffer. Fetch { @@ -441,8 +470,21 @@ pub fn entry(opts: Opts) -> Result<()> { solidity, no_git, jest, - } => init(&opts.cfg_override, name, javascript, solidity, no_git, jest), - Command::New { solidity, name } => new(&opts.cfg_override, solidity, name), + template, + } => init( + &opts.cfg_override, + name, + javascript, + solidity, + no_git, + jest, + template, + ), + Command::New { + solidity, + name, + template, + } => new(&opts.cfg_override, solidity, name, template), Command::Build { idl, idl_ts, @@ -589,6 +631,7 @@ fn init( solidity: bool, no_git: bool, jest: bool, + template: ProgramTemplate, ) -> Result<()> { if Config::discover(cfg_override)?.is_some() { return Err(anyhow!("Workspace already initialized")); @@ -667,17 +710,11 @@ fn init( // Build the program. if solidity { - fs::create_dir("solidity")?; - - new_solidity_program(&project_name)?; + solidity_template::create_program(&project_name)?; } else { - // Build virtual manifest for rust programs - fs::write("Cargo.toml", rust_template::virtual_manifest())?; - - fs::create_dir("programs")?; - - new_rust_program(&project_name)?; + rust_template::create_program(&project_name, template)?; } + // Build the test suite. fs::create_dir("tests")?; // Build the migrations directory. @@ -768,7 +805,12 @@ fn install_node_modules(cmd: &str) -> Result { } // Creates a new program crate in the `programs/` directory. -fn new(cfg_override: &ConfigOverride, solidity: bool, name: String) -> Result<()> { +fn new( + cfg_override: &ConfigOverride, + solidity: bool, + name: String, + template: ProgramTemplate, +) -> Result<()> { with_workspace(cfg_override, |cfg| { match cfg.path().parent() { None => { @@ -787,10 +829,10 @@ fn new(cfg_override: &ConfigOverride, solidity: bool, name: String) -> Result<() name.clone(), ProgramDeployment { address: if solidity { - new_solidity_program(&name)?; + solidity_template::create_program(&name)?; solidity_template::default_program_id() } else { - new_rust_program(&name)?; + rust_template::create_program(&name, template)?; rust_template::get_or_create_program_id(&name) }, path: None, @@ -808,26 +850,32 @@ fn new(cfg_override: &ConfigOverride, solidity: bool, name: String) -> Result<() }) } -// Creates a new rust program crate in the current directory with `name`. -fn new_rust_program(name: &str) -> Result<()> { - if !PathBuf::from("Cargo.toml").exists() { - fs::write("Cargo.toml", rust_template::virtual_manifest())?; +/// Array of (path, content) tuple. +pub type Files = Vec<(PathBuf, String)>; + +/// Create files from the given (path, content) tuple array. +/// +/// # Example +/// +/// ```ignore +/// crate_files(vec![("programs/my_program/src/lib.rs".into(), "// Content".into())])?; +/// ``` +pub fn create_files(files: &Files) -> Result<()> { + for (path, content) in files { + let path = Path::new(path); + if path.exists() { + continue; + } + + match path.extension() { + Some(_) => { + fs::create_dir_all(path.parent().unwrap())?; + fs::write(path, content)?; + } + None => fs::create_dir_all(path)?, + } } - fs::create_dir_all(format!("programs/{name}/src/"))?; - let mut cargo_toml = File::create(format!("programs/{name}/Cargo.toml"))?; - cargo_toml.write_all(rust_template::cargo_toml(name).as_bytes())?; - let mut xargo_toml = File::create(format!("programs/{name}/Xargo.toml"))?; - xargo_toml.write_all(rust_template::xargo_toml().as_bytes())?; - let mut lib_rs = File::create(format!("programs/{name}/src/lib.rs"))?; - lib_rs.write_all(rust_template::lib_rs(name).as_bytes())?; - Ok(()) -} -// Creates a new solidity program in the current directory with `name`. -fn new_solidity_program(name: &str) -> Result<()> { - fs::create_dir_all("solidity")?; - let mut lib_rs = File::create(format!("solidity/{name}.sol"))?; - lib_rs.write_all(solidity_template::solidity(name).as_bytes())?; Ok(()) } @@ -1105,7 +1153,9 @@ fn build_rust_cwd( Some(p) => std::env::set_current_dir(p)?, }; match build_config.verifiable { - false => _build_rust_cwd(cfg, idl_out, idl_ts_out, skip_lint, arch, cargo_args), + false => _build_rust_cwd( + cfg, idl_out, idl_ts_out, skip_lint, no_docs, arch, cargo_args, + ), true => build_cwd_verifiable( cfg, cargo_toml, @@ -1192,28 +1242,28 @@ fn build_cwd_verifiable( Ok(_) => { // Build the idl. println!("Extracting the IDL"); - if let Ok(Some(idl)) = extract_idl(cfg, "src/lib.rs", skip_lint, no_docs) { - // Write out the JSON file. - println!("Writing the IDL file"); - let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.name)); - write_idl(&idl, OutFile::File(out_file))?; - - // Write out the TypeScript type. - println!("Writing the .ts file"); - let ts_file = workspace_dir.join(format!("target/types/{}.ts", idl.name)); - fs::write(&ts_file, rust_template::idl_ts(&idl)?)?; - - // Copy out the TypeScript type. - if !&cfg.workspace.types.is_empty() { - fs::copy( - ts_file, - workspace_dir - .join(&cfg.workspace.types) - .join(idl.name) - .with_extension("ts"), - )?; - } + let idl = generate_idl(cfg, skip_lint, no_docs)?; + // Write out the JSON file. + println!("Writing the IDL file"); + let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.name)); + write_idl(&idl, OutFile::File(out_file))?; + + // Write out the TypeScript type. + println!("Writing the .ts file"); + let ts_file = workspace_dir.join(format!("target/types/{}.ts", idl.name)); + fs::write(&ts_file, rust_template::idl_ts(&idl)?)?; + + // Copy out the TypeScript type. + if !&cfg.workspace.types.is_empty() { + fs::copy( + ts_file, + workspace_dir + .join(&cfg.workspace.types) + .join(idl.name) + .with_extension("ts"), + )?; } + println!("Build success"); } } @@ -1469,6 +1519,7 @@ fn _build_rust_cwd( idl_out: Option, idl_ts_out: Option, skip_lint: bool, + no_docs: bool, arch: &ProgramArch, cargo_args: Vec, ) -> Result<()> { @@ -1484,34 +1535,33 @@ fn _build_rust_cwd( std::process::exit(exit.status.code().unwrap_or(1)); } - // Always assume idl is located at src/lib.rs. - if let Some(idl) = extract_idl(cfg, "src/lib.rs", skip_lint, false)? { - // JSON out path. - let out = match idl_out { - None => PathBuf::from(".").join(&idl.name).with_extension("json"), - Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("json")), - }; - // TS out path. - let ts_out = match idl_ts_out { - None => PathBuf::from(".").join(&idl.name).with_extension("ts"), - Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("ts")), - }; + // Generate IDL + let idl = generate_idl(cfg, skip_lint, no_docs)?; + // JSON out path. + let out = match idl_out { + None => PathBuf::from(".").join(&idl.name).with_extension("json"), + Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("json")), + }; + // TS out path. + let ts_out = match idl_ts_out { + None => PathBuf::from(".").join(&idl.name).with_extension("ts"), + Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("ts")), + }; - // Write out the JSON file. - write_idl(&idl, OutFile::File(out))?; - // Write out the TypeScript type. - fs::write(&ts_out, rust_template::idl_ts(&idl)?)?; - // Copy out the TypeScript type. - let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml"); - if !&cfg.workspace.types.is_empty() { - fs::copy( - &ts_out, - cfg_parent - .join(&cfg.workspace.types) - .join(&idl.name) - .with_extension("ts"), - )?; - } + // Write out the JSON file. + write_idl(&idl, OutFile::File(out))?; + // Write out the TypeScript type. + fs::write(&ts_out, rust_template::idl_ts(&idl)?)?; + // Copy out the TypeScript type. + let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml"); + if !&cfg.workspace.types.is_empty() { + fs::copy( + &ts_out, + cfg_parent + .join(&cfg.workspace.types) + .join(&idl.name) + .with_extension("ts"), + )?; } Ok(()) @@ -1653,13 +1703,12 @@ fn verify( } // Verify IDL (only if it's not a buffer account). - if let Some(local_idl) = extract_idl(&cfg, "src/lib.rs", true, false)? { - if bin_ver.state != BinVerificationState::Buffer { - let deployed_idl = fetch_idl(cfg_override, program_id)?; - if local_idl != deployed_idl { - println!("Error: IDLs don't match"); - std::process::exit(1); - } + let local_idl = generate_idl(&cfg, true, false)?; + if bin_ver.state != BinVerificationState::Buffer { + let deployed_idl = fetch_idl(cfg_override, program_id)?; + if local_idl != deployed_idl { + println!("Error: IDLs don't match"); + std::process::exit(1); } } @@ -1787,62 +1836,6 @@ pub enum BinVerificationState { }, } -// Fetches an IDL for the given program_id. -fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result { - let url = match Config::discover(cfg_override)? { - Some(cfg) => cluster_url(&cfg, &cfg.test_validator), - None => { - // If the command is not run inside a workspace, - // cluster_url will be used from default solana config - // provider.cluster option can be used to override this - - if let Some(cluster) = cfg_override.cluster.clone() { - cluster.url().to_string() - } else { - config::get_solana_cfg_url()? - } - } - }; - - let client = create_client(url); - - let mut account = client.get_account(&idl_addr)?; - if account.executable { - let idl_addr = IdlAccount::address(&idl_addr); - account = client.get_account(&idl_addr)?; - } - - // Cut off account discriminator. - let mut d: &[u8] = &account.data[8..]; - let idl_account: IdlAccount = AnchorDeserialize::deserialize(&mut d)?; - - let compressed_len: usize = idl_account.data_len.try_into().unwrap(); - let compressed_bytes = &account.data[44..44 + compressed_len]; - let mut z = ZlibDecoder::new(compressed_bytes); - let mut s = Vec::new(); - z.read_to_end(&mut s)?; - serde_json::from_slice(&s[..]).map_err(Into::into) -} - -fn extract_idl( - cfg: &WithPath, - file: &str, - skip_lint: bool, - no_docs: bool, -) -> Result> { - let file = shellexpand::tilde(file); - let manifest_from_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap()); - let cargo = Manifest::discover_from_path(manifest_from_path)? - .ok_or_else(|| anyhow!("Cargo.toml not found"))?; - anchor_syn::idl::file::parse( - &*file, - cargo.version(), - cfg.features.seeds, - no_docs, - !(cfg.features.skip_lint || skip_lint), - ) -} - fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> { match subcmd { IdlCommand::Init { @@ -1880,10 +1873,52 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> { out_ts, no_docs, } => idl_parse(cfg_override, file, out, out_ts, no_docs), + IdlCommand::Build { + out, + out_ts, + no_docs, + } => idl_build(out, out_ts, no_docs), IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out), } } +/// Fetch an IDL for the given program id. +fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result { + let url = match Config::discover(cfg_override)? { + Some(cfg) => cluster_url(&cfg, &cfg.test_validator), + None => { + // If the command is not run inside a workspace, + // cluster_url will be used from default solana config + // provider.cluster option can be used to override this + + if let Some(cluster) = cfg_override.cluster.clone() { + cluster.url().to_string() + } else { + config::get_solana_cfg_url()? + } + } + }; + + let client = create_client(url); + + let mut account = client.get_account(&idl_addr)?; + if account.executable { + let idl_addr = IdlAccount::address(&idl_addr); + account = client.get_account(&idl_addr)?; + } + + // Cut off account discriminator. + let mut d: &[u8] = &account.data[8..]; + let idl_account: IdlAccount = AnchorDeserialize::deserialize(&mut d)?; + + let compressed_len: usize = idl_account.data_len.try_into().unwrap(); + let compressed_bytes = &account.data[44..44 + compressed_len]; + let mut z = ZlibDecoder::new(compressed_bytes); + let mut s = Vec::new(); + z.read_to_end(&mut s)?; + serde_json::from_slice(&s[..]).map_err(Into::into) +} + fn get_idl_account(client: &RpcClient, idl_address: &Pubkey) -> Result { let account = client.get_account(idl_address)?; let mut data: &[u8] = &account.data; @@ -2210,7 +2245,18 @@ fn idl_parse( no_docs: bool, ) -> Result<()> { let cfg = Config::discover(cfg_override)?.expect("Not in workspace."); - let idl = extract_idl(&cfg, &file, true, no_docs)?.ok_or_else(|| anyhow!("IDL not parsed"))?; + let file = shellexpand::tilde(&file); + let manifest_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap()); + let manifest = Manifest::discover_from_path(manifest_path)? + .ok_or_else(|| anyhow!("Cargo.toml not found"))?; + let idl = generate_idl_parse( + &*file, + manifest.version(), + cfg.features.seeds, + no_docs, + !cfg.features.skip_lint, + )?; + let out = match out { None => OutFile::Stdout, Some(out) => OutFile::File(PathBuf::from(out)), @@ -2225,6 +2271,255 @@ fn idl_parse( Ok(()) } +fn idl_build(out: Option, out_ts: Option, no_docs: bool) -> Result<()> { + let idls = generate_idl_build(no_docs)?; + if idls.len() == 1 { + let idl = &idls[0]; + let out = match out { + None => OutFile::Stdout, + Some(path) => OutFile::File(PathBuf::from(path)), + }; + write_idl(idl, out)?; + + if let Some(path) = out_ts { + fs::write(path, rust_template::idl_ts(idl)?)?; + } + } else { + println!("{}", serde_json::to_string_pretty(&idls)?); + }; + + Ok(()) +} + +/// Generate IDL with method decided by whether manifest file has `idl-build` feature or not. +fn generate_idl(cfg: &WithPath, skip_lint: bool, no_docs: bool) -> Result { + let manifest = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?; + + // Check whether the manifest has `idl-build` feature + let is_idl_build = manifest + .features + .iter() + .any(|(feature, _)| feature == "idl-build"); + if is_idl_build { + generate_idl_build(no_docs)? + .into_iter() + .next() + .ok_or_else(|| anyhow!("Could not build IDL")) + } else { + generate_idl_parse( + "src/lib.rs", + manifest.version(), + cfg.features.seeds, + no_docs, + !(cfg.features.skip_lint || skip_lint), + ) + } +} + +/// Generate IDL with the parsing method(default). +fn generate_idl_parse( + path: impl AsRef, + version: String, + seeds_feature: bool, + no_docs: bool, + safety_checks: bool, +) -> Result { + anchor_syn::idl::parse::file::parse(path, version, seeds_feature, no_docs, safety_checks) +} + +/// Generate IDL with the build method. +fn generate_idl_build(no_docs: bool) -> Result> { + let no_docs = if no_docs { "TRUE" } else { "FALSE" }; + + let cfg = Config::discover(&ConfigOverride::default())?.expect("Not in workspace."); + let seeds_feature = if cfg.features.seeds { "TRUE" } else { "FALSE" }; + + let exit = std::process::Command::new("cargo") + .args([ + "test", + "__anchor_private_print_idl", + "--features", + "idl-build", + "--", + "--show-output", + "--quiet", + ]) + .env("ANCHOR_IDL_BUILD_NO_DOCS", no_docs) + .env("ANCHOR_IDL_BUILD_SEEDS_FEATURE", seeds_feature) + .stderr(Stdio::inherit()) + .output() + .map_err(|e| anyhow::format_err!("{}", e.to_string()))?; + if !exit.status.success() { + std::process::exit(exit.status.code().unwrap_or(1)); + } + + enum State { + Pass, + ConstLines(Vec), + EventLines(Vec), + ErrorsLines(Vec), + ProgramLines(Vec), + } + + #[derive(Serialize, Deserialize)] + struct IdlBuildEventPrint { + event: IdlEvent, + defined_types: Vec, + } + + let mut state = State::Pass; + + let mut events: Vec = vec![]; + let mut error_codes: Option> = None; + let mut constants: Vec = vec![]; + let mut defined_types: BTreeMap = BTreeMap::new(); + let mut curr_idl: Option = None; + + let mut idls: Vec = vec![]; + + let output = String::from_utf8_lossy(&exit.stdout); + for line in output.lines() { + match &mut state { + State::Pass => { + if line == "---- IDL begin const ----" { + state = State::ConstLines(vec![]); + continue; + } else if line == "---- IDL begin event ----" { + state = State::EventLines(vec![]); + continue; + } else if line == "---- IDL begin errors ----" { + state = State::ErrorsLines(vec![]); + continue; + } else if line == "---- IDL begin program ----" { + state = State::ProgramLines(vec![]); + continue; + } else if line.starts_with("test result: ok") { + let events = std::mem::take(&mut events); + let error_codes = error_codes.take(); + let constants = std::mem::take(&mut constants); + let mut defined_types = std::mem::take(&mut defined_types); + let curr_idl = curr_idl.take(); + + let events = if !events.is_empty() { + Some(events) + } else { + None + }; + + let mut idl = match curr_idl { + Some(idl) => idl, + None => continue, + }; + + idl.events = events; + idl.errors = error_codes; + idl.constants = constants; + + idl.constants.sort_by(|a, b| a.name.cmp(&b.name)); + idl.accounts.sort_by(|a, b| a.name.cmp(&b.name)); + if let Some(e) = idl.events.as_mut() { + e.sort_by(|a, b| a.name.cmp(&b.name)) + } + + let prog_ty = std::mem::take(&mut idl.types); + defined_types.extend(prog_ty.into_iter().map(|ty| (ty.name.clone(), ty))); + idl.types = defined_types.into_values().collect::>(); + + idls.push(idl); + continue; + } + } + State::ConstLines(lines) => { + if line == "---- IDL end const ----" { + let constant: IdlConst = serde_json::from_str(&lines.join("\n"))?; + constants.push(constant); + state = State::Pass; + continue; + } + lines.push(line.to_string()); + } + State::EventLines(lines) => { + if line == "---- IDL end event ----" { + let event: IdlBuildEventPrint = serde_json::from_str(&lines.join("\n"))?; + events.push(event.event); + defined_types.extend( + event + .defined_types + .into_iter() + .map(|ty| (ty.name.clone(), ty)), + ); + state = State::Pass; + continue; + } + lines.push(line.to_string()); + } + State::ErrorsLines(lines) => { + if line == "---- IDL end errors ----" { + let errs: Vec = serde_json::from_str(&lines.join("\n"))?; + error_codes = Some(errs); + state = State::Pass; + continue; + } + lines.push(line.to_string()); + } + State::ProgramLines(lines) => { + if line == "---- IDL end program ----" { + let idl: Idl = serde_json::from_str(&lines.join("\n"))?; + curr_idl = Some(idl); + state = State::Pass; + continue; + } + lines.push(line.to_string()); + } + } + } + + // Convert path to name if there are no conflicts + let path_regex = Regex::new(r#""((\w+::)+)(\w+)""#).unwrap(); + let idls = idls + .into_iter() + .filter_map(|idl| { + let mut modified_idl = serde_json::to_string(&idl).unwrap(); + + // TODO: Remove. False positive https://github.com/rust-lang/rust-clippy/issues/10577 + #[allow(clippy::redundant_clone)] + for captures in path_regex.captures_iter(&modified_idl.clone()) { + let path = captures.get(0).unwrap().as_str(); + let name = captures.get(3).unwrap().as_str(); + + // Replace path with name + let replaced_idl = modified_idl.replace(path, &format!(r#""{name}""#)); + + // Check whether there is a conflict + let has_conflict = replaced_idl.contains(&format!(r#"::{name}""#)); + if !has_conflict { + modified_idl = replaced_idl; + } + } + + serde_json::from_str::(&modified_idl).ok() + }) + .collect::>(); + + // Verify IDLs are valid + for idl in &idls { + let full_path_account = idl + .accounts + .iter() + .find(|account| account.name.contains("::")); + + if let Some(account) = full_path_account { + return Err(anyhow!( + "Conflicting accounts names are not allowed.\nProgram: {}\nAccount: {}", + idl.name, + account.name + )); + } + } + + Ok(idls) +} + fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option) -> Result<()> { let idl = fetch_idl(cfg_override, address)?; let out = match out { @@ -2315,11 +2610,12 @@ fn account( }, ); - let mut cluster = &Config::discover(cfg_override) - .map(|cfg| cfg.unwrap()) - .map(|cfg| cfg.provider.cluster.clone()) - .unwrap_or(Cluster::Localnet); - cluster = cfg_override.cluster.as_ref().unwrap_or(cluster); + let cluster = match &cfg_override.cluster { + Some(cluster) => cluster.clone(), + None => Config::discover(cfg_override)? + .map(|cfg| cfg.provider.cluster.clone()) + .unwrap_or(Cluster::Localnet), + }; let data = create_client(cluster.url()).get_account_data(&address)?; if data.len() < 8 { @@ -2500,6 +2796,11 @@ fn deserialize_idl_type_to_json( JsonValue::Array(array_data) } + IdlType::GenericLenArray(_, _) => todo!("Generic length arrays are not yet supported"), + IdlType::Generic(_) => todo!("Generic types are not yet supported"), + IdlType::DefinedWithTypeArgs { name: _, args: _ } => { + todo!("Defined types with type args are not yet supported") + } }) } @@ -2735,7 +3036,7 @@ fn validator_flags( flags.push(address.clone()); flags.push(binary_path); - if let Some(mut idl) = program.idl.as_mut() { + if let Some(idl) = program.idl.as_mut() { // Add program address to the IDL. idl.metadata = Some(serde_json::to_value(IdlTestMetadata { address })?); @@ -3129,7 +3430,7 @@ fn deploy( std::process::exit(exit.status.code().unwrap_or(1)); } - if let Some(mut idl) = program.idl.as_mut() { + if let Some(idl) = program.idl.as_mut() { // Add program address to the IDL. idl.metadata = Some(serde_json::to_value(IdlTestMetadata { address: program_id.to_string(), @@ -3794,7 +4095,7 @@ fn keys_sync(cfg_override: &ConfigOverride, program_name: Option) -> Res // Handle declaration in Anchor.toml 'outer: for programs in cfg.programs.values_mut() { - for (name, mut deployment) in programs { + for (name, deployment) in programs { // Skip other programs if name != &program.lib_name { continue; @@ -3973,6 +4274,7 @@ mod tests { false, false, false, + ProgramTemplate::default(), ) .unwrap(); } @@ -3990,6 +4292,7 @@ mod tests { false, false, false, + ProgramTemplate::default(), ) .unwrap(); } @@ -4007,6 +4310,7 @@ mod tests { false, false, false, + ProgramTemplate::default(), ) .unwrap(); } diff --git a/cli/src/rust_template.rs b/cli/src/rust_template.rs index f05d67f7da..8ec63ee19a 100644 --- a/cli/src/rust_template.rs +++ b/cli/src/rust_template.rs @@ -1,7 +1,8 @@ -use crate::config::ProgramWorkspace; use crate::VERSION; -use anchor_syn::idl::Idl; +use crate::{config::ProgramWorkspace, create_files, Files}; +use anchor_syn::idl::types::Idl; use anyhow::Result; +use clap::{Parser, ValueEnum}; use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase}; use solana_sdk::{ pubkey::Pubkey, @@ -10,22 +11,140 @@ use solana_sdk::{ }; use std::{fmt::Write, path::Path}; -/// Read the program keypair file or create a new one if it doesn't exist. -pub fn get_or_create_program_id(name: &str) -> Pubkey { - let keypair_path = Path::new("target") - .join("deploy") - .join(format!("{}-keypair.json", name.to_snake_case())); +/// Program initialization template +#[derive(Clone, Debug, Default, Eq, PartialEq, Parser, ValueEnum)] +pub enum ProgramTemplate { + /// Program with a single `lib.rs` file + #[default] + Single, + /// Program with multiple files for instructions, state... + Multiple, +} - read_keypair_file(&keypair_path) - .unwrap_or_else(|_| { - let keypair = Keypair::new(); - write_keypair_file(&keypair, keypair_path).expect("Unable to create program keypair"); - keypair - }) - .pubkey() +/// Create a program from the given name and template. +pub fn create_program(name: &str, template: ProgramTemplate) -> Result<()> { + let program_path = Path::new("programs").join(name); + let common_files = vec![ + ("Cargo.toml".into(), workspace_manifest().into()), + (program_path.join("Cargo.toml"), cargo_toml(name)), + (program_path.join("Xargo.toml"), xargo_toml().into()), + ]; + + let template_files = match template { + ProgramTemplate::Single => create_program_template_single(name, &program_path), + ProgramTemplate::Multiple => create_program_template_multiple(name, &program_path), + }; + + create_files(&[common_files, template_files].concat()) +} + +/// Create a program with a single `lib.rs` file. +fn create_program_template_single(name: &str, program_path: &Path) -> Files { + vec![( + program_path.join("src").join("lib.rs"), + format!( + r#"use anchor_lang::prelude::*; + +declare_id!("{}"); + +#[program] +pub mod {} {{ + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> {{ + Ok(()) + }} +}} + +#[derive(Accounts)] +pub struct Initialize {{}} +"#, + get_or_create_program_id(name), + name.to_snake_case(), + ), + )] +} + +/// Create a program with multiple files for instructions, state... +fn create_program_template_multiple(name: &str, program_path: &Path) -> Files { + let src_path = program_path.join("src"); + vec![ + ( + src_path.join("lib.rs"), + format!( + r#"pub mod constants; +pub mod error; +pub mod instructions; +pub mod state; + +use anchor_lang::prelude::*; + +pub use constants::*; +pub use instructions::*; +pub use state::*; + +declare_id!("{}"); + +#[program] +pub mod {} {{ + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> {{ + initialize::handler(ctx) + }} +}} +"#, + get_or_create_program_id(name), + name.to_snake_case(), + ), + ), + ( + src_path.join("constants.rs"), + r#"use anchor_lang::prelude::*; + +#[constant] +pub const SEED: &str = "anchor"; +"# + .into(), + ), + ( + src_path.join("error.rs"), + r#"use anchor_lang::prelude::*; + +#[error_code] +pub enum ErrorCode { + #[msg("Custom error message")] + CustomError, +} +"# + .into(), + ), + ( + src_path.join("instructions").join("mod.rs"), + r#"pub mod initialize; + +pub use initialize::*; +"# + .into(), + ), + ( + src_path.join("instructions").join("initialize.rs"), + r#"use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct Initialize {} + +pub fn handler(ctx: Context) -> Result<()> { + Ok(()) +} +"# + .into(), + ), + (src_path.join("state").join("mod.rs"), r#""#.into()), + ] } -pub fn virtual_manifest() -> &'static str { +const fn workspace_manifest() -> &'static str { r#"[workspace] members = [ "programs/*" @@ -42,33 +161,7 @@ codegen-units = 1 "# } -pub fn credentials(token: &str) -> String { - format!( - r#"[registry] -token = "{token}" -"# - ) -} - -pub fn idl_ts(idl: &Idl) -> Result { - let mut idl = idl.clone(); - for acc in idl.accounts.iter_mut() { - acc.name = acc.name.to_lower_camel_case(); - } - let idl_json = serde_json::to_string_pretty(&idl)?; - Ok(format!( - r#"export type {} = {}; - -export const IDL: {} = {}; -"#, - idl.name.to_upper_camel_case(), - idl_json, - idl.name.to_upper_camel_case(), - idl_json - )) -} - -pub fn cargo_toml(name: &str) -> String { +fn cargo_toml(name: &str) -> String { format!( r#"[package] name = "{0}" @@ -96,6 +189,53 @@ anchor-lang = "{2}" ) } +fn xargo_toml() -> &'static str { + r#"[target.bpfel-unknown-unknown.dependencies.std] +features = [] +"# +} + +/// Read the program keypair file or create a new one if it doesn't exist. +pub fn get_or_create_program_id(name: &str) -> Pubkey { + let keypair_path = Path::new("target") + .join("deploy") + .join(format!("{}-keypair.json", name.to_snake_case())); + + read_keypair_file(&keypair_path) + .unwrap_or_else(|_| { + let keypair = Keypair::new(); + write_keypair_file(&keypair, keypair_path).expect("Unable to create program keypair"); + keypair + }) + .pubkey() +} + +pub fn credentials(token: &str) -> String { + format!( + r#"[registry] +token = "{token}" +"# + ) +} + +pub fn idl_ts(idl: &Idl) -> Result { + let mut idl = idl.clone(); + for acc in idl.accounts.iter_mut() { + acc.name = acc.name.to_lower_camel_case(); + } + let idl_json = serde_json::to_string_pretty(&idl)?; + Ok(format!( + r#"export type {} = {}; + +export const IDL: {} = {}; +"#, + idl.name.to_upper_camel_case(), + idl_json, + idl.name.to_upper_camel_case(), + idl_json + )) +} + pub fn deploy_js_script_host(cluster_url: &str, script_path: &str) -> String { format!( r#" @@ -181,35 +321,6 @@ module.exports = async function (provider) { "# } -pub fn xargo_toml() -> &'static str { - r#"[target.bpfel-unknown-unknown.dependencies.std] -features = [] -"# -} - -pub fn lib_rs(name: &str) -> String { - format!( - r#"use anchor_lang::prelude::*; - -declare_id!("{}"); - -#[program] -pub mod {} {{ - use super::*; - - pub fn initialize(ctx: Context) -> Result<()> {{ - Ok(()) - }} -}} - -#[derive(Accounts)] -pub struct Initialize {{}} -"#, - get_or_create_program_id(name), - name.to_snake_case(), - ) -} - pub fn mocha(name: &str) -> String { format!( r#"const anchor = require("@coral-xyz/anchor"); diff --git a/cli/src/solidity_template.rs b/cli/src/solidity_template.rs index 238b2ab62a..f1211bda67 100644 --- a/cli/src/solidity_template.rs +++ b/cli/src/solidity_template.rs @@ -1,10 +1,20 @@ -use crate::config::ProgramWorkspace; use crate::VERSION; -use anchor_syn::idl::Idl; +use crate::{config::ProgramWorkspace, create_files}; +use anchor_syn::idl::types::Idl; use anyhow::Result; use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase}; use solana_sdk::pubkey::Pubkey; use std::fmt::Write; +use std::path::Path; + +/// Create a solidity program. +pub fn create_program(name: &str) -> Result<()> { + let files = vec![( + Path::new("solidity").join(name).with_extension("sol"), + solidity(name), + )]; + create_files(&files) +} pub fn default_program_id() -> Pubkey { "F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC" diff --git a/client/Cargo.toml b/client/Cargo.toml index 1abfc9de59..0f0e51aae5 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -8,13 +8,13 @@ license = "Apache-2.0" description = "Rust client for Anchor programs" [features] -debug = [] async = [] +debug = [] [dependencies] anchor-lang = { path = "../lang", version = "0.28.0" } anyhow = "1" -futures = { version = "0.3" } +futures = "0.3" regex = "1" serde = { version = "1", features = ["derive"] } solana-client = ">=1.14, <1.17" diff --git a/client/example/Cargo.toml b/client/example/Cargo.toml index f18138d3fe..c3d71eb33f 100644 --- a/client/example/Cargo.toml +++ b/client/example/Cargo.toml @@ -17,8 +17,8 @@ basic-4 = { path = "../../examples/tutorial/basic-4/programs/basic-4", features composite = { path = "../../tests/composite/programs/composite", features = ["no-entrypoint"] } optional = { path = "../../tests/optional/programs/optional", features = ["no-entrypoint"] } events = { path = "../../tests/events/programs/events", features = ["no-entrypoint"] } -shellexpand = "2.1.0" anyhow = "1.0.32" clap = { version = "4.2.4", features = ["derive"] } -tokio = { version = "1", features = ["full"] } +shellexpand = "2.1.0" solana-sdk = ">=1.14, <1.17" +tokio = { version = "1", features = ["full"] } diff --git a/client/src/lib.rs b/client/src/lib.rs index c25624a11c..6f5e90a4a9 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -98,6 +98,36 @@ impl> Client { } } +/// Auxiliary data structure to align the types of the Solana CLI utils with Anchor client. +/// Client implementation requires > which does not comply with Box +/// that's used when loaded Signer from keypair file. This struct is used to wrap the usage. +pub struct DynSigner(pub Arc); + +impl Signer for DynSigner { + fn pubkey(&self) -> Pubkey { + self.0.pubkey() + } + + fn try_pubkey(&self) -> Result { + self.0.try_pubkey() + } + + fn sign_message(&self, message: &[u8]) -> solana_sdk::signature::Signature { + self.0.sign_message(message) + } + + fn try_sign_message( + &self, + message: &[u8], + ) -> Result { + self.0.try_sign_message(message) + } + + fn is_interactive(&self) -> bool { + self.0.is_interactive() + } +} + // Internal configuration for a client. #[derive(Debug)] pub struct Config { @@ -249,9 +279,10 @@ impl + Clone> Program { client.logs_subscribe(filter, config).await?; tx.send(unsubscribe).map_err(|e| { - ClientError::SolanaClientPubsubError(PubsubClientError::UnexpectedMessageError( - e.to_string(), - )) + ClientError::SolanaClientPubsubError(PubsubClientError::RequestFailed { + message: "Unsubscribe failed".to_string(), + reason: e.to_string(), + }) })?; while let Some(logs) = notifications.next().await { diff --git a/docs/src/pages/docs/errors.md b/docs/src/pages/docs/errors.md index 72acc4f774..b299b32d75 100644 --- a/docs/src/pages/docs/errors.md +++ b/docs/src/pages/docs/errors.md @@ -16,7 +16,7 @@ custom errors which the user (you!) can return. - Custom Errors - Non-anchor errors. -[AnchorErrors](https://docs.rs/anchor-lang/latest/anchor_lang/error/struct.AnchorError.html) provide a range of information like the error name and number or the location in the code where the anchor was thrown, or the account that violated a constraint (e.g. a `mut` constraint). Once thrown inside the program, [you can access the error information](https://coral-xyz.github.io/anchor/ts/classes/AnchorError.html) in the anchor clients like the typescript client. The typescript client also enriches the error with additional information about which program the error was thrown in and the CPI calls (which are explained [here](./cross-program-invocations) in the book) that led to the program from which the error was thrown from. [The milestone chapter](./milestone_project_tic-tac-toe.md) explores how all of this works together in practice. For now, let's look at how different errors can be returned from inside a program. +[AnchorErrors](https://docs.rs/anchor-lang/latest/anchor_lang/error/struct.AnchorError.html) provides a range of information like the error name and number or the location in the code where the error was thrown, or the account that violated a constraint (e.g. a `mut` constraint). Once thrown inside the program, [you can access the error information](https://coral-xyz.github.io/anchor/ts/classes/AnchorError.html) in the anchor clients like the typescript client. The typescript client also enriches the error with additional information about which program the error was thrown in and the CPI calls (which are explained [here](./cross-program-invocations) in the book) that led to the program from which the error was thrown from. [The milestone chapter](./milestone_project_tic-tac-toe.md) explores how all of this works together in practice. For now, let's look at how different errors can be returned from inside a program. ## Anchor Internal Errors diff --git a/lang/Cargo.toml b/lang/Cargo.toml index 7fa25bd652..e2915f1f27 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -10,10 +10,6 @@ description = "Solana Sealevel eDSL" [features] allow-missing-optionals = ["anchor-derive-accounts/allow-missing-optionals"] -init-if-needed = ["anchor-derive-accounts/init-if-needed"] -derive = [] -default = [] -event-cpi = ["anchor-attribute-event/event-cpi"] anchor-debug = [ "anchor-attribute-access-control/anchor-debug", "anchor-attribute-account/anchor-debug", @@ -21,9 +17,21 @@ anchor-debug = [ "anchor-attribute-error/anchor-debug", "anchor-attribute-event/anchor-debug", "anchor-attribute-program/anchor-debug", - "anchor-attribute-program/anchor-debug", "anchor-derive-accounts/anchor-debug" ] +derive = [] +event-cpi = ["anchor-attribute-event/event-cpi"] +idl-build = [ + "anchor-attribute-account/idl-build", + "anchor-attribute-constant/idl-build", + "anchor-attribute-event/idl-build", + "anchor-attribute-error/idl-build", + "anchor-attribute-program/idl-build", + "anchor-derive-accounts/idl-build", + "anchor-derive-serde/idl-build", + "anchor-syn/idl-build", +] +init-if-needed = ["anchor-derive-accounts/init-if-needed"] [dependencies] anchor-attribute-access-control = { path = "./attribute/access-control", version = "0.28.0" } @@ -33,7 +41,10 @@ anchor-attribute-error = { path = "./attribute/error", version = "0.28.0" } anchor-attribute-event = { path = "./attribute/event", version = "0.28.0" } anchor-attribute-program = { path = "./attribute/program", version = "0.28.0" } anchor-derive-accounts = { path = "./derive/accounts", version = "0.28.0" } +anchor-derive-serde = { path = "./derive/serde", version = "0.28.0" } anchor-derive-space = { path = "./derive/space", version = "0.28.0" } +# `anchor-syn` should only be included with `idl-build` feature +anchor-syn = { path = "./syn", version = "0.28.0", optional = true } arrayref = "0.3" base64 = "0.13" bincode = "1" diff --git a/lang/attribute/access-control/Cargo.toml b/lang/attribute/access-control/Cargo.toml index ed7fdca286..0dc0312a24 100644 --- a/lang/attribute/access-control/Cargo.toml +++ b/lang/attribute/access-control/Cargo.toml @@ -16,8 +16,6 @@ anchor-debug = ["anchor-syn/anchor-debug"] [dependencies] anchor-syn = { path = "../../syn", version = "0.28.0" } -anyhow = "1" proc-macro2 = "1" quote = "1" -regex = "1" syn = { version = "1", features = ["full"] } diff --git a/lang/attribute/account/Cargo.toml b/lang/attribute/account/Cargo.toml index f64f1172e4..f9c714cd29 100644 --- a/lang/attribute/account/Cargo.toml +++ b/lang/attribute/account/Cargo.toml @@ -13,12 +13,11 @@ proc-macro = true [features] anchor-debug = ["anchor-syn/anchor-debug"] +idl-build = ["anchor-syn/idl-build"] [dependencies] anchor-syn = { path = "../../syn", version = "0.28.0", features = ["hash"] } -anyhow = "1" bs58 = "0.5" proc-macro2 = "1" quote = "1" -rustversion = "1" syn = { version = "1", features = ["full"] } diff --git a/lang/attribute/account/src/lib.rs b/lang/attribute/account/src/lib.rs index 52e975cf48..8e9a732cb8 100644 --- a/lang/attribute/account/src/lib.rs +++ b/lang/attribute/account/src/lib.rs @@ -1,5 +1,7 @@ extern crate proc_macro; +#[cfg(feature = "idl-build")] +use anchor_syn::idl::build::*; use quote::quote; use syn::parse_macro_input; @@ -392,13 +394,26 @@ pub fn zero_copy( quote! {#[derive(::bytemuck::Zeroable)]} }; - proc_macro::TokenStream::from(quote! { + let ret = quote! { #[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)] #repr #pod #zeroable #account_strct - }) + }; + + #[cfg(feature = "idl-build")] + { + let no_docs = get_no_docs(); + let idl_gen_impl = gen_idl_gen_impl_for_struct(&account_strct, no_docs); + return proc_macro::TokenStream::from(quote! { + #ret + #idl_gen_impl + }); + } + + #[allow(unreachable_code)] + proc_macro::TokenStream::from(ret) } /// Defines the program's ID. This should be used at the root of all Anchor diff --git a/lang/attribute/constant/Cargo.toml b/lang/attribute/constant/Cargo.toml index df95b1e9d1..1026560da7 100644 --- a/lang/attribute/constant/Cargo.toml +++ b/lang/attribute/constant/Cargo.toml @@ -13,8 +13,9 @@ proc-macro = true [features] anchor-debug = ["anchor-syn/anchor-debug"] +idl-build = ["anchor-syn/idl-build"] [dependencies] anchor-syn = { path = "../../syn", version = "0.28.0" } -proc-macro2 = "1" +quote = "1" syn = { version = "1", features = ["full"] } diff --git a/lang/attribute/constant/src/lib.rs b/lang/attribute/constant/src/lib.rs index 98092f4b0f..fd1f4ad680 100644 --- a/lang/attribute/constant/src/lib.rs +++ b/lang/attribute/constant/src/lib.rs @@ -1,5 +1,8 @@ extern crate proc_macro; +#[cfg(feature = "idl-build")] +use {anchor_syn::idl::build::gen_idl_print_function_for_constant, quote::quote, syn}; + /// A marker attribute used to mark const values that should be included in the /// generated IDL but functionally does nothing. #[proc_macro_attribute] @@ -7,5 +10,24 @@ pub fn constant( _attr: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { + #[cfg(feature = "idl-build")] + { + let ts = match syn::parse(input).unwrap() { + syn::Item::Const(item) => { + let idl_print = gen_idl_print_function_for_constant(&item); + quote! { + #item + #idl_print + } + } + item => quote! {#item}, + }; + + return proc_macro::TokenStream::from(quote! { + #ts + }); + }; + + #[allow(unreachable_code)] input } diff --git a/lang/attribute/error/Cargo.toml b/lang/attribute/error/Cargo.toml index 2b2eae0e54..6052f2a561 100644 --- a/lang/attribute/error/Cargo.toml +++ b/lang/attribute/error/Cargo.toml @@ -13,9 +13,9 @@ proc-macro = true [features] anchor-debug = ["anchor-syn/anchor-debug"] +idl-build = ["anchor-syn/idl-build"] [dependencies] anchor-syn = { path = "../../syn", version = "0.28.0" } -proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full"] } diff --git a/lang/attribute/event/Cargo.toml b/lang/attribute/event/Cargo.toml index 2a117c8569..9f5aeebcf1 100644 --- a/lang/attribute/event/Cargo.toml +++ b/lang/attribute/event/Cargo.toml @@ -14,10 +14,10 @@ proc-macro = true [features] anchor-debug = ["anchor-syn/anchor-debug"] event-cpi = ["anchor-syn/event-cpi"] +idl-build = ["anchor-syn/idl-build"] [dependencies] anchor-syn = { path = "../../syn", version = "0.28.0", features = ["hash"] } -anyhow = "1" proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full"] } diff --git a/lang/attribute/event/src/lib.rs b/lang/attribute/event/src/lib.rs index 4826618204..61a1b65d26 100644 --- a/lang/attribute/event/src/lib.rs +++ b/lang/attribute/event/src/lib.rs @@ -29,7 +29,7 @@ pub fn event( format!("{discriminator:?}").parse().unwrap() }; - proc_macro::TokenStream::from(quote! { + let ret = quote! { #[derive(anchor_lang::__private::EventIndex, AnchorSerialize, AnchorDeserialize)] #event_strct @@ -44,7 +44,19 @@ pub fn event( impl anchor_lang::Discriminator for #event_name { const DISCRIMINATOR: [u8; 8] = #discriminator; } - }) + }; + + #[cfg(feature = "idl-build")] + { + let idl_gen = anchor_syn::idl::build::gen_idl_print_function_for_event(&event_strct); + return proc_macro::TokenStream::from(quote! { + #ret + #idl_gen + }); + } + + #[allow(unreachable_code)] + proc_macro::TokenStream::from(ret) } // EventIndex is a marker macro. It functionally does nothing other than diff --git a/lang/attribute/program/Cargo.toml b/lang/attribute/program/Cargo.toml index 3090b3ab7c..ad72db1f8f 100644 --- a/lang/attribute/program/Cargo.toml +++ b/lang/attribute/program/Cargo.toml @@ -13,10 +13,9 @@ proc-macro = true [features] anchor-debug = ["anchor-syn/anchor-debug"] +idl-build = ["anchor-syn/idl-build"] [dependencies] anchor-syn = { path = "../../syn", version = "0.28.0" } -anyhow = "1" -proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full"] } diff --git a/lang/derive/accounts/Cargo.toml b/lang/derive/accounts/Cargo.toml index 65a990ec2b..4f02f59555 100644 --- a/lang/derive/accounts/Cargo.toml +++ b/lang/derive/accounts/Cargo.toml @@ -13,13 +13,11 @@ proc-macro = true [features] allow-missing-optionals = ["anchor-syn/allow-missing-optionals"] -init-if-needed = ["anchor-syn/init-if-needed"] -default = [] anchor-debug = ["anchor-syn/anchor-debug"] +idl-build = ["anchor-syn/idl-build"] +init-if-needed = ["anchor-syn/init-if-needed"] [dependencies] -anyhow = "1" -proc-macro2 = "1" +anchor-syn = { path = "../../syn", version = "0.28.0" } quote = "1" syn = { version = "1", features = ["full"] } -anchor-syn = { path = "../../syn", version = "0.28.0" } diff --git a/lang/derive/serde/Cargo.toml b/lang/derive/serde/Cargo.toml new file mode 100644 index 0000000000..6073246f5e --- /dev/null +++ b/lang/derive/serde/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "anchor-derive-serde" +version = "0.28.0" +authors = ["Anchor Maintainers "] +repository = "https://github.com/coral-xyz/anchor" +license = "Apache-2.0" +description = "Anchor Derive macro for serialization and deserialization" +rust-version = "1.60" +edition = "2021" + +[lib] +proc-macro = true + +[features] +idl-build = ["anchor-syn/idl-build"] + +[dependencies] +anchor-syn = { path = "../../syn", version = "0.28.0" } +borsh-derive-internal = ">=0.9, <0.11" +proc-macro2 = "1" +syn = { version = "1", features = ["full"] } +quote = "1" diff --git a/lang/derive/serde/src/lib.rs b/lang/derive/serde/src/lib.rs new file mode 100644 index 0000000000..4fbbdb3517 --- /dev/null +++ b/lang/derive/serde/src/lib.rs @@ -0,0 +1,82 @@ +extern crate proc_macro; + +use borsh_derive_internal::*; +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use syn::{Ident, Item}; + +#[cfg(feature = "idl-build")] +use {anchor_syn::idl::build::*, quote::quote}; + +fn gen_borsh_serialize(input: TokenStream) -> TokenStream2 { + let cratename = Ident::new("borsh", Span::call_site()); + + let item: Item = syn::parse(input).unwrap(); + let res = match item { + Item::Struct(item) => struct_ser(&item, cratename), + Item::Enum(item) => enum_ser(&item, cratename), + Item::Union(item) => union_ser(&item, cratename), + // Derive macros can only be defined on structs, enums, and unions. + _ => unreachable!(), + }; + + match res { + Ok(res) => res, + Err(err) => err.to_compile_error(), + } +} + +#[proc_macro_derive(AnchorSerialize, attributes(borsh_skip))] +pub fn anchor_serialize(input: TokenStream) -> TokenStream { + #[cfg(not(feature = "idl-build"))] + let ret = gen_borsh_serialize(input); + #[cfg(feature = "idl-build")] + let ret = gen_borsh_serialize(input.clone()); + + #[cfg(feature = "idl-build")] + { + let no_docs = get_no_docs(); + + let idl_gen_impl = match syn::parse(input).unwrap() { + Item::Struct(item) => gen_idl_gen_impl_for_struct(&item, no_docs), + Item::Enum(item) => gen_idl_gen_impl_for_enum(item, no_docs), + Item::Union(item) => { + // unions are not included in the IDL - TODO print a warning + idl_gen_impl_skeleton(quote! {None}, quote! {}, &item.ident, &item.generics) + } + // Derive macros can only be defined on structs, enums, and unions. + _ => unreachable!(), + }; + + return TokenStream::from(quote! { + #ret + #idl_gen_impl + }); + }; + + #[allow(unreachable_code)] + TokenStream::from(ret) +} + +fn gen_borsh_deserialize(input: TokenStream) -> TokenStream2 { + let cratename = Ident::new("borsh", Span::call_site()); + + let item: Item = syn::parse(input).unwrap(); + let res = match item { + Item::Struct(item) => struct_de(&item, cratename), + Item::Enum(item) => enum_de(&item, cratename), + Item::Union(item) => union_de(&item, cratename), + // Derive macros can only be defined on structs, enums, and unions. + _ => unreachable!(), + }; + + match res { + Ok(res) => res, + Err(err) => err.to_compile_error(), + } +} + +#[proc_macro_derive(AnchorDeserialize, attributes(borsh_skip, borsh_init))] +pub fn borsh_deserialize(input: TokenStream) -> TokenStream { + TokenStream::from(gen_borsh_deserialize(input)) +} diff --git a/lang/derive/space/Cargo.toml b/lang/derive/space/Cargo.toml index f5cbb5d1b7..dfee809e6b 100644 --- a/lang/derive/space/Cargo.toml +++ b/lang/derive/space/Cargo.toml @@ -14,4 +14,4 @@ proc-macro = true [dependencies] proc-macro2 = "1" quote = "1" -syn = { version = "1", features = ["extra-traits"]} +syn = { version = "1", features = ["extra-traits"] } diff --git a/lang/derive/space/src/lib.rs b/lang/derive/space/src/lib.rs index bd045b0c4e..f60565a9df 100644 --- a/lang/derive/space/src/lib.rs +++ b/lang/derive/space/src/lib.rs @@ -1,10 +1,11 @@ +use std::collections::VecDeque; + use proc_macro::TokenStream; -use proc_macro2::{Ident, TokenStream as TokenStream2}; +use proc_macro2::{Ident, TokenStream as TokenStream2, TokenTree}; use quote::{quote, quote_spanned, ToTokens}; use syn::{ - parse_macro_input, - punctuated::{IntoIter, Punctuated}, - Attribute, DeriveInput, Fields, GenericArgument, LitInt, PathArguments, Token, Type, TypeArray, + parse::ParseStream, parse2, parse_macro_input, Attribute, DeriveInput, Fields, GenericArgument, + LitInt, PathArguments, Type, TypeArray, }; /// Implements a [`Space`](./trait.Space.html) trait on the given @@ -93,7 +94,7 @@ fn gen_max>(mut iter: T) -> TokenStream2 { } } -fn len_from_type(ty: Type, attrs: &mut Option>) -> TokenStream2 { +fn len_from_type(ty: Type, attrs: &mut Option>) -> TokenStream2 { match ty { Type::Array(TypeArray { elem, len, .. }) => { let array_len = len.to_token_stream(); @@ -156,20 +157,33 @@ fn get_first_ty_arg(args: &PathArguments) -> Option { } } -fn get_max_len_args(attributes: &[Attribute]) -> Option> { +fn parse_len_arg(item: ParseStream) -> Result, syn::Error> { + let mut result = VecDeque::new(); + while let Some(token_tree) = item.parse()? { + match token_tree { + TokenTree::Ident(ident) => result.push_front(quote!((#ident as usize))), + TokenTree::Literal(lit) => { + if let Ok(lit_int) = parse2::(lit.into_token_stream()) { + result.push_front(quote!(#lit_int)) + } + } + _ => (), + } + } + + Ok(result) +} + +fn get_max_len_args(attributes: &[Attribute]) -> Option> { attributes .iter() .find(|a| a.path.is_ident("max_len")) - .and_then(|a| { - a.parse_args_with(Punctuated::::parse_terminated) - .ok() - }) - .map(|p| p.into_iter()) + .and_then(|a| a.parse_args_with(parse_len_arg).ok()) } -fn get_next_arg(ident: &Ident, args: &mut Option>) -> TokenStream2 { +fn get_next_arg(ident: &Ident, args: &mut Option>) -> TokenStream2 { if let Some(arg_list) = args { - if let Some(arg) = arg_list.next() { + if let Some(arg) = arg_list.pop_back() { quote!(#arg) } else { quote_spanned!(ident.span() => compile_error!("The number of lengths are invalid.")) diff --git a/lang/src/accounts/account.rs b/lang/src/accounts/account.rs index 7f8ff72a0f..e9edb0bcde 100644 --- a/lang/src/accounts/account.rs +++ b/lang/src/accounts/account.rs @@ -28,7 +28,7 @@ use std::ops::{Deref, DerefMut}; /// This means that the data type that Accounts wraps around (`=T`) needs to /// implement the [Owner trait](crate::Owner). /// The `#[account]` attribute implements the Owner trait for -/// a struct using the `crate::ID` declared by [`declareId`](crate::declare_id) +/// a struct using the `crate::ID` declared by [`declare_id`](crate::declare_id) /// in the same program. It follows that Account can also be used /// with a `T` that comes from a different program. /// diff --git a/lang/src/error.rs b/lang/src/error.rs index 79e6a21890..09ea04366c 100644 --- a/lang/src/error.rs +++ b/lang/src/error.rs @@ -222,8 +222,8 @@ pub enum ErrorCode { #[derive(Debug, PartialEq, Eq)] pub enum Error { - AnchorError(AnchorError), - ProgramError(ProgramErrorWithOrigin), + AnchorError(Box), + ProgramError(Box), } impl std::error::Error for Error {} @@ -239,24 +239,24 @@ impl Display for Error { impl From for Error { fn from(ae: AnchorError) -> Self { - Self::AnchorError(ae) + Self::AnchorError(Box::new(ae)) } } impl From for Error { fn from(program_error: ProgramError) -> Self { - Self::ProgramError(program_error.into()) + Self::ProgramError(Box::new(program_error.into())) } } impl From for Error { fn from(error: BorshIoError) -> Self { - Error::ProgramError(ProgramError::from(error).into()) + Error::ProgramError(Box::new(ProgramError::from(error).into())) } } impl From for Error { fn from(pe: ProgramErrorWithOrigin) -> Self { - Self::ProgramError(pe) + Self::ProgramError(Box::new(pe)) } } @@ -503,10 +503,10 @@ impl Eq for AnchorError {} impl std::convert::From for anchor_lang::solana_program::program_error::ProgramError { fn from(e: Error) -> anchor_lang::solana_program::program_error::ProgramError { match e { - Error::AnchorError(AnchorError { - error_code_number, .. - }) => { - anchor_lang::solana_program::program_error::ProgramError::Custom(error_code_number) + Error::AnchorError(error) => { + anchor_lang::solana_program::program_error::ProgramError::Custom( + error.error_code_number, + ) } Error::ProgramError(program_error) => program_error.program_error, } diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 4fffe41846..d378d6960a 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -53,11 +53,16 @@ pub use anchor_attribute_event::{emit, event}; pub use anchor_attribute_event::{emit_cpi, event_cpi}; pub use anchor_attribute_program::program; pub use anchor_derive_accounts::Accounts; +pub use anchor_derive_serde::{AnchorDeserialize, AnchorSerialize}; pub use anchor_derive_space::InitSpace; /// Borsh is the default serialization format for instructions and accounts. -pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; +pub use borsh::de::BorshDeserialize as AnchorDeserialize; +pub use borsh::ser::BorshSerialize as AnchorSerialize; pub use solana_program; +#[cfg(feature = "idl-build")] +pub use anchor_syn; + pub type Result = std::result::Result; /// A data structure of validated accounts that can be deserialized from the @@ -165,6 +170,51 @@ where } } +/// Lamports related utility methods for accounts. +pub trait Lamports<'info>: AsRef> { + /// Get the lamports of the account. + fn get_lamports(&self) -> u64 { + self.as_ref().lamports() + } + + /// Add lamports to the account. + /// + /// This method is useful for transfering lamports from a PDA. + /// + /// # Requirements + /// + /// 1. The account must be marked `mut`. + /// 2. The total lamports **before** the transaction must equal to total lamports **after** + /// the transaction. + /// 3. `lamports` field of the account info should not currently be borrowed. + /// + /// See [`Lamports::sub_lamports`] for subtracting lamports. + fn add_lamports(&self, amount: u64) -> Result<&Self> { + **self.as_ref().try_borrow_mut_lamports()? += amount; + Ok(self) + } + + /// Subtract lamports from the account. + /// + /// This method is useful for transfering lamports from a PDA. + /// + /// # Requirements + /// + /// 1. The account must be owned by the executing program. + /// 2. The account must be marked `mut`. + /// 3. The total lamports **before** the transaction must equal to total lamports **after** + /// the transaction. + /// 4. `lamports` field of the account info should not currently be borrowed. + /// + /// See [`Lamports::add_lamports`] for adding lamports. + fn sub_lamports(&self, amount: u64) -> Result<&Self> { + **self.as_ref().try_borrow_mut_lamports()? -= amount; + Ok(self) + } +} + +impl<'info, T: AsRef>> Lamports<'info> for T {} + /// A data structure that can be serialized and stored into account storage, /// i.e. an /// [`AccountInfo`](../solana_program/account_info/struct.AccountInfo.html#structfield.data)'s @@ -328,8 +378,8 @@ pub mod prelude { require, require_eq, require_gt, require_gte, require_keys_eq, require_keys_neq, require_neq, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, source, system_program::System, zero_copy, AccountDeserialize, AccountSerialize, Accounts, - AccountsClose, AccountsExit, AnchorDeserialize, AnchorSerialize, Id, InitSpace, Key, Owner, - ProgramData, Result, Space, ToAccountInfo, ToAccountInfos, ToAccountMetas, + AccountsClose, AccountsExit, AnchorDeserialize, AnchorSerialize, Id, InitSpace, Key, + Lamports, Owner, ProgramData, Result, Space, ToAccountInfo, ToAccountInfos, ToAccountMetas, }; #[cfg(feature = "event-cpi")] pub use super::{emit_cpi, event_cpi}; diff --git a/lang/syn/Cargo.toml b/lang/syn/Cargo.toml index e1a8b7b0fd..0fafc72457 100644 --- a/lang/syn/Cargo.toml +++ b/lang/syn/Cargo.toml @@ -10,19 +10,20 @@ edition = "2021" [features] allow-missing-optionals = [] -init-if-needed = [] -idl = [] -hash = [] -default = [] anchor-debug = [] -seeds = [] event-cpi = [] +hash = [] +idl-build = ["idl-parse", "idl-types"] +idl-parse = ["idl-types"] +idl-types = [] +init-if-needed = [] +seeds = [] [dependencies] anyhow = "1" bs58 = "0.5" heck = "0.3" -proc-macro2 = { version = "1", features=["span-locations"]} +proc-macro2 = { version = "1", features = ["span-locations"] } quote = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/lang/syn/src/codegen/accounts/__client_accounts.rs b/lang/syn/src/codegen/accounts/__client_accounts.rs index b188fdf53d..5433beaf4f 100644 --- a/lang/syn/src/codegen/accounts/__client_accounts.rs +++ b/lang/syn/src/codegen/accounts/__client_accounts.rs @@ -62,12 +62,12 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { if f.is_optional { quote! { #docs - pub #name: Option + pub #name: Option } } else { quote! { #docs - pub #name: anchor_lang::solana_program::pubkey::Pubkey + pub #name: Pubkey } } } diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 006e5b1cf5..f1178453f7 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -56,8 +56,8 @@ pub fn generate(f: &Field, accs: &AccountsStruct) -> proc_macro2::TokenStream { pub fn generate_composite(f: &CompositeField) -> proc_macro2::TokenStream { let checks: Vec = linearize(&f.constraints) .iter() - .filter_map(|c| match c { - Constraint::Raw(_) => Some(c), + .map(|c| match c { + Constraint::Raw(_) => c, _ => panic!("Invariant violation: composite constraints can only be raw or literals"), }) .map(|c| generate_constraint_composite(f, c)) @@ -646,7 +646,7 @@ fn generate_constraint_init_group( return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociatedTokenTokenProgram).with_account_name(#name_str).with_pubkeys((*owner_program, #token_program.key()))); } - if pa.key() != ::anchor_spl::associated_token::get_associated_token_address(&#owner.key(), &#mint.key()) { + if pa.key() != ::anchor_spl::associated_token::get_associated_token_address_with_program_id(&#owner.key(), &#mint.key(), &#token_program.key()) { return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount).with_account_name(#name_str)); } } @@ -913,6 +913,7 @@ fn generate_constraint_associated_token( let name_str = name.to_string(); let wallet_address = &c.wallet; let spl_token_mint_address = &c.mint; + let mut optional_check_scope = OptionalCheckScope::new_with_field(accs, name); let wallet_address_optional_check = optional_check_scope.generate_check(wallet_address); let spl_token_mint_address_optional_check = @@ -921,6 +922,7 @@ fn generate_constraint_associated_token( #wallet_address_optional_check #spl_token_mint_address_optional_check }; + let token_program_check = match &c.token_program { Some(token_program) => { let token_program_optional_check = optional_check_scope.generate_check(token_program); @@ -931,6 +933,14 @@ fn generate_constraint_associated_token( } None => quote! {}, }; + let get_associated_token_address = match &c.token_program { + Some(token_program) => quote! { + ::anchor_spl::associated_token::get_associated_token_address_with_program_id(&wallet_address, &#spl_token_mint_address.key(), &#token_program.key()) + }, + None => quote! { + ::anchor_spl::associated_token::get_associated_token_address(&wallet_address, &#spl_token_mint_address.key()) + }, + }; quote! { { @@ -942,7 +952,7 @@ fn generate_constraint_associated_token( if my_owner != wallet_address { return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((my_owner, wallet_address))); } - let __associated_token_address = ::anchor_spl::associated_token::get_associated_token_address(&wallet_address, &#spl_token_mint_address.key()); + let __associated_token_address = #get_associated_token_address; let my_key = #name.key(); if my_key != __associated_token_address { return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociated).with_account_name(#name_str).with_pubkeys((my_key, __associated_token_address))); diff --git a/lang/syn/src/codegen/accounts/mod.rs b/lang/syn/src/codegen/accounts/mod.rs index 50f0977e8e..64bf91982c 100644 --- a/lang/syn/src/codegen/accounts/mod.rs +++ b/lang/syn/src/codegen/accounts/mod.rs @@ -19,13 +19,12 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { let impl_to_account_infos = to_account_infos::generate(accs); let impl_to_account_metas = to_account_metas::generate(accs); let impl_exit = exit::generate(accs); + let bumps_struct = bumps::generate(accs); let __client_accounts_mod = __client_accounts::generate(accs); let __cpi_client_accounts_mod = __cpi_client_accounts::generate(accs); - let bumps_struct = bumps::generate(accs); - - quote! { + let ret = quote! { #impl_try_accounts #impl_to_account_infos #impl_to_account_metas @@ -34,7 +33,21 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { #__client_accounts_mod #__cpi_client_accounts_mod + }; + + #[cfg(feature = "idl-build")] + { + #![allow(warnings)] + let no_docs = crate::idl::build::get_no_docs(); + let idl_gen_impl = crate::idl::build::gen_idl_gen_impl_for_accounts_strct(&accs, no_docs); + return quote! { + #ret + #idl_gen_impl + }; } + + #[allow(unreachable_code)] + ret } fn generics(accs: &AccountsStruct) -> ParsedGenerics { diff --git a/lang/syn/src/codegen/error.rs b/lang/syn/src/codegen/error.rs index 4dea4ea2b7..62c5e511be 100644 --- a/lang/syn/src/codegen/error.rs +++ b/lang/syn/src/codegen/error.rs @@ -1,6 +1,9 @@ use crate::Error; use quote::quote; +#[cfg(feature = "idl-build")] +use crate::idl::build::gen_idl_print_function_for_error; + pub fn generate(error: Error) -> proc_macro2::TokenStream { let error_enum = &error.raw_enum; let enum_name = &error.ident; @@ -47,7 +50,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { }) .collect(); - let offset = match error.args { + let offset = match &error.args { None => quote! { anchor_lang::error::ERROR_CODE_OFFSET}, Some(args) => { let offset = &args.offset; @@ -55,7 +58,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { } }; - quote! { + let ret = quote! { #[derive(std::fmt::Debug, Clone, Copy)] #[repr(u32)] #error_enum @@ -96,5 +99,17 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { } } } - } + }; + + #[cfg(feature = "idl-build")] + { + let idl_gen = gen_idl_print_function_for_error(&error); + return quote! { + #ret + #idl_gen + }; + }; + + #[allow(unreachable_code)] + ret } diff --git a/lang/syn/src/codegen/program/cpi.rs b/lang/syn/src/codegen/program/cpi.rs index 6a8e0d9290..117fbf7b2d 100644 --- a/lang/syn/src/codegen/program/cpi.rs +++ b/lang/syn/src/codegen/program/cpi.rs @@ -40,7 +40,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { data.append(&mut ix_data); let accounts = ctx.to_account_metas(None); anchor_lang::solana_program::instruction::Instruction { - program_id: crate::ID, + program_id: ctx.program.key(), accounts, data, } diff --git a/lang/syn/src/codegen/program/entry.rs b/lang/syn/src/codegen/program/entry.rs index 38fe38db7c..393001a779 100644 --- a/lang/syn/src/codegen/program/entry.rs +++ b/lang/syn/src/codegen/program/entry.rs @@ -17,7 +17,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { /// code wrapping these user defined methods into something that can be /// executed on Solana. /// - /// These methods fall into one categorie for now. + /// These methods fall into one category for now. /// /// Global methods - regular methods inside of the `#[program]`. /// diff --git a/lang/syn/src/codegen/program/mod.rs b/lang/syn/src/codegen/program/mod.rs index 22bec8adcf..a519d1eabf 100644 --- a/lang/syn/src/codegen/program/mod.rs +++ b/lang/syn/src/codegen/program/mod.rs @@ -21,16 +21,33 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let cpi = cpi::generate(program); let accounts = accounts::generate(program); - quote! { - // TODO: remove once we allow segmented paths in `Accounts` structs. - use self::#mod_name::*; - - #entry - #dispatch - #handlers - #user_defined_program - #instruction - #cpi - #accounts - } + #[allow(clippy::let_and_return)] + let ret = { + quote! { + // TODO: remove once we allow segmented paths in `Accounts` structs. + use self::#mod_name::*; + + #entry + #dispatch + #handlers + #user_defined_program + #instruction + #cpi + #accounts + } + }; + + #[cfg(feature = "idl-build")] + { + let no_docs = crate::idl::build::get_no_docs(); + let idl_gen = crate::idl::build::gen_idl_print_function_for_program(program, no_docs); + + return quote! { + #ret + #idl_gen + }; + }; + + #[allow(unreachable_code)] + ret } diff --git a/lang/syn/src/idl/build.rs b/lang/syn/src/idl/build.rs new file mode 100644 index 0000000000..db627594d1 --- /dev/null +++ b/lang/syn/src/idl/build.rs @@ -0,0 +1,918 @@ +use crate::{parser::docs, AccountField, AccountsStruct, Error, Program}; +use heck::MixedCase; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +pub use serde_json; +use syn::{Ident, ItemEnum, ItemStruct}; + +#[inline(always)] +fn get_module_paths() -> (TokenStream, TokenStream) { + ( + quote!(anchor_lang::anchor_syn::idl::types), + quote!(anchor_lang::anchor_syn::idl::build::serde_json), + ) +} + +#[inline(always)] +pub fn get_no_docs() -> bool { + std::option_env!("ANCHOR_IDL_BUILD_NO_DOCS") + .map(|val| val == "TRUE") + .unwrap_or(false) +} + +#[inline(always)] +pub fn get_seeds_feature() -> bool { + std::option_env!("ANCHOR_IDL_BUILD_SEEDS_FEATURE") + .map(|val| val == "TRUE") + .unwrap_or(false) +} + +// Returns TokenStream for IdlType enum and the syn::TypePath for the defined +// type if any. +// Returns Err when the type wasn't parsed successfully. +#[allow(clippy::result_unit_err)] +pub fn idl_type_ts_from_syn_type( + ty: &syn::Type, + type_params: &Vec, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + fn the_only_segment_is(path: &syn::TypePath, cmp: &str) -> bool { + if path.path.segments.len() != 1 { + return false; + }; + return path.path.segments.first().unwrap().ident == cmp; + } + + // Foo -> first::path + fn get_first_angle_bracketed_path_arg(segment: &syn::PathSegment) -> Option<&syn::Type> { + match &segment.arguments { + syn::PathArguments::AngleBracketed(arguments) => match arguments.args.first() { + Some(syn::GenericArgument::Type(ty)) => Some(ty), + _ => None, + }, + _ => None, + } + } + + match ty { + syn::Type::Path(path) if the_only_segment_is(path, "bool") => { + Ok((quote! { #idl::IdlType::Bool }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u8") => { + Ok((quote! { #idl::IdlType::U8 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i8") => { + Ok((quote! { #idl::IdlType::I8 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u16") => { + Ok((quote! { #idl::IdlType::U16 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i16") => { + Ok((quote! { #idl::IdlType::I16 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u32") => { + Ok((quote! { #idl::IdlType::U32 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i32") => { + Ok((quote! { #idl::IdlType::I32 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "f32") => { + Ok((quote! { #idl::IdlType::F32 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u64") => { + Ok((quote! { #idl::IdlType::U64 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i64") => { + Ok((quote! { #idl::IdlType::I64 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "f64") => { + Ok((quote! { #idl::IdlType::F64 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u128") => { + Ok((quote! { #idl::IdlType::U128 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i128") => { + Ok((quote! { #idl::IdlType::I128 }, vec![])) + } + syn::Type::Path(path) + if the_only_segment_is(path, "String") || the_only_segment_is(path, "&str") => + { + Ok((quote! { #idl::IdlType::String }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "Pubkey") => { + Ok((quote! { #idl::IdlType::PublicKey }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "Vec") => { + let segment = path.path.segments.first().unwrap(); + let arg = match get_first_angle_bracketed_path_arg(segment) { + Some(arg) => arg, + None => unreachable!("Vec arguments can only be of AngleBracketed variant"), + }; + match arg { + syn::Type::Path(path) if the_only_segment_is(path, "u8") => { + return Ok((quote! {#idl::IdlType::Bytes}, vec![])); + } + _ => (), + }; + let (inner, defined) = idl_type_ts_from_syn_type(arg, type_params)?; + Ok((quote! { #idl::IdlType::Vec(Box::new(#inner)) }, defined)) + } + syn::Type::Path(path) if the_only_segment_is(path, "Option") => { + let segment = path.path.segments.first().unwrap(); + let arg = match get_first_angle_bracketed_path_arg(segment) { + Some(arg) => arg, + None => unreachable!("Option arguments can only be of AngleBracketed variant"), + }; + let (inner, defined) = idl_type_ts_from_syn_type(arg, type_params)?; + Ok((quote! { #idl::IdlType::Option(Box::new(#inner)) }, defined)) + } + syn::Type::Path(path) if the_only_segment_is(path, "Box") => { + let segment = path.path.segments.first().unwrap(); + let arg = match get_first_angle_bracketed_path_arg(segment) { + Some(arg) => arg, + None => unreachable!("Box arguments can only be of AngleBracketed variant"), + }; + let (ts, defined) = idl_type_ts_from_syn_type(arg, type_params)?; + Ok((quote! { #ts }, defined)) + } + syn::Type::Array(arr) => { + let len = arr.len.clone(); + let len_is_generic = type_params.iter().any(|param| match len { + syn::Expr::Path(ref path) => path.path.is_ident(param), + _ => false, + }); + + let (inner, defined) = idl_type_ts_from_syn_type(&arr.elem, type_params)?; + + if len_is_generic { + match len { + syn::Expr::Path(ref len) => { + let len = len.path.get_ident().unwrap().to_string(); + Ok(( + quote! { #idl::IdlType::GenericLenArray(Box::new(#inner), #len.into()) }, + defined, + )) + } + _ => unreachable!("Array length can only be a generic parameter"), + } + } else { + Ok(( + quote! { #idl::IdlType::Array(Box::new(#inner), #len) }, + defined, + )) + } + } + syn::Type::Path(path) => { + let is_generic_param = type_params.iter().any(|param| path.path.is_ident(param)); + + if is_generic_param { + let generic = format!("{}", path.path.get_ident().unwrap()); + Ok((quote! { #idl::IdlType::Generic(#generic.into()) }, vec![])) + } else { + let mut params = vec![]; + let mut defined = vec![path.clone()]; + + if let Some(segment) = &path.path.segments.last() { + if let syn::PathArguments::AngleBracketed(ref args) = segment.arguments { + for arg in &args.args { + match arg { + syn::GenericArgument::Type(ty) => { + let (ts, def) = idl_type_ts_from_syn_type(ty, type_params)?; + params.push(quote! { #idl::IdlDefinedTypeArg::Type(#ts) }); + defined.extend(def); + } + syn::GenericArgument::Const(c) => params.push( + quote! { #idl::IdlDefinedTypeArg::Value(format!("{}", #c))}, + ), + _ => (), + } + } + } + } + + if !params.is_empty() { + let params = quote! { vec![#(#params),*] }; + Ok(( + quote! { #idl::IdlType::DefinedWithTypeArgs { + name: <#path>::__anchor_private_full_path(), + args: #params + } }, + defined, + )) + } else { + Ok(( + quote! { #idl::IdlType::Defined(<#path>::__anchor_private_full_path()) }, + vec![path.clone()], + )) + } + } + } + syn::Type::Reference(reference) => match reference.elem.as_ref() { + syn::Type::Slice(slice) if matches!(&*slice.elem, syn::Type::Path(path) if the_only_segment_is(path, "u8")) => + { + return Ok((quote! {#idl::IdlType::Bytes}, vec![])); + } + _ => panic!("Reference types other than byte slice(`&[u8]`) are not allowed"), + }, + _ => Err(()), + } +} + +// Returns TokenStream for IdlField struct and the syn::TypePath for the defined +// type if any. +// Returns Err when the type wasn't parsed successfully +#[allow(clippy::result_unit_err)] +pub fn idl_field_ts_from_syn_field( + field: &syn::Field, + no_docs: bool, + type_params: &Vec, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + let name = field.ident.as_ref().unwrap().to_string().to_mixed_case(); + let docs = match docs::parse(&field.attrs) { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + let (ty, defined) = idl_type_ts_from_syn_type(&field.ty, type_params)?; + + Ok(( + quote! { + #idl::IdlField { + name: #name.into(), + docs: #docs, + ty: #ty, + } + }, + defined, + )) +} + +// Returns TokenStream for IdlEventField struct and the syn::TypePath for the defined +// type if any. +// Returns Err when the type wasn't parsed successfully +#[allow(clippy::result_unit_err)] +pub fn idl_event_field_ts_from_syn_field( + field: &syn::Field, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + let name = field.ident.as_ref().unwrap().to_string().to_mixed_case(); + let (ty, defined) = idl_type_ts_from_syn_type(&field.ty, &vec![])?; + + let index: bool = field + .attrs + .get(0) + .and_then(|attr| attr.path.segments.first()) + .map(|segment| segment.ident == "index") + .unwrap_or(false); + + Ok(( + quote! { + #idl::IdlEventField { + name: #name.into(), + ty: #ty, + index: #index, + } + }, + defined, + )) +} + +// Returns TokenStream for IdlTypeDefinitionTy::Struct and Vec<&syn::TypePath> +// for the defined types if any. +// Returns Err if any of the fields weren't parsed successfully. +#[allow(clippy::result_unit_err)] +pub fn idl_type_definition_ts_from_syn_struct( + item_strct: &syn::ItemStruct, + no_docs: bool, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + let docs = match docs::parse(&item_strct.attrs) { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + + let type_params = item_strct + .generics + .params + .iter() + .filter_map(|p| match p { + syn::GenericParam::Type(ty) => Some(ty.ident.clone()), + syn::GenericParam::Const(c) => Some(c.ident.clone()), + _ => None, + }) + .collect::>(); + let (fields, defined): (Vec, Vec>) = match &item_strct.fields { + syn::Fields::Named(fields) => fields + .named + .iter() + .map(|f: &syn::Field| idl_field_ts_from_syn_field(f, no_docs, &type_params)) + .collect::, _>>()? + .into_iter() + .unzip::<_, _, Vec<_>, Vec<_>>(), + _ => return Err(()), + }; + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + let generics = if !type_params.is_empty() { + let g: Vec = type_params.iter().map(|id| id.to_string()).collect(); + quote! { Some(vec![#(#g.into()),*]) } + } else { + quote! { None } + }; + + Ok(( + quote! { + #idl::IdlTypeDefinition { + name: Self::__anchor_private_full_path(), + generics: #generics, + docs: #docs, + ty: #idl::IdlTypeDefinitionTy::Struct{ + fields: vec![ + #(#fields),* + ] + } + }, + }, + defined, + )) +} + +// Returns TokenStream for IdlTypeDefinitionTy::Enum and the Vec<&syn::TypePath> +// for the defined types if any. +// Returns Err if any of the fields didn't parse successfully. +#[allow(clippy::result_unit_err)] +pub fn idl_type_definition_ts_from_syn_enum( + enum_item: &syn::ItemEnum, + no_docs: bool, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + let docs = match docs::parse(&enum_item.attrs) { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + + let type_params = enum_item + .generics + .params + .iter() + .filter_map(|p| match p { + syn::GenericParam::Type(ty) => Some(ty.ident.clone()), + syn::GenericParam::Const(c) => Some(c.ident.clone()), + _ => None, + }) + .collect::>(); + + let (variants, defined): (Vec, Vec>) = enum_item.variants.iter().map(|variant: &syn::Variant| { + let name = variant.ident.to_string(); + let (fields, defined): (TokenStream, Vec) = match &variant.fields { + syn::Fields::Unit => (quote!{None}, vec![]), + syn::Fields::Unnamed(fields) => { + let (types, defined) = fields.unnamed + .iter() + .map(|f| idl_type_ts_from_syn_type(&f.ty, &type_params)) + .collect::, _>>()? + .into_iter() + .unzip::, Vec, Vec>>(); + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + (quote!{ Some(#idl::EnumFields::Tuple(vec![#(#types),*]))}, defined) + } + syn::Fields::Named(fields) => { + let (fields, defined) = fields.named + .iter() + .map(|f| idl_field_ts_from_syn_field(f, no_docs, &type_params)) + .collect::, _>>()? + .into_iter() + .unzip::, Vec, Vec>>(); + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + (quote!{ Some(#idl::EnumFields::Named(vec![#(#fields),*]))}, defined) + } + }; + + Ok((quote!{ #idl::IdlEnumVariant{ name: #name.into(), fields: #fields }}, defined)) + }) + .collect::, _>>()? + .into_iter() + .unzip::, Vec, Vec>>(); + + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + let generics = if !type_params.is_empty() { + let g: Vec = type_params.iter().map(|id| id.to_string()).collect(); + quote! { Some(vec![#(#g.into()),*]) } + } else { + quote! { None } + }; + + Ok(( + quote! { + #idl::IdlTypeDefinition { + name: Self::__anchor_private_full_path(), + generics: #generics, + docs: #docs, + ty: #idl::IdlTypeDefinitionTy::Enum{ + variants: vec![ + #(#variants),* + ] + } + } + }, + defined, + )) +} + +pub fn idl_gen_impl_skeleton( + idl_type_definition_ts: TokenStream, + insert_defined_ts: TokenStream, + ident: &Ident, + input_generics: &syn::Generics, +) -> TokenStream { + let (idl, _) = get_module_paths(); + let name = ident.to_string(); + let (impl_generics, ty_generics, where_clause) = input_generics.split_for_impl(); + + quote! { + impl #impl_generics #ident #ty_generics #where_clause { + pub fn __anchor_private_full_path() -> String { + format!("{}::{}", std::module_path!(), #name) + } + + pub fn __anchor_private_gen_idl_type() -> Option<#idl::IdlTypeDefinition> { + #idl_type_definition_ts + } + + pub fn __anchor_private_insert_idl_defined( + defined_types: &mut std::collections::HashMap + ) { + #insert_defined_ts + } + } + } +} + +// generates the IDL generation impl for for a struct +pub fn gen_idl_gen_impl_for_struct(strct: &ItemStruct, no_docs: bool) -> TokenStream { + let idl_type_definition_ts: TokenStream; + let insert_defined_ts: TokenStream; + + if let Ok((ts, defined)) = idl_type_definition_ts_from_syn_struct(strct, no_docs) { + idl_type_definition_ts = quote! {Some(#ts)}; + insert_defined_ts = quote! { + #({ + <#defined>::__anchor_private_insert_idl_defined(defined_types); + + let path = <#defined>::__anchor_private_full_path(); + <#defined>::__anchor_private_gen_idl_type() + .and_then(|ty| defined_types.insert(path, ty)); + });* + }; + } else { + idl_type_definition_ts = quote! {None}; + insert_defined_ts = quote! {}; + } + + let ident = &strct.ident; + let input_generics = &strct.generics; + + idl_gen_impl_skeleton( + idl_type_definition_ts, + insert_defined_ts, + ident, + input_generics, + ) +} + +// generates the IDL generation impl for for an enum +pub fn gen_idl_gen_impl_for_enum(enm: ItemEnum, no_docs: bool) -> TokenStream { + let idl_type_definition_ts: TokenStream; + let insert_defined_ts: TokenStream; + + if let Ok((ts, defined)) = idl_type_definition_ts_from_syn_enum(&enm, no_docs) { + idl_type_definition_ts = quote! {Some(#ts)}; + insert_defined_ts = quote! { + #({ + <#defined>::__anchor_private_insert_idl_defined(defined_types); + + let path = <#defined>::__anchor_private_full_path(); + <#defined>::__anchor_private_gen_idl_type() + .and_then(|ty| defined_types.insert(path, ty)); + });* + }; + } else { + idl_type_definition_ts = quote! {None}; + insert_defined_ts = quote! {}; + } + + let ident = &enm.ident; + let input_generics = &enm.generics; + + idl_gen_impl_skeleton( + idl_type_definition_ts, + insert_defined_ts, + ident, + input_generics, + ) +} + +// generates the IDL generation impl for for an event +pub fn gen_idl_gen_impl_for_event(event_strct: &ItemStruct) -> TokenStream { + fn parse_fields( + fields: &syn::FieldsNamed, + ) -> Result<(Vec, Vec), ()> { + let (fields, defined) = fields + .named + .iter() + .map(idl_event_field_ts_from_syn_field) + .collect::, _>>()? + .into_iter() + .unzip::<_, _, Vec<_>, Vec<_>>(); + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + Ok((fields, defined)) + } + + let res = match &event_strct.fields { + syn::Fields::Named(fields) => parse_fields(fields), + _ => Err(()), + }; + + let (idl, _) = get_module_paths(); + let name = event_strct.ident.to_string(); + + let (ret_ts, types_ts) = match res { + Ok((fields, defined)) => { + let ret_ts = quote! { + Some( + #idl::IdlEvent { + name: #name.into(), + fields: vec![#(#fields),*], + } + ) + }; + let types_ts = quote! { + #({ + <#defined>::__anchor_private_insert_idl_defined(defined_types); + + let path = <#defined>::__anchor_private_full_path(); + <#defined>::__anchor_private_gen_idl_type() + .and_then(|ty| defined_types.insert(path, ty)); + });* + }; + (ret_ts, types_ts) + } + Err(()) => (quote! { None }, quote! {}), + }; + + let ident = &event_strct.ident; + let input_generics = &event_strct.generics; + let (impl_generics, ty_generics, where_clause) = input_generics.split_for_impl(); + + quote! { + impl #impl_generics #ident #ty_generics #where_clause { + pub fn __anchor_private_gen_idl_event( + defined_types: &mut std::collections::HashMap, + ) -> Option<#idl::IdlEvent> { + #types_ts + #ret_ts + } + } + } +} + +// generates the IDL generation impl for the Accounts struct +pub fn gen_idl_gen_impl_for_accounts_strct( + accs_strct: &AccountsStruct, + no_docs: bool, +) -> TokenStream { + let (idl, _) = get_module_paths(); + + let ident = &accs_strct.ident; + let (impl_generics, ty_generics, where_clause) = accs_strct.generics.split_for_impl(); + + let (accounts, acc_types): (Vec, Vec>) = accs_strct + .fields + .iter() + .map(|acc: &AccountField| match acc { + AccountField::CompositeField(comp_f) => { + let ty = if let syn::Type::Path(path) = &comp_f.raw_field.ty { + // some::path::Foo<'info> -> some::path::Foo + let mut res = syn::Path { + leading_colon: path.path.leading_colon, + segments: syn::punctuated::Punctuated::new(), + }; + for segment in &path.path.segments { + let s = syn::PathSegment { + ident: segment.ident.clone(), + arguments: syn::PathArguments::None, + }; + res.segments.push(s); + }; + res + } else { + panic!("expecting path") + }; + let name = comp_f.ident.to_string().to_mixed_case(); + (quote!{ + #idl::IdlAccountItem::IdlAccounts(#idl::IdlAccounts { + name: #name.into(), + accounts: <#ty>::__anchor_private_gen_idl_accounts(accounts, defined_types), + }) + }, None) + } + AccountField::Field(acc) => { + let name = acc.ident.to_string().to_mixed_case(); + let is_mut = acc.constraints.is_mutable(); + let is_signer = match acc.ty { + crate::Ty::Signer => true, + _ => acc.constraints.is_signer() + }; + let is_optional = if acc.is_optional { quote!{Some(true)} } else { quote!{None} }; + let docs = match &acc.docs { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + let pda = quote!{None}; // TODO + let relations = super::parse::relations::parse(acc, get_seeds_feature()); + + let acc_type_path = match &acc.ty { + crate::Ty::Account(ty) => Some(&ty.account_type_path), + crate::Ty::AccountLoader(ty) => Some(&ty.account_type_path), + crate::Ty::InterfaceAccount(ty) => Some(&ty.account_type_path), + _ => None, + }; + + (quote!{ + #idl::IdlAccountItem::IdlAccount(#idl::IdlAccount{ + name: #name.into(), + is_mut: #is_mut, + is_signer: #is_signer, + is_optional: #is_optional, + docs: #docs, + pda: #pda, + relations: vec![#(#relations.into()),*], + }) + }, acc_type_path) + } + }) + .unzip::, Vec, Vec>>(); + let acc_types = acc_types + .into_iter() + .flatten() + .collect::>(); + + quote! { + impl #impl_generics #ident #ty_generics #where_clause { + pub fn __anchor_private_gen_idl_accounts( + accounts: &mut std::collections::HashMap, + defined_types: &mut std::collections::HashMap, + ) -> Vec<#idl::IdlAccountItem> { + #({ + <#acc_types>::__anchor_private_insert_idl_defined(defined_types); + + let path = <#acc_types>::__anchor_private_full_path(); + <#acc_types>::__anchor_private_gen_idl_type() + .and_then(|ty| accounts.insert(path, ty)); + + });* + + vec![#(#accounts),*] + } + } + } +} + +// generates the IDL generation print function for the program module +pub fn gen_idl_print_function_for_program(program: &Program, no_docs: bool) -> TokenStream { + let (idl, serde_json) = get_module_paths(); + + let (instructions, defined) = program + .ixs + .iter() + .flat_map(|ix| -> Result<_, ()> { + let name = ix.ident.to_string().to_mixed_case(); + let docs = match &ix.docs { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + let ctx_ident = &ix.anchor_ident; + + let (args, mut defined) = ix + .args + .iter() + .map(|arg| { + let arg_name = arg.name.to_string().to_mixed_case(); + let docs = match docs::parse(&arg.raw_arg.attrs) { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + let (ty, defined) = idl_type_ts_from_syn_type(&arg.raw_arg.ty, &vec![])?; + + Ok((quote! { + #idl::IdlField { + name: #arg_name.into(), + docs: #docs, + ty: #ty, + } + }, defined)) + }) + .collect::, ()>>()? + .into_iter() + .unzip::, Vec, Vec>>(); + + let returns = match idl_type_ts_from_syn_type(&ix.returns.ty, &vec![]) { + Ok((ty, def)) => { + defined.push(def); + quote!{ Some(#ty) } + }, + Err(()) => quote!{ None } + }; + + Ok((quote! { + #idl::IdlInstruction { + name: #name.into(), + docs: #docs, + accounts: #ctx_ident::__anchor_private_gen_idl_accounts( + &mut accounts, + &mut defined_types, + ), + args: vec![#(#args),*], + returns: #returns, + } + }, defined)) + }) + .unzip::>, Vec, Vec>>>(); + let defined = defined + .into_iter() + .flatten() + .flatten() + .collect::>(); + + let name = program.name.to_string(); + let docs = match &program.docs { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + + quote! { + #[test] + pub fn __anchor_private_print_idl_program() { + let mut accounts: std::collections::HashMap = + std::collections::HashMap::new(); + let mut defined_types: std::collections::HashMap = + std::collections::HashMap::new(); + + #({ + <#defined>::__anchor_private_insert_idl_defined(&mut defined_types); + + let path = <#defined>::__anchor_private_full_path(); + <#defined>::__anchor_private_gen_idl_type() + .and_then(|ty| defined_types.insert(path, ty)); + });* + + let instructions = vec![#(#instructions),*]; + + let idl = #idl::Idl { + version: env!("CARGO_PKG_VERSION").into(), + name: #name.into(), + docs: #docs, + constants: vec![], + instructions, + accounts: accounts.into_values().collect(), + types: defined_types.into_values().collect(), + events: None, + errors: None, + metadata: None, + }; + + println!("---- IDL begin program ----"); + println!("{}", #serde_json::to_string_pretty(&idl).unwrap()); + println!("---- IDL end program ----"); + } + } +} + +pub fn gen_idl_print_function_for_event(event: &ItemStruct) -> TokenStream { + let (idl, serde_json) = get_module_paths(); + + let ident = &event.ident; + let fn_name = format_ident!("__anchor_private_print_idl_event_{}", ident.to_string()); + let impl_gen = gen_idl_gen_impl_for_event(event); + + quote! { + #impl_gen + + #[test] + pub fn #fn_name() { + let mut defined_types: std::collections::HashMap = std::collections::HashMap::new(); + let event = #ident::__anchor_private_gen_idl_event(&mut defined_types); + + if let Some(event) = event { + let json = #serde_json::json!({ + "event": event, + "defined_types": defined_types.into_values().collect::>() + }); + + println!("---- IDL begin event ----"); + println!("{}", #serde_json::to_string_pretty(&json).unwrap()); + println!("---- IDL end event ----"); + } + } + } +} + +pub fn gen_idl_print_function_for_constant(item: &syn::ItemConst) -> TokenStream { + let fn_name = format_ident!( + "__anchor_private_print_idl_const_{}", + item.ident.to_string() + ); + let (idl, serde_json) = get_module_paths(); + + let name = item.ident.to_string(); + let expr = &item.expr; + + let impl_ts = match idl_type_ts_from_syn_type(&item.ty, &vec![]) { + Ok((ty, _)) => quote! { + let value = format!("{:?}", #expr); + + let idl = #idl::IdlConst { + name: #name.into(), + ty: #ty, + value, + }; + + println!("---- IDL begin const ----"); + println!("{}", #serde_json::to_string_pretty(&idl).unwrap()); + println!("---- IDL end const ----"); + }, + Err(()) => quote! {}, + }; + + quote! { + #[test] + pub fn #fn_name() { + #impl_ts + } + } +} + +pub fn gen_idl_print_function_for_error(error: &Error) -> TokenStream { + let fn_name = format_ident!( + "__anchor_private_print_idl_error_{}", + error.ident.to_string() + ); + let (idl, serde_json) = get_module_paths(); + + let error_codes = error + .codes + .iter() + .map(|code| { + let id = code.id; + let name = code.ident.to_string(); + + let msg = match code.msg.clone() { + Some(msg) => quote! { Some(#msg.to_string()) }, + None => quote! { None }, + }; + + quote! { + #idl::IdlErrorCode { + code: anchor_lang::error::ERROR_CODE_OFFSET + #id, + name: #name.into(), + msg: #msg, + } + } + }) + .collect::>(); + + quote! { + #[test] + pub fn #fn_name() { + let errors = vec![#(#error_codes),*]; + + println!("---- IDL begin errors ----"); + println!("{}", #serde_json::to_string_pretty(&errors).unwrap()); + println!("---- IDL end errors ----"); + } + } +} diff --git a/lang/syn/src/idl/mod.rs b/lang/syn/src/idl/mod.rs index 6729ca4a55..3cd27cc4f8 100644 --- a/lang/syn/src/idl/mod.rs +++ b/lang/syn/src/idl/mod.rs @@ -1,330 +1,8 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value as JsonValue; +#[cfg(feature = "idl-build")] +pub mod build; -pub mod file; -pub mod pda; -pub mod relations; +#[cfg(feature = "idl-parse")] +pub mod parse; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Idl { - pub version: String, - pub name: String, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub docs: Option>, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub constants: Vec, - pub instructions: Vec, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub accounts: Vec, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub types: Vec, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub events: Option>, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub errors: Option>, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub metadata: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IdlConst { - pub name: String, - #[serde(rename = "type")] - pub ty: IdlType, - pub value: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IdlState { - #[serde(rename = "struct")] - pub strct: IdlTypeDefinition, - pub methods: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IdlInstruction { - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub docs: Option>, - pub accounts: Vec, - pub args: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub returns: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct IdlAccounts { - pub name: String, - pub accounts: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(untagged)] -pub enum IdlAccountItem { - IdlAccount(IdlAccount), - IdlAccounts(IdlAccounts), -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct IdlAccount { - pub name: String, - pub is_mut: bool, - pub is_signer: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub is_optional: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub docs: Option>, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub pda: Option, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub relations: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct IdlPda { - pub seeds: Vec, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub program_id: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase", tag = "kind")] -pub enum IdlSeed { - Const(IdlSeedConst), - Arg(IdlSeedArg), - Account(IdlSeedAccount), -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct IdlSeedAccount { - #[serde(rename = "type")] - pub ty: IdlType, - // account_ty points to the entry in the "accounts" section. - // Some only if the `Account` type is used. - #[serde(skip_serializing_if = "Option::is_none")] - pub account: Option, - pub path: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct IdlSeedArg { - #[serde(rename = "type")] - pub ty: IdlType, - pub path: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct IdlSeedConst { - #[serde(rename = "type")] - pub ty: IdlType, - pub value: serde_json::Value, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IdlField { - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub docs: Option>, - #[serde(rename = "type")] - pub ty: IdlType, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IdlEvent { - pub name: String, - pub fields: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IdlEventField { - pub name: String, - #[serde(rename = "type")] - pub ty: IdlType, - pub index: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IdlTypeDefinition { - pub name: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub docs: Option>, - #[serde(rename = "type")] - pub ty: IdlTypeDefinitionTy, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase", tag = "kind")] -pub enum IdlTypeDefinitionTy { - Struct { fields: Vec }, - Enum { variants: Vec }, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IdlEnumVariant { - pub name: String, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub fields: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(untagged)] -pub enum EnumFields { - Named(Vec), - Tuple(Vec), -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub enum IdlType { - Bool, - U8, - I8, - U16, - I16, - U32, - I32, - F32, - U64, - I64, - F64, - U128, - I128, - U256, - I256, - Bytes, - String, - PublicKey, - Defined(String), - Option(Box), - Vec(Box), - Array(Box, usize), -} - -impl std::str::FromStr for IdlType { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - let mut s = s.to_string(); - fn array_from_str(inner: &str) -> IdlType { - match inner.strip_suffix(']') { - None => { - let (raw_type, raw_length) = inner.rsplit_once(';').unwrap(); - let ty = IdlType::from_str(raw_type).unwrap(); - let len = raw_length.replace('_', "").parse::().unwrap(); - IdlType::Array(Box::new(ty), len) - } - Some(nested_inner) => array_from_str(&nested_inner[1..]), - } - } - s.retain(|c| !c.is_whitespace()); - - let r = match s.as_str() { - "bool" => IdlType::Bool, - "u8" => IdlType::U8, - "i8" => IdlType::I8, - "u16" => IdlType::U16, - "i16" => IdlType::I16, - "u32" => IdlType::U32, - "i32" => IdlType::I32, - "f32" => IdlType::F32, - "u64" => IdlType::U64, - "i64" => IdlType::I64, - "f64" => IdlType::F64, - "u128" => IdlType::U128, - "i128" => IdlType::I128, - "u256" => IdlType::U256, - "i256" => IdlType::I256, - "Vec" => IdlType::Bytes, - "String" | "&str" | "&'staticstr" => IdlType::String, - "Pubkey" => IdlType::PublicKey, - _ => match s.to_string().strip_prefix("Option<") { - None => match s.to_string().strip_prefix("Vec<") { - None => { - if s.to_string().starts_with('[') { - array_from_str(&s) - } else { - IdlType::Defined(s.to_string()) - } - } - Some(inner) => { - let inner_ty = Self::from_str( - inner - .strip_suffix('>') - .ok_or_else(|| anyhow::anyhow!("Invalid option"))?, - )?; - IdlType::Vec(Box::new(inner_ty)) - } - }, - Some(inner) => { - let inner_ty = Self::from_str( - inner - .strip_suffix('>') - .ok_or_else(|| anyhow::anyhow!("Invalid option"))?, - )?; - IdlType::Option(Box::new(inner_ty)) - } - }, - }; - Ok(r) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct IdlErrorCode { - pub code: u32, - pub name: String, - #[serde(skip_serializing_if = "Option::is_none", default)] - pub msg: Option, -} - -#[cfg(test)] -mod tests { - use crate::idl::IdlType; - use std::str::FromStr; - - #[test] - fn multidimensional_array() { - assert_eq!( - IdlType::from_str("[[u8;16];32]").unwrap(), - IdlType::Array(Box::new(IdlType::Array(Box::new(IdlType::U8), 16)), 32) - ); - } - - #[test] - fn array() { - assert_eq!( - IdlType::from_str("[Pubkey;16]").unwrap(), - IdlType::Array(Box::new(IdlType::PublicKey), 16) - ); - } - - #[test] - fn array_with_underscored_length() { - assert_eq!( - IdlType::from_str("[u8;50_000]").unwrap(), - IdlType::Array(Box::new(IdlType::U8), 50000) - ); - } - - #[test] - fn option() { - assert_eq!( - IdlType::from_str("Option").unwrap(), - IdlType::Option(Box::new(IdlType::Bool)) - ) - } - - #[test] - fn vector() { - assert_eq!( - IdlType::from_str("Vec").unwrap(), - IdlType::Vec(Box::new(IdlType::Bool)) - ) - } -} +#[cfg(feature = "idl-types")] +pub mod types; diff --git a/lang/syn/src/idl/file.rs b/lang/syn/src/idl/parse/file.rs similarity index 93% rename from lang/syn/src/idl/file.rs rename to lang/syn/src/idl/parse/file.rs index 908a1285e1..3a2b331762 100644 --- a/lang/syn/src/idl/file.rs +++ b/lang/syn/src/idl/parse/file.rs @@ -1,8 +1,9 @@ -use crate::idl::*; +use crate::idl::types::*; use crate::parser::context::CrateContext; use crate::parser::{self, accounts, docs, error, program}; use crate::Ty; use crate::{AccountField, AccountsStruct}; +use anyhow::anyhow; use anyhow::Result; use heck::MixedCase; use quote::ToTokens; @@ -13,32 +14,31 @@ use syn::{ Lit::{Byte, ByteStr}, }; +use super::relations; + const DERIVE_NAME: &str = "Accounts"; // TODO: share this with `anchor_lang` crate. const ERROR_CODE_OFFSET: u32 = 6000; -// Parse an entire interface file. +/// Parse an entire interface file. pub fn parse( - filename: impl AsRef, + path: impl AsRef, version: String, seeds_feature: bool, no_docs: bool, safety_checks: bool, -) -> Result> { - let ctx = CrateContext::parse(filename)?; +) -> Result { + let ctx = CrateContext::parse(path)?; if safety_checks { ctx.safety_checks()?; } - let program_mod = match parse_program_mod(&ctx) { - None => return Ok(None), - Some(m) => m, - }; - let mut p = program::parse(program_mod)?; + let program_mod = parse_program_mod(&ctx)?; + let mut program = program::parse(program_mod)?; if no_docs { - p.docs = None; - for ix in &mut p.ixs { + program.docs = None; + for ix in &mut program.ixs { ix.docs = None; } } @@ -57,7 +57,7 @@ pub fn parse( .collect::>() }); - let instructions = p + let instructions = program .ixs .iter() .map(|ix| { @@ -155,10 +155,10 @@ pub fn parse( .map(|c: &&syn::ItemConst| to_idl_const(c)) .collect::>(); - Ok(Some(Idl { + Ok(Idl { version, - name: p.name.to_string(), - docs: p.docs.clone(), + name: program.name.to_string(), + docs: program.docs.clone(), instructions, types, accounts, @@ -170,11 +170,11 @@ pub fn parse( errors: error_codes, metadata: None, constants, - })) + }) } -// Parse the main program mod. -fn parse_program_mod(ctx: &CrateContext) -> Option { +/// Parse the main program mod. +fn parse_program_mod(ctx: &CrateContext) -> Result { let root = ctx.root_module(); let mods = root .items() @@ -193,15 +193,17 @@ fn parse_program_mod(ctx: &CrateContext) -> Option { _ => None, }) .collect::>(); - if mods.len() != 1 { - return None; + + match mods.len() { + 0 => Err(anyhow!("Program module not found")), + 1 => Ok(mods[0].clone()), + _ => Err(anyhow!("Multiple program modules are not allowed")), } - Some(mods[0].clone()) } fn parse_error_enum(ctx: &CrateContext) -> Option { ctx.enums() - .filter_map(|item_enum| { + .find(|item_enum| { let attrs_count = item_enum .attrs .iter() @@ -211,18 +213,17 @@ fn parse_error_enum(ctx: &CrateContext) -> Option { }) .count(); match attrs_count { - 0 => None, - 1 => Some(item_enum), + 0 => false, + 1 => true, _ => panic!("Invalid syntax: one error attribute allowed"), } }) - .next() .cloned() } fn parse_events(ctx: &CrateContext) -> Vec<&syn::ItemStruct> { ctx.structs() - .filter_map(|item_strct| { + .filter(|item_strct| { let attrs_count = item_strct .attrs .iter() @@ -232,8 +233,8 @@ fn parse_events(ctx: &CrateContext) -> Vec<&syn::ItemStruct> { }) .count(); match attrs_count { - 0 => None, - 1 => Some(item_strct), + 0 => false, + 1 => true, _ => panic!("Invalid syntax: one event attribute allowed"), } }) @@ -242,7 +243,7 @@ fn parse_events(ctx: &CrateContext) -> Vec<&syn::ItemStruct> { fn parse_accounts(ctx: &CrateContext) -> Vec<&syn::ItemStruct> { ctx.structs() - .filter_map(|item_strct| { + .filter(|item_strct| { let attrs_count = item_strct .attrs .iter() @@ -252,9 +253,9 @@ fn parse_accounts(ctx: &CrateContext) -> Vec<&syn::ItemStruct> { }) .count(); match attrs_count { - 0 => None, - 1 => Some(item_strct), - _ => panic!("Invalid syntax: one event attribute allowed"), + 0 => false, + 1 => true, + _ => panic!("Invalid syntax: one account attribute allowed"), } }) .collect() @@ -346,6 +347,7 @@ fn parse_ty_defs(ctx: &CrateContext, no_docs: bool) -> Result Result>(); Some(Ok(IdlTypeDefinition { name, + generics: None, docs: doc, ty: IdlTypeDefinitionTy::Enum { variants }, })) @@ -547,7 +550,7 @@ fn idl_accounts( }, is_optional: if acc.is_optional { Some(true) } else { None }, docs: if !no_docs { acc.docs.clone() } else { None }, - pda: pda::parse(ctx, accounts, acc, seeds_feature), + pda: super::pda::parse(ctx, accounts, acc, seeds_feature), relations: relations::parse(acc, seeds_feature), }), }) diff --git a/lang/syn/src/idl/parse/mod.rs b/lang/syn/src/idl/parse/mod.rs new file mode 100644 index 0000000000..88dc4e7ba7 --- /dev/null +++ b/lang/syn/src/idl/parse/mod.rs @@ -0,0 +1,120 @@ +use crate::idl::types::*; + +pub mod file; +pub mod pda; +pub mod relations; + +impl std::str::FromStr for IdlType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let mut s = s.to_string(); + fn array_from_str(inner: &str) -> IdlType { + match inner.strip_suffix(']') { + None => { + let (raw_type, raw_length) = inner.rsplit_once(';').unwrap(); + let ty = IdlType::from_str(raw_type).unwrap(); + let len = raw_length.replace('_', "").parse::().unwrap(); + IdlType::Array(Box::new(ty), len) + } + Some(nested_inner) => array_from_str(&nested_inner[1..]), + } + } + s.retain(|c| !c.is_whitespace()); + + let r = match s.as_str() { + "bool" => IdlType::Bool, + "u8" => IdlType::U8, + "i8" => IdlType::I8, + "u16" => IdlType::U16, + "i16" => IdlType::I16, + "u32" => IdlType::U32, + "i32" => IdlType::I32, + "f32" => IdlType::F32, + "u64" => IdlType::U64, + "i64" => IdlType::I64, + "f64" => IdlType::F64, + "u128" => IdlType::U128, + "i128" => IdlType::I128, + "u256" => IdlType::U256, + "i256" => IdlType::I256, + "Vec" => IdlType::Bytes, + "String" | "&str" | "&'staticstr" => IdlType::String, + "Pubkey" => IdlType::PublicKey, + _ => match s.to_string().strip_prefix("Option<") { + None => match s.to_string().strip_prefix("Vec<") { + None => { + if s.to_string().starts_with('[') { + array_from_str(&s) + } else { + IdlType::Defined(s.to_string()) + } + } + Some(inner) => { + let inner_ty = Self::from_str( + inner + .strip_suffix('>') + .ok_or_else(|| anyhow::anyhow!("Invalid option"))?, + )?; + IdlType::Vec(Box::new(inner_ty)) + } + }, + Some(inner) => { + let inner_ty = Self::from_str( + inner + .strip_suffix('>') + .ok_or_else(|| anyhow::anyhow!("Invalid option"))?, + )?; + IdlType::Option(Box::new(inner_ty)) + } + }, + }; + Ok(r) + } +} + +#[cfg(test)] +mod tests { + use crate::idl::types::IdlType; + use std::str::FromStr; + + #[test] + fn multidimensional_array() { + assert_eq!( + IdlType::from_str("[[u8;16];32]").unwrap(), + IdlType::Array(Box::new(IdlType::Array(Box::new(IdlType::U8), 16)), 32) + ); + } + + #[test] + fn array() { + assert_eq!( + IdlType::from_str("[Pubkey;16]").unwrap(), + IdlType::Array(Box::new(IdlType::PublicKey), 16) + ); + } + + #[test] + fn array_with_underscored_length() { + assert_eq!( + IdlType::from_str("[u8;50_000]").unwrap(), + IdlType::Array(Box::new(IdlType::U8), 50000) + ); + } + + #[test] + fn option() { + assert_eq!( + IdlType::from_str("Option").unwrap(), + IdlType::Option(Box::new(IdlType::Bool)) + ) + } + + #[test] + fn vector() { + assert_eq!( + IdlType::from_str("Vec").unwrap(), + IdlType::Vec(Box::new(IdlType::Bool)) + ) + } +} diff --git a/lang/syn/src/idl/pda.rs b/lang/syn/src/idl/parse/pda.rs similarity index 99% rename from lang/syn/src/idl/pda.rs rename to lang/syn/src/idl/parse/pda.rs index 630243e873..d475dab431 100644 --- a/lang/syn/src/idl/pda.rs +++ b/lang/syn/src/idl/parse/pda.rs @@ -1,4 +1,4 @@ -use crate::idl::*; +use crate::idl::types::*; use crate::parser; use crate::parser::context::CrateContext; use crate::ConstraintSeedsGroup; diff --git a/lang/syn/src/idl/relations.rs b/lang/syn/src/idl/parse/relations.rs similarity index 100% rename from lang/syn/src/idl/relations.rs rename to lang/syn/src/idl/parse/relations.rs diff --git a/lang/syn/src/idl/types.rs b/lang/syn/src/idl/types.rs new file mode 100644 index 0000000000..33dd1672ea --- /dev/null +++ b/lang/syn/src/idl/types.rs @@ -0,0 +1,232 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Idl { + pub version: String, + pub name: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub docs: Option>, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub constants: Vec, + pub instructions: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub accounts: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub types: Vec, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub events: Option>, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub errors: Option>, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IdlConst { + pub name: String, + #[serde(rename = "type")] + pub ty: IdlType, + pub value: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IdlState { + #[serde(rename = "struct")] + pub strct: IdlTypeDefinition, + pub methods: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IdlInstruction { + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub docs: Option>, + pub accounts: Vec, + pub args: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub returns: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IdlAccounts { + pub name: String, + pub accounts: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum IdlAccountItem { + IdlAccount(IdlAccount), + IdlAccounts(IdlAccounts), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IdlAccount { + pub name: String, + pub is_mut: bool, + pub is_signer: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub is_optional: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub docs: Option>, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub pda: Option, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub relations: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IdlPda { + pub seeds: Vec, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub program_id: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase", tag = "kind")] +pub enum IdlSeed { + Const(IdlSeedConst), + Arg(IdlSeedArg), + Account(IdlSeedAccount), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IdlSeedAccount { + #[serde(rename = "type")] + pub ty: IdlType, + // account_ty points to the entry in the "accounts" section. + // Some only if the `Account` type is used. + #[serde(skip_serializing_if = "Option::is_none")] + pub account: Option, + pub path: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IdlSeedArg { + #[serde(rename = "type")] + pub ty: IdlType, + pub path: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IdlSeedConst { + #[serde(rename = "type")] + pub ty: IdlType, + pub value: serde_json::Value, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IdlField { + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub docs: Option>, + #[serde(rename = "type")] + pub ty: IdlType, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IdlEvent { + pub name: String, + pub fields: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IdlEventField { + pub name: String, + #[serde(rename = "type")] + pub ty: IdlType, + pub index: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IdlTypeDefinition { + /// - `idl-parse`: always the name of the type + /// - `idl-build`: full path if there is a name conflict, otherwise the name of the type + pub name: String, + /// Documentation comments + #[serde(skip_serializing_if = "Option::is_none")] + pub docs: Option>, + /// Generics, only supported with `idl-build` + #[serde(skip_serializing_if = "Option::is_none")] + pub generics: Option>, + /// Type definition, `struct` or `enum` + #[serde(rename = "type")] + pub ty: IdlTypeDefinitionTy, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase", tag = "kind")] +pub enum IdlTypeDefinitionTy { + Struct { fields: Vec }, + Enum { variants: Vec }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct IdlEnumVariant { + pub name: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub fields: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum EnumFields { + Named(Vec), + Tuple(Vec), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum IdlType { + Bool, + U8, + I8, + U16, + I16, + U32, + I32, + F32, + U64, + I64, + F64, + U128, + I128, + U256, + I256, + Bytes, + String, + PublicKey, + Defined(String), + Option(Box), + Vec(Box), + Array(Box, usize), + GenericLenArray(Box, String), + Generic(String), + DefinedWithTypeArgs { + name: String, + args: Vec, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum IdlDefinedTypeArg { + Generic(String), + Value(String), + Type(IdlType), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct IdlErrorCode { + pub code: u32, + pub name: String, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub msg: Option, +} diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index 7bad199db2..650f1decab 100644 --- a/lang/syn/src/lib.rs +++ b/lang/syn/src/lib.rs @@ -23,7 +23,6 @@ pub mod codegen; pub mod hash; #[cfg(not(feature = "hash"))] pub(crate) mod hash; -#[cfg(feature = "idl")] pub mod idl; pub mod parser; diff --git a/lang/tests/space.rs b/lang/tests/space.rs index 4cf81adceb..74c03321d2 100644 --- a/lang/tests/space.rs +++ b/lang/tests/space.rs @@ -88,6 +88,15 @@ pub struct TestFullPath { pub test_path: inside_mod::Data, } +const MAX_LEN: u8 = 10; + +#[derive(InitSpace)] +pub struct TestConst { + #[max_len(MAX_LEN)] + pub test_string: String, + pub test_array: [u8; MAX_LEN as usize], +} + #[test] fn test_empty_struct() { assert_eq!(TestEmptyAccount::INIT_SPACE, 0); @@ -133,3 +142,8 @@ fn test_matrix_struct() { fn test_full_path() { assert_eq!(TestFullPath::INIT_SPACE, 8 + 9) } + +#[test] +fn test_const() { + assert_eq!(TestConst::INIT_SPACE, (4 + 10) + 10) +} diff --git a/setup_tests.sh b/setup_tests.sh new file mode 100755 index 0000000000..fda85b11e9 --- /dev/null +++ b/setup_tests.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +active_version=$(solana -V | awk '{print $2}') +if [ "$active_version" != "1.16.0" ]; then + solana-install init 1.16.0 +fi + +git submodule update --init --recursive --depth 1 +cd ts/packages/borsh && yarn --frozen-lockfile && yarn build && yarn link --force && cd ../../../ +cd ts/packages/anchor && yarn --frozen-lockfile && yarn build:node && yarn link && cd ../../../ +cd ts/packages/spl-associated-token-account && yarn --frozen-lockfile && yarn build:node && yarn link && cd ../../../ +cd ts/packages/spl-token && yarn --frozen-lockfile && yarn build:node && yarn link && cd ../../../ +cd examples/tutorial && yarn link @coral-xyz/anchor @coral-xyz/borsh && yarn --frozen-lockfile && cd ../../ +cd tests && yarn link @coral-xyz/anchor @coral-xyz/borsh @coral-xyz/spl-associated-token-account @coral-xyz/spl-token && yarn --frozen-lockfile && cd .. +cargo install --path cli anchor-cli --locked --force --debug diff --git a/spl/Cargo.toml b/spl/Cargo.toml index e369848938..a52b45227a 100644 --- a/spl/Cargo.toml +++ b/spl/Cargo.toml @@ -8,17 +8,17 @@ license = "Apache-2.0" description = "CPI clients for SPL programs" [features] -default = ["mint", "token", "token_2022", "associated_token"] -mint = [] -token = ["spl-token"] -token_2022 = ["spl-token-2022"] +default = ["associated_token", "mint", "token", "token_2022"] associated_token = ["spl-associated-token-account"] +dex = ["serum_dex"] +devnet = [] governance = [] +metadata = ["mpl-token-metadata"] +mint = [] shmem = [] stake = ["borsh"] -devnet = [] -metadata = ["mpl-token-metadata"] -dex = ["serum_dex"] +token = ["spl-token"] +token_2022 = ["spl-token-2022"] [dependencies] anchor-lang = { path = "../lang", version = "0.28.0", features = ["derive"] } @@ -26,6 +26,6 @@ borsh = { version = ">=0.9, <0.11", optional = true } mpl-token-metadata = { version = "1.11", optional = true, features = ["no-entrypoint"] } serum_dex = { git = "https://github.com/openbook-dex/program/", rev = "1be91f2", version = "0.4.0", features = ["no-entrypoint"], optional = true } solana-program = ">=1.14, <1.17" -spl-associated-token-account = { version = "1", features = ["no-entrypoint"], optional = true } +spl-associated-token-account = { version = "^1.1", features = ["no-entrypoint"], optional = true } spl-token = { version = "3.5", features = ["no-entrypoint"], optional = true } spl-token-2022 = { version = "0.6", features = ["no-entrypoint"], optional = true } diff --git a/spl/src/metadata.rs b/spl/src/metadata.rs index 9e2020d57e..9172b86dab 100644 --- a/spl/src/metadata.rs +++ b/spl/src/metadata.rs @@ -2,11 +2,13 @@ use anchor_lang::context::CpiContext; use anchor_lang::error::ErrorCode; use anchor_lang::{Accounts, Result, ToAccountInfos}; use mpl_token_metadata::state::{CollectionDetails, DataV2, TokenMetadataAccount}; -use mpl_token_metadata::ID; use solana_program::account_info::AccountInfo; use solana_program::pubkey::Pubkey; use std::ops::Deref; +pub use mpl_token_metadata; +pub use mpl_token_metadata::ID; + pub fn approve_collection_authority<'info>( ctx: CpiContext<'_, '_, '_, 'info, ApproveCollectionAuthority<'info>>, ) -> Result<()> { @@ -817,6 +819,42 @@ impl anchor_lang::Owner for MasterEditionAccount { } } +#[derive(Clone, Debug, PartialEq)] +pub struct TokenRecordAccount(mpl_token_metadata::state::TokenRecord); + +impl TokenRecordAccount { + pub const LEN: usize = mpl_token_metadata::state::TOKEN_RECORD_SIZE; +} +impl anchor_lang::AccountDeserialize for TokenRecordAccount { + fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result { + let tr = Self::try_deserialize_unchecked(buf)?; + if tr.key != mpl_token_metadata::state::TokenRecord::key() { + return Err(ErrorCode::AccountNotInitialized.into()); + } + Ok(tr) + } + + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + let tr = mpl_token_metadata::state::TokenRecord::safe_deserialize(buf)?; + Ok(Self(tr)) + } +} + +impl anchor_lang::AccountSerialize for TokenRecordAccount {} + +impl anchor_lang::Owner for TokenRecordAccount { + fn owner() -> Pubkey { + ID + } +} + +impl Deref for TokenRecordAccount { + type Target = mpl_token_metadata::state::TokenRecord; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Clone)] pub struct Metadata; diff --git a/tests/bench/bench.json b/tests/bench/bench.json index e8b20f483c..1a20dbff26 100644 --- a/tests/bench/bench.json +++ b/tests/bench/bench.json @@ -2,6 +2,9 @@ "0.27.0": { "solanaVersion": "1.14.16", "result": { + "binarySize": { + "bench": 1118736 + }, "computeUnits": { "accountInfo1": 954, "accountInfo2": 1567, @@ -90,12 +93,104 @@ "uncheckedAccount2": 1567, "uncheckedAccount4": 2060, "uncheckedAccount8": 3855 + }, + "stackMemory": { + "account_info1": 248, + "account_info2": 392, + "account_info4": 512, + "account_info8": 896, + "account_empty_init1": 584, + "account_empty_init2": 512, + "account_empty_init4": 704, + "account_empty_init8": 1088, + "account_empty1": 200, + "account_empty2": 344, + "account_empty4": 464, + "account_empty8": 848, + "account_sized_init1": 592, + "account_sized_init2": 544, + "account_sized_init4": 768, + "account_sized_init8": 1216, + "account_sized1": 200, + "account_sized2": 360, + "account_sized4": 528, + "account_sized8": 976, + "account_unsized_init1": 608, + "account_unsized_init2": 608, + "account_unsized_init4": 896, + "account_unsized_init8": 1472, + "account_unsized1": 176, + "account_unsized2": 392, + "account_unsized4": 656, + "account_unsized8": 1232, + "boxed_account_empty_init1": 544, + "boxed_account_empty_init2": 408, + "boxed_account_empty_init4": 424, + "boxed_account_empty_init8": 456, + "boxed_account_empty1": 240, + "boxed_account_empty2": 248, + "boxed_account_empty4": 280, + "boxed_account_empty8": 312, + "boxed_account_sized_init1": 544, + "boxed_account_sized_init2": 408, + "boxed_account_sized_init4": 424, + "boxed_account_sized_init8": 456, + "boxed_account_sized1": 240, + "boxed_account_sized2": 248, + "boxed_account_sized4": 280, + "boxed_account_sized8": 312, + "boxed_account_unsized_init1": 544, + "boxed_account_unsized_init2": 408, + "boxed_account_unsized_init4": 424, + "boxed_account_unsized_init8": 456, + "boxed_account_unsized1": 248, + "boxed_account_unsized2": 248, + "boxed_account_unsized4": 280, + "boxed_account_unsized8": 312, + "boxed_interface_account_mint1": 240, + "boxed_interface_account_mint2": 248, + "boxed_interface_account_mint4": 280, + "boxed_interface_account_mint8": 312, + "boxed_interface_account_token1": 240, + "boxed_interface_account_token2": 248, + "boxed_interface_account_token4": 280, + "boxed_interface_account_token8": 312, + "interface_account_mint1": 208, + "interface_account_mint2": 752, + "interface_account_mint4": 1424, + "interface_account_mint8": 2768, + "interface_account_token1": 264, + "interface_account_token2": 1104, + "interface_account_token4": 2128, + "interface1": 200, + "interface2": 344, + "interface4": 464, + "interface8": 848, + "program1": 200, + "program2": 344, + "program4": 464, + "program8": 848, + "signer1": 248, + "signer2": 392, + "signer4": 512, + "signer8": 896, + "system_account1": 248, + "system_account2": 392, + "system_account4": 512, + "system_account8": 896, + "unchecked_account1": 248, + "unchecked_account2": 392, + "unchecked_account4": 512, + "unchecked_account8": 896 } } }, "0.28.0": { "solanaVersion": "1.16.0", "result": { + "binarySize": { + "bench": 1153736 + }, "computeUnits": { "accountInfo1": 1015, "accountInfo2": 1475, @@ -184,100 +279,281 @@ "uncheckedAccount2": 1475, "uncheckedAccount4": 1965, "uncheckedAccount8": 3841 + }, + "stackMemory": { + "account_info1": 328, + "account_info2": 376, + "account_info4": 560, + "account_info8": 728, + "account_empty_init1": 592, + "account_empty_init2": 560, + "account_empty_init4": 632, + "account_empty_init8": 824, + "account_empty1": 320, + "account_empty2": 368, + "account_empty4": 552, + "account_empty8": 728, + "account_sized_init1": 600, + "account_sized_init2": 552, + "account_sized_init4": 664, + "account_sized_init8": 888, + "account_sized1": 328, + "account_sized2": 392, + "account_sized4": 568, + "account_sized8": 792, + "account_unsized_init1": 624, + "account_unsized_init2": 584, + "account_unsized_init4": 728, + "account_unsized_init8": 1016, + "account_unsized1": 344, + "account_unsized2": 456, + "account_unsized4": 632, + "account_unsized8": 920, + "boxed_account_empty_init1": 552, + "boxed_account_empty_init2": 400, + "boxed_account_empty_init4": 432, + "boxed_account_empty_init8": 496, + "boxed_account_empty1": 320, + "boxed_account_empty2": 320, + "boxed_account_empty4": 320, + "boxed_account_empty8": 336, + "boxed_account_sized_init1": 552, + "boxed_account_sized_init2": 400, + "boxed_account_sized_init4": 432, + "boxed_account_sized_init8": 496, + "boxed_account_sized1": 320, + "boxed_account_sized2": 320, + "boxed_account_sized4": 320, + "boxed_account_sized8": 336, + "boxed_account_unsized_init1": 552, + "boxed_account_unsized_init2": 400, + "boxed_account_unsized_init4": 432, + "boxed_account_unsized_init8": 496, + "boxed_account_unsized1": 320, + "boxed_account_unsized2": 320, + "boxed_account_unsized4": 320, + "boxed_account_unsized8": 336, + "boxed_interface_account_mint1": 320, + "boxed_interface_account_mint2": 320, + "boxed_interface_account_mint4": 320, + "boxed_interface_account_mint8": 336, + "boxed_interface_account_token1": 320, + "boxed_interface_account_token2": 320, + "boxed_interface_account_token4": 320, + "boxed_interface_account_token8": 336, + "interface_account_mint1": 504, + "interface_account_mint2": 680, + "interface_account_mint4": 1016, + "interface_account_mint8": 1688, + "interface_account_token1": 680, + "interface_account_token2": 856, + "interface_account_token4": 1368, + "interface1": 320, + "interface2": 368, + "interface4": 552, + "interface8": 728, + "program1": 320, + "program2": 368, + "program4": 552, + "program8": 728, + "signer1": 328, + "signer2": 376, + "signer4": 560, + "signer8": 728, + "system_account1": 328, + "system_account2": 376, + "system_account4": 560, + "system_account8": 728, + "unchecked_account1": 328, + "unchecked_account2": 376, + "unchecked_account4": 560, + "unchecked_account8": 728 } } }, "unreleased": { "solanaVersion": "1.16.0", "result": { + "binarySize": { + "bench": 1049608 + }, "computeUnits": { - "accountInfo1": 810, - "accountInfo2": 1400, - "accountInfo4": 1855, - "accountInfo8": 3674, - "accountEmptyInit1": 5817, - "accountEmpty1": 980, - "accountEmptyInit2": 10402, - "accountEmpty2": 1754, - "accountEmptyInit4": 19508, - "accountEmpty4": 2540, + "accountInfo1": 584, + "accountInfo2": 824, + "accountInfo4": 1319, + "accountInfo8": 2531, + "accountEmptyInit1": 5521, + "accountEmpty1": 777, + "accountEmptyInit2": 10111, + "accountEmpty2": 1207, + "accountEmptyInit4": 19044, + "accountEmpty4": 2074, "accountEmptyInit8": 37265, - "accountEmpty8": 5016, - "accountSizedInit1": 5924, - "accountSized1": 1027, - "accountSizedInit2": 10680, - "accountSized2": 1873, - "accountSizedInit4": 19970, - "accountSized4": 2762, + "accountEmpty8": 3967, + "accountSizedInit1": 5626, + "accountSized1": 786, + "accountSizedInit2": 10322, + "accountSized2": 1234, + "accountSizedInit4": 19462, + "accountSized4": 2135, "accountSizedInit8": 38122, - "accountSized8": 5353, - "accountUnsizedInit1": 6052, - "accountUnsized1": 1116, - "accountUnsizedInit2": 10929, - "accountUnsized2": 1778, - "accountUnsizedInit4": 20339, - "accountUnsized4": 3136, - "accountUnsizedInit8": 39096, - "accountUnsized8": 5952, - "boxedAccountEmptyInit1": 6034, + "accountSized8": 4104, + "accountUnsizedInit1": 5742, + "accountUnsized1": 821, + "accountUnsizedInit2": 10551, + "accountUnsized2": 1312, + "accountUnsizedInit4": 19927, + "accountUnsized4": 2315, + "accountUnsizedInit8": 38699, + "accountUnsized8": 4456, + "boxedAccountEmptyInit1": 5452, "boxedAccountEmpty1": 866, - "boxedAccountEmptyInit2": 10633, - "boxedAccountEmpty2": 1386, - "boxedAccountEmptyInit4": 19311, - "boxedAccountEmpty4": 2424, + "boxedAccountEmptyInit2": 10051, + "boxedAccountEmpty2": 1377, + "boxedAccountEmptyInit4": 19030, + "boxedAccountEmpty4": 2396, "boxedAccountEmptyInit8": 37136, - "boxedAccountEmpty8": 4659, - "boxedAccountSizedInit1": 6130, + "boxedAccountEmpty8": 4472, + "boxedAccountSizedInit1": 5546, "boxedAccountSized1": 895, - "boxedAccountSizedInit2": 10828, - "boxedAccountSized2": 1448, - "boxedAccountSizedInit4": 19703, - "boxedAccountSized4": 2543, + "boxedAccountSizedInit2": 10242, + "boxedAccountSized2": 1439, + "boxedAccountSizedInit4": 19414, + "boxedAccountSized4": 2515, "boxedAccountSizedInit8": 37919, - "boxedAccountSized8": 4898, - "boxedAccountUnsizedInit1": 6240, - "boxedAccountUnsized1": 953, - "boxedAccountUnsizedInit2": 11048, - "boxedAccountUnsized2": 1570, - "boxedAccountUnsizedInit4": 20138, - "boxedAccountUnsized4": 2768, + "boxedAccountSized8": 4711, + "boxedAccountUnsizedInit1": 5823, + "boxedAccountUnsized1": 950, + "boxedAccountUnsizedInit2": 10621, + "boxedAccountUnsized2": 1549, + "boxedAccountUnsizedInit4": 19825, + "boxedAccountUnsized4": 2737, "boxedAccountUnsizedInit8": 38791, - "boxedAccountUnsized8": 5347, - "boxedInterfaceAccountMint1": 2296, - "boxedInterfaceAccountMint2": 4129, - "boxedInterfaceAccountMint4": 7783, - "boxedInterfaceAccountMint8": 15281, - "boxedInterfaceAccountToken1": 2001, - "boxedInterfaceAccountToken2": 3582, - "boxedInterfaceAccountToken4": 6692, - "boxedInterfaceAccountToken8": 13098, - "interfaceAccountMint1": 2291, - "interfaceAccountMint2": 5030, - "interfaceAccountMint4": 9803, - "interfaceAccountMint8": 18400, - "interfaceAccountToken1": 2018, - "interfaceAccountToken2": 3948, - "interfaceAccountToken4": 7547, - "interface1": 892, - "interface2": 1479, - "interface4": 1900, - "interface8": 3646, - "program1": 887, - "program2": 1467, - "program4": 1878, - "program8": 3598, - "signer1": 822, - "signer2": 1424, - "signer4": 1907, - "signer8": 3770, - "systemAccount1": 876, - "systemAccount2": 1530, - "systemAccount4": 2118, - "systemAccount8": 4195, - "uncheckedAccount1": 809, - "uncheckedAccount2": 1400, - "uncheckedAccount4": 1856, - "uncheckedAccount8": 3674 + "boxedAccountUnsized8": 5207, + "boxedInterfaceAccountMint1": 2137, + "boxedInterfaceAccountMint2": 3849, + "boxedInterfaceAccountMint4": 7215, + "boxedInterfaceAccountMint8": 14044, + "boxedInterfaceAccountToken1": 2066, + "boxedInterfaceAccountToken2": 3706, + "boxedInterfaceAccountToken4": 6932, + "boxedInterfaceAccountToken8": 13477, + "interfaceAccountMint1": 2313, + "interfaceAccountMint2": 4270, + "interfaceAccountMint4": 8185, + "interfaceAccountMint8": 16007, + "interfaceAccountToken1": 2059, + "interfaceAccountToken2": 3958, + "interfaceAccountToken4": 7816, + "interface1": 691, + "interface2": 940, + "interface4": 1450, + "interface8": 2605, + "program1": 685, + "program2": 928, + "program4": 1428, + "program8": 2557, + "signer1": 621, + "signer2": 895, + "signer4": 1455, + "signer8": 2721, + "systemAccount1": 675, + "systemAccount2": 1001, + "systemAccount4": 1666, + "systemAccount8": 3146, + "uncheckedAccount1": 583, + "uncheckedAccount2": 824, + "uncheckedAccount4": 1320, + "uncheckedAccount8": 2531 + }, + "stackMemory": { + "account_info1": 128, + "account_info2": 128, + "account_info4": 128, + "account_info8": 128, + "account_empty_init1": 320, + "account_empty_init2": 400, + "account_empty_init4": 448, + "account_empty_init8": 640, + "account_empty1": 128, + "account_empty2": 128, + "account_empty4": 128, + "account_empty8": 128, + "account_sized_init1": 328, + "account_sized_init2": 416, + "account_sized_init4": 480, + "account_sized_init8": 704, + "account_sized1": 128, + "account_sized2": 128, + "account_sized4": 128, + "account_sized8": 128, + "account_unsized_init1": 344, + "account_unsized_init2": 448, + "account_unsized_init4": 544, + "account_unsized_init8": 832, + "account_unsized1": 128, + "account_unsized2": 128, + "account_unsized4": 128, + "account_unsized8": 128, + "boxed_account_empty_init1": 176, + "boxed_account_empty_init2": 208, + "boxed_account_empty_init4": 288, + "boxed_account_empty_init8": 320, + "boxed_account_empty1": 128, + "boxed_account_empty2": 144, + "boxed_account_empty4": 144, + "boxed_account_empty8": 128, + "boxed_account_sized_init1": 176, + "boxed_account_sized_init2": 208, + "boxed_account_sized_init4": 288, + "boxed_account_sized_init8": 320, + "boxed_account_sized1": 128, + "boxed_account_sized2": 144, + "boxed_account_sized4": 144, + "boxed_account_sized8": 128, + "boxed_account_unsized_init1": 280, + "boxed_account_unsized_init2": 320, + "boxed_account_unsized_init4": 288, + "boxed_account_unsized_init8": 320, + "boxed_account_unsized1": 152, + "boxed_account_unsized2": 144, + "boxed_account_unsized4": 176, + "boxed_account_unsized8": 192, + "boxed_interface_account_mint1": 128, + "boxed_interface_account_mint2": 144, + "boxed_interface_account_mint4": 144, + "boxed_interface_account_mint8": 128, + "boxed_interface_account_token1": 128, + "boxed_interface_account_token2": 144, + "boxed_interface_account_token4": 144, + "boxed_interface_account_token8": 128, + "interface_account_mint1": 128, + "interface_account_mint2": 128, + "interface_account_mint4": 128, + "interface_account_mint8": 128, + "interface_account_token1": 128, + "interface_account_token2": 128, + "interface_account_token4": 128, + "interface1": 128, + "interface2": 128, + "interface4": 128, + "interface8": 128, + "program1": 128, + "program2": 128, + "program4": 128, + "program8": 128, + "signer1": 128, + "signer2": 128, + "signer4": 128, + "signer8": 128, + "system_account1": 128, + "system_account2": 128, + "system_account4": 128, + "system_account8": 128, + "unchecked_account1": 128, + "unchecked_account2": 128, + "unchecked_account4": 128, + "unchecked_account8": 128 } } } diff --git a/tests/bench/scripts/sync-markdown.ts b/tests/bench/scripts/sync-markdown.ts index 781d99839d..2ced9e47e9 100644 --- a/tests/bench/scripts/sync-markdown.ts +++ b/tests/bench/scripts/sync-markdown.ts @@ -1,79 +1,69 @@ /** Sync Markdown files in /bench based on the data from bench.json */ -import { BenchData, Markdown } from "./utils"; +import { BenchData, BenchResult, Markdown } from "./utils"; (async () => { const bench = await BenchData.open(); await BenchData.forEachMarkdown((markdown, fileName) => { - if (fileName === "COMPUTE_UNITS.md") { - const versions = bench.getVersions(); + const resultType = fileName + .toLowerCase() + .replace(".md", "") + .replace(/_\w/g, (match) => match[1].toUpperCase()) as keyof BenchResult; - // On the first version, compare with itself to update it with no changes - versions.unshift(versions[0]); + const versions = bench.getVersions(); - for (const i in versions) { - const currentVersion = versions[i]; - const nextVersion = versions[+i + 1]; + // On the first version, compare with itself to update it with no changes + versions.unshift(versions[0]); - if (currentVersion === "unreleased") { - return; - } + for (const i in versions) { + const currentVersion = versions[i]; + if (currentVersion === "unreleased") return; - const newData = bench.get(nextVersion); - const oldData = bench.get(currentVersion); + const nextVersion = versions[+i + 1]; + const newData = bench.get(nextVersion); + const oldData = bench.get(currentVersion); - // Create table - const table = Markdown.createTable( - "Instruction", - "Compute Units", - "+/-" - ); + // Create table + const table = Markdown.createTable(); - bench.compareComputeUnits( - newData.result.computeUnits, - oldData.result.computeUnits, - ({ ixName, newComputeUnits, oldComputeUnits }) => { - if (newComputeUnits === null) { - // Deleted instruction - return; - } + bench.compare({ + newResult: newData.result[resultType], + oldResult: oldData.result[resultType], + changeCb: ({ name, newValue, oldValue }) => { + if (newValue === null) { + // Deleted key + return; + } - let changeText; - if (oldComputeUnits === null) { - // New instruction - changeText = "N/A"; - } else { - const percentChange = ( - (newComputeUnits / oldComputeUnits - 1) * - 100 - ).toFixed(2); + let changeText: string; + if (oldValue === null) { + // New key + changeText = "N/A"; + } else { + const delta = (newValue - oldValue).toLocaleString(); + const percentChange = ((newValue / oldValue - 1) * 100).toFixed(2); - if (+percentChange > 0) { - changeText = `🔴 **+${percentChange}%**`; - } else { - changeText = `🟢 **${percentChange}%**`; - } + if (+percentChange > 0) { + changeText = `🔴 **+${delta} (${percentChange}%)**`; + } else { + changeText = `🟢 **${delta} (${percentChange.slice(1)}%)**`; } - - table.insert(ixName, newComputeUnits.toString(), changeText); - }, - (ixName, computeUnits) => { - table.insert( - ixName, - computeUnits.toString(), - +i === 0 ? "N/A" : "-" - ); } - ); - // Update version data - markdown.updateVersion({ - version: nextVersion, - solanaVersion: newData.solanaVersion, - table, - }); - } + table.insert(name, newValue.toLocaleString(), changeText); + }, + noChangeCb: ({ name, value }) => { + table.insert(name, value.toLocaleString(), +i === 0 ? "N/A" : "-"); + }, + }); + + // Update version data + markdown.updateVersion({ + version: nextVersion, + solanaVersion: newData.solanaVersion, + table, + }); } }); })(); diff --git a/tests/bench/scripts/sync.ts b/tests/bench/scripts/sync.ts index 62b8aef1d9..970e6dc341 100644 --- a/tests/bench/scripts/sync.ts +++ b/tests/bench/scripts/sync.ts @@ -14,6 +14,7 @@ import { Toml, VersionManager, runAnchorTest, + spawn, } from "./utils"; (async () => { @@ -66,7 +67,6 @@ import { // Run the command to update the current version's results const result = runAnchorTest(); - console.log(result.output.toString()); // Check failure if (result.status !== 0) { @@ -75,4 +75,7 @@ import { return; } } + + // Sync markdown files + spawn("anchor", ["run", "sync-markdown"]); })(); diff --git a/tests/bench/scripts/utils.ts b/tests/bench/scripts/utils.ts index 3b9b80233a..7b1b1e7a69 100644 --- a/tests/bench/scripts/utils.ts +++ b/tests/bench/scripts/utils.ts @@ -15,15 +15,28 @@ type Bench = { */ solanaVersion: Version; /** Benchmark results for a version */ - result: { - /** Benchmark result for compute units consumed */ - computeUnits: ComputeUnits; - }; + result: BenchResult; }; }; +/** Benchmark result per version */ +export type BenchResult = { + /** Benchmark result for program binary size */ + binarySize: BinarySize; + /** Benchmark result for compute units consumed */ + computeUnits: ComputeUnits; + /** Benchmark result for stack memory usage */ + stackMemory: StackMemory; +}; + +/** `program name -> binary size` */ +export type BinarySize = { [programName: string]: number }; + /** `instruction name -> compute units consumed` */ -export type ComputeUnits = { [key: string]: number }; +export type ComputeUnits = { [ixName: string]: number }; + +/** `instruction name -> stack memory used` */ +export type StackMemory = { [ixName: string]: number }; /** * How much of a percentage difference between the current and the previous data @@ -50,7 +63,7 @@ export class BenchData { this.#data = data; } - /** Open the benchmark data file */ + /** Open the benchmark data file. */ static async open() { let bench: Bench; try { @@ -65,86 +78,92 @@ export class BenchData { return new BenchData(bench); } - /** Save the benchmark data file */ + /** Save the benchmark data file. */ async save() { await fs.writeFile(BenchData.#PATH, JSON.stringify(this.#data, null, 2)); } - /** Get the stored results based on version */ + /** Get the stored results based on version. */ get(version: Version) { return this.#data[version]; } - /** Get all versions */ + /** Get all versions. */ getVersions() { return Object.keys(this.#data) as Version[]; } - /** Compare and update compute units changes */ - compareComputeUnits( - newComputeUnitsResult: ComputeUnits, - oldComputeUnitsResult: ComputeUnits, + /** Compare benchmark changes. */ + compare({ + newResult, + oldResult, + changeCb, + noChangeCb, + treshold = 0, + }: { + /** New bench result */ + newResult: BenchResult[K]; + /** Old bench result */ + oldResult: BenchResult[K]; + /** Callback to run when there is a change(considering `threshold`) */ changeCb: (args: { - ixName: string; - newComputeUnits: number | null; - oldComputeUnits: number | null; - }) => void, - noChangeCb?: (ixName: string, computeUnits: number) => void - ) { + name: string; + newValue: number | null; + oldValue: number | null; + }) => void; + /** Callback to run when there is no change(considering `threshold`) */ + noChangeCb?: (args: { name: string; value: number }) => void; + /** Change threshold percentage(maximum allowed difference between results) */ + treshold?: number; + }) { let needsUpdate = false; + const executeChangeCb = (...args: Parameters) => { + changeCb(...args); + needsUpdate = true; + }; - const checkIxs = ( - comparedFrom: ComputeUnits, - comparedTo: ComputeUnits, - cb: (ixName: string, computeUnits: number) => void + const compare = ( + compareFrom: BenchResult[K], + compareTo: BenchResult[K], + cb: (name: string, value: number) => void ) => { - for (const ixName in comparedFrom) { - if (comparedTo[ixName] === undefined) { - cb(ixName, comparedFrom[ixName]); + for (const name in compareFrom) { + if (compareTo[name] === undefined) { + cb(name, compareFrom[name]); } } }; - // New instruction - checkIxs( - newComputeUnitsResult, - oldComputeUnitsResult, - (ixName, computeUnits) => { - console.log(`New instruction '${ixName}'`); - changeCb({ - ixName, - newComputeUnits: computeUnits, - oldComputeUnits: null, - }); - needsUpdate = true; - } - ); + // New key + compare(newResult, oldResult, (name, value) => { + console.log(`New key '${name}'`); + executeChangeCb({ + name, + newValue: value, + oldValue: null, + }); + }); - // Deleted instruction - checkIxs( - oldComputeUnitsResult, - newComputeUnitsResult, - (ixName, computeUnits) => { - console.log(`Deleted instruction '${ixName}'`); - changeCb({ - ixName, - newComputeUnits: null, - oldComputeUnits: computeUnits, - }); - needsUpdate = true; - } - ); + // Deleted key + compare(oldResult, newResult, (name, value) => { + console.log(`Deleted key '${name}'`); + executeChangeCb({ + name, + newValue: null, + oldValue: value, + }); + }); // Compare compute units changes - for (const ixName in newComputeUnitsResult) { - const oldComputeUnits = oldComputeUnitsResult[ixName]; - const newComputeUnits = newComputeUnitsResult[ixName]; + for (const name in newResult) { + const oldValue = oldResult[name]; + const newValue = newResult[name]; - const percentage = THRESHOLD_PERCENTAGE / 100; - const oldMaximumAllowedDelta = oldComputeUnits * percentage; - const newMaximumAllowedDelta = newComputeUnits * percentage; + const percentage = treshold / 100; + const oldMaximumAllowedDelta = oldValue * percentage; + const newMaximumAllowedDelta = newValue * percentage; - const delta = newComputeUnits - oldComputeUnits; + const delta = newValue - oldValue; const absDelta = Math.abs(delta); if ( @@ -155,31 +174,60 @@ export class BenchData { if (process.env.CI) { throw new Error( [ - `Compute units for instruction '${ixName}' has changed more than ${THRESHOLD_PERCENTAGE}% but is not saved.`, + `Key '${name}' has changed more than ${treshold}% but is not saved.`, "Run `anchor test --skip-lint` in tests/bench and commit the changes.", ].join(" ") ); } - console.log( - `Compute units change '${ixName}' (${oldComputeUnits} -> ${newComputeUnits})` - ); + console.log(`'${name}' (${oldValue} -> ${newValue})`); - changeCb({ - ixName, - newComputeUnits, - oldComputeUnits, + executeChangeCb({ + name, + newValue, + oldValue, }); - needsUpdate = true; } else { - noChangeCb?.(ixName, newComputeUnits); + noChangeCb?.({ name, value: newValue }); } } return { needsUpdate }; } - /** Bump benchmark data version to the given version */ + /** Compare and update benchmark changes. */ + async update(result: Partial) { + const resultType = Object.keys(result)[0] as keyof typeof result; + const newResult = result[resultType]!; + + // Compare and update benchmark changes + const version = getVersionFromArgs(); + const oldResult = this.get(version).result[resultType]; + const { needsUpdate } = this.compare({ + newResult, + oldResult, + changeCb: ({ name, newValue }) => { + if (newValue === null) delete oldResult[name]; + else oldResult[name] = newValue; + }, + treshold: THRESHOLD_PERCENTAGE, + }); + + if (needsUpdate) { + console.log("Updating benchmark files..."); + + // Save bench data file + // (needs to happen before running the `sync-markdown` script) + await this.save(); + + // Only update markdown files on `unreleased` version + if (version === "unreleased") { + spawn("anchor", ["run", "sync-markdown"]); + } + } + } + + /** Bump benchmark data version to the given version. */ bumpVersion(newVersion: string) { if (this.#data[newVersion]) { throw new Error(`Version '${newVersion}' already exists!`); @@ -240,23 +288,23 @@ export class Markdown { this.#text = text; } - /** Open the markdown file */ + /** Open the markdown file. */ static async open(path: string) { const text = await fs.readFile(path, { encoding: "utf8" }); return new Markdown(path, text); } - /** Create a markdown table */ + /** Create a markdown table. */ static createTable(...args: string[]) { return new MarkdownTable([args]); } - /** Save the markdown file */ + /** Save the markdown file. */ async save() { await fs.writeFile(this.#path, this.#text); } - /** Change the version's content with the given `solanaVersion` and `table` */ + /** Change the version's content with the given `solanaVersion` and `table`. */ updateVersion(params: { version: Version; solanaVersion: string; @@ -274,17 +322,20 @@ export class Markdown { const tableStartIndex = titleStartIndex + md.slice(titleStartIndex).indexOf("|"); + const tableRowStartIndex = + tableStartIndex + md.slice(tableStartIndex).indexOf("\n"); const tableEndIndex = tableStartIndex + md.slice(tableStartIndex).indexOf("\n\n"); this.#text = md.slice(0, titleContentStartIndex) + `Solana version: ${params.solanaVersion}\n\n` + + md.slice(tableStartIndex, tableRowStartIndex - 1) + params.table.toString() + md.slice(tableEndIndex + 1); } - /** Bump the version to the given version */ + /** Bump the version to the given version. */ bumpVersion(newVersion: string) { newVersion = `[${newVersion}]`; if (this.#text.includes(newVersion)) { @@ -342,12 +393,12 @@ class MarkdownTable { this.insert("-", "-", "-"); } - /** Insert a new row to the markdown table */ + /** Insert a new row to the markdown table. */ insert(...args: string[]) { this.#rows.push(args); } - /** Convert the stored rows to a markdown table */ + /** Convert the stored rows to a markdown table. */ toString() { return this.#rows.reduce( (acc, row) => @@ -370,7 +421,7 @@ export class Toml { this.#text = text; } - /** Open the TOML file */ + /** Open the TOML file. */ static async open(tomlPath: string) { tomlPath = path.join(__dirname, tomlPath); const text = await fs.readFile(tomlPath, { @@ -379,12 +430,12 @@ export class Toml { return new Toml(tomlPath, text); } - /** Save the TOML file */ + /** Save the TOML file. */ async save() { await fs.writeFile(this.#path, this.#text); } - /** Replace the value for the given key */ + /** Replace the value for the given key. */ replaceValue( key: string, cb: (previous: string) => string, @@ -402,7 +453,7 @@ export class LockFile { /** Cargo lock file name */ static #CARGO_LOCK = "Cargo.lock"; - /** Replace the Cargo.lock with the given version's cached lock file */ + /** Replace the Cargo.lock with the given version's cached lock file. */ static async replace(version: Version) { // Remove Cargo.lock try { @@ -416,7 +467,7 @@ export class LockFile { } } - /** Cache the current Cargo.lock in ./locks */ + /** Cache the current Cargo.lock in `./locks`. */ static async cache(version: Version) { try { await fs.rename(this.#CARGO_LOCK, this.#getLockPath(version)); @@ -434,7 +485,7 @@ export class LockFile { } } - /** Get the lock file path from the given version */ + /** Get the lock file path from the given version. */ static #getLockPath(version: Version) { return path.join("locks", `${version}.lock`); } @@ -442,7 +493,7 @@ export class LockFile { /** Utility class to manage versions */ export class VersionManager { - /** Set the active Solana version with `solana-install init` command */ + /** Set the active Solana version with `solana-install init` command. */ static setSolanaVersion(version: Version) { const activeVersion = this.#getSolanaVersion(); if (activeVersion === version) return; @@ -453,7 +504,7 @@ export class VersionManager { }); } - /** Get the active Solana version */ + /** Get the active Solana version. */ static #getSolanaVersion() { // `solana-cli 1.14.16 (src:0fb2ffda; feat:3488713414)\n` const result = execSync("solana --version"); @@ -476,12 +527,12 @@ export const getVersionFromArgs = () => { : (args[anchorVersionArgIndex + 1] as Version); }; -/** Run `anchor test` command */ +/** Run `anchor test` command. */ export const runAnchorTest = () => { return spawn("anchor", ["test", "--skip-lint"]); }; -/** Spawn a blocking process */ +/** Spawn a blocking process. */ export const spawn = ( cmd: string, args: string[], diff --git a/tests/bench/tests/binary-size.ts b/tests/bench/tests/binary-size.ts new file mode 100644 index 0000000000..0e60a56697 --- /dev/null +++ b/tests/bench/tests/binary-size.ts @@ -0,0 +1,19 @@ +import * as fs from "fs/promises"; +import path from "path"; + +import { IDL } from "../target/types/bench"; +import { BenchData, BinarySize } from "../scripts/utils"; + +describe("Binary size", () => { + const binarySize: BinarySize = {}; + + it("Measure binary size", async () => { + const stat = await fs.stat(path.join("target", "deploy", `${IDL.name}.so`)); + binarySize[IDL.name] = stat.size; + }); + + after(async () => { + const bench = await BenchData.open(); + await bench.update({ binarySize }); + }); +}); diff --git a/tests/bench/tests/compute-units.ts b/tests/bench/tests/compute-units.ts index 078a1afe7a..3465bcf1b8 100644 --- a/tests/bench/tests/compute-units.ts +++ b/tests/bench/tests/compute-units.ts @@ -2,14 +2,9 @@ import * as anchor from "@coral-xyz/anchor"; import * as token from "@coral-xyz/spl-token"; import { Bench, IDL } from "../target/types/bench"; -import { - BenchData, - ComputeUnits, - getVersionFromArgs, - spawn, -} from "../scripts/utils"; - -describe(IDL.name, () => { +import { BenchData, ComputeUnits } from "../scripts/utils"; + +describe("Compute units", () => { // Configure the client to use the local cluster anchor.setProvider(anchor.AnchorProvider.env()); @@ -115,8 +110,6 @@ describe(IDL.name, () => { }; before(async () => { - // TODO: Check Solana version - // Create necessary accounts const tokenProgram = token.splTokenProgram({ provider: anchor.AnchorProvider.local(), @@ -229,35 +222,7 @@ describe(IDL.name, () => { }); after(async () => { - // Read the bench data file const bench = await BenchData.open(); - - // Compare and update compute units changes - const version = getVersionFromArgs(); - const oldComputeUnits = bench.get(version).result.computeUnits; - const { needsUpdate } = bench.compareComputeUnits( - computeUnits, - oldComputeUnits, - ({ ixName, newComputeUnits: newValue }) => { - if (newValue === null) { - delete oldComputeUnits[ixName]; - } else { - oldComputeUnits[ixName] = newValue; - } - } - ); - - if (needsUpdate) { - console.log("Updating benchmark files..."); - - // Save bench data file - // (needs to happen before running the `sync-markdown` script) - await bench.save(); - - // Only update markdown files on `unreleased` version - if (version === "unreleased") { - spawn("anchor", ["run", "sync-markdown"]); - } - } + await bench.update({ computeUnits }); }); }); diff --git a/tests/bench/tests/stack-memory.ts b/tests/bench/tests/stack-memory.ts new file mode 100644 index 0000000000..caad67b643 --- /dev/null +++ b/tests/bench/tests/stack-memory.ts @@ -0,0 +1,64 @@ +import path from "path"; +import fs from "fs/promises"; + +import { BenchData, StackMemory, spawn } from "../scripts/utils"; +import { IDL } from "../target/types/bench"; + +describe("Stack memory", () => { + const stackMemory: StackMemory = {}; + + const STACK_CONTENT = [ + "", + `let stack_limit: [u16; 2048] = [1; 2048];`, + `msg!("{}", stack_limit.len());`, + "", + ].join("\n\t\t"); + + it("Measure stack memory usage", async () => { + const libPath = path.join("programs", IDL.name, "src", "lib.rs"); + const lib = await fs.readFile(libPath, "utf8"); + const indices = [...lib.matchAll(/fn\s[\w\d]+\(/g)] + .map((match) => match.index) + .filter(Boolean) as number[]; + + let modifiedLib = lib; + let cumulativeIndex = 0; + + for (const index of indices) { + const curlyIndex = index + lib.slice(index).indexOf("{"); + const nextLineIndex = + curlyIndex + lib.slice(curlyIndex).indexOf("\n") + cumulativeIndex; + modifiedLib = + modifiedLib.slice(0, nextLineIndex) + + STACK_CONTENT + + modifiedLib.slice(nextLineIndex); + + cumulativeIndex += STACK_CONTENT.length; + } + + // Write the modified file + await fs.writeFile(libPath, modifiedLib); + + // Expected error: + // Error: Function _ZN5bench9__private8__global13account_info117h88e5c10f03de9fddE + // Stack offset of 4424 exceeded max offset of 4096 by 328 bytes + const buildResult = spawn("anchor", ["build", "--skip-lint"]); + const output = buildResult.output.toString(); + const matches = output.matchAll( + /global[\d]+([\w\d]+?)17.*by\s(\d+)\sbytes/g + ); + for (const match of matches) { + const ixName = match[1]; + const stackUsage = match[2]; + stackMemory[ixName] = +stackUsage; + } + + // Restore to the original file + await fs.writeFile(libPath, lib); + }); + + after(async () => { + const bench = await BenchData.open(); + await bench.update({ stackMemory }); + }); +}); diff --git a/tests/bench/tsconfig.json b/tests/bench/tsconfig.json index a53432cd4f..d5015c2bb7 100644 --- a/tests/bench/tsconfig.json +++ b/tests/bench/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "types": ["mocha", "node"], - "lib": ["ES6"], + "lib": ["es2020"], "module": "commonjs", "target": "es6", "esModuleInterop": true, diff --git a/tests/idl/Anchor.toml b/tests/idl/Anchor.toml new file mode 100644 index 0000000000..eed2cd3684 --- /dev/null +++ b/tests/idl/Anchor.toml @@ -0,0 +1,20 @@ +[features] +seeds = true + +[programs.localnet] +client_interactions = "C1ient1nteractions1111111111111111111111111" +docs = "Docs111111111111111111111111111111111111111" +external = "Externa1111111111111111111111111111111111111" +generics = "Generics111111111111111111111111111111111111" +idl = "id11111111111111111111111111111111111111111" +idl_build_features = "id1Bui1dFeatures111111111111111111111111111" +relations_derivation = "Re1ationsDerivation111111111111111111111111" +non_existent = { address = "NonExistent11111111111111111111111111111111", idl = "non-existent.json" } +numbers_123 = { address = "Numbers111111111111111111111111111111111111", idl = "idls/relations_build.json" } + +[provider] +cluster = "localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" diff --git a/tests/idl/Cargo.toml b/tests/idl/Cargo.toml new file mode 100644 index 0000000000..ef17a63c0a --- /dev/null +++ b/tests/idl/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +members = [ + "programs/*" +] + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/tests/idl/generate.sh b/tests/idl/generate.sh new file mode 100755 index 0000000000..99c792d2e9 --- /dev/null +++ b/tests/idl/generate.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# `$1` is the directory to generate the IDLs in, defaults to `./idls` +if [ $# = 1 ]; then + dir=$1 +else + dir=$PWD/idls +fi + +cd programs/idl +anchor idl parse --file src/lib.rs -o $dir/parse.json +anchor idl build -o $dir/build.json + +cd ../generics +anchor idl build -o $dir/generics_build.json + +cd ../relations-derivation +anchor idl build -o $dir/relations_build.json \ No newline at end of file diff --git a/tests/idl/idls/build.json b/tests/idl/idls/build.json new file mode 100644 index 0000000000..071bb4f011 --- /dev/null +++ b/tests/idl/idls/build.json @@ -0,0 +1,727 @@ +{ + "version": "0.1.0", + "name": "idl", + "docs": [ + "IDL test program documentation." + ], + "constants": [ + { + "name": "BYTES_STR", + "type": "bytes", + "value": "[116, 101, 115, 116]" + }, + { + "name": "BYTE_STR", + "type": "u8", + "value": "116" + }, + { + "name": "I128", + "type": "i128", + "value": "1000000" + }, + { + "name": "U8", + "type": "u8", + "value": "6" + } + ], + "instructions": [ + { + "name": "initialize", + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true, + "docs": [ + "State account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Sysvar clock" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "zcAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "initializeWithValues", + "docs": [ + "Initializes an account with specified values" + ], + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true, + "docs": [ + "State account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Sysvar clock" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "zcAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "boolField", + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "i8Field", + "type": "i8" + }, + { + "name": "u16Field", + "type": "u16" + }, + { + "name": "i16Field", + "type": "i16" + }, + { + "name": "u32Field", + "type": "u32" + }, + { + "name": "i32Field", + "type": "i32" + }, + { + "name": "f32Field", + "type": "f32" + }, + { + "name": "u64Field", + "type": "u64" + }, + { + "name": "i64Field", + "type": "i64" + }, + { + "name": "f64Field", + "type": "f64" + }, + { + "name": "u128Field", + "type": "u128" + }, + { + "name": "i128Field", + "type": "i128" + }, + { + "name": "bytesField", + "type": "bytes" + }, + { + "name": "stringField", + "type": "string" + }, + { + "name": "pubkeyField", + "type": "publicKey" + }, + { + "name": "vecField", + "type": { + "vec": "u64" + } + }, + { + "name": "vecStructField", + "type": { + "vec": { + "defined": "FooStruct" + } + } + }, + { + "name": "optionField", + "type": { + "option": "bool" + } + }, + { + "name": "optionStructField", + "type": { + "option": { + "defined": "FooStruct" + } + } + }, + { + "name": "structField", + "type": { + "defined": "FooStruct" + } + }, + { + "name": "arrayField", + "type": { + "array": [ + "bool", + 3 + ] + } + }, + { + "name": "enumField1", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField2", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField3", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField4", + "type": { + "defined": "FooEnum" + } + } + ] + }, + { + "name": "initializeWithValues2", + "docs": [ + "a separate instruction due to initialize_with_values having too many arguments", + "https://github.com/solana-labs/solana/issues/23978" + ], + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "vecOfOption", + "type": { + "vec": { + "option": "u64" + } + } + }, + { + "name": "boxField", + "type": "bool" + } + ], + "returns": { + "defined": "SomeRetStruct" + } + }, + { + "name": "causeError", + "accounts": [], + "args": [] + } + ], + "accounts": [ + { + "name": "SomeZcAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field", + "type": { + "defined": "ZcStruct" + } + } + ] + } + }, + { + "name": "State", + "docs": [ + "An account containing various fields" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "boolField", + "docs": [ + "A boolean field" + ], + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "i8Field", + "type": "i8" + }, + { + "name": "u16Field", + "type": "u16" + }, + { + "name": "i16Field", + "type": "i16" + }, + { + "name": "u32Field", + "type": "u32" + }, + { + "name": "i32Field", + "type": "i32" + }, + { + "name": "f32Field", + "type": "f32" + }, + { + "name": "u64Field", + "type": "u64" + }, + { + "name": "i64Field", + "type": "i64" + }, + { + "name": "f64Field", + "type": "f64" + }, + { + "name": "u128Field", + "type": "u128" + }, + { + "name": "i128Field", + "type": "i128" + }, + { + "name": "bytesField", + "type": "bytes" + }, + { + "name": "stringField", + "type": "string" + }, + { + "name": "pubkeyField", + "type": "publicKey" + }, + { + "name": "vecField", + "type": { + "vec": "u64" + } + }, + { + "name": "vecStructField", + "type": { + "vec": { + "defined": "FooStruct" + } + } + }, + { + "name": "optionField", + "type": { + "option": "bool" + } + }, + { + "name": "optionStructField", + "type": { + "option": { + "defined": "FooStruct" + } + } + }, + { + "name": "structField", + "type": { + "defined": "FooStruct" + } + }, + { + "name": "arrayField", + "type": { + "array": [ + "bool", + 3 + ] + } + }, + { + "name": "enumField1", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField2", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField3", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField4", + "type": { + "defined": "FooEnum" + } + } + ] + } + }, + { + "name": "State2", + "type": { + "kind": "struct", + "fields": [ + { + "name": "vecOfOption", + "type": { + "vec": { + "option": "u64" + } + } + }, + { + "name": "boxField", + "type": "bool" + } + ] + } + } + ], + "types": [ + { + "name": "external::Baz", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u8" + } + ] + } + }, + { + "name": "BarStruct", + "docs": [ + "Bar struct type" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "docs": [ + "Some field" + ], + "type": "bool" + }, + { + "name": "otherField", + "type": "u8" + } + ] + } + }, + { + "name": "FooEnum", + "docs": [ + "Enum type" + ], + "type": { + "kind": "enum", + "variants": [ + { + "name": "Unnamed", + "fields": [ + "bool", + "u8", + { + "defined": "BarStruct" + } + ] + }, + { + "name": "UnnamedSingle", + "fields": [ + { + "defined": "BarStruct" + } + ] + }, + { + "name": "Named", + "fields": [ + { + "name": "boolField", + "docs": [ + "A bool field inside a struct tuple kind" + ], + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "nested", + "type": { + "defined": "BarStruct" + } + } + ] + }, + { + "name": "Struct", + "fields": [ + { + "defined": "BarStruct" + } + ] + }, + { + "name": "OptionStruct", + "fields": [ + { + "option": { + "defined": "BarStruct" + } + } + ] + }, + { + "name": "VecStruct", + "fields": [ + { + "vec": { + "defined": "BarStruct" + } + } + ] + }, + { + "name": "NoFields" + } + ] + } + }, + { + "name": "FooStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field1", + "type": "u8" + }, + { + "name": "field2", + "type": "u16" + }, + { + "name": "nested", + "type": { + "defined": "BarStruct" + } + }, + { + "name": "vecNested", + "type": { + "vec": { + "defined": "BarStruct" + } + } + }, + { + "name": "optionNested", + "type": { + "option": { + "defined": "BarStruct" + } + } + }, + { + "name": "enumField", + "type": { + "defined": "FooEnum" + } + } + ] + } + }, + { + "name": "SomeRetStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u8" + } + ] + } + }, + { + "name": "ZcStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u16" + } + ] + } + }, + { + "name": "idl::some_other_module::Baz", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someU8", + "type": "u8" + } + ] + } + } + ], + "events": [ + { + "name": "SomeEvent", + "fields": [ + { + "name": "boolField", + "type": "bool", + "index": false + }, + { + "name": "externalBaz", + "type": { + "defined": "external::Baz" + }, + "index": false + }, + { + "name": "otherModuleBaz", + "type": { + "defined": "idl::some_other_module::Baz" + }, + "index": false + } + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "SomeError", + "msg": "Example error." + }, + { + "code": 6001, + "name": "OtherError", + "msg": "Another error." + }, + { + "code": 6002, + "name": "ErrorWithoutMsg" + } + ] +} \ No newline at end of file diff --git a/tests/idl/idls/generics_build.json b/tests/idl/idls/generics_build.json new file mode 100644 index 0000000000..a3fda000c3 --- /dev/null +++ b/tests/idl/idls/generics_build.json @@ -0,0 +1,418 @@ +{ + "version": "0.1.0", + "name": "generics", + "instructions": [ + { + "name": "generic", + "accounts": [ + { + "name": "genericAcc", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "genericField", + "type": { + "definedWithTypeArgs": { + "name": "GenericType", + "args": [ + { + "type": "u32" + }, + { + "type": "u64" + }, + { + "value": "10" + } + ] + } + } + } + ] + } + ], + "accounts": [ + { + "name": "GenericAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "data", + "type": { + "definedWithTypeArgs": { + "name": "GenericType", + "args": [ + { + "type": "u32" + }, + { + "type": "u64" + }, + { + "value": "10" + } + ] + } + } + } + ] + } + } + ], + "types": [ + { + "name": "Baz", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u8" + } + ] + } + }, + { + "name": "GenericEnum", + "generics": [ + "T", + "U", + "N" + ], + "type": { + "kind": "enum", + "variants": [ + { + "name": "Unnamed", + "fields": [ + { + "generic": "T" + }, + { + "generic": "U" + } + ] + }, + { + "name": "Named", + "fields": [ + { + "name": "gen1", + "type": { + "generic": "T" + } + }, + { + "name": "gen2", + "type": { + "generic": "U" + } + } + ] + }, + { + "name": "Struct", + "fields": [ + { + "definedWithTypeArgs": { + "name": "GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "generic": "U" + } + } + ] + } + } + ] + }, + { + "name": "Arr", + "fields": [ + { + "genericLenArray": [ + { + "generic": "T" + }, + "N" + ] + } + ] + } + ] + } + }, + { + "name": "GenericNested", + "generics": [ + "V", + "Z" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "gen1", + "type": { + "generic": "V" + } + }, + { + "name": "gen2", + "type": { + "generic": "Z" + } + } + ] + } + }, + { + "name": "GenericType", + "generics": [ + "T", + "U", + "N" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "gen1", + "type": { + "generic": "T" + } + }, + { + "name": "gen2", + "type": { + "generic": "U" + } + }, + { + "name": "gen3", + "type": { + "definedWithTypeArgs": { + "name": "GenericNested", + "args": [ + { + "type": "u32" + }, + { + "type": { + "generic": "U" + } + } + ] + } + } + }, + { + "name": "gen4", + "type": { + "definedWithTypeArgs": { + "name": "GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "defined": "Baz" + } + } + ] + } + } + }, + { + "name": "gen5", + "type": { + "definedWithTypeArgs": { + "name": "GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "generic": "U" + } + } + ] + } + } + }, + { + "name": "gen6", + "type": { + "definedWithTypeArgs": { + "name": "GenericNested", + "args": [ + { + "type": "u32" + }, + { + "type": "u64" + } + ] + } + } + }, + { + "name": "gen7", + "type": { + "definedWithTypeArgs": { + "name": "GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "definedWithTypeArgs": { + "name": "GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "generic": "U" + } + } + ] + } + } + } + ] + } + } + }, + { + "name": "arr", + "type": { + "genericLenArray": [ + "u8", + "N" + ] + } + }, + { + "name": "warr", + "type": { + "definedWithTypeArgs": { + "name": "WrappedU8Array", + "args": [ + { + "type": { + "generic": "N" + } + } + ] + } + } + }, + { + "name": "warrval", + "type": { + "definedWithTypeArgs": { + "name": "WrappedU8Array", + "args": [ + { + "value": "10" + } + ] + } + } + }, + { + "name": "enm1", + "type": { + "definedWithTypeArgs": { + "name": "GenericEnum", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "generic": "U" + } + }, + { + "type": { + "generic": "N" + } + } + ] + } + } + }, + { + "name": "enm2", + "type": { + "definedWithTypeArgs": { + "name": "GenericEnum", + "args": [ + { + "type": { + "definedWithTypeArgs": { + "name": "GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": "u64" + } + ] + } + } + }, + { + "type": "u32" + }, + { + "value": "30" + } + ] + } + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/tests/idl/idls/parse.json b/tests/idl/idls/parse.json new file mode 100644 index 0000000000..8ba3f74c26 --- /dev/null +++ b/tests/idl/idls/parse.json @@ -0,0 +1,715 @@ +{ + "version": "0.1.0", + "name": "idl", + "docs": [ + "IDL test program documentation." + ], + "constants": [ + { + "name": "U8", + "type": "u8", + "value": "6" + }, + { + "name": "I128", + "type": "i128", + "value": "1_000_000" + }, + { + "name": "BYTE_STR", + "type": "u8", + "value": "116" + }, + { + "name": "BYTES_STR", + "type": "bytes", + "value": "[116, 101, 115, 116]" + } + ], + "instructions": [ + { + "name": "initialize", + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true, + "docs": [ + "State account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Sysvar clock" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "zcAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "initializeWithValues", + "docs": [ + "Initializes an account with specified values" + ], + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true, + "docs": [ + "State account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Sysvar clock" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "zcAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "boolField", + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "i8Field", + "type": "i8" + }, + { + "name": "u16Field", + "type": "u16" + }, + { + "name": "i16Field", + "type": "i16" + }, + { + "name": "u32Field", + "type": "u32" + }, + { + "name": "i32Field", + "type": "i32" + }, + { + "name": "f32Field", + "type": "f32" + }, + { + "name": "u64Field", + "type": "u64" + }, + { + "name": "i64Field", + "type": "i64" + }, + { + "name": "f64Field", + "type": "f64" + }, + { + "name": "u128Field", + "type": "u128" + }, + { + "name": "i128Field", + "type": "i128" + }, + { + "name": "bytesField", + "type": "bytes" + }, + { + "name": "stringField", + "type": "string" + }, + { + "name": "pubkeyField", + "type": "publicKey" + }, + { + "name": "vecField", + "type": { + "vec": "u64" + } + }, + { + "name": "vecStructField", + "type": { + "vec": { + "defined": "FooStruct" + } + } + }, + { + "name": "optionField", + "type": { + "option": "bool" + } + }, + { + "name": "optionStructField", + "type": { + "option": { + "defined": "FooStruct" + } + } + }, + { + "name": "structField", + "type": { + "defined": "FooStruct" + } + }, + { + "name": "arrayField", + "type": { + "array": [ + "bool", + 3 + ] + } + }, + { + "name": "enumField1", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField2", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField3", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField4", + "type": { + "defined": "FooEnum" + } + } + ] + }, + { + "name": "initializeWithValues2", + "docs": [ + "a separate instruction due to initialize_with_values having too many arguments", + "https://github.com/solana-labs/solana/issues/23978" + ], + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "vecOfOption", + "type": { + "vec": { + "option": "u64" + } + } + }, + { + "name": "boxField", + "type": "bool" + } + ], + "returns": { + "defined": "SomeRetStruct" + } + }, + { + "name": "causeError", + "accounts": [], + "args": [] + } + ], + "accounts": [ + { + "name": "State", + "docs": [ + "An account containing various fields" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "boolField", + "docs": [ + "A boolean field" + ], + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "i8Field", + "type": "i8" + }, + { + "name": "u16Field", + "type": "u16" + }, + { + "name": "i16Field", + "type": "i16" + }, + { + "name": "u32Field", + "type": "u32" + }, + { + "name": "i32Field", + "type": "i32" + }, + { + "name": "f32Field", + "type": "f32" + }, + { + "name": "u64Field", + "type": "u64" + }, + { + "name": "i64Field", + "type": "i64" + }, + { + "name": "f64Field", + "type": "f64" + }, + { + "name": "u128Field", + "type": "u128" + }, + { + "name": "i128Field", + "type": "i128" + }, + { + "name": "bytesField", + "type": "bytes" + }, + { + "name": "stringField", + "type": "string" + }, + { + "name": "pubkeyField", + "type": "publicKey" + }, + { + "name": "vecField", + "type": { + "vec": "u64" + } + }, + { + "name": "vecStructField", + "type": { + "vec": { + "defined": "FooStruct" + } + } + }, + { + "name": "optionField", + "type": { + "option": "bool" + } + }, + { + "name": "optionStructField", + "type": { + "option": { + "defined": "FooStruct" + } + } + }, + { + "name": "structField", + "type": { + "defined": "FooStruct" + } + }, + { + "name": "arrayField", + "type": { + "array": [ + "bool", + 3 + ] + } + }, + { + "name": "enumField1", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField2", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField3", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField4", + "type": { + "defined": "FooEnum" + } + } + ] + } + }, + { + "name": "State2", + "type": { + "kind": "struct", + "fields": [ + { + "name": "vecOfOption", + "type": { + "vec": { + "option": "u64" + } + } + }, + { + "name": "boxField", + "type": "bool" + } + ] + } + }, + { + "name": "SomeZcAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field", + "type": { + "defined": "ZcStruct" + } + } + ] + } + } + ], + "types": [ + { + "name": "Baz", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someU8", + "type": "u8" + } + ] + } + }, + { + "name": "BarStruct", + "docs": [ + "Bar struct type" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "docs": [ + "Some field" + ], + "type": "bool" + }, + { + "name": "otherField", + "type": "u8" + } + ] + } + }, + { + "name": "FooStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field1", + "type": "u8" + }, + { + "name": "field2", + "type": "u16" + }, + { + "name": "nested", + "type": { + "defined": "BarStruct" + } + }, + { + "name": "vecNested", + "type": { + "vec": { + "defined": "BarStruct" + } + } + }, + { + "name": "optionNested", + "type": { + "option": { + "defined": "BarStruct" + } + } + }, + { + "name": "enumField", + "type": { + "defined": "FooEnum" + } + } + ] + } + }, + { + "name": "ZcStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u16" + } + ] + } + }, + { + "name": "SomeRetStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u8" + } + ] + } + }, + { + "name": "FooEnum", + "docs": [ + "Enum type" + ], + "type": { + "kind": "enum", + "variants": [ + { + "name": "Unnamed", + "fields": [ + "bool", + "u8", + { + "defined": "BarStruct" + } + ] + }, + { + "name": "UnnamedSingle", + "fields": [ + { + "defined": "BarStruct" + } + ] + }, + { + "name": "Named", + "fields": [ + { + "name": "bool_field", + "docs": [ + "A bool field inside a struct tuple kind" + ], + "type": "bool" + }, + { + "name": "u8_field", + "type": "u8" + }, + { + "name": "nested", + "type": { + "defined": "BarStruct" + } + } + ] + }, + { + "name": "Struct", + "fields": [ + { + "defined": "BarStruct" + } + ] + }, + { + "name": "OptionStruct", + "fields": [ + { + "option": { + "defined": "BarStruct" + } + } + ] + }, + { + "name": "VecStruct", + "fields": [ + { + "vec": { + "defined": "BarStruct" + } + } + ] + }, + { + "name": "NoFields" + } + ] + } + } + ], + "events": [ + { + "name": "SomeEvent", + "fields": [ + { + "name": "boolField", + "type": "bool", + "index": false + }, + { + "name": "externalBaz", + "type": { + "defined": "external::Baz" + }, + "index": false + }, + { + "name": "otherModuleBaz", + "type": { + "defined": "some_other_module::Baz" + }, + "index": false + } + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "SomeError", + "msg": "Example error." + }, + { + "code": 6001, + "name": "OtherError", + "msg": "Another error." + }, + { + "code": 6002, + "name": "ErrorWithoutMsg" + } + ] +} \ No newline at end of file diff --git a/tests/idl/idls/relations_build.json b/tests/idl/idls/relations_build.json new file mode 100644 index 0000000000..bf70c7fc1d --- /dev/null +++ b/tests/idl/idls/relations_build.json @@ -0,0 +1,82 @@ +{ + "version": "0.1.0", + "name": "relations_derivation", + "instructions": [ + { + "name": "initBase", + "accounts": [ + { + "name": "myAccount", + "isMut": true, + "isSigner": true + }, + { + "name": "account", + "isMut": true, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "testRelation", + "accounts": [ + { + "name": "myAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": false, + "isSigner": false, + "relations": [ + "my_account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "myAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": false, + "isSigner": false, + "relations": [ + "my_account" + ] + } + ] + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "MyAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "myAccount", + "type": "publicKey" + }, + { + "name": "bump", + "type": "u8" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/tests/idl/package.json b/tests/idl/package.json new file mode 100644 index 0000000000..f473ac9965 --- /dev/null +++ b/tests/idl/package.json @@ -0,0 +1,16 @@ +{ + "name": "idl", + "version": "0.28.0", + "license": "(MIT OR Apache-2.0)", + "homepage": "https://github.com/coral-xyz/anchor#readme", + "bugs": { + "url": "https://github.com/coral-xyz/anchor/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/coral-xyz/anchor.git" + }, + "engines": { + "node": ">=17" + } +} diff --git a/tests/misc/programs/idl_doc/Cargo.toml b/tests/idl/programs/client-interactions/Cargo.toml similarity index 68% rename from tests/misc/programs/idl_doc/Cargo.toml rename to tests/idl/programs/client-interactions/Cargo.toml index ceeb57e205..e96129ddf7 100644 --- a/tests/misc/programs/idl_doc/Cargo.toml +++ b/tests/idl/programs/client-interactions/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "idl_doc" +name = "client-interactions" version = "0.1.0" description = "Created with Anchor" rust-version = "1.60" @@ -7,7 +7,7 @@ edition = "2021" [lib] crate-type = ["cdylib", "lib"] -name = "idl_doc" +name = "client_interactions" [features] no-entrypoint = [] @@ -16,4 +16,4 @@ cpi = ["no-entrypoint"] default = [] [dependencies] -anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] } +anchor-lang = { path = "../../../../lang" } diff --git a/tests/misc/programs/idl_doc/Xargo.toml b/tests/idl/programs/client-interactions/Xargo.toml similarity index 100% rename from tests/misc/programs/idl_doc/Xargo.toml rename to tests/idl/programs/client-interactions/Xargo.toml diff --git a/tests/idl/programs/client-interactions/src/lib.rs b/tests/idl/programs/client-interactions/src/lib.rs new file mode 100644 index 0000000000..dde3a08c5f --- /dev/null +++ b/tests/idl/programs/client-interactions/src/lib.rs @@ -0,0 +1,95 @@ +use anchor_lang::prelude::*; + +declare_id!("C1ient1nteractions1111111111111111111111111"); + +#[program] +pub mod client_interactions { + use super::*; + + pub fn int(ctx: Context, i8: i8, i16: i16, i32: i32, i64: i64, i128: i128) -> Result<()> { + ctx.accounts.account.i8 = i8; + ctx.accounts.account.i16 = i16; + ctx.accounts.account.i32 = i32; + ctx.accounts.account.i64 = i64; + ctx.accounts.account.i128 = i128; + Ok(()) + } + + pub fn uint( + ctx: Context, + u8: u8, + u16: u16, + u32: u32, + u64: u64, + u128: u128, + ) -> Result<()> { + ctx.accounts.account.u8 = u8; + ctx.accounts.account.u16 = u16; + ctx.accounts.account.u32 = u32; + ctx.accounts.account.u64 = u64; + ctx.accounts.account.u128 = u128; + Ok(()) + } + + pub fn enm(ctx: Context, enum_arg: MyEnum) -> Result<()> { + ctx.accounts.account.enum_field = enum_arg; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct Int<'info> { + #[account(zero)] + pub account: Account<'info, IntAccount>, +} + +#[account] +pub struct IntAccount { + pub i8: i8, + pub i16: i16, + pub i32: i32, + pub i64: i64, + pub i128: i128, +} + +#[derive(Accounts)] +pub struct UnsignedInt<'info> { + #[account(zero)] + pub account: Account<'info, UnsignedIntAccount>, +} + +#[account] +pub struct UnsignedIntAccount { + pub u8: u8, + pub u16: u16, + pub u32: u32, + pub u64: u64, + pub u128: u128, +} + +#[derive(Accounts)] +pub struct Enum<'info> { + #[account(zero)] + pub account: Account<'info, EnumAccount>, +} + +#[account] +pub struct EnumAccount { + pub enum_field: MyEnum, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub enum MyEnum { + Unit, + Named { x: u64, y: u64 }, + Unnamed(u8, u8, u16, u16), + UnnamedStruct(MyStruct), +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Eq, PartialEq)] +pub struct MyStruct { + pub u8: u8, + pub u16: u16, + pub u32: u32, + pub u64: u64, +} diff --git a/tests/idl/programs/docs/Cargo.toml b/tests/idl/programs/docs/Cargo.toml new file mode 100644 index 0000000000..cfff4e5bfc --- /dev/null +++ b/tests/idl/programs/docs/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "docs" +version = "0.1.0" +description = "Created with Anchor" +rust-version = "1.60" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "docs" + +[features] +no-entrypoint = [] +no-idl = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } diff --git a/tests/idl/programs/docs/Xargo.toml b/tests/idl/programs/docs/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/tests/idl/programs/docs/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/tests/misc/programs/idl_doc/src/lib.rs b/tests/idl/programs/docs/src/lib.rs similarity index 77% rename from tests/misc/programs/idl_doc/src/lib.rs rename to tests/idl/programs/docs/src/lib.rs index 90e2315201..7a203abaf6 100644 --- a/tests/misc/programs/idl_doc/src/lib.rs +++ b/tests/idl/programs/docs/src/lib.rs @@ -2,18 +2,15 @@ use anchor_lang::prelude::*; - -declare_id!("BqmKjZGVa8fqyWuojJzG16zaKSV1GjAisZToNuvEaz6m"); +declare_id!("Docs111111111111111111111111111111111111111"); /// This is a doc comment for the program #[program] -pub mod idl_doc { +pub mod docs { use super::*; /// This instruction doc should appear in the IDL - pub fn test_idl_doc_parse( - _ctx: Context, - ) -> Result<()> { + pub fn test_idl_doc_parse(_ctx: Context) -> Result<()> { Ok(()) } } @@ -25,7 +22,6 @@ pub struct DataWithDoc { pub data: u16, } - #[derive(Accounts)] pub struct TestIdlDocParse<'info> { /// This account doc comment should appear in the IDL diff --git a/tests/idl/programs/external/Cargo.toml b/tests/idl/programs/external/Cargo.toml new file mode 100644 index 0000000000..e551d008d3 --- /dev/null +++ b/tests/idl/programs/external/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "external" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "external" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +idl-build = ["anchor-lang/idl-build"] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } +anchor-spl = { path = "../../../../spl" } diff --git a/tests/idl/programs/external/Xargo.toml b/tests/idl/programs/external/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/idl/programs/external/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/idl/programs/external/src/lib.rs b/tests/idl/programs/external/src/lib.rs new file mode 100644 index 0000000000..93cefe9414 --- /dev/null +++ b/tests/idl/programs/external/src/lib.rs @@ -0,0 +1,20 @@ +use anchor_lang::prelude::*; + +declare_id!("Externa1111111111111111111111111111111111111"); + +#[program] +pub mod external { + use super::*; + + pub fn initialize(_ctx: Context, _baz: Baz) -> Result<()> { + Ok(()) + } +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct Baz { + some_field: u8, +} + +#[derive(Accounts)] +pub struct Initialize {} diff --git a/tests/idl/programs/generics/Cargo.toml b/tests/idl/programs/generics/Cargo.toml new file mode 100644 index 0000000000..6e6d13c25f --- /dev/null +++ b/tests/idl/programs/generics/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "generics" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "generics" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +idl-build = ["anchor-lang/idl-build", "external/idl-build"] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } +anchor-spl = { path = "../../../../spl" } +external = { path = "../external", features = ["no-entrypoint"] } diff --git a/tests/idl/programs/generics/Xargo.toml b/tests/idl/programs/generics/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/idl/programs/generics/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/idl/programs/generics/src/lib.rs b/tests/idl/programs/generics/src/lib.rs new file mode 100644 index 0000000000..40ca903bba --- /dev/null +++ b/tests/idl/programs/generics/src/lib.rs @@ -0,0 +1,75 @@ +use anchor_lang::prelude::*; + +declare_id!("Generics111111111111111111111111111111111111"); + +#[program] +pub mod generics { + use super::*; + + pub fn generic( + ctx: Context, + generic_field: GenericType, + ) -> Result<()> { + ctx.accounts.generic_acc.data = generic_field; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct GenericCtx<'info> { + generic_acc: Account<'info, GenericAccount>, + + #[account(mut)] + payer: Signer<'info>, + system_program: Program<'info, System>, +} + +#[account] +pub struct GenericAccount { + pub data: GenericType, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct GenericType +where + T: AnchorSerialize + AnchorDeserialize, + U: AnchorSerialize + AnchorDeserialize, +{ + pub gen1: T, + pub gen2: U, + pub gen3: GenericNested, + pub gen4: GenericNested, + pub gen5: GenericNested, + pub gen6: GenericNested, + pub gen7: GenericNested>, + pub arr: [u8; N], + pub warr: WrappedU8Array, + pub warrval: WrappedU8Array<10>, + pub enm1: GenericEnum, + pub enm2: GenericEnum, u32, 30>, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Default)] +pub struct GenericNested +where + V: AnchorSerialize + AnchorDeserialize, + Z: AnchorSerialize + AnchorDeserialize, +{ + pub gen1: V, + pub gen2: Z, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct WrappedU8Array(u8); + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub enum GenericEnum +where + T: AnchorSerialize + AnchorDeserialize, + U: AnchorSerialize + AnchorDeserialize, +{ + Unnamed(T, U), + Named { gen1: T, gen2: U }, + Struct(GenericNested), + Arr([T; N]), +} diff --git a/tests/idl/programs/idl-build-features/Cargo.toml b/tests/idl/programs/idl-build-features/Cargo.toml new file mode 100644 index 0000000000..a8577df4cd --- /dev/null +++ b/tests/idl/programs/idl-build-features/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "idl-build-features" +version = "0.1.0" +description = "Created with Anchor" +rust-version = "1.60" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "idl_build_features" + +[features] +no-entrypoint = [] +no-idl = [] +cpi = ["no-entrypoint"] +idl-build = ["anchor-lang/idl-build"] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } diff --git a/tests/idl/programs/idl-build-features/Xargo.toml b/tests/idl/programs/idl-build-features/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/tests/idl/programs/idl-build-features/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/tests/idl/programs/idl-build-features/src/lib.rs b/tests/idl/programs/idl-build-features/src/lib.rs new file mode 100644 index 0000000000..939df83eb8 --- /dev/null +++ b/tests/idl/programs/idl-build-features/src/lib.rs @@ -0,0 +1,47 @@ +use anchor_lang::prelude::*; + +declare_id!("id1Bui1dFeatures111111111111111111111111111"); + +#[program] +pub mod idl_build_features { + use super::*; + + pub fn full_path( + ctx: Context, + my_struct: MyStruct, + some_module_my_struct: some_module::MyStruct, + ) -> Result<()> { + ctx.accounts.account.my_struct = my_struct; + ctx.accounts.account.some_module_my_struct = some_module_my_struct; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct FullPath<'info> { + #[account(zero)] + pub account: Account<'info, FullPathAccount>, +} + +#[account] +pub struct FullPathAccount { + pub my_struct: MyStruct, + pub some_module_my_struct: some_module::MyStruct, +} + +mod some_module { + use super::*; + + #[derive(AnchorSerialize, AnchorDeserialize, Clone)] + pub struct MyStruct { + pub data: u8, + } +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct MyStruct { + pub u8: u8, + pub u16: u16, + pub u32: u32, + pub u64: u64, +} diff --git a/tests/idl/programs/idl/Cargo.toml b/tests/idl/programs/idl/Cargo.toml new file mode 100644 index 0000000000..58a7400cd9 --- /dev/null +++ b/tests/idl/programs/idl/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "idl" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "idl" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +idl-build = ["anchor-lang/idl-build", "external/idl-build"] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } +anchor-spl = { path = "../../../../spl" } +bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"]} +external = { path = "../external", features = ["no-entrypoint"] } diff --git a/tests/idl/programs/idl/Xargo.toml b/tests/idl/programs/idl/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/idl/programs/idl/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/idl/programs/idl/src/lib.rs b/tests/idl/programs/idl/src/lib.rs new file mode 100644 index 0000000000..9f3e5a1707 --- /dev/null +++ b/tests/idl/programs/idl/src/lib.rs @@ -0,0 +1,337 @@ +use anchor_lang::prelude::*; +use std::str::FromStr; + +declare_id!("id11111111111111111111111111111111111111111"); + +#[constant] +pub const U8: u8 = 6; + +#[constant] +pub const I128: i128 = 1_000_000; + +#[constant] +pub const BYTE_STR: u8 = b't'; + +#[constant] +pub const BYTES_STR: &[u8] = b"test"; + +pub const NO_IDL: u16 = 55; + +/// IDL test program documentation. +#[program] +pub mod idl { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + ctx.accounts.state.set_inner(State::default()); + Ok(()) + } + + /// Initializes an account with specified values + pub fn initialize_with_values( + ctx: Context, + bool_field: bool, + u8_field: u8, + i8_field: i8, + u16_field: u16, + i16_field: i16, + u32_field: u32, + i32_field: i32, + f32_field: f32, + u64_field: u64, + i64_field: i64, + f64_field: f64, + u128_field: u128, + i128_field: i128, + bytes_field: Vec, + string_field: String, + pubkey_field: Pubkey, + vec_field: Vec, + vec_struct_field: Vec, + option_field: Option, + option_struct_field: Option, + struct_field: FooStruct, + array_field: [bool; 3], + enum_field_1: FooEnum, + enum_field_2: FooEnum, + enum_field_3: FooEnum, + enum_field_4: FooEnum, + ) -> Result<()> { + ctx.accounts.state.set_inner(State { + bool_field, + u8_field, + i8_field, + u16_field, + i16_field, + u32_field, + i32_field, + f32_field, + u64_field, + i64_field, + f64_field, + u128_field, + i128_field, + bytes_field, + string_field, + pubkey_field, + vec_field, + vec_struct_field, + option_field, + option_struct_field, + struct_field, + array_field, + enum_field_1, + enum_field_2, + enum_field_3, + enum_field_4, + }); + + Ok(()) + } + + /// a separate instruction due to initialize_with_values having too many arguments + /// https://github.com/solana-labs/solana/issues/23978 + pub fn initialize_with_values2( + ctx: Context, + vec_of_option: Vec>, + box_field: Box, + ) -> Result { + ctx.accounts.state.set_inner(State2 { + vec_of_option, + box_field, + }); + Ok(SomeRetStruct { some_field: 3 }) + } + + pub fn cause_error(_ctx: Context) -> Result<()> { + return Err(error!(ErrorCode::SomeError)); + } +} + +/// Enum type +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub enum FooEnum { + /// Tuple kind + Unnamed(bool, u8, BarStruct), + UnnamedSingle(BarStruct), + Named { + /// A bool field inside a struct tuple kind + bool_field: bool, + u8_field: u8, + nested: BarStruct, + }, + Struct(BarStruct), + OptionStruct(Option), + VecStruct(Vec), + NoFields, +} + +/// Bar struct type +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct BarStruct { + /// Some field + some_field: bool, + other_field: u8, +} + +impl Default for BarStruct { + fn default() -> Self { + return BarStruct { + some_field: true, + other_field: 10, + }; + } +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct FooStruct { + field1: u8, + field2: u16, + nested: BarStruct, + vec_nested: Vec, + option_nested: Option, + enum_field: FooEnum, +} + +impl Default for FooStruct { + fn default() -> Self { + return FooStruct { + field1: 123, + field2: 999, + nested: BarStruct::default(), + vec_nested: vec![BarStruct::default()], + option_nested: Some(BarStruct::default()), + enum_field: FooEnum::Named { + bool_field: true, + u8_field: 15, + nested: BarStruct::default(), + }, + }; + } +} + +/// An account containing various fields +#[account] +pub struct State { + /// A boolean field + bool_field: bool, + u8_field: u8, + i8_field: i8, + u16_field: u16, + i16_field: i16, + u32_field: u32, + i32_field: i32, + f32_field: f32, + u64_field: u64, + i64_field: i64, + f64_field: f64, + u128_field: u128, + i128_field: i128, + bytes_field: Vec, + string_field: String, + pubkey_field: Pubkey, + vec_field: Vec, + vec_struct_field: Vec, + option_field: Option, + option_struct_field: Option, + struct_field: FooStruct, + array_field: [bool; 3], + enum_field_1: FooEnum, + enum_field_2: FooEnum, + enum_field_3: FooEnum, + enum_field_4: FooEnum, +} + +impl Default for State { + fn default() -> Self { + // some arbitrary default values + return State { + bool_field: true, + u8_field: 234, + i8_field: -123, + u16_field: 62345, + i16_field: -31234, + u32_field: 1234567891, + i32_field: -1234567891, + f32_field: 123456.5, + u64_field: u64::MAX / 2 + 10, + i64_field: i64::MIN / 2 - 10, + f64_field: 1234567891.345, + u128_field: u128::MAX / 2 + 10, + i128_field: i128::MIN / 2 - 10, + bytes_field: vec![1, 2, 255, 254], + string_field: String::from("hello"), + pubkey_field: Pubkey::from_str("EPZP2wrcRtMxrAPJCXVEQaYD9eH7fH7h12YqKDcd4aS7").unwrap(), + vec_field: vec![1, 2, 100, 1000, u64::MAX], + vec_struct_field: vec![FooStruct::default()], + option_field: None, + option_struct_field: Some(FooStruct::default()), + struct_field: FooStruct::default(), + array_field: [true, false, true], + enum_field_1: FooEnum::Unnamed(false, 10, BarStruct::default()), + enum_field_2: FooEnum::Named { + bool_field: true, + u8_field: 20, + nested: BarStruct::default(), + }, + enum_field_3: FooEnum::Struct(BarStruct::default()), + enum_field_4: FooEnum::NoFields, + }; + } +} + +#[account] +pub struct State2 { + vec_of_option: Vec>, + box_field: Box, +} +impl Default for State2 { + fn default() -> Self { + return State2 { + vec_of_option: vec![None, Some(10)], + box_field: Box::new(true), + }; + } +} + +#[derive(Accounts)] +pub struct NestedAccounts<'info> { + /// Sysvar clock + clock: Sysvar<'info, Clock>, + rent: Sysvar<'info, Rent>, +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + /// State account + #[account( + init, + space = 8 + 1000, // TODO: use exact space required + payer = payer, + )] + state: Account<'info, State>, + + nested: NestedAccounts<'info>, + zc_account: AccountLoader<'info, SomeZcAccount>, + + #[account(mut)] + payer: Signer<'info>, + system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Initialize2<'info> { + #[account( + init, + space = 8 + 1000, // TODO: use exact space required + payer = payer, + )] + state: Account<'info, State2>, + + #[account(mut)] + payer: Signer<'info>, + system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct CauseError {} + +#[error_code] +pub enum ErrorCode { + #[msg("Example error.")] + SomeError, + #[msg("Another error.")] + OtherError, + ErrorWithoutMsg, +} + +mod some_other_module { + use super::*; + + #[derive(AnchorSerialize, AnchorDeserialize, Clone)] + pub struct Baz { + some_u8: u8, + } +} + +#[event] +pub struct SomeEvent { + bool_field: bool, + external_baz: external::Baz, + other_module_baz: some_other_module::Baz, +} + +#[zero_copy] +pub struct ZcStruct { + pub some_field: u16, +} + +#[account(zero_copy)] +pub struct SomeZcAccount { + field: ZcStruct, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct SomeRetStruct { + pub some_field: u8, +} diff --git a/tests/idl/programs/relations-derivation/Cargo.toml b/tests/idl/programs/relations-derivation/Cargo.toml new file mode 100644 index 0000000000..b2fe97ecd2 --- /dev/null +++ b/tests/idl/programs/relations-derivation/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "relations-derivation" +version = "0.1.0" +description = "Created with Anchor" +rust-version = "1.60" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "relations_derivation" + +[features] +no-entrypoint = [] +no-idl = [] +cpi = ["no-entrypoint"] +idl-build = ["anchor-lang/idl-build"] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } diff --git a/tests/idl/programs/relations-derivation/Xargo.toml b/tests/idl/programs/relations-derivation/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/tests/idl/programs/relations-derivation/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/tests/idl/programs/relations-derivation/src/lib.rs b/tests/idl/programs/relations-derivation/src/lib.rs new file mode 100644 index 0000000000..11e03e8859 --- /dev/null +++ b/tests/idl/programs/relations-derivation/src/lib.rs @@ -0,0 +1,65 @@ +use anchor_lang::prelude::*; + +declare_id!("Re1ationsDerivation111111111111111111111111"); + +#[program] +pub mod relations_derivation { + use super::*; + + pub fn init_base(ctx: Context) -> Result<()> { + ctx.accounts.account.my_account = ctx.accounts.my_account.key(); + ctx.accounts.account.bump = ctx.bumps["account"]; + Ok(()) + } + + pub fn test_relation(_ctx: Context) -> Result<()> { + Ok(()) + } +} + +#[derive(Accounts)] +pub struct InitBase<'info> { + /// CHECK: yeah I know + #[account(mut)] + my_account: Signer<'info>, + #[account( + init, + payer = my_account, + seeds = [b"seed"], + space = 100, + bump, + )] + account: Account<'info, MyAccount>, + system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Nested<'info> { + /// CHECK: yeah I know + my_account: UncheckedAccount<'info>, + #[account( + has_one = my_account, + seeds = [b"seed"], + bump = account.bump + )] + account: Account<'info, MyAccount>, +} + +#[derive(Accounts)] +pub struct TestRelation<'info> { + /// CHECK: yeah I know + my_account: UncheckedAccount<'info>, + #[account( + has_one = my_account, + seeds = [b"seed"], + bump = account.bump + )] + account: Account<'info, MyAccount>, + nested: Nested<'info>, +} + +#[account] +pub struct MyAccount { + pub my_account: Pubkey, + pub bump: u8, +} diff --git a/tests/idl/test.sh b/tests/idl/test.sh new file mode 100755 index 0000000000..7b0db254f0 --- /dev/null +++ b/tests/idl/test.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -e + +# Run anchor test +anchor test --skip-lint + +tmp_dir=$(mktemp -d) + +# Generate IDLs +./generate.sh $tmp_dir + +# Exit status +ret=0 + +# Compare IDLs. `$ret` will be non-zero in the case of a mismatch. +compare() { + echo "----------------------------------------------------" + echo "IDL $1 before > after changes" + echo "----------------------------------------------------" + diff -y --color=always --suppress-common-lines idls/$1.json $tmp_dir/$1.json + ret=$(($ret+$?)) + + if [ "$ret" = "0" ]; then + echo "No changes" + fi + + echo "" +} + +compare "parse" +compare "build" +compare "generics_build" +compare "relations_build" + +exit $ret diff --git a/tests/idl/tests/client-interactions.ts b/tests/idl/tests/client-interactions.ts new file mode 100644 index 0000000000..3f486e8590 --- /dev/null +++ b/tests/idl/tests/client-interactions.ts @@ -0,0 +1,121 @@ +import * as anchor from "@coral-xyz/anchor"; +import { assert } from "chai"; + +import { ClientInteractions } from "../target/types/client_interactions"; + +describe("Client interactions", () => { + anchor.setProvider(anchor.AnchorProvider.env()); + const program = anchor.workspace + .clientInteractions as anchor.Program; + + it("Can use integers", async () => { + const kp = anchor.web3.Keypair.generate(); + + const i8 = -3; + const i16 = 1; + const i32 = -5555551; + const i64 = new anchor.BN("384535471"); + const i128 = new anchor.BN(-8342491); + + await program.methods + .int(i8, i16, i32, i64, i128) + .accounts({ account: kp.publicKey }) + .signers([kp]) + .preInstructions([await program.account.intAccount.createInstruction(kp)]) + .rpc(); + + const account = await program.account.intAccount.fetch(kp.publicKey); + assert.strictEqual(account.i8, i8); + assert.strictEqual(account.i16, i16); + assert.strictEqual(account.i32, i32); + assert(account.i64.eq(i64)); + assert(account.i128.eq(i128)); + }); + + it("Can use unsigned integers", async () => { + const kp = anchor.web3.Keypair.generate(); + + const u8 = 123; + const u16 = 7888; + const u32 = 5555551; + const u64 = new anchor.BN("384535471"); + const u128 = new anchor.BN(8888888); + + await program.methods + .uint(u8, u16, u32, u64, u128) + .accounts({ account: kp.publicKey }) + .signers([kp]) + .preInstructions([ + await program.account.unsignedIntAccount.createInstruction(kp), + ]) + .rpc(); + + const account = await program.account.unsignedIntAccount.fetch( + kp.publicKey + ); + assert.strictEqual(account.u8, u8); + assert.strictEqual(account.u16, u16); + assert.strictEqual(account.u32, u32); + assert(account.u64.eq(u64)); + assert(account.u128.eq(u128)); + }); + + it("Can use enum", async () => { + const testAccountEnum = async ( + ...args: Parameters + ) => { + const kp = anchor.web3.Keypair.generate(); + await program.methods + .enm(...(args as any)) + .accounts({ account: kp.publicKey }) + .signers([kp]) + .preInstructions([ + await program.account.enumAccount.createInstruction(kp), + ]) + .rpc(); + return await program.account.enumAccount.fetch(kp.publicKey); + }; + + // Unit + const unit = await testAccountEnum({ unit: {} }); + assert.deepEqual(unit.enumField.unit, {}); + + // Named + const x = new anchor.BN(1); + const y = new anchor.BN(2); + const named = await testAccountEnum({ named: { x, y } }); + assert(named.enumField.named.x.eq(x)); + assert(named.enumField.named.y.eq(y)); + + // Unnamed + const tupleArg = [1, 2, 3, 4] as const; + const unnamed = await testAccountEnum({ unnamed: tupleArg }); + assert.strictEqual(unnamed.enumField.unnamed[0], tupleArg[0]); + assert.strictEqual(unnamed.enumField.unnamed[1], tupleArg[1]); + assert.strictEqual(unnamed.enumField.unnamed[2], tupleArg[2]); + assert.strictEqual(unnamed.enumField.unnamed[3], tupleArg[3]); + + // Unnamed struct + const tupleStructArg = [ + { u8: 1, u16: 11, u32: 111, u64: new anchor.BN(1111) }, + ] as const; + const unnamedStruct = await testAccountEnum({ + unnamedStruct: tupleStructArg, + }); + assert.strictEqual( + unnamedStruct.enumField.unnamedStruct[0].u8, + tupleStructArg[0].u8 + ); + assert.strictEqual( + unnamedStruct.enumField.unnamedStruct[0].u16, + tupleStructArg[0].u16 + ); + assert.strictEqual( + unnamedStruct.enumField.unnamedStruct[0].u32, + tupleStructArg[0].u32 + ); + assert( + unnamedStruct.enumField.unnamedStruct[0].u64.eq(tupleStructArg[0].u64) + ); + }); +}); diff --git a/tests/idl/tests/docs.ts b/tests/idl/tests/docs.ts new file mode 100644 index 0000000000..401888d7b4 --- /dev/null +++ b/tests/idl/tests/docs.ts @@ -0,0 +1,46 @@ +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { assert } from "chai"; + +import { Docs } from "../target/types/docs"; + +describe("Docs", () => { + anchor.setProvider(anchor.AnchorProvider.env()); + const program = anchor.workspace.docs as Program; + + const instruction = program.idl.instructions.find( + (i) => i.name === "testIdlDocParse" + ); + + it("includes instruction doc comment", () => { + assert.deepEqual(instruction.docs, [ + "This instruction doc should appear in the IDL", + ]); + }); + + it("includes account doc comment", () => { + const act = instruction.accounts.find((i) => i.name === "act"); + assert.deepEqual(act.docs, [ + "This account doc comment should appear in the IDL", + "This is a multi-line comment", + ]); + }); + + const dataWithDoc = program.idl.accounts.find( + // @ts-expect-error + (acc) => acc.name === "DataWithDoc" + ); + + it("includes accounts doc comment", () => { + assert.deepEqual(dataWithDoc.docs, [ + "Custom account doc comment should appear in the IDL", + ]); + }); + + it("includes account attribute doc comment", () => { + const dataField = dataWithDoc.type.fields.find((i) => i.name === "data"); + assert.deepEqual(dataField.docs, [ + "Account attribute doc comment should appear in the IDL", + ]); + }); +}); diff --git a/tests/idl/tests/idl-build-features.ts b/tests/idl/tests/idl-build-features.ts new file mode 100644 index 0000000000..4abdb13d34 --- /dev/null +++ b/tests/idl/tests/idl-build-features.ts @@ -0,0 +1,35 @@ +import * as anchor from "@coral-xyz/anchor"; +import { assert } from "chai"; + +import { IdlBuildFeatures } from "../target/types/idl_build_features"; + +describe("idl-build features", () => { + anchor.setProvider(anchor.AnchorProvider.env()); + const program = anchor.workspace + .idlBuildFeatures as anchor.Program; + + it("Can use full module path types", async () => { + const kp = anchor.web3.Keypair.generate(); + + const outerMyStructArg = { u8: 1, u16: 2, u32: 3, u64: new anchor.BN(4) }; + const someModuleMyStructArg = { data: 5 }; + + await program.methods + .fullPath(outerMyStructArg, someModuleMyStructArg) + .accounts({ account: kp.publicKey }) + .preInstructions([ + await program.account.fullPathAccount.createInstruction(kp), + ]) + .signers([kp]) + .rpc(); + + const fullPathAccount = await program.account.fullPathAccount.fetch( + kp.publicKey + ); + assert.strictEqual(fullPathAccount.myStruct.u8, outerMyStructArg.u8); + assert.strictEqual(fullPathAccount.myStruct.u16, outerMyStructArg.u16); + assert.strictEqual(fullPathAccount.myStruct.u32, outerMyStructArg.u32); + assert(fullPathAccount.myStruct.u64.eq(outerMyStructArg.u64)); + assert.deepEqual(fullPathAccount.someModuleMyStruct, someModuleMyStructArg); + }); +}); diff --git a/tests/idl/tests/idl.ts b/tests/idl/tests/idl.ts new file mode 100644 index 0000000000..4aa7e4ddc2 --- /dev/null +++ b/tests/idl/tests/idl.ts @@ -0,0 +1,37 @@ +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { assert } from "chai"; + +import { Idl } from "../target/types/idl"; + +describe("IDL", () => { + anchor.setProvider(anchor.AnchorProvider.env()); + const program = anchor.workspace.idl as Program; + + it("Includes constants that use `#[constant]` macro", () => { + const checkDefined = ( + cb: (constant: typeof program["idl"]["constants"][number]) => boolean + ) => { + program.idl.constants.find((c) => cb(c)); + }; + + checkDefined((c) => c.name === "U8" && c.type === "u8" && c.value === "6"); + checkDefined( + (c) => c.name === "I128" && c.type === "i128" && c.value === "1000000" + ); + checkDefined( + (c) => c.name === "BYTE_STR" && c.type === "u8" && c.value === "116" + ); + checkDefined( + (c) => + c.name === "BYTES_STR" && + c.type === "bytes" && + c.value === "[116, 101, 115, 116]" + ); + }); + + it("Does not include constants that does not use `#[constant]` macro ", () => { + // @ts-expect-error + assert.isUndefined(program.idl.constants.find((c) => c.name === "NO_IDL")); + }); +}); diff --git a/tests/idl/tests/workspace.ts b/tests/idl/tests/workspace.ts new file mode 100644 index 0000000000..c240bdd9b6 --- /dev/null +++ b/tests/idl/tests/workspace.ts @@ -0,0 +1,43 @@ +import * as anchor from "@coral-xyz/anchor"; +import { assert } from "chai"; + +describe("Workspace", () => { + anchor.setProvider(anchor.AnchorProvider.env()); + + it("Can lazy load workspace programs", () => { + assert.doesNotThrow(() => { + // Program exists, should not throw + anchor.workspace.relationsDerivation; + }); + + assert.throws(() => { + // IDL path in Anchor.toml doesn't exist but other tests still run + // successfully because workspace programs are getting loaded on-demand + anchor.workspace.nonExistent; + }, /non-existent\.json/); + }); + + it("Can get workspace programs by their name independent of casing", () => { + const camel = anchor.workspace.relationsDerivation; + const pascal = anchor.workspace.RelationsDerivation; + const kebab = anchor.workspace["relations-derivation"]; + const snake = anchor.workspace["relations_derivation"]; + + const compareProgramNames = (...programs: anchor.Program[]) => { + return programs.every( + (program) => program.idl.name === "relations_derivation" + ); + }; + + assert(compareProgramNames(camel, pascal, kebab, snake)); + }); + + it("Can use numbers in program names", () => { + assert.doesNotThrow(() => { + anchor.workspace.numbers123; + anchor.workspace.Numbers123; + anchor.workspace["numbers-123"]; + anchor.workspace["numbers_123"]; + }); + }); +}); diff --git a/tests/idl/tsconfig.json b/tests/idl/tsconfig.json new file mode 100644 index 0000000000..774260253f --- /dev/null +++ b/tests/idl/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true + } +} diff --git a/tests/misc/Anchor.toml b/tests/misc/Anchor.toml index 5c8022e115..e4990e1ca4 100644 --- a/tests/misc/Anchor.toml +++ b/tests/misc/Anchor.toml @@ -3,10 +3,10 @@ cluster = "localnet" wallet = "~/.config/solana/id.json" [programs.localnet] +init_if_needed = "BZoppwWi6jMnydnUBEJzotgEXHwLr3b3NramJgZtWeF2" +lamports = "Lamports11111111111111111111111111111111111" misc = "3TEqcc8xhrhdspwbvoamUJe2borm4Nr72JxL66k6rgrh" misc_optional = "FNqz6pqLAwvMSds2FYjR4nKV3moVpPNtvkfGFrqLKrgG" -idl_doc = "BqmKjZGVa8fqyWuojJzG16zaKSV1GjAisZToNuvEaz6m" -init_if_needed = "BZoppwWi6jMnydnUBEJzotgEXHwLr3b3NramJgZtWeF2" [workspace] exclude = ["programs/shared"] diff --git a/tests/misc/programs/lamports/Cargo.toml b/tests/misc/programs/lamports/Cargo.toml new file mode 100644 index 0000000000..ea99d59e5b --- /dev/null +++ b/tests/misc/programs/lamports/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "lamports" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] + +[features] +no-entrypoint = [] +cpi = ["no-entrypoint"] + +[dependencies] +anchor-lang = { path = "../../../../lang" } diff --git a/tests/misc/programs/lamports/Xargo.toml b/tests/misc/programs/lamports/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/tests/misc/programs/lamports/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/tests/misc/programs/lamports/src/lib.rs b/tests/misc/programs/lamports/src/lib.rs new file mode 100644 index 0000000000..84ac072d27 --- /dev/null +++ b/tests/misc/programs/lamports/src/lib.rs @@ -0,0 +1,75 @@ +use anchor_lang::prelude::*; + +declare_id!("Lamports11111111111111111111111111111111111"); + +#[program] +pub mod lamports { + use super::*; + + pub fn test_lamports_trait(ctx: Context, amount: u64) -> Result<()> { + let pda = &ctx.accounts.pda; + let signer = &ctx.accounts.signer; + + // Transfer **to** PDA + { + // Get the balance of the PDA **before** the transfer to PDA + let pda_balance_before = pda.get_lamports(); + + // Transfer to the PDA + anchor_lang::system_program::transfer( + CpiContext::new( + ctx.accounts.system_program.to_account_info(), + anchor_lang::system_program::Transfer { + from: signer.to_account_info(), + to: pda.to_account_info(), + }, + ), + amount, + )?; + + // Get the balance of the PDA **after** the transfer to PDA + let pda_balance_after = pda.get_lamports(); + + // Validate balance + require_eq!(pda_balance_after, pda_balance_before + amount); + } + + // Transfer **from** PDA + { + // Get the balance of the PDA **before** the transfer from PDA + let pda_balance_before = pda.get_lamports(); + + // Transfer from the PDA + pda.sub_lamports(amount)?; + signer.add_lamports(amount)?; + + // Get the balance of the PDA **after** the transfer from PDA + let pda_balance_after = pda.get_lamports(); + + // Validate balance + require_eq!(pda_balance_after, pda_balance_before - amount); + } + + Ok(()) + } +} + +#[derive(Accounts)] +pub struct TestLamportsTrait<'info> { + #[account(mut)] + pub signer: Signer<'info>, + + #[account( + init, + payer = signer, + space = 8, + seeds = [b"lamports"], + bump + )] + pub pda: Account<'info, LamportsPda>, + + pub system_program: Program<'info, System>, +} + +#[account] +pub struct LamportsPda {} diff --git a/tests/misc/programs/misc-optional/src/account.rs b/tests/misc/programs/misc-optional/src/account.rs index 893dac256b..352562fc9e 100644 --- a/tests/misc/programs/misc-optional/src/account.rs +++ b/tests/misc/programs/misc-optional/src/account.rs @@ -30,12 +30,6 @@ pub struct DataI8 { } size!(DataI8, 1); -#[account] -pub struct DataI16 { - pub data: i16, // 2 -} -size!(DataI16, 2); - #[account(zero_copy)] pub struct DataZeroCopy { pub data: u16, // 2 diff --git a/tests/misc/programs/misc-optional/src/context.rs b/tests/misc/programs/misc-optional/src/context.rs index 04ec786f49..90a82e80e5 100644 --- a/tests/misc/programs/misc-optional/src/context.rs +++ b/tests/misc/programs/misc-optional/src/context.rs @@ -2,6 +2,7 @@ use crate::account::*; use anchor_lang::prelude::*; use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::{Mint, Token, TokenAccount}; +use anchor_spl::token_interface::{Mint as MintInterface, TokenAccount as TokenAccountInterface}; #[derive(Accounts)] pub struct TestTokenSeedsInit<'info> { @@ -55,8 +56,8 @@ pub struct TestInitAssociatedTokenWithTokenProgram<'info> { associated_token::authority = payer, associated_token::token_program = associated_token_token_program, )] - pub token: Option>, - pub mint: Option>, + pub token: Option>, + pub mint: Option>, #[account(mut)] pub payer: Option>, pub system_program: Option>, @@ -193,27 +194,9 @@ pub struct TestCloseMut<'info> { pub sol_dest: Option>, } -#[derive(Accounts)] -pub struct TestU16<'info> { - #[account(zero)] - pub my_account: Option>, -} - -#[derive(Accounts)] -pub struct TestI16<'info> { - #[account(zero)] - pub data: Option>, -} - #[derive(Accounts)] pub struct TestSimulate {} -#[derive(Accounts)] -pub struct TestI8<'info> { - #[account(zero)] - pub data: Option>, -} - #[derive(Accounts)] pub struct TestCompositePayer<'info> { pub composite: TestInit<'info>, @@ -260,7 +243,7 @@ pub struct TestInitMintWithTokenProgram<'info> { mint::freeze_authority = payer, mint::token_program = mint_token_program, )] - pub mint: Option>, + pub mint: Option>, #[account(mut)] pub payer: Option>, pub system_program: Option>, @@ -457,8 +440,8 @@ pub struct TestInitAssociatedTokenIfNeededWithTokenProgram<'info> { associated_token::authority = authority, associated_token::token_program = associated_token_token_program, )] - pub token: Option>, - pub mint: Option>, + pub token: Option>, + pub mint: Option>, #[account(mut)] pub payer: Option>, pub system_program: Option>, @@ -703,8 +686,8 @@ pub struct TestAssociatedTokenWithTokenProgramConstraint<'info> { associated_token::authority = authority, associated_token::token_program = associated_token_token_program, )] - pub token: Option>, - pub mint: Account<'info, Mint>, + pub token: Option>, + pub mint: InterfaceAccount<'info, MintInterface>, /// CHECK: ignore pub authority: AccountInfo<'info>, /// CHECK: ignore diff --git a/tests/misc/programs/misc-optional/src/event.rs b/tests/misc/programs/misc-optional/src/event.rs index 7a41c33786..80b7dd8f88 100644 --- a/tests/misc/programs/misc-optional/src/event.rs +++ b/tests/misc/programs/misc-optional/src/event.rs @@ -32,24 +32,3 @@ pub struct E5 { pub struct E6 { pub data: [u8; MAX_EVENT_SIZE_U8 as usize], } - -#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] -pub struct TestStruct { - pub data1: u8, - pub data2: u16, - pub data3: u32, - pub data4: u64, -} - -#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] -pub enum TestEnum { - First, - Second { x: u64, y: u64 }, - TupleTest(u8, u8, u16, u16), - TupleStructTest(TestStruct), -} - -#[event] -pub struct E7 { - pub data: TestEnum, -} diff --git a/tests/misc/programs/misc-optional/src/lib.rs b/tests/misc/programs/misc-optional/src/lib.rs index aa71ccb16a..4e447acfdc 100644 --- a/tests/misc/programs/misc-optional/src/lib.rs +++ b/tests/misc/programs/misc-optional/src/lib.rs @@ -1,7 +1,7 @@ //! Misc optional example is a catchall program for testing unrelated features. //! It's not too instructive/coherent by itself, so please see other examples. -use account::MAX_SIZE; +use account::*; use anchor_lang::prelude::*; use context::*; use event::*; @@ -44,11 +44,6 @@ pub mod misc_optional { Ok(()) } - pub fn test_u16(ctx: Context, data: u16) -> Result<()> { - ctx.accounts.my_account.as_mut().unwrap().data = data; - Ok(()) - } - pub fn test_simulate(_ctx: Context, data: u32) -> Result<()> { emit!(E1 { data }); emit!(E2 { data: 1234 }); @@ -62,21 +57,6 @@ pub mod misc_optional { Ok(()) } - pub fn test_input_enum(ctx: Context, data: TestEnum) -> Result<()> { - emit!(E7 { data: data }); - Ok(()) - } - - pub fn test_i8(ctx: Context, data: i8) -> Result<()> { - ctx.accounts.data.as_mut().unwrap().data = data; - Ok(()) - } - - pub fn test_i16(ctx: Context, data: i16) -> Result<()> { - ctx.accounts.data.as_mut().unwrap().data = data; - Ok(()) - } - pub fn test_const_array_size(ctx: Context, data: u8) -> Result<()> { ctx.accounts.data.as_mut().unwrap().data[0] = data; Ok(()) @@ -205,7 +185,7 @@ pub mod misc_optional { } pub fn test_init_associated_token_with_token_program( - ctx: Context, + _ctx: Context, ) -> Result<()> { Ok(()) } @@ -284,7 +264,7 @@ pub mod misc_optional { Ok(()) } - pub fn init_with_space(_ctx: Context, data: u16) -> Result<()> { + pub fn init_with_space(_ctx: Context, _data: u16) -> Result<()> { Ok(()) } diff --git a/tests/misc/programs/misc/src/account.rs b/tests/misc/programs/misc/src/account.rs index c2966b5b44..0a8b736844 100644 --- a/tests/misc/programs/misc/src/account.rs +++ b/tests/misc/programs/misc/src/account.rs @@ -30,12 +30,6 @@ pub struct DataI8 { } size!(DataI8, 1); -#[account] -pub struct DataI16 { - pub data: i16, // 2 -} -size!(DataI16, 2); - #[account(zero_copy)] pub struct DataZeroCopy { pub data: u16, // 2 @@ -94,3 +88,11 @@ pub enum CoolEnum { some_slot: u64, }, } + +#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +pub struct TestStruct { + pub data1: u8, + pub data2: u16, + pub data3: u32, + pub data4: u64, +} diff --git a/tests/misc/programs/misc/src/context.rs b/tests/misc/programs/misc/src/context.rs index 3ef62f3bbf..d844aaae80 100644 --- a/tests/misc/programs/misc/src/context.rs +++ b/tests/misc/programs/misc/src/context.rs @@ -2,6 +2,7 @@ use crate::account::*; use anchor_lang::prelude::*; use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::{Mint, Token, TokenAccount}; +use anchor_spl::token_interface::{Mint as MintInterface, TokenAccount as TokenAccountInterface}; #[derive(Accounts)] pub struct TestTokenSeedsInit<'info> { @@ -56,8 +57,8 @@ pub struct TestInitAssociatedTokenWithTokenProgram<'info> { associated_token::authority = payer, associated_token::token_program = associated_token_token_program, )] - pub token: Account<'info, TokenAccount>, - pub mint: Account<'info, Mint>, + pub token: InterfaceAccount<'info, TokenAccountInterface>, + pub mint: InterfaceAccount<'info, MintInterface>, #[account(mut)] pub payer: Signer<'info>, pub system_program: Program<'info, System>, @@ -200,27 +201,9 @@ pub struct TestCloseMut<'info> { pub sol_dest: AccountInfo<'info>, } -#[derive(Accounts)] -pub struct TestU16<'info> { - #[account(zero)] - pub my_account: Account<'info, DataU16>, -} - -#[derive(Accounts)] -pub struct TestI16<'info> { - #[account(zero)] - pub data: Account<'info, DataI16>, -} - #[derive(Accounts)] pub struct TestSimulate {} -#[derive(Accounts)] -pub struct TestI8<'info> { - #[account(zero)] - pub data: Account<'info, DataI8>, -} - #[derive(Accounts)] pub struct TestInit<'info> { #[account(init, payer = payer, space = DataI8::LEN + 8)] @@ -258,7 +241,7 @@ pub struct TestInitMintWithTokenProgram<'info> { mint::freeze_authority = payer, mint::token_program = mint_token_program, )] - pub mint: Account<'info, Mint>, + pub mint: InterfaceAccount<'info, MintInterface>, #[account(mut)] pub payer: Signer<'info>, pub system_program: Program<'info, System>, @@ -463,8 +446,8 @@ pub struct TestInitAssociatedTokenIfNeededWithTokenProgram<'info> { associated_token::authority = authority, associated_token::token_program = associated_token_token_program, )] - pub token: Account<'info, TokenAccount>, - pub mint: Account<'info, Mint>, + pub token: InterfaceAccount<'info, TokenAccountInterface>, + pub mint: InterfaceAccount<'info, MintInterface>, #[account(mut)] pub payer: Signer<'info>, pub system_program: Program<'info, System>, @@ -718,8 +701,8 @@ pub struct TestAssociatedTokenWithTokenProgramConstraint<'info> { associated_token::authority = authority, associated_token::token_program = associated_token_token_program, )] - pub token: Account<'info, TokenAccount>, - pub mint: Account<'info, Mint>, + pub token: InterfaceAccount<'info, TokenAccountInterface>, + pub mint: InterfaceAccount<'info, MintInterface>, /// CHECK: ignore pub authority: AccountInfo<'info>, /// CHECK: ignore @@ -767,4 +750,4 @@ pub struct TestUsedIdentifiers<'info> { )] /// CHECK: ignore pub test4: AccountInfo<'info>, -} \ No newline at end of file +} diff --git a/tests/misc/programs/misc/src/event.rs b/tests/misc/programs/misc/src/event.rs index 31fcbd31d4..80b7dd8f88 100644 --- a/tests/misc/programs/misc/src/event.rs +++ b/tests/misc/programs/misc/src/event.rs @@ -32,24 +32,3 @@ pub struct E5 { pub struct E6 { pub data: [u8; MAX_EVENT_SIZE_U8 as usize], } - -#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] -pub struct TestStruct { - pub data1: u8, - pub data2: u16, - pub data3: u32, - pub data4: u64, -} - -#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] -pub enum TestEnum { - First, - Second {x: u64, y: u64}, - TupleTest (u8, u8, u16, u16), - TupleStructTest (TestStruct), -} - -#[event] -pub struct E7 { - pub data: TestEnum, -} \ No newline at end of file diff --git a/tests/misc/programs/misc/src/lib.rs b/tests/misc/programs/misc/src/lib.rs index 5074353222..d0056018fe 100644 --- a/tests/misc/programs/misc/src/lib.rs +++ b/tests/misc/programs/misc/src/lib.rs @@ -1,7 +1,7 @@ //! Misc example is a catchall program for testing unrelated features. //! It's not too instructive/coherent by itself, so please see other examples. -use account::MAX_SIZE; +use account::*; use anchor_lang::prelude::*; use context::*; use event::*; @@ -12,16 +12,6 @@ mod event; declare_id!("3TEqcc8xhrhdspwbvoamUJe2borm4Nr72JxL66k6rgrh"); -#[constant] -pub const BASE: u128 = 1_000_000; -#[constant] -pub const DECIMALS: u8 = 6; -#[constant] -pub const BYTES_STR: &[u8] = b"test"; -#[constant] -pub const BYTE_STR: u8 = b't'; -pub const NO_IDL: u16 = 55; - #[program] pub mod misc { use super::*; @@ -48,11 +38,6 @@ pub mod misc { Ok(()) } - pub fn test_u16(ctx: Context, data: u16) -> Result<()> { - ctx.accounts.my_account.data = data; - Ok(()) - } - pub fn test_simulate(_ctx: Context, data: u32) -> Result<()> { emit!(E1 { data }); emit!(E2 { data: 1234 }); @@ -66,21 +51,6 @@ pub mod misc { Ok(()) } - pub fn test_input_enum(_ctx: Context, data: TestEnum) -> Result<()> { - emit!(E7 { data: data }); - Ok(()) - } - - pub fn test_i8(ctx: Context, data: i8) -> Result<()> { - ctx.accounts.data.data = data; - Ok(()) - } - - pub fn test_i16(ctx: Context, data: i16) -> Result<()> { - ctx.accounts.data.data = data; - Ok(()) - } - pub fn test_const_array_size(ctx: Context, data: u8) -> Result<()> { ctx.accounts.data.data[0] = data; Ok(()) diff --git a/tests/misc/tests/idl_doc/Test.toml b/tests/misc/tests/idl_doc/Test.toml deleted file mode 100644 index 680dba8f65..0000000000 --- a/tests/misc/tests/idl_doc/Test.toml +++ /dev/null @@ -1,2 +0,0 @@ -[scripts] -test = "yarn run ts-mocha -t 1000000 ./tests/idl_doc/*.ts" diff --git a/tests/misc/tests/idl_doc/idl_doc.ts b/tests/misc/tests/idl_doc/idl_doc.ts deleted file mode 100644 index f018b8d554..0000000000 --- a/tests/misc/tests/idl_doc/idl_doc.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as anchor from "@coral-xyz/anchor"; -import { Program, Wallet } from "@coral-xyz/anchor"; -import { IdlDoc } from "../../target/types/idl_doc"; -const { expect } = require("chai"); -const idl_doc_idl = require("../../target/idl/idl_doc.json"); - -describe("idl_doc", () => { - // Configure the client to use the local cluster. - const provider = anchor.AnchorProvider.env(); - const wallet = provider.wallet as Wallet; - anchor.setProvider(provider); - const program = anchor.workspace.IdlDoc as Program; - - describe("IDL doc strings", () => { - const instruction = program.idl.instructions.find( - (i) => i.name === "testIdlDocParse" - ); - it("includes instruction doc comment", async () => { - expect(instruction.docs).to.have.same.members([ - "This instruction doc should appear in the IDL", - ]); - }); - - it("includes account doc comment", async () => { - const act = instruction.accounts.find((i) => i.name === "act"); - expect(act.docs).to.have.same.members([ - "This account doc comment should appear in the IDL", - "This is a multi-line comment", - ]); - }); - - const dataWithDoc = program.idl.accounts.find( - // @ts-expect-error - (i) => i.name === "DataWithDoc" - ); - - it("includes accounts doc comment", async () => { - expect(dataWithDoc.docs).to.have.same.members([ - "Custom account doc comment should appear in the IDL", - ]); - }); - - it("includes account attribute doc comment", async () => { - const dataField = dataWithDoc.type.fields.find((i) => i.name === "data"); - expect(dataField.docs).to.have.same.members([ - "Account attribute doc comment should appear in the IDL", - ]); - }); - }); -}); diff --git a/tests/misc/tests/lamports/Test.toml b/tests/misc/tests/lamports/Test.toml new file mode 100644 index 0000000000..a95a50b7ec --- /dev/null +++ b/tests/misc/tests/lamports/Test.toml @@ -0,0 +1,2 @@ +[scripts] +test = "yarn run ts-mocha -t 1000000 ./tests/lamports/*.ts" diff --git a/tests/misc/tests/lamports/lamports.ts b/tests/misc/tests/lamports/lamports.ts new file mode 100644 index 0000000000..678ed5c795 --- /dev/null +++ b/tests/misc/tests/lamports/lamports.ts @@ -0,0 +1,23 @@ +import * as anchor from "@coral-xyz/anchor"; + +import { Lamports, IDL } from "../../target/types/lamports"; + +describe(IDL.name, () => { + // Configure the client to use the local cluster + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.Lamports as anchor.Program; + + it("Can use the Lamports trait", async () => { + const signer = program.provider.publicKey!; + const [pda] = anchor.web3.PublicKey.findProgramAddressSync( + [Buffer.from("lamports")], + program.programId + ); + + await program.methods + .testLamportsTrait(new anchor.BN(anchor.web3.LAMPORTS_PER_SOL)) + .accounts({ signer, pda }) + .rpc(); + }); +}); diff --git a/tests/misc/tests/misc/misc.ts b/tests/misc/tests/misc/misc.ts index 1a526d6ed9..3567d928bb 100644 --- a/tests/misc/tests/misc/misc.ts +++ b/tests/misc/tests/misc/misc.ts @@ -1,5 +1,5 @@ import * as anchor from "@coral-xyz/anchor"; -import { Program, BN, AnchorError, Wallet, IdlEvents } from "@coral-xyz/anchor"; +import { Program, AnchorError, Wallet } from "@coral-xyz/anchor"; import { PublicKey, Keypair, @@ -13,15 +13,22 @@ import { TOKEN_PROGRAM_ID, Token, ASSOCIATED_TOKEN_PROGRAM_ID, + AccountLayout, + MintLayout, } from "@solana/spl-token"; +import { assert, expect } from "chai"; + import { Misc } from "../../target/types/misc"; import { MiscOptional } from "../../target/types/misc_optional"; const utf8 = anchor.utils.bytes.utf8; -const { assert, expect } = require("chai"); const nativeAssert = require("assert"); const miscIdl = require("../../target/idl/misc.json"); +const TOKEN_2022_PROGRAM_ID = new anchor.web3.PublicKey( + "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" +); + const miscTest = ( program: anchor.Program | anchor.Program ) => { @@ -31,36 +38,217 @@ const miscTest = ( const wallet = provider.wallet as Wallet; anchor.setProvider(provider); - const data = anchor.web3.Keypair.generate(); + describe("Data Account", () => { + const data = anchor.web3.Keypair.generate(); + const udata = new anchor.BN(1); + const idata = new anchor.BN(2); + + it("Can initialize data account", async () => { + await program.methods + .initialize(udata, idata) + .accounts({ data: data.publicKey }) + .signers([data]) + .preInstructions([await program.account.data.createInstruction(data)]) + .rpc(); + const dataAccount = await program.account.data.fetch(data.publicKey); + assert(dataAccount.udata.eq(udata)); + assert(dataAccount.idata.eq(idata)); + }); + + it("Can use base58 strings to fetch an account", async () => { + const dataAccount = await program.account.data.fetch( + data.publicKey.toString() + ); + assert(dataAccount.udata.eq(udata)); + assert(dataAccount.idata.eq(idata)); + }); - it("Can use u128 and i128", async () => { - const tx = await program.rpc.initialize( - new anchor.BN(1234), - new anchor.BN(22), - { - accounts: { + it("Can use the owner constraint", async () => { + await program.methods + .testOwner() + .accounts({ data: data.publicKey, + misc: program.programId, + }) + .rpc(); + + await nativeAssert.rejects( + async () => { + await program.methods + .testOwner() + .accounts({ + data: provider.wallet.publicKey, + misc: program.programId, + }) + .rpc(); }, - signers: [data], - instructions: [await program.account.data.createInstruction(data)], + (err) => { + return true; + } + ); + }); + + it("Should fail to close an account when sending lamports to itself", async () => { + try { + await program.methods + .testClose() + .accounts({ + data: data.publicKey, + solDest: data.publicKey, + }) + .rpc(); + expect(false).to.be.true; + } catch (err) { + assert.strictEqual( + err.error.errorMessage, + "A close constraint was violated" + ); + assert.strictEqual(err.error.errorCode.number, 2011); } - ); - const dataAccount = await program.account.data.fetch(data.publicKey); - assert.isTrue(dataAccount.udata.eq(new anchor.BN(1234))); - assert.isTrue(dataAccount.idata.eq(new anchor.BN(22))); - }); + }); - it("Can use u16", async () => { - const data = anchor.web3.Keypair.generate(); - const tx = await program.rpc.testU16(99, { - accounts: { - myAccount: data.publicKey, - }, - signers: [data], - instructions: [await program.account.dataU16.createInstruction(data)], + it("Can close an account", async () => { + const connection = program.provider.connection; + const openAccount = await connection.getAccountInfo(data.publicKey); + + assert.isNotNull(openAccount); + const openAccountBalance = openAccount.lamports; + // double balance to calculate closed balance correctly + const transferIx = anchor.web3.SystemProgram.transfer({ + fromPubkey: provider.wallet.publicKey, + toPubkey: data.publicKey, + lamports: openAccountBalance, + }); + const transferTransaction = new anchor.web3.Transaction().add( + transferIx + ); + await provider.sendAndConfirm(transferTransaction); + + let beforeBalance = ( + await connection.getAccountInfo(provider.wallet.publicKey) + ).lamports; + + await program.methods + .testClose() + .accounts({ + data: data.publicKey, + solDest: provider.wallet.publicKey, + }) + .postInstructions([transferIx]) + .rpc(); + + let afterBalance = ( + await connection.getAccountInfo(provider.wallet.publicKey) + ).lamports; + + // Retrieved rent exemption sol. + expect(afterBalance > beforeBalance).to.be.true; + + const closedAccount = await connection.getAccountInfo(data.publicKey); + + assert.isTrue(closedAccount.data.length === 0); + assert.isTrue(closedAccount.owner.equals(SystemProgram.programId)); + }); + + it("Can close an account twice", async () => { + const data = anchor.web3.Keypair.generate(); + await program.methods + .initialize(new anchor.BN(10), new anchor.BN(10)) + .accounts({ data: data.publicKey }) + .preInstructions([await program.account.data.createInstruction(data)]) + .signers([data]) + .rpc(); + + const connection = program.provider.connection; + const openAccount = await connection.getAccountInfo(data.publicKey); + assert.isNotNull(openAccount); + + const openAccountBalance = openAccount.lamports; + // double balance to calculate closed balance correctly + const transferIx = anchor.web3.SystemProgram.transfer({ + fromPubkey: provider.wallet.publicKey, + toPubkey: data.publicKey, + lamports: openAccountBalance, + }); + const transferTransaction = new anchor.web3.Transaction().add( + transferIx + ); + await provider.sendAndConfirm(transferTransaction); + + let beforeBalance = ( + await connection.getAccountInfo(provider.wallet.publicKey) + ).lamports; + + await program.methods + .testCloseTwice() + .accounts({ + data: data.publicKey, + solDest: provider.wallet.publicKey, + }) + .postInstructions([transferIx]) + .rpc(); + + let afterBalance = ( + await connection.getAccountInfo(provider.wallet.publicKey) + ).lamports; + + // Retrieved rent exemption sol. + expect(afterBalance > beforeBalance).to.be.true; + + const closedAccount = await connection.getAccountInfo(data.publicKey); + assert.isTrue(closedAccount.data.length === 0); + assert.isTrue(closedAccount.owner.equals(SystemProgram.programId)); + }); + + it("Can close a mut account manually", async () => { + const data = anchor.web3.Keypair.generate(); + await program.methods + .initialize(new anchor.BN(10), new anchor.BN(10)) + .accounts({ data: data.publicKey }) + .preInstructions([await program.account.data.createInstruction(data)]) + .signers([data]) + .rpc(); + + const connection = program.provider.connection; + const openAccount = await connection.getAccountInfo(data.publicKey); + + assert.isNotNull(openAccount); + const openAccountBalance = openAccount.lamports; + // double balance to calculate closed balance correctly + const transferIx = anchor.web3.SystemProgram.transfer({ + fromPubkey: provider.wallet.publicKey, + toPubkey: data.publicKey, + lamports: openAccountBalance, + }); + const transferTransaction = new anchor.web3.Transaction().add( + transferIx + ); + await provider.sendAndConfirm(transferTransaction); + + let beforeBalance = ( + await connection.getAccountInfo(provider.wallet.publicKey) + ).lamports; + + await program.methods + .testCloseMut() + .accounts({ + data: data.publicKey, + solDest: provider.wallet.publicKey, + }) + .postInstructions([transferIx]) + .rpc(); + + let afterBalance = ( + await connection.getAccountInfo(provider.wallet.publicKey) + ).lamports; + + // Retrieved rent exemption sol. + expect(afterBalance > beforeBalance).to.be.true; + + const closedAccount = await connection.getAccountInfo(data.publicKey); + assert.isTrue(closedAccount.data.length === 0); + assert.isTrue(closedAccount.owner.equals(SystemProgram.programId)); }); - const dataAccount = await program.account.dataU16.fetch(data.publicKey); - assert.strictEqual(dataAccount.data, 99); }); it("Can send VersionedTransaction", async () => { @@ -216,29 +404,6 @@ const miscTest = ( assert.isTrue(accInfo.executable); }); - it("Can use the owner constraint", async () => { - await program.rpc.testOwner({ - accounts: { - data: data.publicKey, - misc: program.programId, - }, - }); - - await nativeAssert.rejects( - async () => { - await program.rpc.testOwner({ - accounts: { - data: provider.wallet.publicKey, - misc: program.programId, - }, - }); - }, - (err) => { - return true; - } - ); - }); - it("Can use the executable attribute", async () => { await program.rpc.testExecutable({ accounts: { @@ -291,243 +456,6 @@ const miscTest = ( ); }); - it("Can use enum in idl", async () => { - const resp1 = await program.methods - .testInputEnum({ first: {} }) - .simulate(); - const event1 = resp1.events[0].data as IdlEvents< - typeof program.idl - >["E7"]; - assert.deepEqual(event1.data.first, {}); - - const resp2 = await program.methods - .testInputEnum({ second: { x: new BN(1), y: new BN(2) } }) - .simulate(); - const event2 = resp2.events[0].data as IdlEvents< - typeof program.idl - >["E7"]; - assert.isTrue(new BN(1).eq(event2.data.second.x)); - assert.isTrue(new BN(2).eq(event2.data.second.y)); - - const resp3 = await program.methods - .testInputEnum({ - tupleStructTest: [ - { data1: 1, data2: 11, data3: 111, data4: new BN(1111) }, - ], - }) - .simulate(); - const event3 = resp3.events[0].data as IdlEvents< - typeof program.idl - >["E7"]; - assert.strictEqual(event3.data.tupleStructTest[0].data1, 1); - assert.strictEqual(event3.data.tupleStructTest[0].data2, 11); - assert.strictEqual(event3.data.tupleStructTest[0].data3, 111); - assert.isTrue(event3.data.tupleStructTest[0].data4.eq(new BN(1111))); - - const resp4 = await program.methods - .testInputEnum({ tupleTest: [1, 2, 3, 4] }) - .simulate(); - const event4 = resp4.events[0].data as IdlEvents< - typeof program.idl - >["E7"]; - assert.strictEqual(event4.data.tupleTest[0], 1); - assert.strictEqual(event4.data.tupleTest[1], 2); - assert.strictEqual(event4.data.tupleTest[2], 3); - assert.strictEqual(event4.data.tupleTest[3], 4); - }); - - let dataI8; - - it("Can use i8 in the idl", async () => { - dataI8 = anchor.web3.Keypair.generate(); - await program.rpc.testI8(-3, { - accounts: { - data: dataI8.publicKey, - }, - instructions: [await program.account.dataI8.createInstruction(dataI8)], - signers: [dataI8], - }); - const dataAccount = await program.account.dataI8.fetch(dataI8.publicKey); - assert.strictEqual(dataAccount.data, -3); - }); - - let dataPubkey; - - it("Can use i16 in the idl", async () => { - const data = anchor.web3.Keypair.generate(); - await program.rpc.testI16(-2048, { - accounts: { - data: data.publicKey, - }, - instructions: [await program.account.dataI16.createInstruction(data)], - signers: [data], - }); - const dataAccount = await program.account.dataI16.fetch(data.publicKey); - assert.strictEqual(dataAccount.data, -2048); - - dataPubkey = data.publicKey; - }); - - it("Can use base58 strings to fetch an account", async () => { - const dataAccount = await program.account.dataI16.fetch( - dataPubkey.toString() - ); - assert.strictEqual(dataAccount.data, -2048); - }); - - it("Should fail to close an account when sending lamports to itself", async () => { - try { - await program.rpc.testClose({ - accounts: { - data: data.publicKey, - solDest: data.publicKey, - }, - }); - expect(false).to.be.true; - } catch (err) { - const errMsg = "A close constraint was violated"; - assert.strictEqual(err.error.errorMessage, errMsg); - assert.strictEqual(err.error.errorCode.number, 2011); - } - }); - - it("Can close an account", async () => { - const connection = program.provider.connection; - const openAccount = await connection.getAccountInfo(data.publicKey); - - assert.isNotNull(openAccount); - const openAccountBalance = openAccount.lamports; - // double balance to calculate closed balance correctly - const transferIx = anchor.web3.SystemProgram.transfer({ - fromPubkey: provider.wallet.publicKey, - toPubkey: data.publicKey, - lamports: openAccountBalance, - }); - const transferTransaction = new anchor.web3.Transaction().add(transferIx); - await provider.sendAndConfirm(transferTransaction); - - let beforeBalance = ( - await connection.getAccountInfo(provider.wallet.publicKey) - ).lamports; - - await program.methods - .testClose() - .accounts({ - data: data.publicKey, - solDest: provider.wallet.publicKey, - }) - .postInstructions([transferIx]) - .rpc(); - - let afterBalance = ( - await connection.getAccountInfo(provider.wallet.publicKey) - ).lamports; - - // Retrieved rent exemption sol. - expect(afterBalance > beforeBalance).to.be.true; - - const closedAccount = await connection.getAccountInfo(data.publicKey); - - assert.isTrue(closedAccount.data.length === 0); - assert.isTrue(closedAccount.owner.equals(SystemProgram.programId)); - }); - - it("Can close an account twice", async () => { - const data = anchor.web3.Keypair.generate(); - await program.methods - .initialize(new anchor.BN(10), new anchor.BN(10)) - .accounts({ data: data.publicKey }) - .preInstructions([await program.account.data.createInstruction(data)]) - .signers([data]) - .rpc(); - - const connection = program.provider.connection; - const openAccount = await connection.getAccountInfo(data.publicKey); - assert.isNotNull(openAccount); - - const openAccountBalance = openAccount.lamports; - // double balance to calculate closed balance correctly - const transferIx = anchor.web3.SystemProgram.transfer({ - fromPubkey: provider.wallet.publicKey, - toPubkey: data.publicKey, - lamports: openAccountBalance, - }); - const transferTransaction = new anchor.web3.Transaction().add(transferIx); - await provider.sendAndConfirm(transferTransaction); - - let beforeBalance = ( - await connection.getAccountInfo(provider.wallet.publicKey) - ).lamports; - - await program.methods - .testCloseTwice() - .accounts({ - data: data.publicKey, - solDest: provider.wallet.publicKey, - }) - .postInstructions([transferIx]) - .rpc(); - - let afterBalance = ( - await connection.getAccountInfo(provider.wallet.publicKey) - ).lamports; - - // Retrieved rent exemption sol. - expect(afterBalance > beforeBalance).to.be.true; - - const closedAccount = await connection.getAccountInfo(data.publicKey); - assert.isTrue(closedAccount.data.length === 0); - assert.isTrue(closedAccount.owner.equals(SystemProgram.programId)); - }); - - it("Can close a mut account manually", async () => { - const data = anchor.web3.Keypair.generate(); - await program.methods - .initialize(new anchor.BN(10), new anchor.BN(10)) - .accounts({ data: data.publicKey }) - .preInstructions([await program.account.data.createInstruction(data)]) - .signers([data]) - .rpc(); - - const connection = program.provider.connection; - const openAccount = await connection.getAccountInfo(data.publicKey); - - assert.isNotNull(openAccount); - const openAccountBalance = openAccount.lamports; - // double balance to calculate closed balance correctly - const transferIx = anchor.web3.SystemProgram.transfer({ - fromPubkey: provider.wallet.publicKey, - toPubkey: data.publicKey, - lamports: openAccountBalance, - }); - const transferTransaction = new anchor.web3.Transaction().add(transferIx); - await provider.sendAndConfirm(transferTransaction); - - let beforeBalance = ( - await connection.getAccountInfo(provider.wallet.publicKey) - ).lamports; - - await program.methods - .testCloseMut() - .accounts({ - data: data.publicKey, - solDest: provider.wallet.publicKey, - }) - .postInstructions([transferIx]) - .rpc(); - - let afterBalance = ( - await connection.getAccountInfo(provider.wallet.publicKey) - ).lamports; - - // Retrieved rent exemption sol. - expect(afterBalance > beforeBalance).to.be.true; - - const closedAccount = await connection.getAccountInfo(data.publicKey); - assert.isTrue(closedAccount.data.length === 0); - assert.isTrue(closedAccount.owner.equals(SystemProgram.programId)); - }); - it("Can use instruction data in accounts constraints", async () => { // b"my-seed" const seed = Buffer.from([109, 121, 45, 115, 101, 101, 100]); @@ -908,28 +836,27 @@ const miscTest = ( mint: newMint.publicKey, payer: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, - mintTokenProgram: TOKEN_PROGRAM_ID, + mintTokenProgram: TOKEN_2022_PROGRAM_ID, }, signers: [newMint], }); - const client = new Token( - program.provider.connection, - newMint.publicKey, - TOKEN_PROGRAM_ID, - wallet.payer + const rawAccount = await provider.connection.getAccountInfo( + newMint.publicKey ); - const mintAccount = await client.getMintInfo(); + const mintAccount = MintLayout.decode(rawAccount.data); assert.strictEqual(mintAccount.decimals, 6); - assert.isTrue( - mintAccount.mintAuthority.equals(provider.wallet.publicKey) + assert.strictEqual( + new PublicKey(mintAccount.mintAuthority).toString(), + provider.wallet.publicKey.toString() ); - assert.isTrue( - mintAccount.freezeAuthority.equals(provider.wallet.publicKey) + assert.strictEqual( + new PublicKey(mintAccount.freezeAuthority).toString(), + provider.wallet.publicKey.toString() ); - const accInfo = await program.provider.connection.getAccountInfo( - newMint.publicKey + assert.strictEqual( + rawAccount.owner.toString(), + TOKEN_2022_PROGRAM_ID.toString() ); - assert.strictEqual(accInfo.owner.toString(), TOKEN_PROGRAM_ID.toString()); }); it("Can create a random token account with token program", async () => { @@ -945,22 +872,20 @@ const miscTest = ( signers: [token], }); - const client = new Token( - program.provider.connection, - mint.publicKey, - TOKEN_PROGRAM_ID, - wallet.payer + const rawAccount = await provider.connection.getAccountInfo( + token.publicKey ); - const account = await client.getAccountInfo(token.publicKey); - // @ts-expect-error - assert.strictEqual(account.state, 1); - assert.strictEqual(account.amount.toNumber(), 0); - assert.isTrue(account.isInitialized); + const ataAccount = AccountLayout.decode(rawAccount.data); + assert.strictEqual(ataAccount.state, 1); + assert.strictEqual(new anchor.BN(ataAccount.amount).toNumber(), 0); assert.strictEqual( - account.owner.toString(), + new PublicKey(ataAccount.owner).toString(), provider.wallet.publicKey.toString() ); - assert.strictEqual(account.mint.toString(), mint.publicKey.toString()); + assert.strictEqual( + new PublicKey(ataAccount.mint).toString(), + mint.publicKey.toString() + ); }); describe("associated_token constraints", () => { @@ -1006,19 +931,19 @@ const miscTest = ( it("Can create an associated token account with token program", async () => { const newMint = anchor.web3.Keypair.generate(); - await program.rpc.testInitMint({ + await program.rpc.testInitMintWithTokenProgram({ accounts: { mint: newMint.publicKey, payer: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, - tokenProgram: TOKEN_PROGRAM_ID, + mintTokenProgram: TOKEN_2022_PROGRAM_ID, }, signers: [newMint], }); const associatedToken = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, newMint.publicKey, provider.wallet.publicKey ); @@ -1029,36 +954,28 @@ const miscTest = ( mint: newMint.publicKey, payer: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, - associatedTokenTokenProgram: TOKEN_PROGRAM_ID, + associatedTokenTokenProgram: TOKEN_2022_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, }, }); - const token = new Token( - program.provider.connection, - newMint.publicKey, - TOKEN_PROGRAM_ID, - wallet.payer + const rawAta = await provider.connection.getAccountInfo( + associatedToken ); - const ataAccount = await token.getAccountInfo(associatedToken); - // @ts-expect-error + const ataAccount = AccountLayout.decode(rawAta.data); assert.strictEqual(ataAccount.state, 1); - assert.strictEqual(ataAccount.amount.toNumber(), 0); - assert.isTrue(ataAccount.isInitialized); + assert.strictEqual(new anchor.BN(ataAccount.amount).toNumber(), 0); assert.strictEqual( - ataAccount.owner.toString(), + new PublicKey(ataAccount.owner).toString(), provider.wallet.publicKey.toString() ); assert.strictEqual( - ataAccount.mint.toString(), + new PublicKey(ataAccount.mint).toString(), newMint.publicKey.toString() ); - const rawAta = await provider.connection.getAccountInfo( - associatedToken - ); assert.strictEqual( rawAta.owner.toBase58(), - TOKEN_PROGRAM_ID.toBase58() + TOKEN_2022_PROGRAM_ID.toBase58() ); }); @@ -1151,31 +1068,31 @@ const miscTest = ( it("associated_token constraints (no init) - Can make with associated_token::token_program", async () => { const mint = anchor.web3.Keypair.generate(); - await program.rpc.testInitMint({ + await program.rpc.testInitMintWithTokenProgram({ accounts: { mint: mint.publicKey, payer: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, - tokenProgram: TOKEN_PROGRAM_ID, + mintTokenProgram: TOKEN_2022_PROGRAM_ID, }, signers: [mint], }); const associatedToken = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, mint.publicKey, provider.wallet.publicKey ); - await program.rpc.testInitAssociatedToken({ + await program.rpc.testInitAssociatedTokenWithTokenProgram({ accounts: { token: associatedToken, mint: mint.publicKey, payer: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, - tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + associatedTokenTokenProgram: TOKEN_2022_PROGRAM_ID, }, signers: [], }); @@ -1184,7 +1101,7 @@ const miscTest = ( token: associatedToken, mint: mint.publicKey, authority: provider.wallet.publicKey, - associatedTokenTokenProgram: TOKEN_PROGRAM_ID, + associatedTokenTokenProgram: TOKEN_2022_PROGRAM_ID, }, }); @@ -1193,7 +1110,7 @@ const miscTest = ( ); assert.strictEqual( account.owner.toString(), - TOKEN_PROGRAM_ID.toString() + TOKEN_2022_PROGRAM_ID.toString() ); }); @@ -1452,27 +1369,6 @@ const miscTest = ( assert.deepStrictEqual(dataAccount.data, dataArray); }); - it("Should include BASE const in IDL", async () => { - assert.isDefined( - miscIdl.constants.find( - (c) => - c.name === "BASE" && c.type === "u128" && c.value === "1_000_000" - ) - ); - }); - - it("Should include DECIMALS const in IDL", async () => { - assert.isDefined( - miscIdl.constants.find( - (c) => c.name === "DECIMALS" && c.type === "u8" && c.value === "6" - ) - ); - }); - - it("Should not include NO_IDL const in IDL", async () => { - assert.isUndefined(miscIdl.constants.find((c) => c.name === "NO_IDL")); - }); - it("init_if_needed creates mint account if not exists", async () => { const newMint = anchor.web3.Keypair.generate(); @@ -1630,25 +1526,21 @@ const miscTest = ( }, signers: [newToken], }); - const mintClient = new Token( - provider.connection, - newMint.publicKey, - TOKEN_PROGRAM_ID, - wallet.payer + + const rawAccount = await provider.connection.getAccountInfo( + newToken.publicKey ); - const tokenAccount = await mintClient.getAccountInfo(newToken.publicKey); - assert.strictEqual(tokenAccount.amount.toNumber(), 0); + const ataAccount = AccountLayout.decode(rawAccount.data); + assert.strictEqual(new anchor.BN(ataAccount.amount).toNumber(), 0); assert.strictEqual( - tokenAccount.mint.toString(), + new PublicKey(ataAccount.mint).toString(), newMint.publicKey.toString() ); assert.strictEqual( - tokenAccount.owner.toString(), + new PublicKey(ataAccount.owner).toString(), provider.wallet.publicKey.toString() ); - const rawAccount = await provider.connection.getAccountInfo( - newToken.publicKey - ); + assert.strictEqual( rawAccount.owner.toString(), TOKEN_PROGRAM_ID.toString() @@ -1713,19 +1605,19 @@ const miscTest = ( it("init_if_needed creates associated token account if not exists with token program", async () => { const newMint = anchor.web3.Keypair.generate(); - await program.rpc.testInitMint({ + await program.rpc.testInitMintWithTokenProgram({ accounts: { mint: newMint.publicKey, payer: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, - tokenProgram: TOKEN_PROGRAM_ID, + mintTokenProgram: TOKEN_2022_PROGRAM_ID, }, signers: [newMint], }); const associatedToken = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, newMint.publicKey, provider.wallet.publicKey ); @@ -1737,33 +1629,27 @@ const miscTest = ( payer: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, - associatedTokenTokenProgram: TOKEN_PROGRAM_ID, + associatedTokenTokenProgram: TOKEN_2022_PROGRAM_ID, authority: provider.wallet.publicKey, }, }); - const mintClient = new Token( - provider.connection, - newMint.publicKey, - TOKEN_PROGRAM_ID, - wallet.payer + const rawAccount = await provider.connection.getAccountInfo( + associatedToken ); - const ataAccount = await mintClient.getAccountInfo(associatedToken); - assert.strictEqual(ataAccount.amount.toNumber(), 0); + const ataAccount = AccountLayout.decode(rawAccount.data); + assert.strictEqual(new anchor.BN(ataAccount.amount).toNumber(), 0); assert.strictEqual( - ataAccount.mint.toString(), + new PublicKey(ataAccount.mint).toString(), newMint.publicKey.toString() ); assert.strictEqual( - ataAccount.owner.toString(), + new PublicKey(ataAccount.owner).toString(), provider.wallet.publicKey.toString() ); - const rawAccount = await provider.connection.getAccountInfo( - associatedToken - ); assert.strictEqual( rawAccount.owner.toString(), - TOKEN_PROGRAM_ID.toString() + TOKEN_2022_PROGRAM_ID.toString() ); }); @@ -2383,7 +2269,7 @@ const miscTest = ( } }); - it("init_if_needed pass if associated token exists with token program", async () => { + it("init_if_needed pass if associated token exists", async () => { const mint = anchor.web3.Keypair.generate(); await program.rpc.testInitMint({ accounts: { @@ -2413,13 +2299,56 @@ const miscTest = ( }, }); + await program.rpc.testInitAssociatedTokenIfNeeded({ + accounts: { + token: associatedToken, + mint: mint.publicKey, + payer: provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + authority: provider.wallet.publicKey, + }, + }); + }); + + it("init_if_needed pass if associated token exists with token program", async () => { + const mint = anchor.web3.Keypair.generate(); + await program.rpc.testInitMintWithTokenProgram({ + accounts: { + mint: mint.publicKey, + payer: provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + mintTokenProgram: TOKEN_2022_PROGRAM_ID, + }, + signers: [mint], + }); + + const associatedToken = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID, + mint.publicKey, + provider.wallet.publicKey + ); + + await program.rpc.testInitAssociatedTokenWithTokenProgram({ + accounts: { + token: associatedToken, + mint: mint.publicKey, + payer: provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + associatedTokenTokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }, + }); + await program.rpc.testInitAssociatedTokenIfNeededWithTokenProgram({ accounts: { token: associatedToken, mint: mint.publicKey, payer: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, - associatedTokenTokenProgram: TOKEN_PROGRAM_ID, + associatedTokenTokenProgram: TOKEN_2022_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, authority: provider.wallet.publicKey, }, @@ -2629,6 +2558,7 @@ const miscTest = ( }); }); }); + describe("Token Constraint Test", () => { it("Token Constraint Test(no init) - Can make token::mint and token::authority", async () => { const mint = anchor.web3.Keypair.generate(); @@ -3300,22 +3230,6 @@ const miscTest = ( ); assert.isDefined(thisTx); }); - it("Can access enum variant fields using camel case without throwing a type error", async () => { - const anotherProgram = new anchor.Program( - miscIdl, - program.programId, - provider - ); - const enumWrappers = - await anotherProgram.account.coolEnumWrapperAccount.all(); - for (let enumWrapper of enumWrappers) { - // this code never gets run so just putting whatever - console.log(enumWrapper.account.myEnum.variant2?.someSlot); - console.log(enumWrapper.account.myEnum.variant2?.user1); - console.log(enumWrapper.account.myEnum.variant3?.someSlot); - console.log(enumWrapper.account.myEnum.variant3?.user2); - } - }); }); }; }; diff --git a/tests/misc/tsconfig.json b/tests/misc/tsconfig.json index d501e05990..95b932ed50 100644 --- a/tests/misc/tsconfig.json +++ b/tests/misc/tsconfig.json @@ -1,11 +1,10 @@ { - "compilerOptions": { - "types": ["mocha", "chai"], - "typeRoots": ["./node_modules/@types"], - "lib": ["es2015"], - "module": "commonjs", - "target": "es6", - "esModuleInterop": true, - "skipLibCheck": true - }, -} \ No newline at end of file + "compilerOptions": { + "types": ["mocha", "node"], + "lib": ["ES6"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true, + "skipLibCheck": true + } +} diff --git a/tests/package.json b/tests/package.json index 87139e3198..ea3488b888 100644 --- a/tests/package.json +++ b/tests/package.json @@ -18,6 +18,7 @@ "escrow", "events", "floats", + "idl", "ido-pool", "interface", "lockup", diff --git a/tests/zero-copy/rust-toolchain.toml b/tests/zero-copy/rust-toolchain.toml deleted file mode 100644 index 7bd4253328..0000000000 --- a/tests/zero-copy/rust-toolchain.toml +++ /dev/null @@ -1,3 +0,0 @@ -# TODO: Remove when `cargo-test-sbf` works with stable Rust -[toolchain] -channel = "1.66.1-x86_64-unknown-linux-gnu" diff --git a/ts/packages/anchor/package.json b/ts/packages/anchor/package.json index d290f3229c..0ac666de2b 100644 --- a/ts/packages/anchor/package.json +++ b/ts/packages/anchor/package.json @@ -1,6 +1,6 @@ { "name": "@coral-xyz/anchor", - "version": "0.28.1-beta.1", + "version": "0.28.1-beta.2", "description": "Anchor client", "module": "./dist/esm/index.js", "main": "./dist/cjs/index.js", @@ -34,6 +34,7 @@ }, "dependencies": { "@coral-xyz/borsh": "^0.28.0", + "@noble/hashes": "^1.3.1", "@solana/web3.js": "^1.68.0", "base64-js": "^1.5.1", "bn.js": "^5.1.2", @@ -43,7 +44,6 @@ "cross-fetch": "^3.1.5", "crypto-hash": "^1.3.0", "eventemitter3": "^4.0.7", - "js-sha256": "^0.9.0", "pako": "^2.0.3", "snake-case": "^3.0.4", "superstruct": "^0.15.4", diff --git a/ts/packages/anchor/rollup.config.ts b/ts/packages/anchor/rollup.config.ts index bf722778b8..f80591bccb 100644 --- a/ts/packages/anchor/rollup.config.ts +++ b/ts/packages/anchor/rollup.config.ts @@ -39,7 +39,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/anchor/src/coder/borsh/accounts.ts b/ts/packages/anchor/src/coder/borsh/accounts.ts index 5d10b11dd6..742c5bc826 100644 --- a/ts/packages/anchor/src/coder/borsh/accounts.ts +++ b/ts/packages/anchor/src/coder/borsh/accounts.ts @@ -2,16 +2,11 @@ import bs58 from "bs58"; import { Buffer } from "buffer"; import { Layout } from "buffer-layout"; import camelcase from "camelcase"; -import { sha256 } from "js-sha256"; import { Idl, IdlTypeDef } from "../../idl.js"; import { IdlCoder } from "./idl.js"; import { AccountsCoder } from "../index.js"; import { accountSize } from "../common.js"; - -/** - * Number of bytes of the account discriminator. - */ -export const ACCOUNT_DISCRIMINATOR_SIZE = 8; +import { DISCRIMINATOR_SIZE, discriminator } from "./discriminator.js"; /** * Encodes and decodes account objects. @@ -77,7 +72,7 @@ export class BorshAccountsCoder public decodeUnchecked(accountName: A, ix: Buffer): T { // Chop off the discriminator before decoding. - const data = ix.slice(ACCOUNT_DISCRIMINATOR_SIZE); + const data = ix.subarray(DISCRIMINATOR_SIZE); const layout = this.accountLayouts.get(accountName); if (!layout) { throw new Error(`Unknown account: ${accountName}`); @@ -96,9 +91,7 @@ export class BorshAccountsCoder } public size(idlAccount: IdlTypeDef): number { - return ( - ACCOUNT_DISCRIMINATOR_SIZE + (accountSize(this.idl, idlAccount) ?? 0) - ); + return DISCRIMINATOR_SIZE + (accountSize(this.idl, idlAccount) ?? 0); } /** @@ -107,13 +100,10 @@ export class BorshAccountsCoder * @param name The name of the account to calculate the discriminator. */ public static accountDiscriminator(name: string): Buffer { - return Buffer.from( - sha256.digest( - `account:${camelcase(name, { - pascalCase: true, - preserveConsecutiveUppercase: true, - })}` - ) - ).slice(0, ACCOUNT_DISCRIMINATOR_SIZE); + const discriminatorPreimage = `account:${camelcase(name, { + pascalCase: true, + preserveConsecutiveUppercase: true, + })}`; + return discriminator(discriminatorPreimage); } } diff --git a/ts/packages/anchor/src/coder/borsh/discriminator.ts b/ts/packages/anchor/src/coder/borsh/discriminator.ts new file mode 100644 index 0000000000..ec0d24bce6 --- /dev/null +++ b/ts/packages/anchor/src/coder/borsh/discriminator.ts @@ -0,0 +1,10 @@ +import { sha256 } from "@noble/hashes/sha256"; + +/** + * Number of bytes in anchor discriminators + */ +export const DISCRIMINATOR_SIZE = 8; + +export function discriminator(preimage: string): Buffer { + return Buffer.from(sha256(preimage).slice(0, DISCRIMINATOR_SIZE)); +} diff --git a/ts/packages/anchor/src/coder/borsh/event.ts b/ts/packages/anchor/src/coder/borsh/event.ts index 6581c62372..4642ee6028 100644 --- a/ts/packages/anchor/src/coder/borsh/event.ts +++ b/ts/packages/anchor/src/coder/borsh/event.ts @@ -1,11 +1,11 @@ import { Buffer } from "buffer"; import * as base64 from "base64-js"; import { Layout } from "buffer-layout"; -import { sha256 } from "js-sha256"; import { Idl, IdlEvent, IdlTypeDef } from "../../idl.js"; import { Event, EventData } from "../../program/event.js"; import { IdlCoder } from "./idl.js"; import { EventCoder } from "../index.js"; +import { discriminator } from "./discriminator.js"; export class BorshEventCoder implements EventCoder { /** @@ -78,5 +78,5 @@ export class BorshEventCoder implements EventCoder { } export function eventDiscriminator(name: string): Buffer { - return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 8); + return discriminator(`event:${name}`); } diff --git a/ts/packages/anchor/src/coder/borsh/index.ts b/ts/packages/anchor/src/coder/borsh/index.ts index 26ed989c61..daf6cb737d 100644 --- a/ts/packages/anchor/src/coder/borsh/index.ts +++ b/ts/packages/anchor/src/coder/borsh/index.ts @@ -6,7 +6,8 @@ import { BorshTypesCoder } from "./types.js"; import { Coder } from "../index.js"; export { BorshInstructionCoder } from "./instruction.js"; -export { BorshAccountsCoder, ACCOUNT_DISCRIMINATOR_SIZE } from "./accounts.js"; +export { BorshAccountsCoder } from "./accounts.js"; +export { DISCRIMINATOR_SIZE } from "./discriminator.js"; export { BorshEventCoder, eventDiscriminator } from "./event.js"; /** diff --git a/ts/packages/anchor/src/coder/borsh/instruction.ts b/ts/packages/anchor/src/coder/borsh/instruction.ts index d35257583f..91d74f5da1 100644 --- a/ts/packages/anchor/src/coder/borsh/instruction.ts +++ b/ts/packages/anchor/src/coder/borsh/instruction.ts @@ -3,7 +3,6 @@ import { Buffer } from "buffer"; import { Layout } from "buffer-layout"; import camelCase from "camelcase"; import { snakeCase } from "snake-case"; -import { sha256 } from "js-sha256"; import * as borsh from "@coral-xyz/borsh"; import { AccountMeta, PublicKey } from "@solana/web3.js"; import { @@ -21,6 +20,7 @@ import { } from "../../idl.js"; import { IdlCoder } from "./idl.js"; import { InstructionCoder } from "../index.js"; +import { sha256 } from "@noble/hashes/sha256"; /** * Namespace for global instruction function signatures (i.e. functions @@ -352,5 +352,5 @@ function sentenceCase(field: string): string { function sighash(nameSpace: string, ixName: string): Buffer { let name = snakeCase(ixName); let preimage = `${nameSpace}:${name}`; - return Buffer.from(sha256.digest(preimage)).slice(0, 8); + return Buffer.from(sha256(preimage).slice(0, 8)); } diff --git a/ts/packages/anchor/src/coder/common.ts b/ts/packages/anchor/src/coder/common.ts index da31dc58e8..202d10a65e 100644 --- a/ts/packages/anchor/src/coder/common.ts +++ b/ts/packages/anchor/src/coder/common.ts @@ -1,31 +1,32 @@ -import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl.js"; +import { Idl, IdlField, IdlTypeDef, IdlType } from "../idl.js"; import { IdlError } from "../error.js"; -export function accountSize(idl: Idl, idlAccount: IdlTypeDef): number { +export function accountSize(idl: Idl, idlAccount: IdlTypeDef) { if (idlAccount.type.kind === "enum") { - let variantSizes = idlAccount.type.variants.map( - (variant: IdlEnumVariant) => { - if (variant.fields === undefined) { - return 0; - } - return variant.fields - .map((f: IdlField | IdlType) => { - if (!(typeof f === "object" && "name" in f)) { - throw new Error("Tuple enum variants not yet implemented."); - } - return typeSize(idl, f.type); - }) - .reduce((a: number, b: number) => a + b); + const variantSizes = idlAccount.type.variants.map((variant) => { + if (!variant.fields) { + return 0; } - ); + + return variant.fields + .map((f: IdlField | IdlType) => { + // Unnamed enum variant + if (!(typeof f === "object" && "name" in f)) { + return typeSize(idl, f); + } + + // Named enum variant + return typeSize(idl, f.type); + }) + .reduce((acc, size) => acc + size, 0); + }); + return Math.max(...variantSizes) + 1; } - if (idlAccount.type.fields === undefined) { - return 0; - } + return idlAccount.type.fields .map((f) => typeSize(idl, f.type)) - .reduce((a, b) => a + b, 0); + .reduce((acc, size) => acc + size, 0); } // Returns the size of the type in bytes. For variable length types, just return diff --git a/ts/packages/anchor/src/utils/pubkey.ts b/ts/packages/anchor/src/utils/pubkey.ts index ff4e286fef..632aa1765f 100644 --- a/ts/packages/anchor/src/utils/pubkey.ts +++ b/ts/packages/anchor/src/utils/pubkey.ts @@ -1,7 +1,7 @@ import { Buffer } from "buffer"; import { PublicKey } from "@solana/web3.js"; import { Address, translateAddress } from "../program/common.js"; -import { sha256 as sha256Sync } from "js-sha256"; +import { sha256 } from "@noble/hashes/sha256"; // Sync version of web3.PublicKey.createWithSeed. export function createWithSeedSync( @@ -14,8 +14,7 @@ export function createWithSeedSync( Buffer.from(seed), programId.toBuffer(), ]); - const hash = sha256Sync.digest(buffer); - return new PublicKey(Buffer.from(hash)); + return new PublicKey(sha256(buffer)); } export function associated( diff --git a/ts/packages/anchor/src/utils/sha256.ts b/ts/packages/anchor/src/utils/sha256.ts index 64a6c23b60..b7fb2c672e 100644 --- a/ts/packages/anchor/src/utils/sha256.ts +++ b/ts/packages/anchor/src/utils/sha256.ts @@ -1,5 +1,5 @@ -import { sha256 } from "js-sha256"; +import { sha256 } from "@noble/hashes/sha256"; export function hash(data: string): string { - return sha256(data); + return new TextDecoder().decode(sha256(data)); } diff --git a/ts/packages/anchor/src/workspace.ts b/ts/packages/anchor/src/workspace.ts index 3537d4a43a..fe890ce655 100644 --- a/ts/packages/anchor/src/workspace.ts +++ b/ts/packages/anchor/src/workspace.ts @@ -1,12 +1,8 @@ -import camelCase from "camelcase"; import * as toml from "toml"; -import { PublicKey } from "@solana/web3.js"; +import { snakeCase } from "snake-case"; import { Program } from "./program/index.js"; -import { Idl } from "./idl.js"; import { isBrowser } from "./utils/common.js"; -let _populatedWorkspace = false; - /** * The `workspace` namespace provides a convenience API to automatically * search for and deserialize [[Program]] objects defined by compiled IDLs @@ -14,95 +10,77 @@ let _populatedWorkspace = false; * * This API is for Node only. */ -const workspace = new Proxy({} as any, { - get(workspaceCache: { [key: string]: Program }, programName: string) { - if (isBrowser) { - throw new Error("Workspaces aren't available in the browser"); - } +const workspace = new Proxy( + {}, + { + get(workspaceCache: { [key: string]: Program }, programName: string) { + if (isBrowser) { + throw new Error("Workspaces aren't available in the browser"); + } + + // Converting `programName` to snake_case enables the ability to use any + // of the following to access the workspace program: + // `workspace.myProgram`, `workspace.MyProgram`, `workspace["my-program"]`... + programName = snakeCase(programName); + + // Check whether the program name contains any digits + if (/\d/.test(programName)) { + // Numbers cannot be properly converted from camelCase to snake_case, + // e.g. if the `programName` is `myProgram2`, the actual program name could + // be `my_program2` or `my_program_2`. This implementation assumes the + // latter as the default and always converts to `_numbers`. + // + // A solution to the conversion of program names with numbers in them + // would be to always convert the `programName` to camelCase instead of + // snake_case. The problem with this approach is that it would require + // converting everything else e.g. program names in Anchor.toml and IDL + // file names which are both snake_case. + programName = programName + .replace(/\d+/g, (match) => "_" + match) + .replace("__", "_"); + } - const fs = require("fs"); - const process = require("process"); + // Return early if the program is in cache + if (workspaceCache[programName]) return workspaceCache[programName]; - if (!_populatedWorkspace) { + const fs = require("fs"); const path = require("path"); - let projectRoot = process.cwd(); - while (!fs.existsSync(path.join(projectRoot, "Anchor.toml"))) { - const parentDir = path.dirname(projectRoot); - if (parentDir === projectRoot) { - projectRoot = undefined; - } - projectRoot = parentDir; - } + // Override the workspace programs if the user put them in the config. + const anchorToml = toml.parse(fs.readFileSync("Anchor.toml")); + const clusterId = anchorToml.provider.cluster; + const programEntry = anchorToml.programs?.[clusterId]?.[programName]; - if (projectRoot === undefined) { - throw new Error("Could not find workspace root."); + let idlPath: string; + let programId; + if (typeof programEntry === "object" && programEntry.idl) { + idlPath = programEntry.idl; + programId = programEntry.address; + } else { + idlPath = path.join("target", "idl", `${programName}.json`); } - const idlFolder = `${projectRoot}/target/idl`; - if (!fs.existsSync(idlFolder)) { + if (!fs.existsSync(idlPath)) { throw new Error( - `${idlFolder} doesn't exist. Did you use "anchor build"?` + `${idlPath} doesn't exist. Did you run \`anchor build\`?` ); } - const idlMap = new Map(); - fs.readdirSync(idlFolder) - .filter((file) => file.endsWith(".json")) - .forEach((file) => { - const filePath = `${idlFolder}/${file}`; - const idlStr = fs.readFileSync(filePath); - const idl = JSON.parse(idlStr); - idlMap.set(idl.name, idl); - const name = camelCase(idl.name, { pascalCase: true }); - if (idl.metadata && idl.metadata.address) { - workspaceCache[name] = new Program( - idl, - new PublicKey(idl.metadata.address) - ); - } - }); - - // Override the workspace programs if the user put them in the config. - const anchorToml = toml.parse( - fs.readFileSync(path.join(projectRoot, "Anchor.toml"), "utf-8") - ); - const clusterId = anchorToml.provider.cluster; - if (anchorToml.programs && anchorToml.programs[clusterId]) { - attachWorkspaceOverride( - workspaceCache, - anchorToml.programs[clusterId], - idlMap - ); + const idl = JSON.parse(fs.readFileSync(idlPath)); + if (!programId) { + if (!idl.metadata?.address) { + throw new Error( + `IDL for program \`${programName}\` does not have \`metadata.address\` field.\n` + + "To add the missing field, run `anchor deploy` or `anchor test`." + ); + } + programId = idl.metadata.address; } + workspaceCache[programName] = new Program(idl, programId); - _populatedWorkspace = true; - } - - return workspaceCache[programName]; - }, -}); - -function attachWorkspaceOverride( - workspaceCache: { [key: string]: Program }, - overrideConfig: { [key: string]: string | { address: string; idl?: string } }, - idlMap: Map -) { - Object.keys(overrideConfig).forEach((programName) => { - const wsProgramName = camelCase(programName, { pascalCase: true }); - const entry = overrideConfig[programName]; - const overrideAddress = new PublicKey( - typeof entry === "string" ? entry : entry.address - ); - let idl = idlMap.get(programName); - if (typeof entry !== "string" && entry.idl) { - idl = JSON.parse(require("fs").readFileSync(entry.idl, "utf-8")); - } - if (!idl) { - throw new Error(`Error loading workspace IDL for ${programName}`); - } - workspaceCache[wsProgramName] = new Program(idl, overrideAddress); - }); -} + return workspaceCache[programName]; + }, + } +); export default workspace; diff --git a/ts/packages/anchor/tests/coder-accounts.spec.ts b/ts/packages/anchor/tests/coder-accounts.spec.ts index 18f8e817a2..c3da53324a 100644 --- a/ts/packages/anchor/tests/coder-accounts.spec.ts +++ b/ts/packages/anchor/tests/coder-accounts.spec.ts @@ -1,8 +1,7 @@ import * as assert from "assert"; -import { createNodeArray } from "typescript"; import { BorshCoder } from "../src"; -import { sha256 } from "js-sha256"; -import { ACCOUNT_DISCRIMINATOR_SIZE } from "../src/coder/borsh/accounts"; +import { DISCRIMINATOR_SIZE } from "../src/coder/borsh/discriminator"; +import { sha256 } from "@noble/hashes/sha256"; describe("coder.accounts", () => { test("Can encode and decode user-defined accounts, including those with consecutive capital letters", () => { @@ -40,11 +39,8 @@ describe("coder.accounts", () => { coder.accounts.encode("MemberDAO", memberDAO).then((encoded) => { // start of encoded account = account discriminator assert.deepEqual( - encoded.subarray(0, ACCOUNT_DISCRIMINATOR_SIZE), - Buffer.from(sha256.digest("account:MemberDAO")).subarray( - 0, - ACCOUNT_DISCRIMINATOR_SIZE - ) + encoded.subarray(0, DISCRIMINATOR_SIZE), + Buffer.from(sha256("account:MemberDAO").slice(0, DISCRIMINATOR_SIZE)) ); assert.deepEqual(coder.accounts.decode("MemberDAO", encoded), memberDAO); }); diff --git a/ts/packages/spl-associated-token-account/package.json b/ts/packages/spl-associated-token-account/package.json index d8e6960ed7..5d6e88faa2 100644 --- a/ts/packages/spl-associated-token-account/package.json +++ b/ts/packages/spl-associated-token-account/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-associated-token-account/rollup.config.ts b/ts/packages/spl-associated-token-account/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-associated-token-account/rollup.config.ts +++ b/ts/packages/spl-associated-token-account/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-binary-option/package.json b/ts/packages/spl-binary-option/package.json index cf72c38eda..af0f02194f 100644 --- a/ts/packages/spl-binary-option/package.json +++ b/ts/packages/spl-binary-option/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-binary-option/rollup.config.ts b/ts/packages/spl-binary-option/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-binary-option/rollup.config.ts +++ b/ts/packages/spl-binary-option/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-binary-oracle-pair/package.json b/ts/packages/spl-binary-oracle-pair/package.json index 1403e70444..703fba01f8 100644 --- a/ts/packages/spl-binary-oracle-pair/package.json +++ b/ts/packages/spl-binary-oracle-pair/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-binary-oracle-pair/rollup.config.ts b/ts/packages/spl-binary-oracle-pair/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-binary-oracle-pair/rollup.config.ts +++ b/ts/packages/spl-binary-oracle-pair/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-feature-proposal/package.json b/ts/packages/spl-feature-proposal/package.json index ad6c2ee4d7..c9df957018 100644 --- a/ts/packages/spl-feature-proposal/package.json +++ b/ts/packages/spl-feature-proposal/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-feature-proposal/rollup.config.ts b/ts/packages/spl-feature-proposal/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-feature-proposal/rollup.config.ts +++ b/ts/packages/spl-feature-proposal/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-governance/package.json b/ts/packages/spl-governance/package.json index c76c9055ac..420bdad8f1 100644 --- a/ts/packages/spl-governance/package.json +++ b/ts/packages/spl-governance/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-governance/rollup.config.ts b/ts/packages/spl-governance/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-governance/rollup.config.ts +++ b/ts/packages/spl-governance/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-memo/package.json b/ts/packages/spl-memo/package.json index 039d04d926..e0d3f00ab5 100644 --- a/ts/packages/spl-memo/package.json +++ b/ts/packages/spl-memo/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1" + "@coral-xyz/anchor": "=0.28.1-beta.2" }, "devDependencies": { "@rollup/plugin-commonjs": "=21.0.2", diff --git a/ts/packages/spl-memo/rollup.config.ts b/ts/packages/spl-memo/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-memo/rollup.config.ts +++ b/ts/packages/spl-memo/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-name-service/package.json b/ts/packages/spl-name-service/package.json index 902c701d5b..466c451e61 100644 --- a/ts/packages/spl-name-service/package.json +++ b/ts/packages/spl-name-service/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-name-service/rollup.config.ts b/ts/packages/spl-name-service/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-name-service/rollup.config.ts +++ b/ts/packages/spl-name-service/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-record/package.json b/ts/packages/spl-record/package.json index 92b413b69f..848dc874e9 100644 --- a/ts/packages/spl-record/package.json +++ b/ts/packages/spl-record/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-record/rollup.config.ts b/ts/packages/spl-record/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-record/rollup.config.ts +++ b/ts/packages/spl-record/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-stake-pool/package.json b/ts/packages/spl-stake-pool/package.json index f71205d37d..3525bccfcb 100644 --- a/ts/packages/spl-stake-pool/package.json +++ b/ts/packages/spl-stake-pool/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-stake-pool/rollup.config.ts b/ts/packages/spl-stake-pool/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-stake-pool/rollup.config.ts +++ b/ts/packages/spl-stake-pool/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-stateless-asks/package.json b/ts/packages/spl-stateless-asks/package.json index 17b5d08055..3a7b80ab8c 100644 --- a/ts/packages/spl-stateless-asks/package.json +++ b/ts/packages/spl-stateless-asks/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-stateless-asks/rollup.config.ts b/ts/packages/spl-stateless-asks/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-stateless-asks/rollup.config.ts +++ b/ts/packages/spl-stateless-asks/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-token-lending/package.json b/ts/packages/spl-token-lending/package.json index 32dcb161e7..81b98da650 100644 --- a/ts/packages/spl-token-lending/package.json +++ b/ts/packages/spl-token-lending/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-token-lending/rollup.config.ts b/ts/packages/spl-token-lending/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-token-lending/rollup.config.ts +++ b/ts/packages/spl-token-lending/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-token-swap/package.json b/ts/packages/spl-token-swap/package.json index 5399c6c80f..9e7f4bcd7e 100644 --- a/ts/packages/spl-token-swap/package.json +++ b/ts/packages/spl-token-swap/package.json @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-token-swap/rollup.config.ts b/ts/packages/spl-token-swap/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-token-swap/rollup.config.ts +++ b/ts/packages/spl-token-swap/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/packages/spl-token/package.json b/ts/packages/spl-token/package.json index 7c106f1b1f..9ebf88cf5f 100644 --- a/ts/packages/spl-token/package.json +++ b/ts/packages/spl-token/package.json @@ -1,7 +1,7 @@ { "name": "@coral-xyz/spl-token", "description": "Anchor client for Solana Program Library Token", - "version": "0.28.1-beta.1", + "version": "0.28.1-beta.2", "author": "acheron ", "license": "Apache-2.0", "repository": { @@ -27,7 +27,7 @@ "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@coral-xyz/anchor": "=0.28.1-beta.1", + "@coral-xyz/anchor": "=0.28.1-beta.2", "@native-to-anchor/buffer-layout": "=0.1.0" }, "devDependencies": { diff --git a/ts/packages/spl-token/rollup.config.ts b/ts/packages/spl-token/rollup.config.ts index 52920306b7..223412a3a7 100644 --- a/ts/packages/spl-token/rollup.config.ts +++ b/ts/packages/spl-token/rollup.config.ts @@ -41,7 +41,7 @@ export default { "buffer", "camelcase", "eventemitter3", - "js-sha256", + "@noble/hashes/sha256", "pako", "toml", ], diff --git a/ts/yarn.lock b/ts/yarn.lock index f0d37661b1..83b002b2bd 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -735,6 +735,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.3.tgz#360afc77610e0a61f3417e497dcf36862e4f8111" integrity sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A== +"@noble/hashes@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== + "@noble/secp256k1@^1.6.3": version "1.7.0" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1" @@ -3253,11 +3258,6 @@ jest@27.3.1: import-local "^3.0.2" jest-cli "^27.3.1" -js-sha256@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" - integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"