diff --git a/.github/workflows/reusable-tests.yaml b/.github/workflows/reusable-tests.yaml index b6725485ee..1c7d815a32 100644 --- a/.github/workflows/reusable-tests.yaml +++ b/.github/workflows/reusable-tests.yaml @@ -358,9 +358,8 @@ jobs: path: ~/.cargo/bin/ - run: chmod +x ~/.cargo/bin/anchor - # TODO: Re-enable once https://github.com/solana-labs/solana/issues/33504 is resolved - # - run: cd "$(mktemp -d)" && anchor init hello-anchor && cd hello-anchor && yarn link @coral-xyz/anchor && yarn && anchor test && yarn lint:fix - # - uses: ./.github/actions/git-diff/ + - run: cd "$(mktemp -d)" && anchor init hello-anchor && cd hello-anchor && yarn link @coral-xyz/anchor && yarn && anchor test && yarn lint:fix + - uses: ./.github/actions/git-diff/ test-programs: needs: setup-anchor-cli diff --git a/CHANGELOG.md b/CHANGELOG.md index a28327afe4..7d9e1fe469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,10 @@ The minor version will be incremented upon a breaking change and the patch versi - cli: Add completions command to generate shell completions via the clap_complete crate ([#3251](https://github.com/coral-xyz/anchor/pull/3251)). - cli: Always convert IDLs ([#3265](https://github.com/coral-xyz/anchor/pull/3265)). - cli: Check whether the `idl-build` feature exists when using the `idl build` command ([#3273](https://github.com/coral-xyz/anchor/pull/3273)). +- cli: Build IDL if there is only one program when using the `idl build` command ([#3275](https://github.com/coral-xyz/anchor/pull/3275)). +- cli: Add short alias for the `idl build` command ([#3283](https://github.com/coral-xyz/anchor/pull/3283)). +- cli: Add `--program-id` option to `idl convert` command ([#3309](https://github.com/coral-xyz/anchor/pull/3309)). +- lang: Generate documentation of constants in `declare_program!` ([#3311](https://github.com/coral-xyz/anchor/pull/3311)). - spl: Add 'Interest Bearing Config` Extension ([#3278](https://github.com/coral-xyz/anchor/pull/3278)). ### Fixes @@ -76,6 +80,12 @@ The minor version will be incremented upon a breaking change and the patch versi - idl: Fix using full path types with `Program` ([#3228](https://github.com/coral-xyz/anchor/pull/3228)). - lang: Use closures for `init` constraints to reduce the stack usage of `try_accounts` ([#2939](https://github.com/coral-xyz/anchor/pull/2939)). - lang: Allow the `cfg` attribute above the instructions ([#2339](https://github.com/coral-xyz/anchor/pull/2339)). +- idl: Log output with `ANCHOR_LOG` on failure and improve build error message ([#3284](https://github.com/coral-xyz/anchor/pull/3284)). +- lang: Fix constant bytes declarations when using `declare_program!` ([#3287](https://github.com/coral-xyz/anchor/pull/3287)). +- lang: Fix using non-instruction composite accounts with `declare_program!` ([#3290](https://github.com/coral-xyz/anchor/pull/3290)). +- idl: Fix instructions with tuple parameters not producing an error([#3294](https://github.com/coral-xyz/anchor/pull/3294)). +- ts: Update `engines.node` to `>= 17` ([#3301](https://github.com/coral-xyz/anchor/pull/3301)). +- cli: Use OS-agnostic paths ([#3307](https://github.com/coral-xyz/anchor/pull/3307)). ### Breaking diff --git a/avm/src/main.rs b/avm/src/main.rs index f4c21a2b2a..4d7f7a29e0 100644 --- a/avm/src/main.rs +++ b/avm/src/main.rs @@ -3,8 +3,6 @@ use avm::InstallTarget; use clap::{CommandFactory, Parser, Subcommand}; use semver::Version; -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); - #[derive(Parser)] #[clap(name = "avm", about = "Anchor version manager", version)] pub struct Cli { diff --git a/cli/src/config.rs b/cli/src/config.rs index 5eb6d416d2..b844dfc195 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -243,7 +243,10 @@ impl WithPath { let cargo = Manifest::from_path(path.join("Cargo.toml"))?; let lib_name = cargo.lib_name()?; - let idl_filepath = format!("target/idl/{lib_name}.json"); + let idl_filepath = Path::new("target") + .join("idl") + .join(&lib_name) + .with_extension("json"); let idl = fs::read(idl_filepath) .ok() .map(|bytes| serde_json::from_reader(&*bytes)) @@ -257,7 +260,10 @@ impl WithPath { }); } for (lib_name, path) in self.get_solidity_program_list()? { - let idl_filepath = format!("target/idl/{lib_name}.json"); + let idl_filepath = Path::new("target") + .join("idl") + .join(&lib_name) + .with_extension("json"); let idl = fs::read(idl_filepath) .ok() .map(|bytes| serde_json::from_reader(&*bytes)) @@ -1131,7 +1137,7 @@ impl From<_Validator> for Validator { url: _validator.url, ledger: _validator .ledger - .unwrap_or_else(|| DEFAULT_LEDGER_PATH.to_string()), + .unwrap_or_else(|| get_default_ledger_path().display().to_string()), limit_ledger_size: _validator.limit_ledger_size, rpc_port: _validator .rpc_port @@ -1169,7 +1175,10 @@ impl From for _Validator { } } -pub const DEFAULT_LEDGER_PATH: &str = ".anchor/test-ledger"; +pub fn get_default_ledger_path() -> PathBuf { + Path::new(".anchor").join("test-ledger") +} + const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0"; impl Merge for _Validator { @@ -1282,12 +1291,12 @@ impl Program { // Lazily initializes the keypair file with a new key if it doesn't exist. pub fn keypair_file(&self) -> Result> { - let deploy_dir_path = "target/deploy/"; - fs::create_dir_all(deploy_dir_path) - .with_context(|| format!("Error creating directory with path: {deploy_dir_path}"))?; + let deploy_dir_path = Path::new("target").join("deploy"); + fs::create_dir_all(&deploy_dir_path) + .with_context(|| format!("Error creating directory with path: {deploy_dir_path:?}"))?; let path = std::env::current_dir() .expect("Must have current dir") - .join(format!("target/deploy/{}-keypair.json", self.lib_name)); + .join(deploy_dir_path.join(format!("{}-keypair.json", self.lib_name))); if path.exists() { return Ok(WithPath::new( File::open(&path) @@ -1303,11 +1312,10 @@ impl Program { } pub fn binary_path(&self, verifiable: bool) -> PathBuf { - let path = if verifiable { - format!("target/verifiable/{}.so", self.lib_name) - } else { - format!("target/deploy/{}.so", self.lib_name) - }; + let path = Path::new("target") + .join(if verifiable { "verifiable" } else { "deploy" }) + .join(&self.lib_name) + .with_extension("so"); std::env::current_dir() .expect("Must have current dir") @@ -1389,7 +1397,13 @@ macro_rules! home_path { impl Default for $my_struct { fn default() -> Self { - $my_struct(home_dir().unwrap().join($path).display().to_string()) + $my_struct( + home_dir() + .unwrap() + .join($path.replace('/', std::path::MAIN_SEPARATOR_STR)) + .display() + .to_string(), + ) } } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index ce1a93c8a6..31be5dbb17 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,7 +1,7 @@ use crate::config::{ - AnchorPackage, BootstrapMode, BuildConfig, Config, ConfigOverride, Manifest, ProgramArch, - ProgramDeployment, ProgramWorkspace, ScriptsConfig, TestValidator, WithPath, - DEFAULT_LEDGER_PATH, SHUTDOWN_WAIT, STARTUP_WAIT, + get_default_ledger_path, AnchorPackage, BootstrapMode, BuildConfig, Config, ConfigOverride, + Manifest, ProgramArch, ProgramDeployment, ProgramWorkspace, ScriptsConfig, TestValidator, + WithPath, SHUTDOWN_WAIT, STARTUP_WAIT, }; use anchor_client::Cluster; use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY}; @@ -470,6 +470,7 @@ pub enum IdlCommand { program_id: Pubkey, }, /// Generates the IDL for the program using the compilation method. + #[clap(alias = "b")] Build { // Program name to build the IDL of(current dir's program if not specified) #[clap(short, long)] @@ -505,6 +506,9 @@ pub enum IdlCommand { /// Output file for the IDL (stdout if not specified) #[clap(short, long)] out: Option, + /// Address to use (defaults to `metadata.address` value) + #[clap(short, long)] + program_id: Option, }, /// Generate TypeScript type for the IDL Type { @@ -1027,7 +1031,8 @@ fn init( } // Build the migrations directory. - fs::create_dir_all("migrations")?; + let migrations_path = Path::new("migrations"); + fs::create_dir_all(migrations_path)?; let license = get_npm_init_license()?; @@ -1037,8 +1042,7 @@ fn init( let mut package_json = File::create("package.json")?; package_json.write_all(rust_template::package_json(jest, license).as_bytes())?; - let mut deploy = File::create("migrations/deploy.js")?; - + let mut deploy = File::create(migrations_path.join("deploy.js"))?; deploy.write_all(rust_template::deploy_script().as_bytes())?; } else { // Build typescript config @@ -1048,7 +1052,7 @@ fn init( let mut ts_package_json = File::create("package.json")?; ts_package_json.write_all(rust_template::ts_package_json(jest, license).as_bytes())?; - let mut deploy = File::create("migrations/deploy.ts")?; + let mut deploy = File::create(migrations_path.join("deploy.ts"))?; deploy.write_all(rust_template::ts_deploy_script().as_bytes())?; } @@ -1170,7 +1174,11 @@ pub type Files = Vec<(PathBuf, String)>; /// ``` pub fn create_files(files: &Files) -> Result<()> { for (path, content) in files { - let path = Path::new(path); + let path = path + .display() + .to_string() + .replace('/', std::path::MAIN_SEPARATOR_STR); + let path = Path::new(&path); if path.exists() { continue; } @@ -1227,7 +1235,7 @@ pub fn expand( let cfg_parent = workspace_cfg.path().parent().expect("Invalid Anchor.toml"); let cargo = Manifest::discover()?; - let expansions_path = cfg_parent.join(".anchor/expanded-macros"); + let expansions_path = cfg_parent.join(".anchor").join("expanded-macros"); fs::create_dir_all(&expansions_path)?; match cargo { @@ -1346,13 +1354,13 @@ pub fn build( let idl_out = match idl { Some(idl) => Some(PathBuf::from(idl)), - None => Some(cfg_parent.join("target/idl")), + None => Some(cfg_parent.join("target").join("idl")), }; fs::create_dir_all(idl_out.as_ref().unwrap())?; let idl_ts_out = match idl_ts { Some(idl_ts) => Some(PathBuf::from(idl_ts)), - None => Some(cfg_parent.join("target/types")), + None => Some(cfg_parent.join("target").join("types")), }; fs::create_dir_all(idl_ts_out.as_ref().unwrap())?; @@ -1562,9 +1570,10 @@ fn build_cwd_verifiable( ) -> Result<()> { // Create output dirs. let workspace_dir = cfg.path().parent().unwrap().canonicalize()?; - fs::create_dir_all(workspace_dir.join("target/verifiable"))?; - fs::create_dir_all(workspace_dir.join("target/idl"))?; - fs::create_dir_all(workspace_dir.join("target/types"))?; + let target_dir = workspace_dir.join("target"); + fs::create_dir_all(target_dir.join("verifiable"))?; + fs::create_dir_all(target_dir.join("idl"))?; + fs::create_dir_all(target_dir.join("types"))?; if !&cfg.workspace.types.is_empty() { fs::create_dir_all(workspace_dir.join(&cfg.workspace.types))?; } @@ -1594,12 +1603,20 @@ fn build_cwd_verifiable( let idl = generate_idl(cfg, skip_lint, no_docs, &cargo_args)?; // Write out the JSON file. println!("Writing the IDL file"); - let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.metadata.name)); + let out_file = workspace_dir + .join("target") + .join("idl") + .join(&idl.metadata.name) + .with_extension("json"); 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.metadata.name)); + let ts_file = workspace_dir + .join("target") + .join("types") + .join(&idl.metadata.name) + .with_extension("ts"); fs::write(&ts_file, idl_ts(&idl)?)?; // Copy out the TypeScript type. @@ -1801,7 +1818,12 @@ fn docker_build_bpf( println!("Copying out the build artifacts"); let out_file = cfg_parent .canonicalize()? - .join(format!("target/verifiable/{binary_name}.so")) + .join( + Path::new("target") + .join("verifiable") + .join(&binary_name) + .with_extension("so"), + ) .display() .to_string(); @@ -2052,8 +2074,10 @@ fn verify( .path() .parent() .ok_or_else(|| anyhow!("Unable to find workspace root"))? - .join("target/verifiable/") - .join(format!("{binary_name}.so")); + .join("target") + .join("verifiable") + .join(&binary_name) + .with_extension("so"); let url = cluster_url(&cfg, &cfg.test_validator); let bin_ver = verify_bin(program_id, &bin_path, &url)?; @@ -2281,7 +2305,11 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> { cargo_args, ), IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out), - IdlCommand::Convert { path, out } => idl_convert(path, out), + IdlCommand::Convert { + path, + out, + program_id, + } => idl_convert(path, out, program_id), IdlCommand::Type { path, out } => idl_type(path, out), } } @@ -2728,11 +2756,16 @@ fn idl_build( Some(name) => cfg.get_program(&name)?.path, None => { let current_dir = std::env::current_dir()?; - cfg.read_all_programs()? - .into_iter() - .find(|program| program.path == current_dir) - .ok_or_else(|| anyhow!("Not in a program directory"))? - .path + let programs = cfg.read_all_programs()?; + if programs.len() == 1 { + programs.into_iter().next().unwrap().path + } else { + programs + .into_iter() + .find(|program| program.path == current_dir) + .ok_or_else(|| anyhow!("Not in a program directory"))? + .path + } } }; std::env::set_current_dir(program_path)?; @@ -2783,8 +2816,24 @@ fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option write_idl(&idl, out) } -fn idl_convert(path: String, out: Option) -> Result<()> { +fn idl_convert(path: String, out: Option, program_id: Option) -> Result<()> { let idl = fs::read(path)?; + + // Set the `metadata.address` field based on the given `program_id` + let idl = match program_id { + Some(program_id) => { + let mut idl = serde_json::from_slice::(&idl)?; + idl.as_object_mut() + .ok_or_else(|| anyhow!("IDL must be an object"))? + .insert( + "metadata".into(), + serde_json::json!({ "address": program_id.to_string() }), + ); + serde_json::to_vec(&idl)? + } + _ => idl, + }; + let idl = convert_idl(&idl)?; let out = match out { None => OutFile::Stdout, @@ -3411,7 +3460,8 @@ fn validator_flags( idl.address = address; // Persist it. - let idl_out = PathBuf::from("target/idl") + let idl_out = Path::new("target") + .join("idl") .join(&idl.metadata.name) .with_extension("json"); write_idl(idl, OutFile::File(idl_out))?; @@ -3541,20 +3591,24 @@ fn validator_flags( } fn stream_logs(config: &WithPath, rpc_url: &str) -> Result> { - let program_logs_dir = ".anchor/program-logs"; - if Path::new(program_logs_dir).exists() { - fs::remove_dir_all(program_logs_dir)?; + let program_logs_dir = Path::new(".anchor").join("program-logs"); + if program_logs_dir.exists() { + fs::remove_dir_all(&program_logs_dir)?; } - fs::create_dir_all(program_logs_dir)?; + fs::create_dir_all(&program_logs_dir)?; + let mut handles = vec![]; for program in config.read_all_programs()? { - let idl = fs::read(format!("target/idl/{}.json", program.lib_name))?; + let idl_path = Path::new("target") + .join("idl") + .join(&program.lib_name) + .with_extension("json"); + let idl = fs::read(idl_path)?; let idl = convert_idl(&idl)?; - let log_file = File::create(format!( - "{}/{}.{}.log", - program_logs_dir, idl.address, program.lib_name, - ))?; + let log_file = File::create( + program_logs_dir.join(format!("{}.{}.log", idl.address, program.lib_name)), + )?; let stdio = std::process::Stdio::from(log_file); let child = std::process::Command::new("solana") .arg("logs") @@ -3568,7 +3622,8 @@ fn stream_logs(config: &WithPath, rpc_url: &str) -> Result) -> Result<( Some(TestValidator { validator: Some(validator), .. - }) => &validator.ledger, - _ => DEFAULT_LEDGER_PATH, + }) => PathBuf::from(&validator.ledger), + _ => get_default_ledger_path(), }; - let ledger_path = Path::new(ledger_path); if !ledger_path.is_relative() { // Prevent absolute paths to avoid someone using / or similar, as the @@ -3698,15 +3752,13 @@ fn test_validator_file_paths(test_validator: &Option) -> Result<( std::process::exit(1); } if ledger_path.exists() { - fs::remove_dir_all(ledger_path)?; + fs::remove_dir_all(&ledger_path)?; } - fs::create_dir_all(ledger_path)?; + fs::create_dir_all(&ledger_path)?; - Ok(( - ledger_path.to_owned(), - ledger_path.join("test-ledger-log.txt"), - )) + let log_path = ledger_path.join("test-ledger-log.txt"); + Ok((ledger_path, log_path)) } fn cluster_url(cfg: &Config, test_validator: &Option) -> String { @@ -3819,7 +3871,8 @@ fn deploy( idl.address = program_id.to_string(); // Persist it. - let idl_out = PathBuf::from("target/idl") + let idl_out = Path::new("target") + .join("idl") .join(&idl.metadata.name) .with_extension("json"); write_idl(idl, OutFile::File(idl_out))?; @@ -4261,12 +4314,14 @@ fn run(cfg_override: &ConfigOverride, script: String, script_args: Vec) } fn login(_cfg_override: &ConfigOverride, token: String) -> Result<()> { - let dir = shellexpand::tilde("~/.config/anchor"); - if !Path::new(&dir.to_string()).exists() { - fs::create_dir(dir.to_string())?; + let anchor_dir = Path::new(&*shellexpand::tilde("~")) + .join(".config") + .join("anchor"); + if !anchor_dir.exists() { + fs::create_dir(&anchor_dir)?; } - std::env::set_current_dir(dir.to_string())?; + std::env::set_current_dir(&anchor_dir)?; // Freely overwrite the entire file since it's not used for anything else. let mut file = File::create("credentials")?; @@ -4461,8 +4516,11 @@ fn registry_api_token(_cfg_override: &ConfigOverride) -> Result { struct Credentials { registry: Registry, } - let filename = shellexpand::tilde("~/.config/anchor/credentials"); - let mut file = File::open(filename.to_string())?; + let filename = Path::new(&*shellexpand::tilde("~")) + .join(".config") + .join("anchor") + .join("credentials"); + let mut file = File::open(filename)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; @@ -4496,6 +4554,7 @@ fn keys_sync(cfg_override: &ConfigOverride, program_name: Option) -> Res .build() .unwrap(); + let mut changed_src = false; for program in cfg.get_programs(program_name)? { // Get the pubkey from the keypair file let actual_program_id = program.pubkey()?.to_string(); @@ -4521,6 +4580,7 @@ fn keys_sync(cfg_override: &ConfigOverride, program_name: Option) -> Res content.replace_range(program_id_match.range(), &actual_program_id); fs::write(&path, content)?; + changed_src = true; println!("Updated to {actual_program_id}\n"); break; } @@ -4549,6 +4609,9 @@ fn keys_sync(cfg_override: &ConfigOverride, program_name: Option) -> Res } println!("All program id declarations are synced."); + if changed_src { + println!("Please rebuild the program to update the generated artifacts.") + } Ok(()) }) @@ -4719,7 +4782,8 @@ fn get_node_dns_option() -> Result<&'static str> { // of spaces in keypair/binary paths, but this should be fixed in the Solana CLI // and removed here. fn strip_workspace_prefix(absolute_path: String) -> String { - let workspace_prefix = std::env::current_dir().unwrap().display().to_string() + "/"; + let workspace_prefix = + std::env::current_dir().unwrap().display().to_string() + std::path::MAIN_SEPARATOR_STR; absolute_path .strip_prefix(&workspace_prefix) .unwrap_or(&absolute_path) diff --git a/docs/src/components/Navigation.jsx b/docs/src/components/Navigation.jsx index 355a724647..47bc3e6aa0 100644 --- a/docs/src/components/Navigation.jsx +++ b/docs/src/components/Navigation.jsx @@ -18,6 +18,9 @@ export function Navigation({ navigation, className }) {
  • +avm install ``` Install the specified version of anchor-cli. The version argument should follow semver versioning. It is also possible to use `latest` as the version argument to install the latest version. +It's also possible to install based on a specific commit hash: + +```shell +# - +avm install 0.30.1-cfe82aa682138f7c6c58bf7a78f48f7d63e9e466 + +# Full commit hash +avm install cfe82aa682138f7c6c58bf7a78f48f7d63e9e466 + +# Short commit hash +avm install cfe82aa +``` + ## List ```shell diff --git a/docs/src/pages/docs/installation.md b/docs/src/pages/docs/installation.md index eaf48a14ed..f840317b2e 100644 --- a/docs/src/pages/docs/installation.md +++ b/docs/src/pages/docs/installation.md @@ -41,6 +41,27 @@ On Linux systems you may need to install additional dependencies if cargo instal sudo apt-get update && sudo apt-get upgrade && sudo apt-get install -y pkg-config build-essential libudev-dev libssl-dev ``` +If you're using `bash`, add `avm` to PATH for `bash`, then reload the shell: + +```shell +echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc +source ~/.bashrc +``` + +If you're using `fish`, add `avm` to PATH for `fish`, then reload the shell: + +```shell +echo "set -gx PATH \$PATH \$HOME/.cargo/bin" >> ~/.config/fish/config.fish +source ~/.config/fish/config.fish +``` + +If you're using `zsh`, add `avm` to PATH for `zsh`, then reload the shell: + +```shell +echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc +``` + Install the latest version of the CLI using `avm`, and then set it to be the version to use. ```shell diff --git a/examples/tutorial/basic-0/package.json b/examples/tutorial/basic-0/package.json index 57b13033bf..daf7cf1fdd 100644 --- a/examples/tutorial/basic-0/package.json +++ b/examples/tutorial/basic-0/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test --skip-lint && anchor clean" diff --git a/examples/tutorial/basic-1/package.json b/examples/tutorial/basic-1/package.json index 349ee92fa2..b34ba6c6dc 100644 --- a/examples/tutorial/basic-1/package.json +++ b/examples/tutorial/basic-1/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test --skip-lint && anchor clean" diff --git a/examples/tutorial/basic-2/package.json b/examples/tutorial/basic-2/package.json index 7e63322454..e8e143660d 100644 --- a/examples/tutorial/basic-2/package.json +++ b/examples/tutorial/basic-2/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test --skip-lint && anchor clean" diff --git a/examples/tutorial/basic-3/package.json b/examples/tutorial/basic-3/package.json index 2f4763adf5..9afe4bc121 100644 --- a/examples/tutorial/basic-3/package.json +++ b/examples/tutorial/basic-3/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test --skip-lint && anchor clean" diff --git a/examples/tutorial/basic-4/package.json b/examples/tutorial/basic-4/package.json index e162df60a2..adeef85e68 100644 --- a/examples/tutorial/basic-4/package.json +++ b/examples/tutorial/basic-4/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test --skip-lint && anchor clean" diff --git a/examples/tutorial/basic-5/package.json b/examples/tutorial/basic-5/package.json index 4455561c29..6f5d086c26 100644 --- a/examples/tutorial/basic-5/package.json +++ b/examples/tutorial/basic-5/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test --skip-lint && anchor clean" diff --git a/idl/src/build.rs b/idl/src/build.rs index 72af71476a..fe706bac8d 100644 --- a/idl/src/build.rs +++ b/idl/src/build.rs @@ -171,8 +171,16 @@ fn build( .current_dir(program_path) .stderr(Stdio::inherit()) .output()?; + + let stdout = String::from_utf8_lossy(&output.stdout); + if env::var("ANCHOR_LOG").is_ok() { + eprintln!("{}", stdout); + } + if !output.status.success() { - return Err(anyhow!("Building IDL failed")); + return Err(anyhow!( + "Building IDL failed. Run `ANCHOR_LOG=true anchor idl build` to see the logs." + )); } enum State { @@ -191,13 +199,8 @@ fn build( let mut types = BTreeMap::new(); let mut idl: Option = None; - let output = String::from_utf8_lossy(&output.stdout); - if env::var("ANCHOR_LOG").is_ok() { - println!("{}", output); - } - let mut state = State::Pass; - for line in output.lines() { + for line in stdout.lines() { match &mut state { State::Pass => match line { "--- IDL begin address ---" => state = State::Address, diff --git a/lang/attribute/program/src/declare_program/mods/constants.rs b/lang/attribute/program/src/declare_program/mods/constants.rs index 4cb6ec0ed3..cf046fa05f 100644 --- a/lang/attribute/program/src/declare_program/mods/constants.rs +++ b/lang/attribute/program/src/declare_program/mods/constants.rs @@ -1,19 +1,25 @@ use anchor_lang_idl::types::{Idl, IdlType}; use quote::{format_ident, quote, ToTokens}; -use super::common::convert_idl_type_to_str; +use super::common::{convert_idl_type_to_syn_type, gen_docs}; pub fn gen_constants_mod(idl: &Idl) -> proc_macro2::TokenStream { let constants = idl.constants.iter().map(|c| { let name = format_ident!("{}", c.name); - let ty = match &c.ty { - IdlType::String => quote!(&str), - _ => parse_expr_ts(&convert_idl_type_to_str(&c.ty)), + let docs = gen_docs(&c.docs); + let val = syn::parse_str::(&c.value) + .unwrap() + .to_token_stream(); + let (ty, val) = match &c.ty { + IdlType::Bytes => (quote!(&[u8]), quote! { &#val }), + IdlType::String => (quote!(&str), val), + _ => (convert_idl_type_to_syn_type(&c.ty).to_token_stream(), val), }; - let val = parse_expr_ts(&c.value); - // TODO: Docs - quote! { pub const #name: #ty = #val; } + quote! { + #docs + pub const #name: #ty = #val; + } }); quote! { @@ -23,7 +29,3 @@ pub fn gen_constants_mod(idl: &Idl) -> proc_macro2::TokenStream { } } } - -fn parse_expr_ts(s: &str) -> proc_macro2::TokenStream { - syn::parse_str::(s).unwrap().to_token_stream() -} diff --git a/lang/attribute/program/src/declare_program/mods/internal.rs b/lang/attribute/program/src/declare_program/mods/internal.rs index 7152f26705..4a394a5979 100644 --- a/lang/attribute/program/src/declare_program/mods/internal.rs +++ b/lang/attribute/program/src/declare_program/mods/internal.rs @@ -1,4 +1,6 @@ -use anchor_lang_idl::types::{Idl, IdlInstructionAccountItem}; +use anchor_lang_idl::types::{ + Idl, IdlInstruction, IdlInstructionAccountItem, IdlInstructionAccounts, +}; use anchor_syn::{ codegen::accounts::{__client_accounts, __cpi_client_accounts}, parser::accounts, @@ -105,8 +107,53 @@ fn gen_internal_accounts_common( idl: &Idl, gen_accounts: impl Fn(&AccountsStruct, proc_macro2::TokenStream) -> proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { - let accounts = idl + // It's possible to declare an accounts struct and not use it as an instruction, see + // https://github.com/coral-xyz/anchor/issues/3274 + fn get_non_instruction_composite_accounts<'a>( + accs: &'a [IdlInstructionAccountItem], + idl: &'a Idl, + ) -> Vec<&'a IdlInstructionAccounts> { + accs.iter() + .flat_map(|acc| match acc { + IdlInstructionAccountItem::Composite(accs) + if !idl + .instructions + .iter() + .any(|ix| ix.accounts == accs.accounts) => + { + let mut non_ix_composite_accs = + get_non_instruction_composite_accounts(&accs.accounts, idl); + if !non_ix_composite_accs.contains(&accs) { + non_ix_composite_accs.push(accs); + } + non_ix_composite_accs + } + _ => Default::default(), + }) + .collect() + } + + let ix_accs = idl .instructions + .iter() + .flat_map(|ix| ix.accounts.to_owned()) + .collect::>(); + let combined_ixs = get_non_instruction_composite_accounts(&ix_accs, idl) + .into_iter() + .map(|accs| IdlInstruction { + // The name is not guaranteed to be the same as the one used in the actual source code + // of the program because the IDL only stores the field names. + name: accs.name.to_owned(), + accounts: accs.accounts.to_owned(), + args: Default::default(), + discriminator: Default::default(), + docs: Default::default(), + returns: Default::default(), + }) + .chain(idl.instructions.iter().cloned()) + .collect::>(); + + let accounts = combined_ixs .iter() .map(|ix| { let ident = format_ident!("{}", ix.name.to_camel_case()); @@ -143,8 +190,7 @@ fn gen_internal_accounts_common( } IdlInstructionAccountItem::Composite(accs) => { let name = format_ident!("{}", accs.name); - let ty_name = idl - .instructions + let ty_name = combined_ixs .iter() .find(|ix| ix.accounts == accs.accounts) .map(|ix| format_ident!("{}", ix.name.to_camel_case())) diff --git a/lang/attribute/program/src/declare_program/mods/utils.rs b/lang/attribute/program/src/declare_program/mods/utils.rs index 4c359c2c4e..6b7ccf9438 100644 --- a/lang/attribute/program/src/declare_program/mods/utils.rs +++ b/lang/attribute/program/src/declare_program/mods/utils.rs @@ -1,8 +1,6 @@ use anchor_lang_idl::types::Idl; use quote::{format_ident, quote}; -use super::common::gen_discriminator; - pub fn gen_utils_mod(idl: &Idl) -> proc_macro2::TokenStream { let account = gen_account(idl); let event = gen_event(idl); @@ -26,9 +24,8 @@ fn gen_account(idl: &Idl) -> proc_macro2::TokenStream { .map(|name| quote! { #name(#name) }); let if_statements = idl.accounts.iter().map(|acc| { let name = format_ident!("{}", acc.name); - let disc = gen_discriminator(&acc.discriminator); quote! { - if value.starts_with(&#disc) { + if value.starts_with(#name::DISCRIMINATOR) { return #name::try_deserialize_unchecked(&mut &value[..]) .map(Self::#name) .map_err(Into::into) @@ -73,11 +70,9 @@ fn gen_event(idl: &Idl) -> proc_macro2::TokenStream { .map(|name| quote! { #name(#name) }); let if_statements = idl.events.iter().map(|ev| { let name = format_ident!("{}", ev.name); - let disc = gen_discriminator(&ev.discriminator); - let disc_len = ev.discriminator.len(); quote! { - if value.starts_with(&#disc) { - return #name::try_from_slice(&value[#disc_len..]) + if value.starts_with(#name::DISCRIMINATOR) { + return #name::try_from_slice(&value[#name::DISCRIMINATOR.len()..]) .map(Self::#name) .map_err(Into::into) } diff --git a/lang/syn/src/idl/program.rs b/lang/syn/src/idl/program.rs index c616a07c34..d7533ead7e 100644 --- a/lang/syn/src/idl/program.rs +++ b/lang/syn/src/idl/program.rs @@ -2,6 +2,7 @@ use anyhow::{anyhow, Result}; use heck::CamelCase; use proc_macro2::TokenStream; use quote::{format_ident, quote}; +use syn::spanned::Spanned; use super::{ common::{gen_print_section, get_idl_module_path, get_no_docs, get_program_path}, @@ -25,10 +26,10 @@ pub fn gen_idl_print_fn_program(program: &Program) -> TokenStream { _ => quote! { vec![] }, }; - let (instructions, defined) = program + let result = program .ixs .iter() - .flat_map(|ix| -> Result<_> { + .map(|ix| { let name = ix.ident.to_string(); let name_pascal = format_ident!("{}", name.to_camel_case()); let ctx_ident = &ix.anchor_ident; @@ -48,7 +49,8 @@ pub fn gen_idl_print_fn_program(program: &Program) -> TokenStream { Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] }, _ => quote! { vec![] }, }; - let (ty, defined) = gen_idl_type(&arg.raw_arg.ty, &[])?; + let (ty, defined) = gen_idl_type(&arg.raw_arg.ty, &[]) + .map_err(|_| syn::Error::new(arg.raw_arg.ty.span(), "Unsupported type"))?; Ok(( quote! { @@ -61,7 +63,7 @@ pub fn gen_idl_print_fn_program(program: &Program) -> TokenStream { defined, )) }) - .collect::>>()? + .collect::>>()? .into_iter() .unzip::<_, Vec<_>, Vec<_>, Vec<_>>(); @@ -91,7 +93,11 @@ pub fn gen_idl_print_fn_program(program: &Program) -> TokenStream { defined, )) }) - .unzip::<_, Vec<_>, Vec<_>, Vec<_>>(); + .collect::>>(); + let (instructions, defined) = match result { + Err(e) => return e.into_compile_error(), + Ok(v) => v.into_iter().unzip::<_, Vec<_>, Vec<_>, Vec<_>>(), + }; let defined = defined.into_iter().flatten().flatten().collect::>(); let fn_body = gen_print_section( diff --git a/tests/anchor-cli-account/package.json b/tests/anchor-cli-account/package.json index 690bdc5362..e4fa548dab 100644 --- a/tests/anchor-cli-account/package.json +++ b/tests/anchor-cli-account/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/anchor-cli-idl/package.json b/tests/anchor-cli-idl/package.json index 4c31edba4c..f57d8f0ae3 100644 --- a/tests/anchor-cli-idl/package.json +++ b/tests/anchor-cli-idl/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "./test.sh" diff --git a/tests/bpf-upgradeable-state/package.json b/tests/bpf-upgradeable-state/package.json index 5caf6fe106..292dbc95ce 100644 --- a/tests/bpf-upgradeable-state/package.json +++ b/tests/bpf-upgradeable-state/package.json @@ -1,16 +1,16 @@ { - "name": "bpf-upgradeable-state", - "version": "0.24.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": ">=11" - } + "name": "bpf-upgradeable-state", + "version": "0.24.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/cashiers-check/package.json b/tests/cashiers-check/package.json index 4c178102ac..7565523a5a 100644 --- a/tests/cashiers-check/package.json +++ b/tests/cashiers-check/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/cfo/package.json b/tests/cfo/package.json index 46ffb91918..131958525e 100644 --- a/tests/cfo/package.json +++ b/tests/cfo/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor run test-with-build" diff --git a/tests/chat/package.json b/tests/chat/package.json index 3a552e0021..82e87fe5ec 100644 --- a/tests/chat/package.json +++ b/tests/chat/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/composite/package.json b/tests/composite/package.json index d937685d5d..2df9657982 100644 --- a/tests/composite/package.json +++ b/tests/composite/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/cpi-returns/package.json b/tests/cpi-returns/package.json index 69d795796a..fa4f77b2ed 100644 --- a/tests/cpi-returns/package.json +++ b/tests/cpi-returns/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor run test-with-build" diff --git a/tests/custom-coder/package.json b/tests/custom-coder/package.json index e0c6039f27..6235b86a49 100644 --- a/tests/custom-coder/package.json +++ b/tests/custom-coder/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test --skip-lint" diff --git a/tests/declare-id/package.json b/tests/declare-id/package.json index 1fc78018e3..4fb315775b 100644 --- a/tests/declare-id/package.json +++ b/tests/declare-id/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/declare-program/idls/external.json b/tests/declare-program/idls/external.json index 676532e246..3bb4e4b641 100644 --- a/tests/declare-program/idls/external.json +++ b/tests/declare-program/idls/external.json @@ -172,6 +172,52 @@ "type": "u32" } ] + }, + { + "name": "update_non_instruction_composite", + "discriminator": [ + 49, + 218, + 69, + 196, + 204, + 66, + 36, + 29 + ], + "accounts": [ + { + "name": "non_instruction_update", + "accounts": [ + { + "name": "authority", + "signer": true + }, + { + "name": "my_account", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "authority" + } + ] + } + }, + { + "name": "program", + "address": "Externa111111111111111111111111111111111111" + } + ] + } + ], + "args": [ + { + "name": "value", + "type": "u32" + } + ] } ], "accounts": [ @@ -229,5 +275,15 @@ ] } } + ], + "constants": [ + { + "name": "MASTER_SEED", + "docs": [ + "Master seed slice" + ], + "type": "bytes", + "value": "[109, 97, 115, 116, 101, 114]" + } ] } \ No newline at end of file diff --git a/tests/declare-program/idls/external_legacy.json b/tests/declare-program/idls/external_legacy.json index 20da7a7fa2..dcf367ffbc 100644 --- a/tests/declare-program/idls/external_legacy.json +++ b/tests/declare-program/idls/external_legacy.json @@ -1,26 +1,55 @@ { "version": "0.1.0", "name": "external", - "metadata": { - "address": "Externa111111111111111111111111111111111111" - }, + "constants": [ + { + "name": "MASTER_SEED", + "type": "bytes", + "value": "[109, 97, 115, 116, 101, 114]" + } + ], "instructions": [ { "name": "init", "accounts": [ - { "name": "authority", "isMut": true, "isSigner": true }, - { "name": "myAccount", "isMut": true, "isSigner": false }, - { "name": "systemProgram", "isMut": false, "isSigner": false } + { + "name": "authority", + "isMut": true, + "isSigner": true + }, + { + "name": "myAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } ], "args": [] }, { "name": "update", "accounts": [ - { "name": "authority", "isMut": false, "isSigner": true }, - { "name": "myAccount", "isMut": true, "isSigner": false } + { + "name": "authority", + "isMut": false, + "isSigner": true + }, + { + "name": "myAccount", + "isMut": true, + "isSigner": false + } ], - "args": [{ "name": "value", "type": "u32" }] + "args": [ + { + "name": "value", + "type": "u32" + } + ] }, { "name": "updateComposite", @@ -28,21 +57,53 @@ { "name": "update", "accounts": [ - { "name": "authority", "isMut": false, "isSigner": true }, - { "name": "myAccount", "isMut": true, "isSigner": false } + { + "name": "authority", + "isMut": false, + "isSigner": true + }, + { + "name": "myAccount", + "isMut": true, + "isSigner": false + } ] } ], - "args": [{ "name": "value", "type": "u32" }] + "args": [ + { + "name": "value", + "type": "u32" + } + ] }, { "name": "testCompilationDefinedTypeParam", - "accounts": [{ "name": "signer", "isMut": false, "isSigner": true }], - "args": [{ "name": "myAccount", "type": { "defined": "MyAccount" } }] + "accounts": [ + { + "name": "signer", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "myAccount", + "type": { + "defined": "MyAccount" + } + } + ] }, { "name": "testCompilationReturnType", - "accounts": [{ "name": "signer", "isMut": false, "isSigner": true }], + "accounts": [ + { + "name": "signer", + "isMut": false, + "isSigner": true + } + ], "args": [], "returns": "bool" } @@ -52,14 +113,28 @@ "name": "MyAccount", "type": { "kind": "struct", - "fields": [{ "name": "field", "type": "u32" }] + "fields": [ + { + "name": "field", + "type": "u32" + } + ] } } ], "events": [ { "name": "MyEvent", - "fields": [{ "name": "value", "type": "u32", "index": false }] + "fields": [ + { + "name": "value", + "type": "u32", + "index": false + } + ] } - ] + ], + "metadata": { + "address": "Externa111111111111111111111111111111111111" + } } diff --git a/tests/declare-program/programs/declare-program/src/lib.rs b/tests/declare-program/programs/declare-program/src/lib.rs index e3dd6cd18a..dc6c36f8f6 100644 --- a/tests/declare-program/programs/declare-program/src/lib.rs +++ b/tests/declare-program/programs/declare-program/src/lib.rs @@ -35,6 +35,7 @@ pub mod declare_program { pub fn cpi_composite(ctx: Context, value: u32) -> Result<()> { let cpi_my_account = &mut ctx.accounts.cpi_my_account; + // Composite accounts that's also an instruction let cpi_ctx = CpiContext::new( ctx.accounts.external_program.to_account_info(), external::cpi::accounts::UpdateComposite { @@ -44,8 +45,22 @@ pub mod declare_program { }, }, ); - external::cpi::update_composite(cpi_ctx, value)?; + external::cpi::update_composite(cpi_ctx, 42)?; + cpi_my_account.reload()?; + require_eq!(cpi_my_account.field, 42); + // Composite accounts but not an actual instruction + let cpi_ctx = CpiContext::new( + ctx.accounts.external_program.to_account_info(), + external::cpi::accounts::UpdateNonInstructionComposite { + non_instruction_update: external::cpi::accounts::NonInstructionUpdate { + authority: ctx.accounts.authority.to_account_info(), + my_account: cpi_my_account.to_account_info(), + program: ctx.accounts.external_program.to_account_info(), + }, + }, + ); + external::cpi::update_non_instruction_composite(cpi_ctx, value)?; cpi_my_account.reload()?; require_eq!(cpi_my_account.field, value); @@ -60,7 +75,7 @@ pub mod declare_program { return Err(ProgramError::Custom(0).into()); } - const DISC: &[u8] = &external::accounts::MyAccount::DISCRIMINATOR; + const DISC: &[u8] = external::accounts::MyAccount::DISCRIMINATOR; // Correct discriminator but invalid data if Account::try_from_bytes(DISC).is_ok() { @@ -84,8 +99,7 @@ pub mod declare_program { return Err(ProgramError::Custom(0).into()); } - const DISC: &[u8] = - &::DISCRIMINATOR; + const DISC: &[u8] = external::events::MyEvent::DISCRIMINATOR; // Correct discriminator but invalid data if Event::try_from_bytes(DISC).is_ok() { diff --git a/tests/declare-program/programs/external/src/lib.rs b/tests/declare-program/programs/external/src/lib.rs index 71dc571db7..86a08ae0b8 100644 --- a/tests/declare-program/programs/external/src/lib.rs +++ b/tests/declare-program/programs/external/src/lib.rs @@ -2,6 +2,10 @@ use anchor_lang::prelude::*; declare_id!("Externa111111111111111111111111111111111111"); +/// Master seed slice +#[constant] +pub const MASTER_SEED: &[u8] = b"master"; + #[program] pub mod external { use super::*; @@ -20,6 +24,15 @@ pub mod external { Ok(()) } + // Test the issue described in https://github.com/coral-xyz/anchor/issues/3274 + pub fn update_non_instruction_composite( + ctx: Context, + value: u32, + ) -> Result<()> { + ctx.accounts.non_instruction_update.my_account.field = value; + Ok(()) + } + // Compilation test for whether a defined type (an account in this case) can be used in `cpi` client. pub fn test_compilation_defined_type_param( _ctx: Context, @@ -61,11 +74,24 @@ pub struct Update<'info> { pub my_account: Account<'info, MyAccount>, } +#[derive(Accounts)] +pub struct NonInstructionUpdate<'info> { + pub authority: Signer<'info>, + #[account(mut, seeds = [authority.key.as_ref()], bump)] + pub my_account: Account<'info, MyAccount>, + pub program: Program<'info, program::External>, +} + #[derive(Accounts)] pub struct UpdateComposite<'info> { pub update: Update<'info>, } +#[derive(Accounts)] +pub struct UpdateNonInstructionComposite<'info> { + pub non_instruction_update: NonInstructionUpdate<'info>, +} + #[account] pub struct MyAccount { pub field: u32, diff --git a/tests/docs/package.json b/tests/docs/package.json index 7fa3731579..dc6f8f9c95 100644 --- a/tests/docs/package.json +++ b/tests/docs/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/errors/package.json b/tests/errors/package.json index 7fa3731579..dc6f8f9c95 100644 --- a/tests/errors/package.json +++ b/tests/errors/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/escrow/package.json b/tests/escrow/package.json index fafedc4363..79f9780519 100644 --- a/tests/escrow/package.json +++ b/tests/escrow/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/events/package.json b/tests/events/package.json index f39b99711d..97a0f70cf9 100644 --- a/tests/events/package.json +++ b/tests/events/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/floats/package.json b/tests/floats/package.json index be38a18bb9..b63e92b890 100644 --- a/tests/floats/package.json +++ b/tests/floats/package.json @@ -1,19 +1,19 @@ { - "name": "floats", - "version": "0.30.1", - "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": ">=11" - }, - "scripts": { - "test": "anchor test" - } + "name": "floats", + "version": "0.30.1", + "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" + }, + "scripts": { + "test": "anchor test" } +} diff --git a/tests/ido-pool/package.json b/tests/ido-pool/package.json index 9731ddb1b8..a937283c12 100644 --- a/tests/ido-pool/package.json +++ b/tests/ido-pool/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/lockup/package.json b/tests/lockup/package.json index ace9f2986f..c15c3c94ee 100644 --- a/tests/lockup/package.json +++ b/tests/lockup/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/misc/package.json b/tests/misc/package.json index 65396b55fa..c5a61b86a2 100644 --- a/tests/misc/package.json +++ b/tests/misc/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/multiple-suites-run-single/package.json b/tests/multiple-suites-run-single/package.json index f11e4edbf7..b57fb8b715 100644 --- a/tests/multiple-suites-run-single/package.json +++ b/tests/multiple-suites-run-single/package.json @@ -1,19 +1,19 @@ { - "name": "multiple-suites-run-single", - "version": "0.24.2", - "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": ">=11" - }, - "scripts": { - "test": "anchor test" - } + "name": "multiple-suites-run-single", + "version": "0.24.2", + "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" + }, + "scripts": { + "test": "anchor test" } +} diff --git a/tests/multiple-suites/package.json b/tests/multiple-suites/package.json index 1d560b537f..4e71e6d58b 100644 --- a/tests/multiple-suites/package.json +++ b/tests/multiple-suites/package.json @@ -1,19 +1,19 @@ { - "name": "multiple-suites", - "version": "0.30.1", - "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": ">=11" - }, - "scripts": { - "test": "anchor test" - } + "name": "multiple-suites", + "version": "0.30.1", + "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" + }, + "scripts": { + "test": "anchor test" } +} diff --git a/tests/multisig/package.json b/tests/multisig/package.json index 5fc36c1314..c4be117b00 100644 --- a/tests/multisig/package.json +++ b/tests/multisig/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/optional/package.json b/tests/optional/package.json index a5914f01cf..e18cb656e0 100644 --- a/tests/optional/package.json +++ b/tests/optional/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/pda-derivation/package.json b/tests/pda-derivation/package.json index 36b78953d2..a018c0d333 100644 --- a/tests/pda-derivation/package.json +++ b/tests/pda-derivation/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/pyth/package.json b/tests/pyth/package.json index 8d09d64ff0..638d3163b6 100644 --- a/tests/pyth/package.json +++ b/tests/pyth/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/realloc/package.json b/tests/realloc/package.json index 149233a774..4bcc3be0c1 100644 --- a/tests/realloc/package.json +++ b/tests/realloc/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/relations-derivation/package.json b/tests/relations-derivation/package.json index 37870c0c2b..ba767ebdf3 100644 --- a/tests/relations-derivation/package.json +++ b/tests/relations-derivation/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/solang/package.json b/tests/solang/package.json index 9063210c76..c97ba8ca5a 100644 --- a/tests/solang/package.json +++ b/tests/solang/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor run test-with-build" diff --git a/tests/spl/token-extensions/package.json b/tests/spl/token-extensions/package.json index 487bc1297f..a8249709a7 100644 --- a/tests/spl/token-extensions/package.json +++ b/tests/spl/token-extensions/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/spl/token-proxy/package.json b/tests/spl/token-proxy/package.json index bda4e6f519..56c6468a02 100644 --- a/tests/spl/token-proxy/package.json +++ b/tests/spl/token-proxy/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/spl/token-wrapper/package.json b/tests/spl/token-wrapper/package.json index ce70ff4b2f..53e22d29ba 100644 --- a/tests/spl/token-wrapper/package.json +++ b/tests/spl/token-wrapper/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/spl/transfer-hook/package.json b/tests/spl/transfer-hook/package.json index c97b24f510..33320fdc1b 100644 --- a/tests/spl/transfer-hook/package.json +++ b/tests/spl/transfer-hook/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/swap/package.json b/tests/swap/package.json index 4327e5e233..50ae1338cc 100644 --- a/tests/swap/package.json +++ b/tests/swap/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/system-accounts/package.json b/tests/system-accounts/package.json index a3211bfe26..4fd0a11f70 100644 --- a/tests/system-accounts/package.json +++ b/tests/system-accounts/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/sysvars/package.json b/tests/sysvars/package.json index 54ffc6e258..6a861fc4e0 100644 --- a/tests/sysvars/package.json +++ b/tests/sysvars/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/tictactoe/package.json b/tests/tictactoe/package.json index a471abb8f3..588d2a2d9f 100644 --- a/tests/tictactoe/package.json +++ b/tests/tictactoe/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/typescript/package.json b/tests/typescript/package.json index c338ff7f3e..5fe7d650e7 100644 --- a/tests/typescript/package.json +++ b/tests/typescript/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/validator-clone/package.json b/tests/validator-clone/package.json index 338257929c..139fc29e18 100644 --- a/tests/validator-clone/package.json +++ b/tests/validator-clone/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/tests/zero-copy/package.json b/tests/zero-copy/package.json index 386538a86b..0c61dbf9b5 100644 --- a/tests/zero-copy/package.json +++ b/tests/zero-copy/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/coral-xyz/anchor.git" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "test": "anchor test" diff --git a/ts/packages/anchor/package.json b/ts/packages/anchor/package.json index 5e78c50279..f96e0f15fe 100644 --- a/ts/packages/anchor/package.json +++ b/ts/packages/anchor/package.json @@ -19,7 +19,7 @@ "access": "public" }, "engines": { - "node": ">=11" + "node": ">=17" }, "scripts": { "build": "rimraf dist/ && yarn build:node && yarn build:browser", diff --git a/ts/packages/anchor/src/program/namespace/methods.ts b/ts/packages/anchor/src/program/namespace/methods.ts index 7cce79dce8..2dec004506 100644 --- a/ts/packages/anchor/src/program/namespace/methods.ts +++ b/ts/packages/anchor/src/program/namespace/methods.ts @@ -230,6 +230,8 @@ export class MethodsBuilder< * * See {@link accounts} and {@link accountsPartial} methods for automatically * resolving accounts. + * + * @param accounts instruction accounts */ public accountsStrict(accounts: Accounts) { this._resolveAccounts = false; @@ -237,16 +239,40 @@ export class MethodsBuilder< return this; } + /** + * Set instruction signers. + * + * Note that calling this method appends the given signers to the existing + * signers (instead of overriding them). + * + * @param signers signers to append + */ public signers(signers: Array) { this._signers = this._signers.concat(signers); return this; } + /** + * Set remaining accounts. + * + * Note that calling this method appends the given accounts to the existing + * remaining accounts (instead of overriding them). + * + * @param accounts remaining accounts + */ public remainingAccounts(accounts: Array) { this._remainingAccounts = this._remainingAccounts.concat(accounts); return this; } + /** + * Set previous instructions. + * + * See {@link postInstructions} to set the post instructions instead. + * + * @param ixs instructions + * @param prepend whether to prepend to the existing previous instructions + */ public preInstructions(ixs: Array, prepend = false) { if (prepend) { this._preInstructions = ixs.concat(this._preInstructions); @@ -256,6 +282,13 @@ export class MethodsBuilder< return this; } + /** + * Set post instructions. + * + * See {@link preInstructions} to set the previous instructions instead. + * + * @param ixs instructions + */ public postInstructions(ixs: Array) { this._postInstructions = this._postInstructions.concat(ixs); return this; @@ -280,58 +313,59 @@ export class MethodsBuilder< return this._accounts; } - public async rpc(options?: ConfirmOptions): Promise { + /** + * Create an instruction based on the current configuration. + * + * See {@link transaction} to create a transaction instead. + * + * @returns the transaction instruction + */ + public async instruction(): Promise { if (this._resolveAccounts) { await this._accountsResolver.resolve(); } // @ts-ignore - return this._rpcFn(...this._args, { + return this._ixFn(...this._args, { accounts: this._accounts, signers: this._signers, remainingAccounts: this._remainingAccounts, preInstructions: this._preInstructions, postInstructions: this._postInstructions, - options, }); } - public async rpcAndKeys(options?: ConfirmOptions): Promise<{ - pubkeys: InstructionAccountAddresses; - signature: TransactionSignature; - }> { - const pubkeys = await this.pubkeys(); - return { - pubkeys: pubkeys as Required>, - signature: await this.rpc(options), - }; - } - - public async view(options?: ConfirmOptions): Promise { + /** + * Create a transaction based on the current configuration. + * + * This method doesn't send the created transaction. Use {@link rpc} method + * to conveniently send an confirm the configured transaction. + * + * See {@link instruction} to only create an instruction instead. + * + * @returns the transaction + */ + public async transaction(): Promise { if (this._resolveAccounts) { await this._accountsResolver.resolve(); } - if (!this._viewFn) { - throw new Error( - [ - "Method does not support views.", - "The instruction should return a value, and its accounts must be read-only", - ].join(" ") - ); - } - // @ts-ignore - return this._viewFn(...this._args, { + return this._txFn(...this._args, { accounts: this._accounts, signers: this._signers, remainingAccounts: this._remainingAccounts, preInstructions: this._preInstructions, postInstructions: this._postInstructions, - options, }); } + /** + * Simulate the configured transaction. + * + * @param options confirmation options + * @returns the simulation response + */ public async simulate(options?: ConfirmOptions): Promise { if (this._resolveAccounts) { await this._accountsResolver.resolve(); @@ -348,52 +382,102 @@ export class MethodsBuilder< }); } - public async instruction(): Promise { + /** + * View the configured transaction. + * + * Note that to use this method, the instruction needs to return a value and + * all its accounts must be read-only. + * + * @param options confirmation options + * @returns the return value of the instruction + */ + public async view(options?: ConfirmOptions): Promise { if (this._resolveAccounts) { await this._accountsResolver.resolve(); } + if (!this._viewFn) { + throw new Error( + [ + "Method does not support views.", + "The instruction should return a value, and its accounts must be read-only", + ].join(" ") + ); + } + // @ts-ignore - return this._ixFn(...this._args, { + return this._viewFn(...this._args, { accounts: this._accounts, signers: this._signers, remainingAccounts: this._remainingAccounts, preInstructions: this._preInstructions, postInstructions: this._postInstructions, + options, }); } /** - * Convenient shortcut to get instructions and pubkeys via: + * Send and confirm the configured transaction. * - * ```ts - * const { pubkeys, instructions } = await method.prepare(); - * ``` + * See {@link rpcAndKeys} to both send the transaction and get the resolved + * account public keys. + * + * @param options confirmation options + * @returns the transaction signature */ - public async prepare(): Promise<{ - pubkeys: Partial>; - instruction: TransactionInstruction; - signers: Signer[]; - }> { - return { - instruction: await this.instruction(), - pubkeys: await this.pubkeys(), - signers: this._signers, - }; - } - - public async transaction(): Promise { + public async rpc(options?: ConfirmOptions): Promise { if (this._resolveAccounts) { await this._accountsResolver.resolve(); } // @ts-ignore - return this._txFn(...this._args, { + return this._rpcFn(...this._args, { accounts: this._accounts, signers: this._signers, remainingAccounts: this._remainingAccounts, preInstructions: this._preInstructions, postInstructions: this._postInstructions, + options, }); } + + /** + * Conveniently call both {@link rpc} and {@link pubkeys} methods. + * + * @param options confirmation options + * @returns the transaction signature and account public keys + */ + public async rpcAndKeys(options?: ConfirmOptions): Promise<{ + signature: TransactionSignature; + pubkeys: InstructionAccountAddresses; + }> { + return { + signature: await this.rpc(options), + pubkeys: (await this.pubkeys()) as Required< + InstructionAccountAddresses + >, + }; + } + + /** + * Get instruction information necessary to include the instruction inside a + * transaction. + * + * # Example + * + * ```ts + * const { instruction, signers, pubkeys } = await method.prepare(); + * ``` + */ + public async prepare(): Promise<{ + instruction: TransactionInstruction; + signers: Signer[]; + pubkeys: Partial>; + }> { + return { + instruction: await this.instruction(), + signers: this._signers, + pubkeys: await this.pubkeys(), + }; + } }