diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 9dd20afb..adf82ad0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -15,6 +15,7 @@ jobs: test-core: name: Core Tests runs-on: ubuntu-18.04 + timeout-minutes: 30 steps: - uses: actions/checkout@v2 - uses: ./.github/actions/setup/ @@ -49,6 +50,7 @@ jobs: setup-anchor-cli: name: Setup Anchor cli runs-on: ubuntu-18.04 + timeout-minutes: 30 steps: - uses: actions/checkout@v2 - uses: ./.github/actions/setup/ @@ -74,6 +76,7 @@ jobs: needs: setup-anchor-cli name: Examples Test runs-on: ubuntu-18.04 + timeout-minutes: 30 steps: - uses: actions/checkout@v2 - uses: actions/download-artifact@v2 @@ -120,7 +123,9 @@ jobs: needs: setup-anchor-cli name: Setup Client Example Test runs-on: ubuntu-18.04 + timeout-minutes: 30 strategy: + fail-fast: false matrix: node: - path: tests/events/ @@ -141,7 +146,7 @@ jobs: name: anchor-binary path: ~/.cargo/bin/ - run: chmod +rwx ~/.cargo/bin/anchor - - run: cd ${{ matrix.node.path }} && anchor build + - run: cd ${{ matrix.node.path }} && anchor build --skip-lint - uses: actions/upload-artifact@v2 with: name: ${{ matrix.node.name }} @@ -151,6 +156,7 @@ jobs: needs: setup-client-example name: Client Example Test runs-on: ubuntu-18.04 + timeout-minutes: 30 steps: - uses: actions/checkout@v2 - uses: ./.github/actions/setup/ @@ -189,6 +195,7 @@ jobs: needs: setup-anchor-cli name: Test tests/bpf-upgradeable-state runs-on: ubuntu-18.04 + timeout-minutes: 30 steps: - uses: actions/checkout@v2 - uses: ./.github/actions/setup/ @@ -223,60 +230,62 @@ jobs: name: start validator - run: cd tests/bpf-upgradeable-state && yarn - run: cd tests/bpf-upgradeable-state && yarn link @project-serum/anchor - - run: cd tests/bpf-upgradeable-state && anchor build + - run: cd tests/bpf-upgradeable-state && anchor build --skip-lint - run: cd tests/bpf-upgradeable-state && solana program deploy --program-id program_with_different_programdata.json target/deploy/bpf_upgradeable_state.so - - run: cd tests/bpf-upgradeable-state && cp bpf_upgradeable_state-keypair.json target/deploy/bpf_upgradeable_state-keypair.json && anchor deploy && anchor test --skip-deploy --skip-build + - run: cd tests/bpf-upgradeable-state && cp bpf_upgradeable_state-keypair.json target/deploy/bpf_upgradeable_state-keypair.json && anchor deploy && anchor test --skip-deploy --skip-build --skip-lint test-programs: needs: setup-anchor-cli name: Test ${{ matrix.node.path }} runs-on: ubuntu-18.04 + timeout-minutes: 30 strategy: + fail-fast: false matrix: node: - - cmd: cd tests/sysvars && anchor test + - cmd: cd tests/sysvars && anchor test --skip-lint path: tests/sysvars - - cmd: cd tests/composite && anchor test + - cmd: cd tests/composite && anchor test --skip-lint path: tests/composite - - cmd: cd tests/errors && anchor test + - cmd: cd tests/errors && anchor test --skip-lint path: tests/errors - - cmd: cd tests/spl/token-proxy && anchor test + - cmd: cd tests/spl/token-proxy && anchor test --skip-lint path: spl/token-proxy - - cmd: cd tests/multisig && anchor test + - cmd: cd tests/multisig && anchor test --skip-lint path: tests/multisig - - cmd: cd tests/interface && anchor test + - cmd: cd tests/interface && anchor test --skip-lint path: tests/interface - - cmd: cd tests/lockup && anchor test + - cmd: cd tests/lockup && anchor test --skip-lint path: tests/lockup - - cmd: cd tests/swap/deps/serum-dex/dex && cargo build-bpf && cd ../../../ && anchor test + - cmd: cd tests/swap/deps/serum-dex/dex && cargo build-bpf && cd ../../../ && anchor test --skip-lint path: tests/swap - - cmd: cd tests/escrow && anchor test + - cmd: cd tests/escrow && anchor test --skip-lint path: tests/escrow - - cmd: cd tests/pyth && anchor test + - cmd: cd tests/pyth && anchor test --skip-lint path: tests/pyth - - cmd: cd tests/system-accounts && anchor test + - cmd: cd tests/system-accounts && anchor test --skip-lint path: tests/system-accounts - - cmd: cd tests/misc && anchor test + - cmd: cd tests/misc && anchor test --skip-lint path: tests/misc - - cmd: cd tests/events && anchor test + - cmd: cd tests/events && anchor test --skip-lint path: tests/events - - cmd: cd tests/cashiers-check && anchor test + - cmd: cd tests/cashiers-check && anchor test --skip-lint path: tests/cashiers-check - - cmd: cd tests/declare-id && anchor test + - cmd: cd tests/declare-id && anchor test --skip-lint path: tests/declare-id - - cmd: cd tests/typescript && anchor test + - cmd: cd tests/typescript && anchor test --skip-lint path: tests/typescript - - cmd: cd tests/zero-copy && anchor test && cd programs/zero-copy && cargo test-bpf + - cmd: cd tests/zero-copy && anchor test --skip-lint && cd programs/zero-copy && cargo test-bpf path: tests/zero-copy - - cmd: cd tests/chat && anchor test + - cmd: cd tests/chat && anchor test --skip-lint path: tests/chat - - cmd: cd tests/ido-pool && anchor test + - cmd: cd tests/ido-pool && anchor test --skip-lint path: tests/ido-pool - cmd: cd tests/cfo && anchor run test-with-build path: tests/cfo - - cmd: cd tests/auction-house && yarn && anchor test + - cmd: cd tests/auction-house && yarn && anchor test --skip-lint path: tests/auction-house - - cmd: cd tests/floats && yarn && anchor test + - cmd: cd tests/floats && yarn && anchor test --skip-lint path: tests/floats - cmd: cd tests/safety-checks && ./test.sh path: tests/safety-checks diff --git a/CHANGELOG.md b/CHANGELOG.md index 198ce1cf..816f26aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,17 @@ incremented for features. ## [Unreleased] +### Fixes + +* cli: Fix rust template ([#1488](https://github.com/project-serum/anchor/pull/1488)). + +## [0.22.0] - 2022-02-20 + ### Features -* lang: add check that declared id == program id ([#1451](https://github.com/project-serum/anchor/pull/1451)) +* lang: Add check that declared id == program id ([#1451](https://github.com/project-serum/anchor/pull/1451)). * ts: Added float types support ([#1425](https://github.com/project-serum/anchor/pull/1425)). +* cli: Add `--skip-lint` option to disable check linting introduced in ([#1452](https://github.com/project-serum/anchor/pull/1452)) for rapid prototyping ([#1482](https://github.com/project-serum/anchor/pull/1482)). ### Fixes @@ -27,6 +34,11 @@ incremented for features. * lang: The `AccountLoader` and `Loader`'s `load_init` method has been removed. Instead, use `load_mut` ([#1415](https://github.com/project-serum/anchor/pull/1415)). * lang: All error-related code is now in the error module ([#1426](https://github.com/project-serum/anchor/pull/1426)). * lang: Require doc comments when using AccountInfo or UncheckedAccount types ([#1452](https://github.com/project-serum/anchor/pull/1452)). +* lang: add [`error!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.error.html) and [`err!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.err.html) macro and `Result` type ([#1462](https://github.com/project-serum/anchor/pull/1462)). +This change will break most programs. Do the following to upgrade: + * change all `ProgramResult`'s to `Result<()>` + * change `#[error]` to `#[error_code]` + * change all `Err(MyError::SomeError.into())` to `Err(error!(MyError::SomeError))` and all `Err(ProgramError::SomeProgramError)` to `Err(ProgramError::SomeProgramError.into())` or `Err(Error::from(ProgramError::SomeProgramError).with_source(source!()))` to provide file and line source of the error (`with_source` is most useful with `ProgramError`s. `error!` already adds source information for custom and anchor internal errors). * ts: `BorshAccountsCoder.accountDiscriminator` method has been replaced with `BorshAccountHeader.discriminator` ([#1415](https://github.com/project-serum/anchor/pull/1415)). ## [0.21.0] - 2022-02-07 diff --git a/Cargo.lock b/Cargo.lock index 2d8c7c58..fd2195df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,7 +56,7 @@ checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483" [[package]] name = "anchor-attribute-access-control" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-syn", "anyhow", @@ -68,7 +68,7 @@ dependencies = [ [[package]] name = "anchor-attribute-account" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-common", "anchor-syn", @@ -82,7 +82,7 @@ dependencies = [ [[package]] name = "anchor-attribute-constant" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-syn", "proc-macro2 1.0.32", @@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "anchor-attribute-error" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-syn", "proc-macro2 1.0.32", @@ -101,7 +101,7 @@ dependencies = [ [[package]] name = "anchor-attribute-event" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-common", "anchor-syn", @@ -113,7 +113,7 @@ dependencies = [ [[package]] name = "anchor-attribute-interface" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-syn", "anyhow", @@ -125,7 +125,7 @@ dependencies = [ [[package]] name = "anchor-attribute-program" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-syn", "anyhow", @@ -136,7 +136,7 @@ dependencies = [ [[package]] name = "anchor-attribute-state" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-syn", "anyhow", @@ -147,7 +147,7 @@ dependencies = [ [[package]] name = "anchor-cli" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-client", "anchor-lang", @@ -179,7 +179,7 @@ dependencies = [ [[package]] name = "anchor-client" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-lang", "anyhow", @@ -202,7 +202,7 @@ dependencies = [ [[package]] name = "anchor-derive-accounts" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-syn", "anyhow", @@ -213,7 +213,7 @@ dependencies = [ [[package]] name = "anchor-lang" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-attribute-access-control", "anchor-attribute-account", @@ -236,7 +236,7 @@ dependencies = [ [[package]] name = "anchor-spl" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anchor-lang", "serum_dex", @@ -247,7 +247,7 @@ dependencies = [ [[package]] name = "anchor-syn" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anyhow", "arrayref", @@ -317,7 +317,7 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "avm" -version = "0.21.0" +version = "0.22.0" dependencies = [ "anyhow", "cfg-if 1.0.0", diff --git a/Makefile b/Makefile index ec4acaa6..bbab90a2 100644 --- a/Makefile +++ b/Makefile @@ -53,27 +53,27 @@ clean: .PHONY: publish publish: cd lang/syn/ && cargo publish && cd ../../ - sleep 10 + sleep 25 cd lang/derive/accounts/ && cargo publish && cd ../../../ - sleep 10 + sleep 25 cd lang/attribute/access-control/ && cargo publish && cd ../../../ - sleep 10 + sleep 25 cd lang/attribute/account/ && cargo publish && cd ../../../ - sleep 10 + sleep 25 cd lang/attribute/constant/ && cargo publish && cd ../../../ - sleep 10 + sleep 25 cd lang/attribute/error/ && cargo publish && cd ../../../ - sleep 10 + sleep 25 cd lang/attribute/interface/ && cargo publish && cd ../../../ - sleep 10 + sleep 25 cd lang/attribute/program/ && cargo publish && cd ../../.. - sleep 10 + sleep 25 cd lang/attribute/state/ && cargo publish && cd ../../../ - sleep 10 + sleep 25 cd lang/attribute/event/ && cargo publish && cd ../../../ - sleep 10 + sleep 25 cd lang/ && cargo publish && cd../ - sleep 10 + sleep 25 cd spl/ && cargo publish && cd ../ - sleep 10 + sleep 25 cd client/ && cargo publish && cd ../ diff --git a/VERSION b/VERSION index 88541566..21574090 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.21.0 +0.22.0 diff --git a/avm/Cargo.toml b/avm/Cargo.toml index cb5e985e..e7f3b8d9 100644 --- a/avm/Cargo.toml +++ b/avm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "avm" -version = "0.21.0" +version = "0.22.0" edition = "2018" [[bin]] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 1cac92da..28523998 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-cli" -version = "0.21.0" +version = "0.22.0" authors = ["armaniferrante "] edition = "2018" diff --git a/cli/npm-package/package.json b/cli/npm-package/package.json index 1ba5478f..510fdec7 100644 --- a/cli/npm-package/package.json +++ b/cli/npm-package/package.json @@ -1,6 +1,6 @@ { "name": "@project-serum/anchor-cli", - "version": "0.21.0", + "version": "0.22.0", "description": "Anchor CLI tool", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/cli/src/config.rs b/cli/src/config.rs index f3c18ca7..16f691d6 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -256,26 +256,10 @@ pub struct Config { pub test: Option, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, Serialize, Deserialize)] pub struct FeaturesConfig { #[serde(default)] pub seeds: bool, - #[serde(default = "default_safety_checks")] - pub safety_checks: bool, -} - -impl Default for FeaturesConfig { - fn default() -> Self { - Self { - seeds: false, - // Anchor safety checks on by default - safety_checks: true, - } - } -} - -fn default_safety_checks() -> bool { - true } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/cli/src/lib.rs b/cli/src/lib.rs index be2b4196..e9bca6b5 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -70,6 +70,10 @@ pub enum Command { /// Output directory for the IDL. #[clap(short, long)] idl: Option, + /// True if the build should not fail even if there are + /// no "CHECK" comments where normally required + #[clap(long)] + skip_lint: bool, /// Output directory for the TypeScript IDL. #[clap(short = 't', long)] idl_ts: Option, @@ -151,6 +155,10 @@ pub enum Command { /// programs. #[clap(long)] skip_deploy: bool, + /// True if the build should not fail even if there are + /// no "CHECK" comments where normally required + #[clap(long)] + skip_lint: bool, /// Flag to skip starting a local validator, if the configured cluster /// url is a localnet. #[clap(long)] @@ -250,6 +258,10 @@ pub enum Command { /// programs. #[clap(long)] skip_deploy: bool, + /// True if the build should not fail even if there are + /// no "CHECK" comments where normally required + #[clap(long)] + skip_lint: bool, /// Arguments to pass to the underlying `cargo build-bpf` command. #[clap( required = false, @@ -360,11 +372,13 @@ pub fn entry(opts: Opts) -> Result<()> { docker_image, bootstrap, cargo_args, + skip_lint, } => build( &opts.cfg_override, idl, idl_ts, verifiable, + skip_lint, program_name, solana_version, docker_image, @@ -407,11 +421,13 @@ pub fn entry(opts: Opts) -> Result<()> { detach, args, cargo_args, + skip_lint, } => test( &opts.cfg_override, skip_deploy, skip_local_validator, skip_build, + skip_lint, detach, args, cargo_args, @@ -430,8 +446,15 @@ pub fn entry(opts: Opts) -> Result<()> { Command::Localnet { skip_build, skip_deploy, + skip_lint, cargo_args, - } => localnet(&opts.cfg_override, skip_build, skip_deploy, cargo_args), + } => localnet( + &opts.cfg_override, + skip_build, + skip_deploy, + skip_lint, + cargo_args, + ), } } @@ -685,6 +708,7 @@ pub fn build( idl: Option, idl_ts: Option, verifiable: bool, + skip_lint: bool, program_name: Option, solana_version: Option, docker_image: Option, @@ -736,6 +760,7 @@ pub fn build( stdout, stderr, cargo_args, + skip_lint, )?, // If the Cargo.toml is at the root, build the entire workspace. Some(cargo) if cargo.path().parent() == cfg.path().parent() => build_all( @@ -747,6 +772,7 @@ pub fn build( stdout, stderr, cargo_args, + skip_lint, )?, // Cargo.toml represents a single package. Build it. Some(cargo) => build_cwd( @@ -758,6 +784,7 @@ pub fn build( stdout, stderr, cargo_args, + skip_lint, )?, } @@ -776,6 +803,7 @@ fn build_all( stdout: Option, // Used for the package registry server. stderr: Option, // Used for the package registry server. cargo_args: Vec, + skip_lint: bool, ) -> Result<()> { let cur_dir = std::env::current_dir()?; let r = match cfg_path.parent() { @@ -791,6 +819,7 @@ fn build_all( stdout.as_ref().map(|f| f.try_clone()).transpose()?, stderr.as_ref().map(|f| f.try_clone()).transpose()?, cargo_args.clone(), + skip_lint, )?; } Ok(()) @@ -811,14 +840,23 @@ fn build_cwd( stdout: Option, stderr: Option, cargo_args: Vec, + skip_lint: bool, ) -> Result<()> { match cargo_toml.parent() { None => return Err(anyhow!("Unable to find parent")), Some(p) => std::env::set_current_dir(&p)?, }; match build_config.verifiable { - false => _build_cwd(cfg, idl_out, idl_ts_out, cargo_args), - true => build_cwd_verifiable(cfg, cargo_toml, build_config, stdout, stderr, cargo_args), + false => _build_cwd(cfg, idl_out, idl_ts_out, skip_lint, cargo_args), + true => build_cwd_verifiable( + cfg, + cargo_toml, + build_config, + stdout, + stderr, + skip_lint, + cargo_args, + ), } } @@ -830,6 +868,7 @@ fn build_cwd_verifiable( build_config: &BuildConfig, stdout: Option, stderr: Option, + skip_lint: bool, cargo_args: Vec, ) -> Result<()> { // Create output dirs. @@ -861,7 +900,7 @@ fn build_cwd_verifiable( Ok(_) => { // Build the idl. println!("Extracting the IDL"); - if let Ok(Some(idl)) = extract_idl(cfg, "src/lib.rs") { + if let Ok(Some(idl)) = extract_idl(cfg, "src/lib.rs", skip_lint) { // Write out the JSON file. println!("Writing the IDL file"); let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.name)); @@ -1121,6 +1160,7 @@ fn _build_cwd( cfg: &WithPath, idl_out: Option, idl_ts_out: Option, + skip_lint: bool, cargo_args: Vec, ) -> Result<()> { let exit = std::process::Command::new("cargo") @@ -1135,7 +1175,7 @@ fn _build_cwd( } // Always assume idl is located at src/lib.rs. - if let Some(idl) = extract_idl(cfg, "src/lib.rs")? { + if let Some(idl) = extract_idl(cfg, "src/lib.rs", skip_lint)? { // JSON out path. let out = match idl_out { None => PathBuf::from(".").join(&idl.name).with_extension("json"), @@ -1192,6 +1232,7 @@ fn verify( None, // idl None, // idl ts true, // verifiable + true, // skip lint None, // program name solana_version.or_else(|| cfg.solana_version.clone()), // solana version docker_image, // docker image @@ -1219,7 +1260,7 @@ fn verify( } // Verify IDL (only if it's not a buffer account). - if let Some(local_idl) = extract_idl(&cfg, "src/lib.rs")? { + if let Some(local_idl) = extract_idl(&cfg, "src/lib.rs", true)? { if bin_ver.state != BinVerificationState::Buffer { let deployed_idl = fetch_idl(cfg_override, program_id)?; if local_idl != deployed_idl { @@ -1383,7 +1424,7 @@ fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result { serde_json::from_slice(&s[..]).map_err(Into::into) } -fn extract_idl(cfg: &WithPath, file: &str) -> Result> { +fn extract_idl(cfg: &WithPath, file: &str, skip_lint: 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)? @@ -1393,6 +1434,7 @@ fn extract_idl(cfg: &WithPath, file: &str) -> Result> { &*file, cfg.features.seeds, cfg.features.safety_checks, + !skip_lint, ) } @@ -1686,7 +1728,7 @@ fn idl_parse( out_ts: Option, ) -> Result<()> { let cfg = Config::discover(cfg_override)?.expect("Not in workspace."); - let idl = extract_idl(&cfg, &file)?.ok_or_else(|| anyhow!("IDL not parsed"))?; + let idl = extract_idl(&cfg, &file, true)?.ok_or_else(|| anyhow!("IDL not parsed"))?; let out = match out { None => OutFile::Stdout, Some(out) => OutFile::File(PathBuf::from(out)), @@ -1726,11 +1768,13 @@ enum OutFile { } // Builds, deploys, and tests all workspace programs in a single command. +#[allow(clippy::too_many_arguments)] fn test( cfg_override: &ConfigOverride, skip_deploy: bool, skip_local_validator: bool, skip_build: bool, + skip_lint: bool, detach: bool, extra_args: Vec, cargo_args: Vec, @@ -1743,6 +1787,7 @@ fn test( None, None, false, + skip_lint, None, None, None, @@ -2667,6 +2712,7 @@ fn publish( None, None, true, + false, Some(program_name), None, None, @@ -2754,6 +2800,7 @@ fn localnet( cfg_override: &ConfigOverride, skip_build: bool, skip_deploy: bool, + skip_lint: bool, cargo_args: Vec, ) -> Result<()> { with_workspace(cfg_override, |cfg| { @@ -2764,6 +2811,7 @@ fn localnet( None, None, false, + skip_lint, None, None, None, diff --git a/cli/src/template.rs b/cli/src/template.rs index a9070d64..4d1d0806 100644 --- a/cli/src/template.rs +++ b/cli/src/template.rs @@ -176,7 +176,8 @@ declare_id!("{}"); #[program] pub mod {} {{ use super::*; - pub fn initialize(ctx: Context) -> ProgramResult {{ + + pub fn initialize(ctx: Context) -> Result<()> {{ Ok(()) }} }} diff --git a/client/Cargo.toml b/client/Cargo.toml index b2464a81..bf99a7e7 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-client" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] edition = "2018" license = "Apache-2.0" @@ -11,7 +11,7 @@ debug = [] deprecated-layout = ["anchor-lang/deprecated-layout"] [dependencies] -anchor-lang = { path = "../lang", version = "0.21.0" } +anchor-lang = { path = "../lang", version = "0.22.0" } anyhow = "1.0.32" regex = "1.4.5" serde = { version = "1.0.122", features = ["derive"] } diff --git a/client/src/lib.rs b/client/src/lib.rs index 106c0d93..4dbfb0bd 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -375,6 +375,8 @@ pub enum ClientError { #[error("Account not found")] AccountNotFound, #[error("{0}")] + AnchorError(#[from] anchor_lang::error::Error), + #[error("{0}")] ProgramError(#[from] ProgramError), #[error("{0}")] SolanaClientError(#[from] SolanaClientError), diff --git a/docs/src/getting-started/installation.md b/docs/src/getting-started/installation.md index 25cd897d..3759de81 100644 --- a/docs/src/getting-started/installation.md +++ b/docs/src/getting-started/installation.md @@ -44,7 +44,7 @@ npm i -g @project-serum/anchor-cli For now, we can use Cargo to install the CLI. ```bash -cargo install --git https://github.com/project-serum/anchor --tag v0.21.0 anchor-cli --locked +cargo install --git https://github.com/project-serum/anchor --tag v0.22.0 anchor-cli --locked ``` On Linux systems you may need to install additional dependencies if `cargo install` fails. On Ubuntu, diff --git a/docs/src/getting-started/publishing.md b/docs/src/getting-started/publishing.md index 5a48d37c..35a3c0fe 100644 --- a/docs/src/getting-started/publishing.md +++ b/docs/src/getting-started/publishing.md @@ -30,7 +30,7 @@ have an `Anchor.toml` to define the build. An example `Anchor.toml` config looks as follows, ```toml -anchor_version = "0.21.0" +anchor_version = "0.22.0" [workspace] members = ["programs/multisig"] diff --git a/docs/src/getting-started/verification.md b/docs/src/getting-started/verification.md index 11d26b17..9b72a68b 100644 --- a/docs/src/getting-started/verification.md +++ b/docs/src/getting-started/verification.md @@ -32,10 +32,10 @@ If the program has an IDL, it will also check the IDL deployed on chain matches. ## Images -A docker image for each version of Anchor is published on [Docker Hub](https://hub.docker.com/r/projectserum/build). They are tagged in the form `projectserum/build:`. For example, to get the image for Anchor `v0.21.0` one can run +A docker image for each version of Anchor is published on [Docker Hub](https://hub.docker.com/r/projectserum/build). They are tagged in the form `projectserum/build:`. For example, to get the image for Anchor `v0.22.0` one can run ``` -docker pull projectserum/build:v0.21.0 +docker pull projectserum/build:v0.22.0 ``` ## Removing an Image diff --git a/docs/src/tutorials/tutorial-4.md b/docs/src/tutorials/tutorial-4.md index 26f99c29..26e63c26 100644 --- a/docs/src/tutorials/tutorial-4.md +++ b/docs/src/tutorials/tutorial-4.md @@ -1,8 +1,8 @@ # Errors If you've ever programmed on a blockchain, you've probably been frustrated by -either non existant or opaque error codes. Anchor attempts to address this by -providing the `#[error]` attribute, which can be used to create typed Errors with +either non existent or opaque error codes. Anchor attempts to address this by +providing the `#[error_code]` attribute, which can be used to create typed Errors with descriptive messages that automatically propagate to the client. ## Defining a Program @@ -16,31 +16,26 @@ use anchor_lang::prelude::*; mod errors { use super::*; pub fn hello(_ctx: Context) -> Result<()> { - Err(ErrorCode::Hello.into()) + Err(error!(ErrorCode::Hello)) } } #[derive(Accounts)] pub struct Hello {} -#[error] +#[error_code] pub enum ErrorCode { #[msg("This is an error message clients will automatically display")] Hello, } ``` -Observe the [#[error]](https://docs.rs/anchor-lang/latest/anchor_lang/attr.error.html) attribute on the `ErrorCode` enum. This macro generates two types: an `Error` and a `Result`, both of which can be used when returning from your program. +Observe the [#[error_code]](https://docs.rs/anchor-lang/latest/anchor_lang/attr.error_code.html) attribute on the `ErrorCode` enum. +This macro generates internal anchor code that helps anchor turn the error code into an error and display it properly. -To use the `Error`, you can simply use the user defined `ErrorCode` with Rust's [From](https://doc.rust-lang.org/std/convert/trait.From.html) trait. If you're unfamiliar with `From`, no worries. Just know that you need to either call -`.into()` when using your `ErrorCode`. Or use Rust's `?` operator, when returning an error. -Both of these will automatically convert *into* the correct `Error`. +To create an error, use the [`error!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.error.html) macro together with an error code. This macro creates an [`AnchorError`](https://docs.rs/anchor-lang/latest/anchor_lang/error/struct.AnchorError.html) that includes helpful information like the file and line the error was created in. -::: details -What's the deal with this From stuff? Well, because the Solana runtime expects a [ProgramError](https://docs.rs/solana-program/1.5.5/solana_program/program_error/enum.ProgramError.html) in the return value. The framework needs to wrap the user defined error code into a -`ProgramError::Code` variant, before returning. The alternative would be to use the -`ProgramError` directly. -::: +To make writing errors even easier, anchor also provides the [`err!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.err.html and the [`require!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.require.html macros. ## Using the Client diff --git a/examples/tutorial/basic-0/package.json b/examples/tutorial/basic-0/package.json index 5db74db5..44b07657 100644 --- a/examples/tutorial/basic-0/package.json +++ b/examples/tutorial/basic-0/package.json @@ -1,6 +1,6 @@ { "name": "basic-0", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { @@ -14,6 +14,6 @@ "node": ">=11" }, "scripts": { - "test": "anchor test" + "test": "anchor test --skip-lint" } } diff --git a/examples/tutorial/basic-0/programs/basic-0/src/lib.rs b/examples/tutorial/basic-0/programs/basic-0/src/lib.rs index 82f0bd07..799fa9e1 100644 --- a/examples/tutorial/basic-0/programs/basic-0/src/lib.rs +++ b/examples/tutorial/basic-0/programs/basic-0/src/lib.rs @@ -5,7 +5,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); #[program] mod basic_0 { use super::*; - pub fn initialize(_ctx: Context) -> ProgramResult { + pub fn initialize(_ctx: Context) -> Result<()> { Ok(()) } } diff --git a/examples/tutorial/basic-1/package.json b/examples/tutorial/basic-1/package.json index 7dbb539a..7ad4e01b 100644 --- a/examples/tutorial/basic-1/package.json +++ b/examples/tutorial/basic-1/package.json @@ -1,6 +1,6 @@ { "name": "basic-1", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { @@ -14,6 +14,6 @@ "node": ">=11" }, "scripts": { - "test": "anchor test" + "test": "anchor test --skip-lint" } } diff --git a/examples/tutorial/basic-1/programs/basic-1/src/lib.rs b/examples/tutorial/basic-1/programs/basic-1/src/lib.rs index a7c5ec05..b2d346ea 100644 --- a/examples/tutorial/basic-1/programs/basic-1/src/lib.rs +++ b/examples/tutorial/basic-1/programs/basic-1/src/lib.rs @@ -6,13 +6,13 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); mod basic_1 { use super::*; - pub fn initialize(ctx: Context, data: u64) -> ProgramResult { + pub fn initialize(ctx: Context, data: u64) -> Result<()> { let my_account = &mut ctx.accounts.my_account; my_account.data = data; Ok(()) } - pub fn update(ctx: Context, data: u64) -> ProgramResult { + pub fn update(ctx: Context, data: u64) -> Result<()> { let my_account = &mut ctx.accounts.my_account; my_account.data = data; Ok(()) diff --git a/examples/tutorial/basic-2/package.json b/examples/tutorial/basic-2/package.json index 8998b9f3..9c12676c 100644 --- a/examples/tutorial/basic-2/package.json +++ b/examples/tutorial/basic-2/package.json @@ -1,6 +1,6 @@ { "name": "basic-2", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { @@ -14,6 +14,6 @@ "node": ">=11" }, "scripts": { - "test": "anchor test" + "test": "anchor test --skip-lint" } } diff --git a/examples/tutorial/basic-2/programs/basic-2/src/lib.rs b/examples/tutorial/basic-2/programs/basic-2/src/lib.rs index 1c6140e2..6b9c66b3 100644 --- a/examples/tutorial/basic-2/programs/basic-2/src/lib.rs +++ b/examples/tutorial/basic-2/programs/basic-2/src/lib.rs @@ -6,14 +6,14 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); mod basic_2 { use super::*; - pub fn create(ctx: Context, authority: Pubkey) -> ProgramResult { + pub fn create(ctx: Context, authority: Pubkey) -> Result<()> { let counter = &mut ctx.accounts.counter; counter.authority = authority; counter.count = 0; Ok(()) } - pub fn increment(ctx: Context) -> ProgramResult { + pub fn increment(ctx: Context) -> Result<()> { let counter = &mut ctx.accounts.counter; counter.count += 1; Ok(()) diff --git a/examples/tutorial/basic-3/package.json b/examples/tutorial/basic-3/package.json index 73d9f226..b596176d 100644 --- a/examples/tutorial/basic-3/package.json +++ b/examples/tutorial/basic-3/package.json @@ -1,6 +1,6 @@ { "name": "basic-3", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { @@ -14,6 +14,6 @@ "node": ">=11" }, "scripts": { - "test": "anchor test" + "test": "anchor test --skip-lint" } } diff --git a/examples/tutorial/basic-3/programs/puppet-master/src/lib.rs b/examples/tutorial/basic-3/programs/puppet-master/src/lib.rs index 90f870a7..29d32bac 100644 --- a/examples/tutorial/basic-3/programs/puppet-master/src/lib.rs +++ b/examples/tutorial/basic-3/programs/puppet-master/src/lib.rs @@ -9,7 +9,7 @@ declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L"); #[program] mod puppet_master { use super::*; - pub fn pull_strings(ctx: Context, data: u64) -> ProgramResult { + pub fn pull_strings(ctx: Context, data: u64) -> anchor_lang::Result<()> { let cpi_program = ctx.accounts.puppet_program.to_account_info(); let cpi_accounts = SetData { puppet: ctx.accounts.puppet.to_account_info(), diff --git a/examples/tutorial/basic-3/programs/puppet/src/lib.rs b/examples/tutorial/basic-3/programs/puppet/src/lib.rs index 1563479e..11646bf3 100644 --- a/examples/tutorial/basic-3/programs/puppet/src/lib.rs +++ b/examples/tutorial/basic-3/programs/puppet/src/lib.rs @@ -5,11 +5,11 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); #[program] pub mod puppet { use super::*; - pub fn initialize(_ctx: Context) -> ProgramResult { + pub fn initialize(_ctx: Context) -> Result<()> { Ok(()) } - pub fn set_data(ctx: Context, data: u64) -> ProgramResult { + pub fn set_data(ctx: Context, data: u64) -> Result<()> { let puppet = &mut ctx.accounts.puppet; puppet.data = data; Ok(()) diff --git a/examples/tutorial/basic-4/package.json b/examples/tutorial/basic-4/package.json index d34c9fdf..a4818eae 100644 --- a/examples/tutorial/basic-4/package.json +++ b/examples/tutorial/basic-4/package.json @@ -1,6 +1,6 @@ { "name": "basic-4", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { @@ -14,6 +14,6 @@ "node": ">=11" }, "scripts": { - "test": "anchor test" + "test": "anchor test --skip-lint" } } diff --git a/examples/tutorial/basic-4/programs/basic-4/src/lib.rs b/examples/tutorial/basic-4/programs/basic-4/src/lib.rs index 0c182a50..79d5d97e 100644 --- a/examples/tutorial/basic-4/programs/basic-4/src/lib.rs +++ b/examples/tutorial/basic-4/programs/basic-4/src/lib.rs @@ -14,16 +14,16 @@ pub mod basic_4 { } impl Counter { - pub fn new(ctx: Context) -> Result { + pub fn new(ctx: Context) -> anchor_lang::Result { Ok(Self { authority: *ctx.accounts.authority.key, count: 0, }) } - pub fn increment(&mut self, ctx: Context) -> Result<()> { + pub fn increment(&mut self, ctx: Context) -> anchor_lang::Result<()> { if &self.authority != ctx.accounts.authority.key { - return Err(ErrorCode::Unauthorized.into()); + return Err(error!(ErrorCode::Unauthorized)); } self.count += 1; Ok(()) @@ -37,7 +37,7 @@ pub struct Auth<'info> { } // #endregion code -#[error] +#[error_code] pub enum ErrorCode { #[msg("You are not authorized to perform this action.")] Unauthorized, diff --git a/examples/tutorial/package.json b/examples/tutorial/package.json index a126bcd1..860b78aa 100644 --- a/examples/tutorial/package.json +++ b/examples/tutorial/package.json @@ -13,7 +13,7 @@ "basic-4" ], "dependencies": { - "@project-serum/anchor": "^0.21.0" + "@project-serum/anchor": "^0.22.0" }, "devDependencies": { "mocha": "^9.1.3", diff --git a/lang/Cargo.toml b/lang/Cargo.toml index 9ddae1d5..60e7109b 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-lang" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" edition = "2018" @@ -30,16 +30,16 @@ anchor-debug = [ ] [dependencies] -anchor-attribute-access-control = { path = "./attribute/access-control", version = "0.21.0" } -anchor-attribute-account = { path = "./attribute/account", version = "0.21.0" } -anchor-attribute-constant = { path = "./attribute/constant", version = "0.21.0" } -anchor-attribute-error = { path = "./attribute/error", version = "0.21.0" } -anchor-attribute-program = { path = "./attribute/program", version = "0.21.0" } -anchor-attribute-state = { path = "./attribute/state", version = "0.21.0" } -anchor-attribute-interface = { path = "./attribute/interface", version = "0.21.0" } -anchor-attribute-event = { path = "./attribute/event", version = "0.21.0" } -anchor-derive-accounts = { path = "./derive/accounts", version = "0.21.0" } -anchor-common = { path = "./common", version = "0.21.0" } +anchor-attribute-access-control = { path = "./attribute/access-control", version = "0.22.0" } +anchor-attribute-account = { path = "./attribute/account", version = "0.22.0" } +anchor-attribute-constant = { path = "./attribute/constant", version = "0.22.0" } +anchor-attribute-error = { path = "./attribute/error", version = "0.22.0" } +anchor-attribute-program = { path = "./attribute/program", version = "0.22.0" } +anchor-attribute-state = { path = "./attribute/state", version = "0.22.0" } +anchor-attribute-interface = { path = "./attribute/interface", version = "0.22.0" } +anchor-attribute-event = { path = "./attribute/event", version = "0.22.0" } +anchor-derive-accounts = { path = "./derive/accounts", version = "0.22.0" } +anchor-common = { path = "./common", version = "0.22.0" } arrayref = "0.3.6" base64 = "0.13.0" borsh = "0.9" diff --git a/lang/attribute/access-control/Cargo.toml b/lang/attribute/access-control/Cargo.toml index 4dc917cf..d51f6022 100644 --- a/lang/attribute/access-control/Cargo.toml +++ b/lang/attribute/access-control/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-attribute-access-control" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" @@ -18,5 +18,5 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0.60", features = ["full"] } anyhow = "1.0.32" -anchor-syn = { path = "../../syn", version = "0.21.0" } +anchor-syn = { path = "../../syn", version = "0.22.0" } regex = "1.0" diff --git a/lang/attribute/account/Cargo.toml b/lang/attribute/account/Cargo.toml index 6693fbf5..6be5d28f 100644 --- a/lang/attribute/account/Cargo.toml +++ b/lang/attribute/account/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-attribute-account" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" @@ -19,7 +19,7 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0.60", features = ["full"] } anyhow = "1.0.32" -anchor-syn = { path = "../../syn", version = "0.21.0", features = ["hash"] } +anchor-syn = { path = "../../syn", version = "0.22.0", features = ["hash"] } anchor-common = { path = "../../common", version = "0.21.0" } rustversion = "1.0.3" bs58 = "0.4.0" diff --git a/lang/attribute/account/src/lib.rs b/lang/attribute/account/src/lib.rs index a3ed7b5e..5483ab1f 100644 --- a/lang/attribute/account/src/lib.rs +++ b/lang/attribute/account/src/lib.rs @@ -149,7 +149,7 @@ pub fn account( // It's expected on-chain programs deserialize via zero-copy. #[automatically_derived] impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause { - fn try_deserialize(buf: &mut &[u8]) -> std::result::Result { + fn try_deserialize(buf: &mut &[u8]) -> std::result::Result { // Header is always 8 bytes. if buf.len() < anchor_lang::accounts::header::HEADER_LEN { return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into()); @@ -161,7 +161,7 @@ pub fn account( Self::try_deserialize_unchecked(buf) } - fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { let data: &[u8] = &buf[8..]; // Re-interpret raw bytes into the POD data structure. let account = anchor_lang::__private::bytemuck::from_bytes(data); @@ -179,7 +179,7 @@ pub fn account( #[automatically_derived] impl #impl_gen anchor_lang::AccountSerialize for #account_name #type_gen #where_clause { - fn try_serialize(&self, buf: &mut [u8]) -> std::result::Result<(), ProgramError> { + fn try_serialize(&self, buf: &mut [u8]) -> anchor_lang::Result<()> { let dst = anchor_lang::accounts::header::read_data_mut(buf); let mut writer = std::io::Cursor::new(dst); AnchorSerialize::serialize( @@ -193,7 +193,7 @@ pub fn account( #[automatically_derived] impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause { - fn try_deserialize(buf: &mut &[u8]) -> std::result::Result { + fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result { if buf.len() < #discriminator.len() { return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into()); } @@ -204,7 +204,7 @@ pub fn account( Self::try_deserialize_unchecked(buf) } - fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { let mut data: &[u8] = &buf[8..]; AnchorDeserialize::deserialize(&mut data) .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into()) diff --git a/lang/attribute/constant/Cargo.toml b/lang/attribute/constant/Cargo.toml index 9c5bc529..1d0870ee 100644 --- a/lang/attribute/constant/Cargo.toml +++ b/lang/attribute/constant/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-attribute-constant" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" @@ -16,4 +16,4 @@ anchor-debug = ["anchor-syn/anchor-debug"] [dependencies] proc-macro2 = "1.0" syn = { version = "1.0.60", features = ["full"] } -anchor-syn = { path = "../../syn", version = "0.21.0" } +anchor-syn = { path = "../../syn", version = "0.22.0" } diff --git a/lang/attribute/error/Cargo.toml b/lang/attribute/error/Cargo.toml index ef30a0dd..e43a0176 100644 --- a/lang/attribute/error/Cargo.toml +++ b/lang/attribute/error/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-attribute-error" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" @@ -17,4 +17,4 @@ anchor-debug = ["anchor-syn/anchor-debug"] proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0.60", features = ["full"] } -anchor-syn = { path = "../../syn", version = "0.21.0" } \ No newline at end of file +anchor-syn = { path = "../../syn", version = "0.22.0" } diff --git a/lang/attribute/error/src/lib.rs b/lang/attribute/error/src/lib.rs index 4f1748d2..0ddb047c 100644 --- a/lang/attribute/error/src/lib.rs +++ b/lang/attribute/error/src/lib.rs @@ -1,9 +1,12 @@ extern crate proc_macro; -use anchor_syn::codegen::error as error_codegen; -use anchor_syn::parser::error as error_parser; +use proc_macro::TokenStream; +use quote::quote; + +use anchor_syn::parser::error::{self as error_parser, ErrorWithAccountNameInput}; use anchor_syn::ErrorArgs; -use syn::parse_macro_input; +use anchor_syn::{codegen, parser::error::ErrorInput}; +use syn::{parse_macro_input, Expr}; /// Generates `Error` and `type Result = Result` types to be /// used as return types from Anchor instruction handlers. Importantly, the @@ -21,14 +24,14 @@ use syn::parse_macro_input; /// mod errors { /// use super::*; /// pub fn hello(_ctx: Context) -> Result<()> { -/// Err(MyError::Hello.into()) +/// Err(error!(MyError::Hello)) /// } /// } /// /// #[derive(Accounts)] /// pub struct Hello {} /// -/// #[error] +/// #[error_code] /// pub enum MyError { /// #[msg("This is an error message clients will automatically display")] /// Hello, @@ -40,14 +43,14 @@ use syn::parse_macro_input; /// [`ProgramError`](../solana_program/enum.ProgramError.html), which is used /// pervasively, throughout solana program crates. The generated `Error` type /// should almost never be used directly, as the user defined error is -/// preferred. In the example above, `MyError::Hello.into()`. +/// preferred. In the example above, `error!(MyError::Hello)`. /// /// # Msg /// /// The `#[msg(..)]` attribute is inert, and is used only as a marker so that /// parsers and IDLs can map error codes to error messages. #[proc_macro_attribute] -pub fn error( +pub fn error_code( args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { @@ -56,6 +59,69 @@ pub fn error( false => Some(parse_macro_input!(args as ErrorArgs)), }; let mut error_enum = parse_macro_input!(input as syn::ItemEnum); - let error = error_codegen::generate(error_parser::parse(&mut error_enum, args)); + let error = codegen::error::generate(error_parser::parse(&mut error_enum, args)); proc_macro::TokenStream::from(error) } + +/// Generates an [`Error::AnchorError`](../../anchor_lang/error/enum.Error.html) that includes file and line information. +/// +/// # Example +/// ```rust,ignore +/// #[program] +/// mod errors { +/// use super::*; +/// pub fn example(_ctx: Context) -> Result<()> { +/// Err(error!(MyError::Hello)) +/// } +/// } +/// +/// #[error_code] +/// pub enum MyError { +/// #[msg("This is an error message clients will automatically display")] +/// Hello, +/// } +/// ``` +#[proc_macro] +pub fn error(ts: proc_macro::TokenStream) -> TokenStream { + let input = parse_macro_input!(ts as ErrorInput); + let error_code = input.error_code; + create_error(error_code, true, None) +} + +#[proc_macro] +pub fn error_with_account_name(ts: proc_macro::TokenStream) -> TokenStream { + let input = parse_macro_input!(ts as ErrorWithAccountNameInput); + let error_code = input.error_code; + let account_name = input.account_name; + create_error(error_code, false, Some(account_name)) +} + +fn create_error(error_code: Expr, source: bool, account_name: Option) -> TokenStream { + let source = if source { + quote! { + Some(anchor_lang::error::Source { + filename: file!(), + line: line!() + }) + } + } else { + quote! { + None + } + }; + let account_name = match account_name { + Some(_) => quote! { Some(#account_name.to_string()) }, + None => quote! { None }, + }; + TokenStream::from(quote! { + anchor_lang::error::Error::from( + anchor_lang::error::AnchorError { + error_name: #error_code.name(), + error_code_number: #error_code.into(), + error_msg: #error_code.to_string(), + source: #source, + account_name: #account_name + } + ) + }) +} diff --git a/lang/attribute/event/Cargo.toml b/lang/attribute/event/Cargo.toml index c635373e..d5bef566 100644 --- a/lang/attribute/event/Cargo.toml +++ b/lang/attribute/event/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-attribute-event" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" @@ -19,5 +19,6 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0.60", features = ["full"] } anyhow = "1.0.32" -anchor-syn = { path = "../../syn", version = "0.21.0", features = ["hash"] } -anchor-common = { path = "../../common", version = "0.21.0" } +anchor-syn = { path = "../../syn", version = "0.22.0", features = ["hash"] } +anchor-common = { path = "../../common", version = "0.22.0" } + diff --git a/lang/attribute/interface/Cargo.toml b/lang/attribute/interface/Cargo.toml index c060695a..91d95902 100644 --- a/lang/attribute/interface/Cargo.toml +++ b/lang/attribute/interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-attribute-interface" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" @@ -18,5 +18,5 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0.60", features = ["full"] } anyhow = "1.0.32" -anchor-syn = { path = "../../syn", version = "0.21.0" } +anchor-syn = { path = "../../syn", version = "0.22.0" } heck = "0.3.2" diff --git a/lang/attribute/interface/src/lib.rs b/lang/attribute/interface/src/lib.rs index e264b764..c2656eb5 100644 --- a/lang/attribute/interface/src/lib.rs +++ b/lang/attribute/interface/src/lib.rs @@ -26,7 +26,7 @@ use syn::parse_macro_input; /// /// #[interface] /// pub trait Auth<'info, T: Accounts<'info>> { -/// fn is_authorized(ctx: Context, current: u64, new: u64) -> ProgramResult; +/// fn is_authorized(ctx: Context, current: u64, new: u64) -> anchor_lang::Result<()>; /// } /// /// #[program] @@ -74,13 +74,13 @@ use syn::parse_macro_input; /// impl<'info> SetCount<'info> { /// pub fn accounts(counter: &Counter, ctx: &Context) -> Result<()> { /// if ctx.accounts.auth_program.key != &counter.auth_program { -/// return Err(ErrorCode::InvalidAuthProgram.into()); +/// return Err(error!(ErrorCode::InvalidAuthProgram)); /// } /// Ok(()) /// } /// } /// -/// #[error] +/// #[error_code] /// pub enum ErrorCode { /// #[msg("Invalid auth program.")] /// InvalidAuthProgram, @@ -104,14 +104,14 @@ use syn::parse_macro_input; /// pub struct CounterAuth; /// /// impl<'info> Auth<'info, Empty> for CounterAuth { -/// fn is_authorized(_ctx: Context, current: u64, new: u64) -> ProgramResult { +/// fn is_authorized(_ctx: Context, current: u64, new: u64) -> Result<()> { /// if current % 2 == 0 { /// if new % 2 == 0 { -/// return Err(ProgramError::Custom(50)); // Arbitrary error code. +/// return Err(ProgramError::Custom(50).into()); // Arbitrary error code. /// } /// } else { /// if new % 2 == 1 { -/// return Err(ProgramError::Custom(60)); // Arbitrary error code. +/// return Err(ProgramError::Custom(60).into()); // Arbitrary error code. /// } /// } /// Ok(()) @@ -200,7 +200,7 @@ pub fn interface( pub fn #method_name<'a,'b, 'c, 'info, T: anchor_lang::Accounts<'info> + anchor_lang::ToAccountMetas + anchor_lang::ToAccountInfos<'info>>( ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, T>, #(#args),* - ) -> anchor_lang::solana_program::entrypoint::ProgramResult { + ) -> anchor_lang::Result<()> { #args_struct let ix = { @@ -224,7 +224,7 @@ pub fn interface( &ix, &acc_infos, ctx.signer_seeds, - ) + ).map_err(Into::into) } } }) diff --git a/lang/attribute/program/Cargo.toml b/lang/attribute/program/Cargo.toml index b2741bf9..f6a0e3c1 100644 --- a/lang/attribute/program/Cargo.toml +++ b/lang/attribute/program/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-attribute-program" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" @@ -18,4 +18,4 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0.60", features = ["full"] } anyhow = "1.0.32" -anchor-syn = { path = "../../syn", version = "0.21.0" } +anchor-syn = { path = "../../syn", version = "0.22.0" } diff --git a/lang/attribute/state/Cargo.toml b/lang/attribute/state/Cargo.toml index 5410025e..9b6e9500 100644 --- a/lang/attribute/state/Cargo.toml +++ b/lang/attribute/state/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-attribute-state" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" @@ -18,4 +18,4 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0.60", features = ["full"] } anyhow = "1.0.32" -anchor-syn = { path = "../../syn", version = "0.21.0" } +anchor-syn = { path = "../../syn", version = "0.22.0" } diff --git a/lang/attribute/state/src/lib.rs b/lang/attribute/state/src/lib.rs index 671c3690..05807c49 100644 --- a/lang/attribute/state/src/lib.rs +++ b/lang/attribute/state/src/lib.rs @@ -42,18 +42,18 @@ pub fn state( // as the initialized value. Use the default implementation. quote! { impl anchor_lang::__private::AccountSize for #struct_ident { - fn size(&self) -> std::result::Result { + fn size(&self) -> anchor_lang::Result { Ok(8 + self - .try_to_vec() - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)? - .len() as u64) + .try_to_vec() + .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)? + .len() as u64) } } } } else if is_zero_copy { quote! { impl anchor_lang::__private::AccountSize for #struct_ident { - fn size(&self) -> std::result::Result { + fn size(&self) -> anchor_lang::Result { let len = anchor_lang::__private::bytemuck::bytes_of(self).len() as u64; Ok(8 + len) } @@ -64,7 +64,7 @@ pub fn state( // Size override given to the macro. Use it. quote! { impl anchor_lang::__private::AccountSize for #struct_ident { - fn size(&self) -> std::result::Result { + fn size(&self) -> anchor_lang::Result { Ok(#size) } } diff --git a/lang/derive/accounts/Cargo.toml b/lang/derive/accounts/Cargo.toml index faee825a..21dc6f1d 100644 --- a/lang/derive/accounts/Cargo.toml +++ b/lang/derive/accounts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-derive-accounts" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" @@ -20,4 +20,4 @@ proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0.60", features = ["full"] } anyhow = "1.0.32" -anchor-syn = { path = "../../syn", version = "0.21.0" } +anchor-syn = { path = "../../syn", version = "0.22.0" } diff --git a/lang/derive/accounts/src/lib.rs b/lang/derive/accounts/src/lib.rs index a01ff6f2..02704f56 100644 --- a/lang/derive/accounts/src/lib.rs +++ b/lang/derive/accounts/src/lib.rs @@ -22,7 +22,7 @@ use syn::parse_macro_input; /// /// ```ignore /// ... -/// pub fn initialize(ctx: Context, bump: u8, authority: Pubkey, data: u64) -> ProgramResult { +/// pub fn initialize(ctx: Context, bump: u8, authority: Pubkey, data: u64) -> anchor_lang::Result<()> { /// ... /// Ok(()) /// } diff --git a/lang/src/accounts/account.rs b/lang/src/accounts/account.rs index 95a9aa4e..20c24338 100644 --- a/lang/src/accounts/account.rs +++ b/lang/src/accounts/account.rs @@ -3,9 +3,7 @@ use crate::error::ErrorCode; use crate::*; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::fmt; @@ -44,7 +42,7 @@ use std::ops::{Deref, DerefMut}; /// #[program] /// mod hello_anchor { /// use super::*; -/// pub fn set_data(ctx: Context, data: u64) -> ProgramResult { +/// pub fn set_data(ctx: Context, data: u64) -> Result<()> { /// if (*ctx.accounts.auth_account).authorized { /// (*ctx.accounts.my_account).data = data; /// } @@ -104,7 +102,7 @@ use std::ops::{Deref, DerefMut}; /// // "try_deserialize_unchecked" by default which is what we want here /// // because non-anchor accounts don't have a discriminator to check /// impl anchor_lang::AccountDeserialize for Mint { -/// fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { +/// fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { /// spl_token::state::Mint::unpack(buf).map(Mint) /// } /// } @@ -161,7 +159,7 @@ use std::ops::{Deref, DerefMut}; /// pub fn set_initial_admin( /// ctx: Context, /// admin_key: Pubkey -/// ) -> ProgramResult { +/// ) -> Result<()> { /// ctx.accounts.admin_settings.admin_key = admin_key; /// Ok(()) /// } @@ -243,7 +241,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T /// Deserializes the given `info` into a `Account`. #[inline(never)] - pub fn try_from(info: &AccountInfo<'a>) -> Result, ProgramError> { + pub fn try_from(info: &AccountInfo<'a>) -> Result> { if info.owner == &system_program::ID && info.lamports() == 0 { return Err(ErrorCode::AccountNotInitialized.into()); } @@ -258,7 +256,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T /// the account discriminator. Be careful when using this and avoid it if /// possible. #[inline(never)] - pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result, ProgramError> { + pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result> { if info.owner == &system_program::ID && info.lamports() == 0 { return Err(ErrorCode::AccountNotInitialized.into()); } @@ -274,7 +272,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T /// Reloads the account from storage. This is useful, for example, when /// observing side effects after CPI. - pub fn reload(&mut self) -> ProgramResult { + pub fn reload(&mut self) -> Result<()> { let mut data: &[u8] = &self.info.try_borrow_data()?; self.account = T::try_deserialize(&mut data)?; Ok(()) @@ -288,7 +286,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T /// /// Instead of this: /// ```ignore - /// pub fn new_user(ctx: Context, new_user:User) -> ProgramResult { + /// pub fn new_user(ctx: Context, new_user:User) -> Result<()> { /// (*ctx.accounts.user_to_create).name = new_user.name; /// (*ctx.accounts.user_to_create).age = new_user.age; /// (*ctx.accounts.user_to_create).address = new_user.address; @@ -296,7 +294,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T /// ``` /// You can do this: /// ```ignore - /// pub fn new_user(ctx: Context, new_user:User) -> ProgramResult { + /// pub fn new_user(ctx: Context, new_user:User) -> Result<()> { /// ctx.accounts.user_to_create.set_inner(new_user); /// } /// ``` @@ -316,7 +314,7 @@ where accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } @@ -329,7 +327,7 @@ where impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsExit<'info> for Account<'info, T> { - fn exit(&self, program_id: &Pubkey) -> ProgramResult { + fn exit(&self, program_id: &Pubkey) -> Result<()> { // Only persist if the owner is the current program. if &T::owner() == program_id { let info = self.to_account_info(); @@ -340,10 +338,17 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsEx } } +/// This function is for INTERNAL USE ONLY. +/// Do NOT use this function in a program. +/// Manual closing of `Account<'info, T>` types is NOT supported. +/// +/// Details: Using `close` with `Account<'info, T>` is not safe because +/// it requires the `mut` constraint but for that type the constraint +/// overwrites the "closed account" discriminator at the end of the instruction. impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsClose<'info> for Account<'info, T> { - fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult { + fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> { crate::common::close(self.to_account_info(), sol_destination) } } diff --git a/lang/src/accounts/account_info.rs b/lang/src/accounts/account_info.rs index 186c4848..25374c45 100644 --- a/lang/src/accounts/account_info.rs +++ b/lang/src/accounts/account_info.rs @@ -3,10 +3,9 @@ //! should be used instead. use crate::error::ErrorCode; -use crate::{Accounts, AccountsExit, ToAccountInfos, ToAccountMetas}; +use crate::{Accounts, AccountsExit, Result, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; @@ -16,7 +15,7 @@ impl<'info> Accounts<'info> for AccountInfo<'info> { accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } diff --git a/lang/src/accounts/account_loader.rs b/lang/src/accounts/account_loader.rs index 54f9994c..1d39f623 100644 --- a/lang/src/accounts/account_loader.rs +++ b/lang/src/accounts/account_loader.rs @@ -3,10 +3,9 @@ use crate::accounts::header; use crate::error::ErrorCode; use crate::*; +use arrayref::array_ref; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::cell::{Ref, RefMut}; use std::collections::BTreeMap; @@ -46,14 +45,14 @@ use std::ops::DerefMut; /// pub mod bar { /// use super::*; /// -/// pub fn create_bar(ctx: Context, data: u64) -> ProgramResult { +/// pub fn create_bar(ctx: Context, data: u64) -> Result<()> { /// let bar = &mut ctx.accounts.bar.load_init()?; /// bar.authority = ctx.accounts.authority.key(); /// bar.data = data; /// Ok(()) /// } /// -/// pub fn update_bar(ctx: Context, data: u64) -> ProgramResult { +/// pub fn update_bar(ctx: Context, data: u64) -> Result<()> { /// (*ctx.accounts.bar.load_mut()?).data = data; /// Ok(()) /// } @@ -113,9 +112,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> { /// Constructs a new `AccountLoader` from a previously initialized account. #[inline(never)] - pub fn try_from( - acc_info: &AccountInfo<'info>, - ) -> Result, ProgramError> { + pub fn try_from(acc_info: &AccountInfo<'info>) -> Result> { if acc_info.owner != &T::owner() { return Err(ErrorCode::AccountOwnedByWrongProgram.into()); } @@ -133,14 +130,14 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> { pub fn try_from_unchecked( _program_id: &Pubkey, acc_info: &AccountInfo<'info>, - ) -> Result, ProgramError> { + ) -> Result> { if acc_info.owner != &T::owner() { return Err(ErrorCode::AccountOwnedByWrongProgram.into()); } Ok(AccountLoader::new(acc_info.clone())) } /// Returns a Ref to the account data structure for reading. - pub fn load(&self) -> Result, ProgramError> { + pub fn load(&self) -> Result> { let data = self.acc_info.try_borrow_data()?; let disc_bytes = header::read_discriminator(&data); if disc_bytes != &T::discriminator() { @@ -152,7 +149,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> { } /// Returns a `RefMut` to the account data structure for reading or writing. - pub fn load_mut(&self) -> Result, ProgramError> { + pub fn load_mut(&self) -> Result> { // AccountInfo api allows you to borrow mut even if the account isn't // writable, so add this check for a better dev experience. if !self.acc_info.is_writable { @@ -178,7 +175,7 @@ impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> { accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } @@ -197,8 +194,15 @@ impl<'info, T: ZeroCopy + Owner> AccountsExit<'info> for AccountLoader<'info, T> } } +/// This function is for INTERNAL USE ONLY. +/// Do NOT use this function in a program. +/// Manual closing of `AccountLoader<'info, T>` types is NOT supported. +/// +/// Details: Using `close` with `AccountLoader<'info, T>` is not safe because +/// it requires the `mut` constraint but for that type the constraint +/// overwrites the "closed account" discriminator at the end of the instruction. impl<'info, T: ZeroCopy + Owner> AccountsClose<'info> for AccountLoader<'info, T> { - fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult { + fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> { crate::common::close(self.to_account_info(), sol_destination) } } diff --git a/lang/src/accounts/boxed.rs b/lang/src/accounts/boxed.rs index 5282fb19..83ef8357 100644 --- a/lang/src/accounts/boxed.rs +++ b/lang/src/accounts/boxed.rs @@ -13,11 +13,9 @@ //! } //! ``` -use crate::{Accounts, AccountsClose, AccountsExit, ToAccountInfos, ToAccountMetas}; +use crate::{Accounts, AccountsClose, AccountsExit, Result, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::ops::Deref; @@ -28,13 +26,13 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Box { accounts: &mut &[AccountInfo<'info>], ix_data: &[u8], bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { T::try_accounts(program_id, accounts, ix_data, bumps).map(Box::new) } } impl<'info, T: AccountsExit<'info>> AccountsExit<'info> for Box { - fn exit(&self, program_id: &Pubkey) -> ProgramResult { + fn exit(&self, program_id: &Pubkey) -> Result<()> { T::exit(Deref::deref(self), program_id) } } @@ -52,7 +50,7 @@ impl ToAccountMetas for Box { } impl<'info, T: AccountsClose<'info>> AccountsClose<'info> for Box { - fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult { + fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> { T::close(self, sol_destination) } } diff --git a/lang/src/accounts/cpi_account.rs b/lang/src/accounts/cpi_account.rs index eeff02a9..c6332f17 100644 --- a/lang/src/accounts/cpi_account.rs +++ b/lang/src/accounts/cpi_account.rs @@ -1,9 +1,7 @@ use crate::*; use crate::{error::ErrorCode, prelude::Account}; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::ops::{Deref, DerefMut}; @@ -23,7 +21,7 @@ impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> { } /// Deserializes the given `info` into a `CpiAccount`. - pub fn try_from(info: &AccountInfo<'a>) -> Result, ProgramError> { + pub fn try_from(info: &AccountInfo<'a>) -> Result> { let mut data: &[u8] = &info.try_borrow_data()?; Ok(CpiAccount::new( info.clone(), @@ -31,13 +29,13 @@ impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> { )) } - pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result, ProgramError> { + pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result> { Self::try_from(info) } /// Reloads the account from storage. This is useful, for example, when /// observing side effects after CPI. - pub fn reload(&mut self) -> ProgramResult { + pub fn reload(&mut self) -> Result<()> { let mut data: &[u8] = &self.info.try_borrow_data()?; self.account = Box::new(T::try_deserialize(&mut data)?); Ok(()) @@ -55,7 +53,7 @@ where accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } diff --git a/lang/src/accounts/cpi_state.rs b/lang/src/accounts/cpi_state.rs index 3fba2284..2ce75b3a 100644 --- a/lang/src/accounts/cpi_state.rs +++ b/lang/src/accounts/cpi_state.rs @@ -2,11 +2,11 @@ use crate::error::ErrorCode; #[allow(deprecated)] use crate::{accounts::state::ProgramState, context::CpiStateContext}; use crate::{ - AccountDeserialize, AccountSerialize, Accounts, AccountsExit, ToAccountInfos, ToAccountMetas, + AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Result, ToAccountInfos, + ToAccountMetas, }; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::ops::{Deref, DerefMut}; @@ -35,7 +35,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> CpiState<'info, T> /// Deserializes the given `info` into a `CpiState`. #[inline(never)] - pub fn try_from(info: &AccountInfo<'info>) -> Result, ProgramError> { + pub fn try_from(info: &AccountInfo<'info>) -> Result> { let mut data: &[u8] = &info.try_borrow_data()?; Ok(CpiState::new(info.clone(), T::try_deserialize(&mut data)?)) } @@ -72,7 +72,7 @@ where accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } diff --git a/lang/src/accounts/loader.rs b/lang/src/accounts/loader.rs index 04f3ca50..027527b4 100644 --- a/lang/src/accounts/loader.rs +++ b/lang/src/accounts/loader.rs @@ -1,10 +1,9 @@ use crate::accounts::header; use crate::error::ErrorCode; use crate::*; +use arrayref::array_ref; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::cell::{Ref, RefMut}; use std::collections::BTreeMap; @@ -54,7 +53,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> { pub fn try_from( program_id: &Pubkey, acc_info: &AccountInfo<'info>, - ) -> Result, ProgramError> { + ) -> Result> { if acc_info.owner != program_id { return Err(ErrorCode::AccountOwnedByWrongProgram.into()); } @@ -73,7 +72,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> { pub fn try_from_unchecked( program_id: &Pubkey, acc_info: &AccountInfo<'info>, - ) -> Result, ProgramError> { + ) -> Result> { if acc_info.owner != program_id { return Err(ErrorCode::AccountOwnedByWrongProgram.into()); } @@ -82,7 +81,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> { /// Returns a Ref to the account data structure for reading. #[allow(deprecated)] - pub fn load(&self) -> Result, ProgramError> { + pub fn load(&self) -> Result> { let data = self.acc_info.try_borrow_data()?; let disc_bytes = header::read_discriminator(&data); if disc_bytes != &T::discriminator() { @@ -94,7 +93,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> { /// Returns a `RefMut` to the account data structure for reading or writing. #[allow(deprecated)] - pub fn load_mut(&self) -> Result, ProgramError> { + pub fn load_mut(&self) -> Result> { // AccountInfo api allows you to borrow mut even if the account isn't // writable, so add this check for a better dev experience. if !self.acc_info.is_writable { @@ -121,7 +120,7 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> { accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } @@ -141,9 +140,16 @@ impl<'info, T: ZeroCopy> AccountsExit<'info> for Loader<'info, T> { } } +/// This function is for INTERNAL USE ONLY. +/// Do NOT use this function in a program. +/// Manual closing of `Loader<'info, T>` types is NOT supported. +/// +/// Details: Using `close` with `Loader<'info, T>` is not safe because +/// it requires the `mut` constraint but for that type the constraint +/// overwrites the "closed account" discriminator at the end of the instruction. #[allow(deprecated)] impl<'info, T: ZeroCopy> AccountsClose<'info> for Loader<'info, T> { - fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult { + fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> { crate::common::close(self.to_account_info(), sol_destination) } } diff --git a/lang/src/accounts/program.rs b/lang/src/accounts/program.rs index f31b11a0..1a20af98 100644 --- a/lang/src/accounts/program.rs +++ b/lang/src/accounts/program.rs @@ -5,7 +5,6 @@ use crate::*; use solana_program::account_info::AccountInfo; use solana_program::bpf_loader_upgradeable::{self, UpgradeableLoaderState}; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::fmt; @@ -85,7 +84,7 @@ impl<'a, T: Id + Clone> Program<'a, T> { /// Deserializes the given `info` into a `Program`. #[inline(never)] - pub fn try_from(info: &AccountInfo<'a>) -> Result, ProgramError> { + pub fn try_from(info: &AccountInfo<'a>) -> Result> { if info.key != &T::id() { return Err(ErrorCode::InvalidProgramId.into()); } @@ -137,7 +136,7 @@ where accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } diff --git a/lang/src/accounts/program_account.rs b/lang/src/accounts/program_account.rs index 2e444fc2..bd09bdd3 100644 --- a/lang/src/accounts/program_account.rs +++ b/lang/src/accounts/program_account.rs @@ -3,9 +3,7 @@ use crate::accounts::cpi_account::CpiAccount; use crate::error::ErrorCode; use crate::*; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::ops::{Deref, DerefMut}; @@ -34,10 +32,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T> /// Deserializes the given `info` into a `ProgramAccount`. #[inline(never)] - pub fn try_from( - program_id: &Pubkey, - info: &AccountInfo<'a>, - ) -> Result, ProgramError> { + pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result> { if info.owner != program_id { return Err(ErrorCode::AccountOwnedByWrongProgram.into()); } @@ -55,7 +50,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T> pub fn try_from_unchecked( program_id: &Pubkey, info: &AccountInfo<'a>, - ) -> Result, ProgramError> { + ) -> Result> { if info.owner != program_id { return Err(ErrorCode::AccountOwnedByWrongProgram.into()); } @@ -82,7 +77,7 @@ where accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } @@ -96,7 +91,7 @@ where impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info> for ProgramAccount<'info, T> { - fn exit(&self, _program_id: &Pubkey) -> ProgramResult { + fn exit(&self, _program_id: &Pubkey) -> Result<()> { let info = self.to_account_info(); let mut data = info.try_borrow_mut_data()?; self.inner.account.try_serialize(&mut data)?; @@ -104,11 +99,18 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info } } +/// This function is for INTERNAL USE ONLY. +/// Do NOT use this function in a program. +/// Manual closing of `ProgramAccount<'info, T>` types is NOT supported. +/// +/// Details: Using `close` with `ProgramAccount<'info, T>` is not safe because +/// it requires the `mut` constraint but for that type the constraint +/// overwrites the "closed account" discriminator at the end of the instruction. #[allow(deprecated)] impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsClose<'info> for ProgramAccount<'info, T> { - fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult { + fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> { crate::common::close(self.to_account_info(), sol_destination) } } diff --git a/lang/src/accounts/signer.rs b/lang/src/accounts/signer.rs index fd05e706..ffd0fc2b 100644 --- a/lang/src/accounts/signer.rs +++ b/lang/src/accounts/signer.rs @@ -3,7 +3,6 @@ use crate::error::ErrorCode; use crate::*; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::ops::Deref; @@ -47,7 +46,7 @@ impl<'info> Signer<'info> { /// Deserializes the given `info` into a `Signer`. #[inline(never)] - pub fn try_from(info: &AccountInfo<'info>) -> Result, ProgramError> { + pub fn try_from(info: &AccountInfo<'info>) -> Result> { if !info.is_signer { return Err(ErrorCode::AccountNotSigner.into()); } @@ -62,7 +61,7 @@ impl<'info> Accounts<'info> for Signer<'info> { accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } diff --git a/lang/src/accounts/state.rs b/lang/src/accounts/state.rs index 3c746be4..a7f867ae 100644 --- a/lang/src/accounts/state.rs +++ b/lang/src/accounts/state.rs @@ -2,13 +2,11 @@ use crate::accounts::cpi_account::CpiAccount; use crate::error::ErrorCode; use crate::{ - AccountDeserialize, AccountSerialize, Accounts, AccountsExit, ToAccountInfo, ToAccountInfos, - ToAccountMetas, + AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Result, ToAccountInfo, + ToAccountInfos, ToAccountMetas, }; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::ops::{Deref, DerefMut}; @@ -39,10 +37,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramState<'a, T> { /// Deserializes the given `info` into a `ProgramState`. #[inline(never)] - pub fn try_from( - program_id: &Pubkey, - info: &AccountInfo<'a>, - ) -> Result, ProgramError> { + pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result> { if info.owner != program_id { return Err(ErrorCode::AccountOwnedByWrongProgram.into()); } @@ -77,7 +72,7 @@ where accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } @@ -149,7 +144,7 @@ where impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info> for ProgramState<'info, T> { - fn exit(&self, _program_id: &Pubkey) -> ProgramResult { + fn exit(&self, _program_id: &Pubkey) -> Result<()> { let info = self.to_account_info(); let mut data = info.try_borrow_mut_data()?; self.inner.account.try_serialize(&mut data)?; diff --git a/lang/src/accounts/system_account.rs b/lang/src/accounts/system_account.rs index 8495834b..30cc33ea 100644 --- a/lang/src/accounts/system_account.rs +++ b/lang/src/accounts/system_account.rs @@ -4,7 +4,6 @@ use crate::error::ErrorCode; use crate::*; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use solana_program::system_program; use std::collections::BTreeMap; @@ -26,7 +25,7 @@ impl<'info> SystemAccount<'info> { } #[inline(never)] - pub fn try_from(info: &AccountInfo<'info>) -> Result, ProgramError> { + pub fn try_from(info: &AccountInfo<'info>) -> Result> { if *info.owner != system_program::ID { return Err(ErrorCode::AccountNotSystemOwned.into()); } @@ -41,7 +40,7 @@ impl<'info> Accounts<'info> for SystemAccount<'info> { accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } diff --git a/lang/src/accounts/sysvar.rs b/lang/src/accounts/sysvar.rs index 024264a1..25e8698f 100644 --- a/lang/src/accounts/sysvar.rs +++ b/lang/src/accounts/sysvar.rs @@ -1,10 +1,9 @@ //! Type validating that the account is a sysvar and deserializing it use crate::error::ErrorCode; -use crate::{Accounts, AccountsExit, ToAccountInfos, ToAccountMetas}; +use crate::{Accounts, AccountsExit, Result, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::fmt; @@ -27,7 +26,7 @@ use std::ops::{Deref, DerefMut}; /// pub clock: Sysvar<'info, Clock> /// } /// // BETTER - via syscall in the instruction function -/// fn better(ctx: Context) -> ProgramResult { +/// fn better(ctx: Context) -> Result<()> { /// let clock = Clock::get()?; /// } /// ``` @@ -46,9 +45,7 @@ impl<'info, T: solana_program::sysvar::Sysvar + fmt::Debug> fmt::Debug for Sysva } impl<'info, T: solana_program::sysvar::Sysvar> Sysvar<'info, T> { - pub fn from_account_info( - acc_info: &AccountInfo<'info>, - ) -> Result, ProgramError> { + pub fn from_account_info(acc_info: &AccountInfo<'info>) -> Result> { Ok(Sysvar { info: acc_info.clone(), account: T::from_account_info(acc_info)?, @@ -71,7 +68,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } diff --git a/lang/src/accounts/unchecked_account.rs b/lang/src/accounts/unchecked_account.rs index 7da7ea15..d28d6e1f 100644 --- a/lang/src/accounts/unchecked_account.rs +++ b/lang/src/accounts/unchecked_account.rs @@ -2,10 +2,9 @@ //! that no checks are performed use crate::error::ErrorCode; -use crate::{Accounts, AccountsExit, ToAccountInfos, ToAccountMetas}; +use crate::{Accounts, AccountsExit, Result, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; use std::ops::Deref; @@ -27,7 +26,7 @@ impl<'info> Accounts<'info> for UncheckedAccount<'info> { accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], _bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); } diff --git a/lang/src/bpf_upgradeable_state.rs b/lang/src/bpf_upgradeable_state.rs index e4d28298..70efcdf5 100644 --- a/lang/src/bpf_upgradeable_state.rs +++ b/lang/src/bpf_upgradeable_state.rs @@ -1,4 +1,5 @@ -use crate::{AccountDeserialize, AccountSerialize, Owner}; +use crate::error::ErrorCode; +use crate::{AccountDeserialize, AccountSerialize, Owner, Result}; use solana_program::{ bpf_loader_upgradeable::UpgradeableLoaderState, program_error::ProgramError, pubkey::Pubkey, }; @@ -10,27 +11,21 @@ pub struct ProgramData { } impl AccountDeserialize for ProgramData { - fn try_deserialize( - buf: &mut &[u8], - ) -> Result { + fn try_deserialize(buf: &mut &[u8]) -> Result { ProgramData::try_deserialize_unchecked(buf) } - fn try_deserialize_unchecked( - buf: &mut &[u8], - ) -> Result { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { let program_state = AccountDeserialize::try_deserialize_unchecked(buf)?; match program_state { - UpgradeableLoaderState::Uninitialized => { - Err(anchor_lang::error::ErrorCode::AccountNotProgramData.into()) - } + UpgradeableLoaderState::Uninitialized => Err(ErrorCode::AccountNotProgramData.into()), UpgradeableLoaderState::Buffer { authority_address: _, - } => Err(anchor_lang::error::ErrorCode::AccountNotProgramData.into()), + } => Err(ErrorCode::AccountNotProgramData.into()), UpgradeableLoaderState::Program { programdata_address: _, - } => Err(anchor_lang::error::ErrorCode::AccountNotProgramData.into()), + } => Err(ErrorCode::AccountNotProgramData.into()), UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address, @@ -59,11 +54,11 @@ impl Owner for UpgradeableLoaderState { impl AccountSerialize for UpgradeableLoaderState {} impl AccountDeserialize for UpgradeableLoaderState { - fn try_deserialize(buf: &mut &[u8]) -> Result { + fn try_deserialize(buf: &mut &[u8]) -> Result { UpgradeableLoaderState::try_deserialize_unchecked(buf) } - fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { - bincode::deserialize(buf).map_err(|_| ProgramError::InvalidAccountData) + fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { + bincode::deserialize(buf).map_err(|_| ProgramError::InvalidAccountData.into()) } } diff --git a/lang/src/common.rs b/lang/src/common.rs index 88d2ba2d..8e571360 100644 --- a/lang/src/common.rs +++ b/lang/src/common.rs @@ -1,12 +1,10 @@ use crate::error::ErrorCode; +use crate::prelude::error; +use crate::Result; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; use std::io::Write; -pub fn close<'info>( - info: AccountInfo<'info>, - sol_destination: AccountInfo<'info>, -) -> ProgramResult { +pub fn close<'info>(info: AccountInfo<'info>, sol_destination: AccountInfo<'info>) -> Result<()> { // Transfer tokens from the account to the sol_destination. let dest_starting_lamports = sol_destination.lamports(); **sol_destination.lamports.borrow_mut() = @@ -19,6 +17,5 @@ pub fn close<'info>( let mut cursor = std::io::Cursor::new(dst); cursor .write_all(&crate::__private::CLOSED_ACCOUNT_DISCRIMINATOR) - .map_err(|_| ErrorCode::AccountDidNotSerialize)?; - Ok(()) + .map_err(|_| error!(ErrorCode::AccountDidNotSerialize)) } diff --git a/lang/src/context.rs b/lang/src/context.rs index 736f248c..f007bc51 100644 --- a/lang/src/context.rs +++ b/lang/src/context.rs @@ -11,7 +11,7 @@ use std::fmt; /// /// # Example /// ```ignore -/// pub fn set_data(ctx: Context, age: u64, other_data: u32) -> ProgramResult { +/// pub fn set_data(ctx: Context, age: u64, other_data: u32) -> Result<()> { /// // Set account data like this /// (*ctx.accounts.my_account).age = age; /// (*ctx.accounts.my_account).other_data = other_data; @@ -76,12 +76,12 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> Context<'a, 'b, 'c, 'info, T> { /// #[program] /// pub mod callee { /// use super::*; -/// pub fn init(ctx: Context) -> ProgramResult { +/// pub fn init(ctx: Context) -> Result<()> { /// (*ctx.accounts.data).authority = ctx.accounts.authority.key(); /// Ok(()) /// } /// -/// pub fn set_data(ctx: Context, data: u64) -> ProgramResult { +/// pub fn set_data(ctx: Context, data: u64) -> Result<()> { /// (*ctx.accounts.data_acc).data = data; /// Ok(()) /// } @@ -120,7 +120,7 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> Context<'a, 'b, 'c, 'info, T> { /// #[program] /// pub mod caller { /// use super::*; -/// pub fn do_cpi(ctx: Context, data: u64) -> ProgramResult { +/// pub fn do_cpi(ctx: Context, data: u64) -> Result<()> { /// let callee_id = ctx.accounts.callee.to_account_info(); /// let callee_accounts = callee::cpi::accounts::SetData { /// data_acc: ctx.accounts.data_acc.to_account_info(), @@ -130,7 +130,7 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> Context<'a, 'b, 'c, 'info, T> { /// callee::cpi::set_data(cpi_ctx, data) /// } /// -/// pub fn do_cpi_with_pda_authority(ctx: Context, bump: u8, data: u64) -> ProgramResult { +/// pub fn do_cpi_with_pda_authority(ctx: Context, bump: u8, data: u64) -> Result<()> { /// let seeds = &[&[b"example_seed", bytemuck::bytes_of(&bump)][..]]; /// let callee_id = ctx.accounts.callee.to_account_info(); /// let callee_accounts = callee::cpi::accounts::SetData { diff --git a/lang/src/error.rs b/lang/src/error.rs index 97a67cbe..ce2b519e 100644 --- a/lang/src/error.rs +++ b/lang/src/error.rs @@ -1,4 +1,7 @@ -use crate::error; +use anchor_attribute_error::error_code; +use borsh::maybestd::io::Error as BorshIoError; +use solana_program::program_error::ProgramError; +use std::fmt::{Debug, Display}; /// The starting point for user defined error codes. pub const ERROR_CODE_OFFSET: u32 = 6000; @@ -15,7 +18,7 @@ pub const ERROR_CODE_OFFSET: u32 = 6000; /// /// The starting point for user-defined errors is defined /// by the [ERROR_CODE_OFFSET](crate::error::ERROR_CODE_OFFSET). -#[error(offset = 0)] +#[error_code(offset = 0)] pub enum ErrorCode { // Instructions /// 100 - 8 byte instruction identifier not provided @@ -165,3 +168,187 @@ pub enum ErrorCode { #[msg("The API being used is deprecated and should no longer be used")] Deprecated = 5000, } + +#[derive(Debug)] +pub enum Error { + AnchorError(AnchorError), + ProgramError(ProgramErrorWithOrigin), +} + +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::AnchorError(ae) => Display::fmt(&ae, f), + Error::ProgramError(pe) => Display::fmt(&pe, f), + } + } +} + +impl Display for AnchorError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self, f) + } +} + +impl From for Error { + fn from(ae: AnchorError) -> Self { + Self::AnchorError(ae) + } +} + +impl From for Error { + fn from(program_error: ProgramError) -> Self { + Self::ProgramError(program_error.into()) + } +} +impl From for Error { + fn from(error: BorshIoError) -> Self { + Error::ProgramError(ProgramError::from(error).into()) + } +} + +impl From for Error { + fn from(pe: ProgramErrorWithOrigin) -> Self { + Self::ProgramError(pe) + } +} + +impl Error { + pub fn log(&self) { + match self { + Error::ProgramError(program_error) => program_error.log(), + Error::AnchorError(anchor_error) => anchor_error.log(), + } + } + + pub fn with_account_name(mut self, account_name: impl ToString) -> Self { + match &mut self { + Error::AnchorError(ae) => ae.account_name = Some(account_name.to_string()), + Error::ProgramError(pe) => pe.account_name = Some(account_name.to_string()), + }; + self + } + + pub fn with_source(mut self, source: Source) -> Self { + match &mut self { + Error::AnchorError(ae) => ae.source = Some(source), + Error::ProgramError(pe) => pe.source = Some(source), + }; + self + } +} + +#[derive(Debug)] +pub struct ProgramErrorWithOrigin { + pub program_error: ProgramError, + pub source: Option, + pub account_name: Option, +} + +impl Display for ProgramErrorWithOrigin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.program_error, f) + } +} + +impl ProgramErrorWithOrigin { + pub fn log(&self) { + if let Some(source) = &self.source { + anchor_lang::solana_program::msg!( + "ProgramError thrown in {}:{}. Error Code: {:?}. Error Number: {}. Error Message: {}.", + source.filename, + source.line, + self.program_error, + u64::from(self.program_error.clone()), + self.program_error + ); + } else if let Some(account_name) = &self.account_name { + anchor_lang::solana_program::log::sol_log(&format!( + "ProgramError caused by account: {}. Error Code: {:?}. Error Number: {}. Error Message: {}.", + account_name, + self.program_error, + u64::from(self.program_error.clone()), + self.program_error + )); + } else { + anchor_lang::solana_program::log::sol_log(&format!( + "ProgramError occurred. Error Code: {:?}. Error Number: {}. Error Message: {}.", + self.program_error, + u64::from(self.program_error.clone()), + self.program_error + )); + } + } +} + +impl From for ProgramErrorWithOrigin { + fn from(program_error: ProgramError) -> Self { + Self { + program_error, + source: None, + account_name: None, + } + } +} + +#[derive(Debug)] +pub struct AnchorError { + pub error_name: String, + pub error_code_number: u32, + pub error_msg: String, + pub source: Option, + pub account_name: Option, +} + +impl AnchorError { + pub fn log(&self) { + if let Some(source) = &self.source { + anchor_lang::solana_program::msg!( + "AnchorError thrown in {}:{}. Error Code: {}. Error Number: {}. Error Message: {}.", + source.filename, + source.line, + self.error_name, + self.error_code_number, + self.error_msg + ); + } else if let Some(account_name) = &self.account_name { + anchor_lang::solana_program::log::sol_log(&format!( + "AnchorError caused by account: {}. Error Code: {}. Error Number: {}. Error Message: {}.", + account_name, + self.error_name, + self.error_code_number, + self.error_msg + )); + } else { + anchor_lang::solana_program::log::sol_log(&format!( + "AnchorError occurred. Error Code: {}. Error Number: {}. Error Message: {}.", + self.error_name, self.error_code_number, self.error_msg + )); + } + } +} + +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_name: _, + error_code_number, + error_msg: _, + source: _, + account_name: _, + }) => { + anchor_lang::solana_program::program_error::ProgramError::Custom(error_code_number) + } + Error::ProgramError(program_error) => program_error.program_error, + } + } +} + +#[derive(Debug)] +pub struct Source { + pub filename: &'static str, + pub line: u32, +} diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 6631456b..d22166a6 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -25,9 +25,7 @@ extern crate self as anchor_lang; use bytemuck::{Pod, Zeroable}; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; @@ -48,7 +46,7 @@ pub use crate::bpf_upgradeable_state::*; pub use anchor_attribute_access_control::access_control; pub use anchor_attribute_account::{account, declare_id, zero_copy}; pub use anchor_attribute_constant::constant; -pub use anchor_attribute_error::error; +pub use anchor_attribute_error; pub use anchor_attribute_event::{emit, event}; pub use anchor_attribute_interface::interface; pub use anchor_attribute_program::program; @@ -58,6 +56,8 @@ pub use anchor_derive_accounts::Accounts; pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; pub use solana_program; +pub type Result = std::result::Result; + /// A data structure of validated accounts that can be deserialized from the /// input to a Solana program. Implementations of this trait should perform any /// and all requisite constraint checks on accounts to ensure the accounts @@ -81,14 +81,14 @@ pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized { accounts: &mut &[AccountInfo<'info>], ix_data: &[u8], bumps: &mut BTreeMap, - ) -> Result; + ) -> Result; } /// The exit procedure for an account. Any cleanup or persistence to storage /// should be done here. pub trait AccountsExit<'info>: ToAccountMetas + ToAccountInfos<'info> { /// `program_id` is the currently executing program. - fn exit(&self, _program_id: &Pubkey) -> ProgramResult { + fn exit(&self, _program_id: &Pubkey) -> Result<()> { // no-op Ok(()) } @@ -97,7 +97,7 @@ pub trait AccountsExit<'info>: ToAccountMetas + ToAccountInfos<'info> { /// The close procedure to initiate garabage collection of an account, allowing /// one to retrieve the rent exemption. pub trait AccountsClose<'info>: ToAccountInfos<'info> { - fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult; + fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()>; } /// Transformation to @@ -164,14 +164,14 @@ pub trait AccountDeserialize: Sized { /// For example, if the SPL token program were to implement this trait, /// it should be impossible to deserialize a `Mint` account into a token /// `Account`. - fn try_deserialize(buf: &mut &[u8]) -> Result { + fn try_deserialize(buf: &mut &[u8]) -> Result { Self::try_deserialize_unchecked(buf) } /// Deserializes account data without checking the account discriminator. /// This should only be used on account initialization, when the bytes of /// the account are zeroed. - fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result; + fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result; } /// An account data structure capable of zero copy deserialization. @@ -243,16 +243,16 @@ pub mod prelude { accounts::account_loader::AccountLoader, accounts::program::Program, accounts::signer::Signer, accounts::system_account::SystemAccount, accounts::sysvar::Sysvar, accounts::unchecked_account::UncheckedAccount, constant, - context::Context, context::CpiContext, declare_id, emit, error, event, interface, program, - require, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, state, zero_copy, - AccountDeserialize, AccountSerialize, Accounts, AccountsExit, AnchorDeserialize, - AnchorSerialize, Id, Key, Owner, ProgramData, System, ToAccountInfo, ToAccountInfos, - ToAccountMetas, + context::Context, context::CpiContext, declare_id, emit, err, error, event, interface, + program, require, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, source, + state, zero_copy, AccountDeserialize, AccountSerialize, Accounts, AccountsExit, + AnchorDeserialize, AnchorSerialize, Id, Key, Owner, ProgramData, Result, System, + ToAccountInfo, ToAccountInfos, ToAccountMetas, }; - + pub use anchor_attribute_error::*; pub use borsh; + pub use error::*; pub use solana_program::account_info::{next_account_info, AccountInfo}; - pub use solana_program::entrypoint::ProgramResult; pub use solana_program::instruction::AccountMeta; pub use solana_program::msg; pub use solana_program::program_error::ProgramError; @@ -274,6 +274,7 @@ pub mod prelude { /// Internal module used by macros and unstable apis. #[doc(hidden)] pub mod __private { + use super::Result; /// The discriminator anchor uses to mark an account as closed. pub const CLOSED_ACCOUNT_DISCRIMINATOR: [u8; 8] = [255, 255, 255, 255, 255, 255, 255, 255]; @@ -287,8 +288,6 @@ pub mod __private { pub use bytemuck; - use solana_program::program_error::ProgramError; - use solana_program::pubkey::Pubkey; pub mod state { @@ -299,7 +298,7 @@ pub mod __private { // data in it. This trait is currently only used for `#[state]` accounts. #[doc(hidden)] pub trait AccountSize { - fn size(&self) -> Result; + fn size(&self) -> Result; } // Very experimental trait. @@ -323,20 +322,20 @@ pub mod __private { pub use crate::accounts::state::PROGRAM_STATE_SEED; } -/// Ensures a condition is true, otherwise returns the given error. +/// Ensures a condition is true, otherwise returns with the given error. /// Use this with a custom error type. /// /// # Example /// ```ignore /// // Instruction function -/// pub fn set_data(ctx: Context, data: u64) -> ProgramResult { +/// pub fn set_data(ctx: Context, data: u64) -> Result<()> { /// require!(ctx.accounts.data.mutation_allowed, MyError::MutationForbidden); /// ctx.accounts.data.data = data; /// Ok(()) /// } /// /// // An enum for custom error codes -/// #[error] +/// #[error_code] /// pub enum MyError { /// MutationForbidden /// } @@ -360,12 +359,53 @@ pub mod __private { macro_rules! require { ($invariant:expr, $error:tt $(,)?) => { if !($invariant) { - return Err(crate::ErrorCode::$error.into()); + return Err(anchor_lang::anchor_attribute_error::error!( + crate::ErrorCode::$error + )); } }; ($invariant:expr, $error:expr $(,)?) => { if !($invariant) { - return Err($error.into()); + return Err(anchor_lang::anchor_attribute_error::error!($error)); + } + }; +} + +/// Returns with the given error. +/// Use this with a custom error type. +/// +/// # Example +/// ```ignore +/// // Instruction function +/// pub fn example(ctx: Context) -> Result<()> { +/// err!(MyError::SomeError) +/// } +/// +/// // An enum for custom error codes +/// #[error_code] +/// pub enum MyError { +/// SomeError +/// } +/// ``` +#[macro_export] +macro_rules! err { + ($error:tt $(,)?) => { + Err(anchor_lang::anchor_attribute_error::error!( + crate::ErrorCode::$error + )) + }; + ($error:expr $(,)?) => { + Err(anchor_lang::anchor_attribute_error::error!($error)) + }; +} + +/// Creates a [`Source`](crate::error::Source) +#[macro_export] +macro_rules! source { + () => { + anchor_lang::error::Source { + filename: file!(), + line: line!(), } }; } diff --git a/lang/src/vec.rs b/lang/src/vec.rs index c66f8f60..1036f674 100644 --- a/lang/src/vec.rs +++ b/lang/src/vec.rs @@ -1,7 +1,6 @@ -use crate::{Accounts, ToAccountInfos, ToAccountMetas}; +use crate::{Accounts, Result, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; -use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; use std::collections::BTreeMap; @@ -27,7 +26,7 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Vec { accounts: &mut &[AccountInfo<'info>], ix_data: &[u8], bumps: &mut BTreeMap, - ) -> Result { + ) -> Result { let mut vec: Vec = Vec::new(); T::try_accounts(program_id, accounts, ix_data, bumps).map(|item| vec.push(item))?; Ok(vec) diff --git a/lang/syn/Cargo.toml b/lang/syn/Cargo.toml index 4b6e727e..ab9246bf 100644 --- a/lang/syn/Cargo.toml +++ b/lang/syn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-syn" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] repository = "https://github.com/project-serum/anchor" license = "Apache-2.0" diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index f86d1b41..7765b390 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -110,8 +110,8 @@ fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream { Constraint::Mut(c) => generate_constraint_mut(f, c), Constraint::HasOne(c) => generate_constraint_has_one(f, c), Constraint::Signer(c) => generate_constraint_signer(f, c), - Constraint::Literal(c) => generate_constraint_literal(c), - Constraint::Raw(c) => generate_constraint_raw(c), + Constraint::Literal(c) => generate_constraint_literal(&f.ident, c), + Constraint::Raw(c) => generate_constraint_raw(&f.ident, c), Constraint::Owner(c) => generate_constraint_owner(f, c), Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c), Constraint::Seeds(c) => generate_constraint_seeds(f, c), @@ -123,10 +123,10 @@ fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream { } } -fn generate_constraint_composite(_f: &CompositeField, c: &Constraint) -> proc_macro2::TokenStream { +fn generate_constraint_composite(f: &CompositeField, c: &Constraint) -> proc_macro2::TokenStream { match c { - Constraint::Raw(c) => generate_constraint_raw(c), - Constraint::Literal(c) => generate_constraint_literal(c), + Constraint::Raw(c) => generate_constraint_raw(&f.ident, c), + Constraint::Literal(c) => generate_constraint_literal(&f.ident, c), _ => panic!("Invariant violation"), } } @@ -134,16 +134,17 @@ fn generate_constraint_composite(_f: &CompositeField, c: &Constraint) -> proc_ma fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream { let field = &f.ident; let addr = &c.address; - let error = generate_custom_error(&c.error, quote! { ConstraintAddress }); + let error = generate_custom_error(field, &c.error, quote! { ConstraintAddress }); quote! { if #field.key() != #addr { - return Err(#error); + return #error; } } } pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macro2::TokenStream { let field = &f.ident; + let name_str = field.to_string(); let ty_decl = f.ty_decl(); let account_ty = f.account_ty(); let from_account_info = f.from_account_info(None); @@ -162,7 +163,7 @@ pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macr __header_bytes.copy_from_slice(&__data[..8]); let __header = u64::from_le_bytes(__header_bytes); if __header != 0 { - return Err(anchor_lang::error::ErrorCode::ConstraintZero.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintZero, #name_str)); } #header_write @@ -174,20 +175,21 @@ pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macr pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2::TokenStream { let field = &f.ident; + let name_str = field.to_string(); let target = &c.sol_dest; quote! { if #field.key() == #target.key() { - return Err(anchor_lang::error::ErrorCode::ConstraintClose.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintClose, #name_str)); } } } pub fn generate_constraint_mut(f: &Field, c: &ConstraintMut) -> proc_macro2::TokenStream { let ident = &f.ident; - let error = generate_custom_error(&c.error, quote! { ConstraintMut }); + let error = generate_custom_error(ident, &c.error, quote! { ConstraintMut }); quote! { if !#ident.to_account_info().is_writable { - return Err(#error); + return #error; } } } @@ -200,10 +202,10 @@ pub fn generate_constraint_has_one(f: &Field, c: &ConstraintHasOne) -> proc_macr Ty::AccountLoader(_) => quote! {#ident.load()?}, _ => quote! {#ident}, }; - let error = generate_custom_error(&c.error, quote! { ConstraintHasOne }); + let error = generate_custom_error(ident, &c.error, quote! { ConstraintHasOne }); quote! { if #field.#target != #target.key() { - return Err(#error); + return #error; } } } @@ -219,15 +221,19 @@ pub fn generate_constraint_signer(f: &Field, c: &ConstraintSigner) -> proc_macro Ty::CpiAccount(_) => quote! { #ident.to_account_info() }, _ => panic!("Invalid syntax: signer cannot be specified."), }; - let error = generate_custom_error(&c.error, quote! { ConstraintSigner }); + let error = generate_custom_error(ident, &c.error, quote! { ConstraintSigner }); quote! { if !#info.is_signer { - return Err(#error); + return #error; } } } -pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenStream { +pub fn generate_constraint_literal( + ident: &Ident, + c: &ConstraintLiteral, +) -> proc_macro2::TokenStream { + let name_str = ident.to_string(); let lit: proc_macro2::TokenStream = { let lit = &c.lit; let constraint = lit.value().replace('\"', ""); @@ -240,17 +246,17 @@ pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenS }; quote! { if !(#lit) { - return Err(anchor_lang::error::ErrorCode::Deprecated.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::Deprecated, #name_str)); } } } -pub fn generate_constraint_raw(c: &ConstraintRaw) -> proc_macro2::TokenStream { +pub fn generate_constraint_raw(ident: &Ident, c: &ConstraintRaw) -> proc_macro2::TokenStream { let raw = &c.raw; - let error = generate_custom_error(&c.error, quote! { ConstraintRaw }); + let error = generate_custom_error(ident, &c.error, quote! { ConstraintRaw }); quote! { if !(#raw) { - return Err(#error); + return #error; } } } @@ -258,10 +264,10 @@ pub fn generate_constraint_raw(c: &ConstraintRaw) -> proc_macro2::TokenStream { pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream { let ident = &f.ident; let owner_address = &c.owner_address; - let error = generate_custom_error(&c.error, quote! { ConstraintOwner }); + let error = generate_custom_error(ident, &c.error, quote! { ConstraintOwner }); quote! { if #ident.as_ref().owner != &#owner_address { - return Err(#error); + return #error; } } } @@ -271,6 +277,7 @@ pub fn generate_constraint_rent_exempt( c: &ConstraintRentExempt, ) -> proc_macro2::TokenStream { let ident = &f.ident; + let name_str = ident.to_string(); let info = quote! { #ident.to_account_info() }; @@ -278,7 +285,7 @@ pub fn generate_constraint_rent_exempt( ConstraintRentExempt::Skip => quote! {}, ConstraintRentExempt::Enforce => quote! { if !__anchor_rent.is_exempt(#info.lamports(), #info.try_data_len()?) { - return Err(anchor_lang::error::ErrorCode::ConstraintRentExempt.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintRentExempt, #name_str)); } }, } @@ -286,6 +293,7 @@ pub fn generate_constraint_rent_exempt( fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream { let field = &f.ident; + let name_str = f.ident.to_string(); let ty_decl = f.ty_decl(); let if_needed = if c.if_needed { quote! {true} @@ -309,7 +317,6 @@ fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2:: let (find_pda, seeds_with_bump) = match &c.seeds { None => (quote! {}, quote! {}), Some(c) => { - let name_str = f.ident.to_string(); let seeds = &mut c.seeds.clone(); // If the seeds came with a trailing comma, we need to chop it off @@ -375,10 +382,10 @@ fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2:: let pa: #ty_decl = #from_account_info; if #if_needed { if pa.mint != #mint.key() { - return Err(anchor_lang::error::ErrorCode::ConstraintTokenMint.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenMint, #name_str)); } if pa.owner != #owner.key() { - return Err(anchor_lang::error::ErrorCode::ConstraintTokenOwner.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenOwner, #name_str)); } } pa @@ -410,14 +417,14 @@ fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2:: let pa: #ty_decl = #from_account_info; if #if_needed { if pa.mint != #mint.key() { - return Err(anchor_lang::error::ErrorCode::ConstraintTokenMint.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenMint, #name_str)); } if pa.owner != #owner.key() { - return Err(anchor_lang::error::ErrorCode::ConstraintTokenOwner.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenOwner, #name_str)); } if pa.key() != anchor_spl::associated_token::get_associated_token_address(&#owner.key(), &#mint.key()) { - return Err(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount, #name_str)); } } pa @@ -463,16 +470,16 @@ fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2:: let pa: #ty_decl = #from_account_info; if #if_needed { if pa.mint_authority != anchor_lang::solana_program::program_option::COption::Some(#owner.key()) { - return Err(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority, #name_str)); } if pa.freeze_authority .as_ref() .map(|fa| #freeze_authority.as_ref().map(|expected_fa| fa != *expected_fa).unwrap_or(true)) .unwrap_or(#freeze_authority.is_some()) { - return Err(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority, #name_str)); } if pa.decimals != #decimals { - return Err(anchor_lang::error::ErrorCode::ConstraintMintDecimals.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintMintDecimals, #name_str)); } } pa @@ -575,17 +582,17 @@ fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2:: // Assert the account was created correctly. if #if_needed { if space != actual_field.data_len() { - return Err(anchor_lang::error::ErrorCode::ConstraintSpace.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSpace, #name_str)); } if actual_owner != #owner { - return Err(anchor_lang::error::ErrorCode::ConstraintOwner.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintOwner, #name_str)); } { let required_lamports = __anchor_rent.minimum_balance(space); if pa.to_account_info().lamports() < required_lamports { - return Err(anchor_lang::error::ErrorCode::ConstraintRentExempt.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintRentExempt, #name_str)); } } } @@ -627,10 +634,10 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 let b = c.bump.as_ref().unwrap(); quote! { if #name.key() != __pda_address { - return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str)); } if __bump != #b { - return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str)); } } } @@ -642,7 +649,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 else if c.is_init { quote! { if #name.key() != __pda_address { - return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str)); } } } @@ -651,6 +658,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 let maybe_seeds_plus_comma = (!s.is_empty()).then(|| { quote! { #s, } }); + let define_pda = match c.bump.as_ref() { // Bump target not given. Find it. None => quote! { @@ -665,7 +673,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 let __pda_address = Pubkey::create_program_address( &[#maybe_seeds_plus_comma &[#b][..]], &#deriving_program_id, - ).map_err(|_| anchor_lang::error::ErrorCode::ConstraintSeeds)?; + ).map_err(|_| anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str))?; }, }; quote! { @@ -674,7 +682,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 // Check it. if #name.key() != __pda_address { - return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str)); } } } @@ -685,15 +693,16 @@ fn generate_constraint_associated_token( c: &ConstraintAssociatedToken, ) -> proc_macro2::TokenStream { let name = &f.ident; + let name_str = name.to_string(); let wallet_address = &c.wallet; let spl_token_mint_address = &c.mint; quote! { if #name.owner != #wallet_address.key() { - return Err(anchor_lang::error::ErrorCode::ConstraintTokenOwner.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenOwner, #name_str)); } let __associated_token_address = anchor_spl::associated_token::get_associated_token_address(&#wallet_address.key(), &#spl_token_mint_address.key()); if #name.key() != __associated_token_address { - return Err(anchor_lang::error::ErrorCode::ConstraintAssociated.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintAssociated, #name_str)); } } } @@ -786,9 +795,10 @@ pub fn generate_constraint_executable( _c: &ConstraintExecutable, ) -> proc_macro2::TokenStream { let name = &f.ident; + let name_str = name.to_string(); quote! { if !#name.to_account_info().executable { - return Err(anchor_lang::error::ErrorCode::ConstraintExecutable.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintExecutable, #name_str)); } } } @@ -796,6 +806,7 @@ pub fn generate_constraint_executable( pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2::TokenStream { let program_target = c.program_target.clone(); let ident = &f.ident; + let name_str = ident.to_string(); let account_ty = match &f.ty { Ty::CpiState(ty) => &ty.account_type_path, _ => panic!("Invalid state constraint"), @@ -804,20 +815,26 @@ pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2: // Checks the given state account is the canonical state account for // the target program. if #ident.key() != anchor_lang::accounts::cpi_state::CpiState::<#account_ty>::address(&#program_target.key()) { - return Err(anchor_lang::error::ErrorCode::ConstraintState.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintState, #name_str)); } if #ident.as_ref().owner != &#program_target.key() { - return Err(anchor_lang::error::ErrorCode::ConstraintState.into()); + return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintState, #name_str)); } } } fn generate_custom_error( + account_name: &Ident, custom_error: &Option, error: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { + let account_name = account_name.to_string(); match custom_error { - Some(error) => quote! { #error.into() }, - None => quote! { anchor_lang::error::ErrorCode::#error.into() }, + Some(error) => { + quote! { Err(anchor_lang::anchor_attribute_error::error_with_account_name!(#error, #account_name)) } + } + None => { + quote! { Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::#error, #account_name)) } + } } } diff --git a/lang/syn/src/codegen/accounts/exit.rs b/lang/syn/src/codegen/accounts/exit.rs index 60b024b1..735f2256 100644 --- a/lang/syn/src/codegen/accounts/exit.rs +++ b/lang/syn/src/codegen/accounts/exit.rs @@ -18,25 +18,29 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { .map(|af: &AccountField| match af { AccountField::CompositeField(s) => { let name = &s.ident; + let name_str = name.to_string(); quote! { - anchor_lang::AccountsExit::exit(&self.#name, program_id)?; + anchor_lang::AccountsExit::exit(&self.#name, program_id) + .map_err(|e| e.with_account_name(#name_str))?; } } AccountField::Field(f) => { let ident = &f.ident; + let name_str = ident.to_string(); if f.constraints.is_close() { let close_target = &f.constraints.close.as_ref().unwrap().sol_dest; quote! { anchor_lang::AccountsClose::close( &self.#ident, self.#close_target.to_account_info(), - )?; + ).map_err(|e| e.with_account_name(#name_str))?; } } else { match f.constraints.is_mutable() { false => quote! {}, true => quote! { - anchor_lang::AccountsExit::exit(&self.#ident, program_id)?; + anchor_lang::AccountsExit::exit(&self.#ident, program_id) + .map_err(|e| e.with_account_name(#name_str))?; }, } } @@ -46,7 +50,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { quote! { #[automatically_derived] impl<#combined_generics> anchor_lang::AccountsExit<#trait_generics> for #name<#struct_generics> #where_clause{ - fn exit(&self, program_id: &anchor_lang::solana_program::pubkey::Pubkey) -> anchor_lang::solana_program::entrypoint::ProgramResult { + fn exit(&self, program_id: &anchor_lang::solana_program::pubkey::Pubkey) -> anchor_lang::Result<()> { #(#on_save)* Ok(()) } diff --git a/lang/syn/src/codegen/accounts/try_accounts.rs b/lang/syn/src/codegen/accounts/try_accounts.rs index c0205113..e14fc977 100644 --- a/lang/syn/src/codegen/accounts/try_accounts.rs +++ b/lang/syn/src/codegen/accounts/try_accounts.rs @@ -39,11 +39,13 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { *accounts = &accounts[1..]; } } else { - let name = f.typed_ident(); + let name = f.ident.to_string(); + let typed_name = f.typed_ident(); quote! { #[cfg(feature = "anchor-debug")] - ::solana_program::log::sol_log(stringify!(#name)); - let #name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data, __bumps)?; + ::solana_program::log::sol_log(stringify!(#typed_name)); + let #typed_name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data, __bumps) + .map_err(|e| e.with_account_name(#name))?; } } } @@ -93,7 +95,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>], ix_data: &[u8], __bumps: &mut std::collections::BTreeMap, - ) -> std::result::Result { + ) -> anchor_lang::Result { // Deserialize instruction, if declared. #ix_de // Deserialize each account. diff --git a/lang/syn/src/codegen/error.rs b/lang/syn/src/codegen/error.rs index a1857df5..ad12215c 100644 --- a/lang/syn/src/codegen/error.rs +++ b/lang/syn/src/codegen/error.rs @@ -6,7 +6,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { let enum_name = &error.ident; // Each arm of the `match` statement for implementing `std::fmt::Display` // on the user defined error code. - let variant_dispatch: Vec = error + let display_variant_dispatch: Vec = error .raw_enum .variants .iter() @@ -14,7 +14,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { .map(|(idx, variant)| { let ident = &variant.ident; let error_code = &error.codes[idx]; - let msg = match &error_code.msg { + let display_msg = match &error_code.msg { None => { quote! { ::fmt(self, fmt) @@ -27,7 +27,22 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { } }; quote! { - #enum_name::#ident => #msg + #enum_name::#ident => #display_msg + } + }) + .collect(); + + // Each arm of the `match` statement for implementing the `name` function + // on the user defined error code. + let name_variant_dispatch: Vec = error + .raw_enum + .variants + .iter() + .map(|variant| { + let ident = &variant.ident; + let ident_name = ident.to_string(); + quote! { + #enum_name::#ident => #ident_name.to_string() } }) .collect(); @@ -41,50 +56,44 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { }; quote! { - /// Anchor generated Result to be used as the return type for the - /// program. - pub type Result = std::result::Result; - - /// Anchor generated error allowing one to easily return a - /// `ProgramError` or a custom, user defined error code by utilizing - /// its `From` implementation. - #[doc(hidden)] - #[derive(thiserror::Error, Debug)] - pub enum Error { - #[error(transparent)] - ProgramError(#[from] anchor_lang::solana_program::program_error::ProgramError), - #[error(transparent)] - ErrorCode(#[from] #enum_name), - } - #[derive(std::fmt::Debug, Clone, Copy)] #[repr(u32)] #error_enum + impl #enum_name { + pub fn name(&self) -> String { + match self { + #(#name_variant_dispatch),* + } + } + } + + impl From<#enum_name> for u32 { + fn from(e: #enum_name) -> u32 { + e as u32 + #offset + } + } + + impl From<#enum_name> for anchor_lang::error::Error { + fn from(error_code: #enum_name) -> Error { + anchor_lang::error::Error::from( + anchor_lang::error::AnchorError { + error_name: error_code.name(), + error_code_number: error_code.into(), + error_msg: error_code.to_string(), + source: None, + account_name: None + } + ) + } + } + impl std::fmt::Display for #enum_name { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { match self { - #(#variant_dispatch),* + #(#display_variant_dispatch),* } } } - - impl std::error::Error for #enum_name {} - - 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::ProgramError(e) => e, - Error::ErrorCode(c) => anchor_lang::solana_program::program_error::ProgramError::Custom(c as u32 + #offset), - } - } - } - - impl std::convert::From<#enum_name> for anchor_lang::solana_program::program_error::ProgramError { - fn from(e: #enum_name) -> anchor_lang::solana_program::program_error::ProgramError { - let err: Error = e.into(); - err.into() - } - } } } diff --git a/lang/syn/src/codegen/program/cpi.rs b/lang/syn/src/codegen/program/cpi.rs index c3639153..768292c6 100644 --- a/lang/syn/src/codegen/program/cpi.rs +++ b/lang/syn/src/codegen/program/cpi.rs @@ -31,7 +31,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { pub fn #method_name<'a, 'b, 'c, 'info>( ctx: anchor_lang::context::CpiStateContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>, #(#args),* - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { let ix = { let ix = instruction::state::#ix_variant; let data = anchor_lang::InstructionData::data(&ix); @@ -47,7 +47,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { &ix, &acc_infos, ctx.signer_seeds(), - ) + ).map_err(Into::into) } } }) @@ -74,7 +74,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { pub fn #method_name<'a, 'b, 'c, 'info>( ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>, #(#args),* - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { let ix = { let ix = instruction::#ix_variant; let mut ix_data = AnchorSerialize::try_to_vec(&ix) @@ -93,7 +93,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { &ix, &acc_infos, ctx.signer_seeds, - ) + ).map_err(Into::into) } } }; diff --git a/lang/syn/src/codegen/program/dispatch.rs b/lang/syn/src/codegen/program/dispatch.rs index 93f2d625..d8023266 100644 --- a/lang/syn/src/codegen/program/dispatch.rs +++ b/lang/syn/src/codegen/program/dispatch.rs @@ -139,7 +139,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8], - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { // Split the instruction data into the first 8 byte method // identifier (sighash) and the serialized instruction data. let mut ix_data: &[u8] = data; diff --git a/lang/syn/src/codegen/program/entry.rs b/lang/syn/src/codegen/program/entry.rs index af3dd360..16ca7873 100644 --- a/lang/syn/src/codegen/program/entry.rs +++ b/lang/syn/src/codegen/program/entry.rs @@ -6,7 +6,7 @@ use quote::quote; pub fn generate(program: &Program) -> proc_macro2::TokenStream { let name: proc_macro2::TokenStream = program.name.to_string().to_camel_case().parse().unwrap(); let fallback_maybe = dispatch::gen_fallback(program).unwrap_or(quote! { - Err(anchor_lang::error::ErrorCode::InstructionMissing.into()); + Err(anchor_lang::error::ErrorCode::InstructionMissing.into()) }); quote! { #[cfg(not(feature = "no-entrypoint"))] @@ -51,7 +51,14 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { /// /// The `entry` function here, defines the standard entry to a Solana /// program, where execution begins. - pub fn entry(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + pub fn entry(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> anchor_lang::solana_program::entrypoint::ProgramResult { + try_entry(program_id, accounts, data).map_err(|e| { + e.log(); + e.into() + }) + } + + fn try_entry(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> anchor_lang::Result<()> { #[cfg(feature = "anchor-debug")] { msg!("anchor-debug is active"); @@ -60,14 +67,10 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { return Err(anchor_lang::error::ErrorCode::DeclaredProgramIdMismatch.into()); } if data.len() < 8 { - return #fallback_maybe + return #fallback_maybe; } dispatch(program_id, accounts, data) - .map_err(|e| { - anchor_lang::solana_program::msg!(&e.to_string()); - e - }) } pub mod program { diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index d5ec4ee7..2f30a617 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -16,7 +16,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { // on chain. #[inline(never)] #[cfg(not(feature = "no-idl"))] - pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult { + pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> anchor_lang::Result<()> { let mut accounts = accounts; let mut data: &[u8] = idl_ix_data; @@ -65,7 +65,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { #[inline(never)] #[cfg(feature = "no-idl")] - pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult { + pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> anchor_lang::Result<()> { Err(anchor_lang::error::ErrorCode::IdlInstructionStub.into()) } @@ -76,7 +76,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id: &Pubkey, accounts: &mut anchor_lang::idl::IdlCreateAccounts, data_len: u64, - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!("Instruction: IdlCreateAccount"); @@ -137,7 +137,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { pub fn __idl_create_buffer( program_id: &Pubkey, accounts: &mut anchor_lang::idl::IdlCreateBuffer, - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!("Instruction: IdlCreateBuffer"); @@ -151,7 +151,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id: &Pubkey, accounts: &mut anchor_lang::idl::IdlAccounts, idl_data: Vec, - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!("Instruction: IdlWrite"); @@ -165,7 +165,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id: &Pubkey, accounts: &mut anchor_lang::idl::IdlAccounts, new_authority: Pubkey, - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!("Instruction: IdlSetAuthority"); @@ -177,7 +177,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { pub fn __idl_set_buffer( program_id: &Pubkey, accounts: &mut anchor_lang::idl::IdlSetBuffer, - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!("Instruction: IdlSetBuffer"); @@ -214,7 +214,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { // One time state account initializer. Will faill on subsequent // invocations. #[inline(never)] - pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult { + pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!(#ix_name_log); @@ -296,7 +296,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { // One time state account initializer. Will faill on subsequent // invocations. #[inline(never)] - pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult { + pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!(#ix_name_log); @@ -405,7 +405,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8], - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!(#ix_name_log); @@ -461,7 +461,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8], - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!(#ix_name_log); @@ -589,7 +589,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8], - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!(#ix_name_log); @@ -649,7 +649,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8], - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!(#ix_name_log); @@ -691,6 +691,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { }) .unwrap_or_default(), }; + let non_inlined_handlers: Vec = program .ixs .iter() @@ -707,7 +708,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8], - ) -> ProgramResult { + ) -> anchor_lang::Result<()> { #[cfg(not(feature = "no-log-ix-name"))] anchor_lang::prelude::msg!(#ix_name_log); diff --git a/lang/syn/src/idl/file.rs b/lang/syn/src/idl/file.rs index bc79a5f9..8cb268a7 100644 --- a/lang/syn/src/idl/file.rs +++ b/lang/syn/src/idl/file.rs @@ -316,7 +316,7 @@ fn parse_error_enum(ctx: &CrateContext) -> Option { .iter() .filter(|attr| { let segment = attr.path.segments.last().unwrap(); - segment.ident == "error" + segment.ident == "error_code" }) .count(); match attrs_count { diff --git a/lang/syn/src/parser/error.rs b/lang/syn/src/parser/error.rs index 10bdf72c..59b830ce 100644 --- a/lang/syn/src/parser/error.rs +++ b/lang/syn/src/parser/error.rs @@ -1,4 +1,6 @@ use crate::{Error, ErrorArgs, ErrorCode}; +use syn::parse::{Parse, Result as ParseResult}; +use syn::{Expr, Token}; // Removes any internal #[msg] attributes, as they are inert. pub fn parse(error_enum: &mut syn::ItemEnum, args: Option) -> Error { @@ -75,3 +77,31 @@ fn parse_error_attribute(variant: &syn::Variant) -> Option { } } } + +pub struct ErrorInput { + pub error_code: Expr, +} + +impl Parse for ErrorInput { + fn parse(stream: syn::parse::ParseStream) -> ParseResult { + let error_code = stream.call(Expr::parse)?; + Ok(Self { error_code }) + } +} + +pub struct ErrorWithAccountNameInput { + pub error_code: Expr, + pub account_name: Expr, +} + +impl Parse for ErrorWithAccountNameInput { + fn parse(stream: syn::parse::ParseStream) -> ParseResult { + let error_code = stream.call(Expr::parse)?; + let _ = stream.parse::(); + let account_name = stream.call(Expr::parse)?; + Ok(Self { + error_code, + account_name, + }) + } +} diff --git a/spl/Cargo.toml b/spl/Cargo.toml index 12c88a0c..dd3c13e4 100644 --- a/spl/Cargo.toml +++ b/spl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anchor-spl" -version = "0.21.0" +version = "0.22.0" authors = ["Serum Foundation "] edition = "2018" license = "Apache-2.0" @@ -17,7 +17,7 @@ devnet = [] dex = ["serum_dex"] [dependencies] -anchor-lang = { path = "../lang", version = "0.21.0", features = ["derive"] } +anchor-lang = { path = "../lang", version = "0.22.0", features = ["derive"] } serum_dex = { git = "https://github.com/project-serum/serum-dex", rev = "1be91f2", version = "0.4.0", features = ["no-entrypoint"], optional = true } solana-program = "1.8.5" spl-token = { version = "3.1.1", features = ["no-entrypoint"], optional = true } diff --git a/spl/src/dex.rs b/spl/src/dex.rs index a8cb0ccd..460f9cbe 100644 --- a/spl/src/dex.rs +++ b/spl/src/dex.rs @@ -1,7 +1,7 @@ use anchor_lang::solana_program::account_info::AccountInfo; -use anchor_lang::solana_program::entrypoint::ProgramResult; +use anchor_lang::solana_program::program_error::ProgramError; use anchor_lang::solana_program::pubkey::Pubkey; -use anchor_lang::{context::CpiContext, Accounts, ToAccountInfos}; +use anchor_lang::{context::CpiContext, Accounts, Result, ToAccountInfos}; use serum_dex::instruction::SelfTradeBehavior; use serum_dex::matching::{OrderType, Side}; use std::num::NonZeroU64; @@ -25,7 +25,7 @@ pub fn new_order_v3<'info>( order_type: OrderType, client_order_id: u64, limit: u16, -) -> ProgramResult { +) -> Result<()> { let referral = ctx.remaining_accounts.get(0); let ix = serum_dex::instruction::new_order( ctx.accounts.market.key, @@ -50,7 +50,8 @@ pub fn new_order_v3<'info>( self_trade_behavior, limit, max_native_pc_qty_including_fees, - )?; + ) + .map_err(|pe| ProgramError::from(pe))?; solana_program::program::invoke_signed( &ix, &ToAccountInfos::to_account_infos(&ctx), @@ -63,7 +64,7 @@ pub fn cancel_order_v2<'info>( ctx: CpiContext<'_, '_, '_, 'info, CancelOrderV2<'info>>, side: Side, order_id: u128, -) -> ProgramResult { +) -> Result<()> { let ix = serum_dex::instruction::cancel_order( &ID, ctx.accounts.market.key, @@ -74,7 +75,8 @@ pub fn cancel_order_v2<'info>( ctx.accounts.event_queue.key, side, order_id, - )?; + ) + .map_err(|pe| ProgramError::from(pe))?; solana_program::program::invoke_signed( &ix, &ToAccountInfos::to_account_infos(&ctx), @@ -83,9 +85,7 @@ pub fn cancel_order_v2<'info>( Ok(()) } -pub fn settle_funds<'info>( - ctx: CpiContext<'_, '_, '_, 'info, SettleFunds<'info>>, -) -> ProgramResult { +pub fn settle_funds<'info>(ctx: CpiContext<'_, '_, '_, 'info, SettleFunds<'info>>) -> Result<()> { let referral = ctx.remaining_accounts.get(0); let ix = serum_dex::instruction::settle_funds( &ID, @@ -99,7 +99,8 @@ pub fn settle_funds<'info>( ctx.accounts.pc_wallet.key, referral.map(|r| r.key), ctx.accounts.vault_signer.key, - )?; + ) + .map_err(|pe| ProgramError::from(pe))?; solana_program::program::invoke_signed( &ix, &ToAccountInfos::to_account_infos(&ctx), @@ -110,14 +111,15 @@ pub fn settle_funds<'info>( pub fn init_open_orders<'info>( ctx: CpiContext<'_, '_, '_, 'info, InitOpenOrders<'info>>, -) -> ProgramResult { +) -> Result<()> { let ix = serum_dex::instruction::init_open_orders( &ID, ctx.accounts.open_orders.key, ctx.accounts.authority.key, ctx.accounts.market.key, ctx.remaining_accounts.first().map(|acc| acc.key), - )?; + ) + .map_err(|pe| ProgramError::from(pe))?; solana_program::program::invoke_signed( &ix, &ToAccountInfos::to_account_infos(&ctx), @@ -128,14 +130,15 @@ pub fn init_open_orders<'info>( pub fn close_open_orders<'info>( ctx: CpiContext<'_, '_, '_, 'info, CloseOpenOrders<'info>>, -) -> ProgramResult { +) -> Result<()> { let ix = serum_dex::instruction::close_open_orders( &ID, ctx.accounts.open_orders.key, ctx.accounts.authority.key, ctx.accounts.destination.key, ctx.accounts.market.key, - )?; + ) + .map_err(|pe| ProgramError::from(pe))?; solana_program::program::invoke_signed( &ix, &ToAccountInfos::to_account_infos(&ctx), @@ -144,7 +147,7 @@ pub fn close_open_orders<'info>( Ok(()) } -pub fn sweep_fees<'info>(ctx: CpiContext<'_, '_, '_, 'info, SweepFees<'info>>) -> ProgramResult { +pub fn sweep_fees<'info>(ctx: CpiContext<'_, '_, '_, 'info, SweepFees<'info>>) -> Result<()> { let ix = serum_dex::instruction::sweep_fees( &ID, ctx.accounts.market.key, @@ -153,7 +156,8 @@ pub fn sweep_fees<'info>(ctx: CpiContext<'_, '_, '_, 'info, SweepFees<'info>>) - ctx.accounts.sweep_receiver.key, ctx.accounts.vault_signer.key, ctx.accounts.token_program.key, - )?; + ) + .map_err(|pe| ProgramError::from(pe))?; solana_program::program::invoke_signed( &ix, &ToAccountInfos::to_account_infos(&ctx), @@ -168,7 +172,7 @@ pub fn initialize_market<'info>( pc_lot_size: u64, vault_signer_nonce: u64, pc_dust_threshold: u64, -) -> ProgramResult { +) -> Result<()> { let authority = ctx.remaining_accounts.get(0); let prune_authority = ctx.remaining_accounts.get(1); let ix = serum_dex::instruction::initialize_market( @@ -188,7 +192,8 @@ pub fn initialize_market<'info>( pc_lot_size, vault_signer_nonce, pc_dust_threshold, - )?; + ) + .map_err(|pe| ProgramError::from(pe))?; solana_program::program::invoke_signed( &ix, &ToAccountInfos::to_account_infos(&ctx), diff --git a/spl/src/token.rs b/spl/src/token.rs index fcf20e07..2ed7558a 100644 --- a/spl/src/token.rs +++ b/spl/src/token.rs @@ -1,10 +1,9 @@ -use anchor_lang::solana_program; use anchor_lang::solana_program::account_info::AccountInfo; -use anchor_lang::solana_program::entrypoint::ProgramResult; -use anchor_lang::solana_program::program_error::ProgramError; + use anchor_lang::solana_program::program_pack::Pack; use anchor_lang::solana_program::pubkey::Pubkey; use anchor_lang::{context::CpiContext, Accounts}; +use anchor_lang::{solana_program, Result}; use std::ops::Deref; pub use spl_token::ID; @@ -12,7 +11,7 @@ pub use spl_token::ID; pub fn transfer<'a, 'b, 'c, 'info>( ctx: CpiContext<'a, 'b, 'c, 'info, Transfer<'info>>, amount: u64, -) -> ProgramResult { +) -> Result<()> { let ix = spl_token::instruction::transfer( &spl_token::ID, ctx.accounts.from.key, @@ -30,12 +29,13 @@ pub fn transfer<'a, 'b, 'c, 'info>( ], ctx.signer_seeds, ) + .map_err(Into::into) } pub fn mint_to<'a, 'b, 'c, 'info>( ctx: CpiContext<'a, 'b, 'c, 'info, MintTo<'info>>, amount: u64, -) -> ProgramResult { +) -> Result<()> { let ix = spl_token::instruction::mint_to( &spl_token::ID, ctx.accounts.mint.key, @@ -53,12 +53,13 @@ pub fn mint_to<'a, 'b, 'c, 'info>( ], ctx.signer_seeds, ) + .map_err(Into::into) } pub fn burn<'a, 'b, 'c, 'info>( ctx: CpiContext<'a, 'b, 'c, 'info, Burn<'info>>, amount: u64, -) -> ProgramResult { +) -> Result<()> { let ix = spl_token::instruction::burn( &spl_token::ID, ctx.accounts.to.key, @@ -76,12 +77,13 @@ pub fn burn<'a, 'b, 'c, 'info>( ], ctx.signer_seeds, ) + .map_err(Into::into) } pub fn approve<'a, 'b, 'c, 'info>( ctx: CpiContext<'a, 'b, 'c, 'info, Approve<'info>>, amount: u64, -) -> ProgramResult { +) -> Result<()> { let ix = spl_token::instruction::approve( &spl_token::ID, ctx.accounts.to.key, @@ -99,11 +101,12 @@ pub fn approve<'a, 'b, 'c, 'info>( ], ctx.signer_seeds, ) + .map_err(Into::into) } pub fn initialize_account<'a, 'b, 'c, 'info>( ctx: CpiContext<'a, 'b, 'c, 'info, InitializeAccount<'info>>, -) -> ProgramResult { +) -> Result<()> { let ix = spl_token::instruction::initialize_account( &spl_token::ID, ctx.accounts.account.key, @@ -120,11 +123,12 @@ pub fn initialize_account<'a, 'b, 'c, 'info>( ], ctx.signer_seeds, ) + .map_err(Into::into) } pub fn close_account<'a, 'b, 'c, 'info>( ctx: CpiContext<'a, 'b, 'c, 'info, CloseAccount<'info>>, -) -> ProgramResult { +) -> Result<()> { let ix = spl_token::instruction::close_account( &spl_token::ID, ctx.accounts.account.key, @@ -141,11 +145,12 @@ pub fn close_account<'a, 'b, 'c, 'info>( ], ctx.signer_seeds, ) + .map_err(Into::into) } pub fn freeze_account<'a, 'b, 'c, 'info>( ctx: CpiContext<'a, 'b, 'c, 'info, FreezeAccount<'info>>, -) -> ProgramResult { +) -> Result<()> { let ix = spl_token::instruction::freeze_account( &spl_token::ID, ctx.accounts.account.key, @@ -162,11 +167,12 @@ pub fn freeze_account<'a, 'b, 'c, 'info>( ], ctx.signer_seeds, ) + .map_err(Into::into) } pub fn thaw_account<'a, 'b, 'c, 'info>( ctx: CpiContext<'a, 'b, 'c, 'info, ThawAccount<'info>>, -) -> ProgramResult { +) -> Result<()> { let ix = spl_token::instruction::thaw_account( &spl_token::ID, ctx.accounts.account.key, @@ -183,6 +189,7 @@ pub fn thaw_account<'a, 'b, 'c, 'info>( ], ctx.signer_seeds, ) + .map_err(Into::into) } pub fn initialize_mint<'a, 'b, 'c, 'info>( @@ -190,7 +197,7 @@ pub fn initialize_mint<'a, 'b, 'c, 'info>( decimals: u8, authority: &Pubkey, freeze_authority: Option<&Pubkey>, -) -> ProgramResult { +) -> Result<()> { let ix = spl_token::instruction::initialize_mint( &spl_token::ID, ctx.accounts.mint.key, @@ -203,13 +210,14 @@ pub fn initialize_mint<'a, 'b, 'c, 'info>( &[ctx.accounts.mint.clone(), ctx.accounts.rent.clone()], ctx.signer_seeds, ) + .map_err(Into::into) } pub fn set_authority<'a, 'b, 'c, 'info>( ctx: CpiContext<'a, 'b, 'c, 'info, SetAuthority<'info>>, authority_type: spl_token::instruction::AuthorityType, new_authority: Option, -) -> ProgramResult { +) -> Result<()> { let mut spl_new_authority: Option<&Pubkey> = None; if new_authority.is_some() { spl_new_authority = new_authority.as_ref() @@ -231,6 +239,7 @@ pub fn set_authority<'a, 'b, 'c, 'info>( ], ctx.signer_seeds, ) + .map_err(Into::into) } #[derive(Accounts)] @@ -310,8 +319,10 @@ impl TokenAccount { } impl anchor_lang::AccountDeserialize for TokenAccount { - fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { - spl_token::state::Account::unpack(buf).map(TokenAccount) + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + spl_token::state::Account::unpack(buf) + .map(TokenAccount) + .map_err(Into::into) } } @@ -339,8 +350,10 @@ impl Mint { } impl anchor_lang::AccountDeserialize for Mint { - fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { - spl_token::state::Mint::unpack(buf).map(Mint) + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + spl_token::state::Mint::unpack(buf) + .map(Mint) + .map_err(Into::into) } } @@ -374,21 +387,21 @@ impl anchor_lang::Id for Token { pub mod accessor { use super::*; - pub fn amount(account: &AccountInfo) -> Result { + pub fn amount(account: &AccountInfo) -> Result { let bytes = account.try_borrow_data()?; let mut amount_bytes = [0u8; 8]; amount_bytes.copy_from_slice(&bytes[64..72]); Ok(u64::from_le_bytes(amount_bytes)) } - pub fn mint(account: &AccountInfo) -> Result { + pub fn mint(account: &AccountInfo) -> Result { let bytes = account.try_borrow_data()?; let mut mint_bytes = [0u8; 32]; mint_bytes.copy_from_slice(&bytes[..32]); Ok(Pubkey::new_from_array(mint_bytes)) } - pub fn authority(account: &AccountInfo) -> Result { + pub fn authority(account: &AccountInfo) -> Result { let bytes = account.try_borrow_data()?; let mut owner_bytes = [0u8; 32]; owner_bytes.copy_from_slice(&bytes[32..64]); diff --git a/tests/auction-house b/tests/auction-house index 2b1b1e04..63e7bb81 160000 --- a/tests/auction-house +++ b/tests/auction-house @@ -1 +1 @@ -Subproject commit 2b1b1e04986106715ab53794bcb63d3641673f64 +Subproject commit 63e7bb81beb76f2722245a37c16a7b0b00d6905a diff --git a/tests/bpf-upgradeable-state/package.json b/tests/bpf-upgradeable-state/package.json index 178ce3f7..f20be7b5 100644 --- a/tests/bpf-upgradeable-state/package.json +++ b/tests/bpf-upgradeable-state/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "@project-serum/anchor": "^0.21.0" + "@project-serum/anchor": "^0.22.0" }, "devDependencies": { "chai": "^4.3.4", diff --git a/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs b/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs index 661d6a99..09087114 100644 --- a/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs +++ b/tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs @@ -5,17 +5,17 @@ declare_id!("Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e"); #[program] pub mod bpf_upgradeable_state { use super::*; - pub fn set_admin_settings(ctx: Context, admin_data: u64) -> ProgramResult { + pub fn set_admin_settings(ctx: Context, admin_data: u64) -> Result<()> { match *ctx.accounts.program { UpgradeableLoaderState::Program { programdata_address, } => { if programdata_address != ctx.accounts.program_data.key() { - return Err(CustomError::InvalidProgramDataAddress.into()); + return err!(CustomError::InvalidProgramDataAddress); } } _ => { - return Err(CustomError::AccountNotProgram.into()); + return err!(CustomError::AccountNotProgram); } }; ctx.accounts.settings.admin_data = admin_data; @@ -25,7 +25,7 @@ pub mod bpf_upgradeable_state { pub fn set_admin_settings_use_program_state( ctx: Context, admin_data: u64, - ) -> ProgramResult { + ) -> Result<()> { ctx.accounts.settings.admin_data = admin_data; Ok(()) } @@ -37,7 +37,7 @@ pub struct Settings { admin_data: u64, } -#[error] +#[error_code] pub enum CustomError { InvalidProgramDataAddress, AccountNotProgram, diff --git a/tests/cashiers-check/Anchor.toml b/tests/cashiers-check/Anchor.toml index 6b39d257..5a733e1a 100644 --- a/tests/cashiers-check/Anchor.toml +++ b/tests/cashiers-check/Anchor.toml @@ -9,4 +9,3 @@ cashiers_check = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" test = "yarn run mocha -t 1000000 tests/" [features] -safety_checks = false diff --git a/tests/cashiers-check/package.json b/tests/cashiers-check/package.json index 1e7b994f..cbf96d98 100644 --- a/tests/cashiers-check/package.json +++ b/tests/cashiers-check/package.json @@ -1,6 +1,6 @@ { "name": "cashiers-check", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/cashiers-check/programs/cashiers-check/src/lib.rs b/tests/cashiers-check/programs/cashiers-check/src/lib.rs index 250339f4..9cdae60d 100644 --- a/tests/cashiers-check/programs/cashiers-check/src/lib.rs +++ b/tests/cashiers-check/programs/cashiers-check/src/lib.rs @@ -109,9 +109,9 @@ impl<'info> CreateCheck<'info> { &[ctx.accounts.check.to_account_info().key.as_ref(), &[nonce]], ctx.program_id, ) - .map_err(|_| ErrorCode::InvalidCheckNonce)?; + .map_err(|_| error!(ErrorCode::InvalidCheckNonce))?; if &signer != ctx.accounts.check_signer.to_account_info().key { - return Err(ErrorCode::InvalidCheckSigner.into()); + return err!(ErrorCode::InvalidCheckSigner); } Ok(()) } @@ -162,7 +162,7 @@ pub struct Check { burned: bool, } -#[error] +#[error_code] pub enum ErrorCode { #[msg("The given nonce does not create a valid program derived address.")] InvalidCheckNonce, @@ -174,7 +174,7 @@ pub enum ErrorCode { fn not_burned(check: &Check) -> Result<()> { if check.burned { - return Err(ErrorCode::AlreadyBurned.into()); + return err!(ErrorCode::AlreadyBurned); } Ok(()) } diff --git a/tests/cfo/Anchor.toml b/tests/cfo/Anchor.toml index 640628a3..903bbe80 100644 --- a/tests/cfo/Anchor.toml +++ b/tests/cfo/Anchor.toml @@ -12,15 +12,15 @@ lockup = { address = "6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks", idl = "./de # Testing. # test = "yarn run mocha -t 1000000 tests/" -test-with-build = "anchor run build && anchor test --skip-build" +test-with-build = "anchor run build && anchor test --skip-build --skip-lint" # # Build the program and all CPI dependencies. # -build = "anchor run build-deps && anchor build" +build = "anchor run build-deps && anchor build --skip-lint" build-deps = "anchor run build-dex && anchor run build-swap && anchor run build-stake" build-dex = "pushd deps/serum-dex/dex/ && cargo build-bpf && popd" -build-swap = "cd deps/swap && pwd && anchor build && cd ../../" -build-stake = "pushd deps/stake && anchor build && popd" +build-swap = "cd deps/swap && pwd && anchor build --skip-lint && cd ../../" +build-stake = "pushd deps/stake && anchor build --skip-lint && popd" # # Runs a localnet with all the programs deployed. # @@ -43,4 +43,3 @@ address = "6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks" program = "./deps/stake/target/deploy/lockup.so" [features] -safety_checks = false diff --git a/tests/cfo/deps/stake b/tests/cfo/deps/stake index 990eaa79..6a1c128e 160000 --- a/tests/cfo/deps/stake +++ b/tests/cfo/deps/stake @@ -1 +1 @@ -Subproject commit 990eaa7944c6682838fdaa6c14cc07ed680007f0 +Subproject commit 6a1c128e859b13b39812c0c75d202b2bf9ee1e8c diff --git a/tests/cfo/deps/swap b/tests/cfo/deps/swap index 96e3b1e2..3da36aaa 160000 --- a/tests/cfo/deps/swap +++ b/tests/cfo/deps/swap @@ -1 +1 @@ -Subproject commit 96e3b1e2a53a95ef56e6ec2da68348ffd6a5c091 +Subproject commit 3da36aaae7af6ce901d68c0280aac34817fe7fd8 diff --git a/tests/cfo/package.json b/tests/cfo/package.json index 3ddc967d..81c62016 100644 --- a/tests/cfo/package.json +++ b/tests/cfo/package.json @@ -1,6 +1,6 @@ { "name": "cfo", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/cfo/programs/cfo/src/lib.rs b/tests/cfo/programs/cfo/src/lib.rs index 88cba51c..99e759ad 100644 --- a/tests/cfo/programs/cfo/src/lib.rs +++ b/tests/cfo/programs/cfo/src/lib.rs @@ -155,7 +155,7 @@ pub mod cfo { .checked_div(100) .unwrap() .try_into() - .map_err(|_| ErrorCode::U128CannotConvert)?; + .map_err(|_| error!(ErrorCode::U128CannotConvert))?; token::burn(ctx.accounts.into_burn().with_signer(&[&seeds]), burn_amount)?; // Stake. @@ -165,7 +165,7 @@ pub mod cfo { .checked_div(100) .unwrap() .try_into() - .map_err(|_| ErrorCode::U128CannotConvert)?; + .map_err(|_| error!(ErrorCode::U128CannotConvert))?; token::transfer( ctx.accounts.into_stake_transfer().with_signer(&[&seeds]), stake_amount, @@ -178,7 +178,7 @@ pub mod cfo { .checked_div(100) .unwrap() .try_into() - .map_err(|_| ErrorCode::U128CannotConvert)?; + .map_err(|_| error!(ErrorCode::U128CannotConvert))?; token::transfer( ctx.accounts.into_treasury_transfer().with_signer(&[&seeds]), treasury_amount, @@ -195,7 +195,7 @@ pub mod cfo { let expiry_ts = 1853942400; // 9/30/2028. let expiry_receiver = *ctx.accounts.officer.to_account_info().key; let locked_kind = { - let start_ts = 1633017600; // 9/30.21.0. + let start_ts = 1633017600; // 9/30.22.0. let end_ts = 1822320000; // 9/30/2027. let period_count = 2191; RewardVendorKind::Locked { @@ -236,7 +236,7 @@ pub mod cfo { .checked_div(total_pool_value) .unwrap() .try_into() - .map_err(|_| ErrorCode::U128CannotConvert)?; + .map_err(|_| error!(ErrorCode::U128CannotConvert))?; // Proportion of the reward going to the msrm pool. // @@ -248,7 +248,7 @@ pub mod cfo { .checked_div(total_pool_value) .unwrap() .try_into() - .map_err(|_| ErrorCode::U128CannotConvert)?; + .map_err(|_| error!(ErrorCode::U128CannotConvert))?; // SRM drop. { @@ -606,9 +606,9 @@ pub struct Distribute<'info> { has_one = treasury, has_one = stake, )] - officer: Account<'info, Officer>, + officer: Box>, #[account(mut)] - treasury: Account<'info, TokenAccount>, + treasury: Box>, #[account(mut)] stake: Account<'info, TokenAccount>, #[account(mut)] @@ -900,7 +900,7 @@ pub struct OfficerDidCreate { // Error. -#[error] +#[error_code] pub enum ErrorCode { #[msg("Distribution does not add to 100")] InvalidDistribution, @@ -918,14 +918,14 @@ pub enum ErrorCode { fn is_distribution_valid(d: &Distribution) -> Result<()> { if d.burn + d.stake + d.treasury != 100 { - return Err(ErrorCode::InvalidDistribution.into()); + return err!(ErrorCode::InvalidDistribution); } Ok(()) } fn is_distribution_ready(accounts: &Distribute) -> Result<()> { if accounts.srm_vault.amount < 1_000_000 { - return Err(ErrorCode::InsufficientDistributionAmount.into()); + return err!(ErrorCode::InsufficientDistributionAmount); } Ok(()) } @@ -934,7 +934,7 @@ fn is_distribution_ready(accounts: &Distribute) -> Result<()> { fn is_not_trading(ixs: &UncheckedAccount) -> Result<()> { let data = ixs.try_borrow_data()?; match tx_instructions::load_instruction_at(1, &data) { - Ok(_) => Err(ErrorCode::TooManyInstructions.into()), + Ok(_) => err!(ErrorCode::TooManyInstructions), Err(_) => Ok(()), } } @@ -943,7 +943,7 @@ fn is_stake_reward_ready(accounts: &DropStakeReward) -> Result<()> { // Min drop is 15,0000 SRM. let min_reward: u64 = 15_000_000_000; if accounts.stake.amount < min_reward { - return Err(ErrorCode::InsufficientStakeReward.into()); + return err!(ErrorCode::InsufficientStakeReward); } Ok(()) } diff --git a/tests/chat/Anchor.toml b/tests/chat/Anchor.toml index 1249c722..ec4727cb 100644 --- a/tests/chat/Anchor.toml +++ b/tests/chat/Anchor.toml @@ -9,4 +9,3 @@ chat = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" test = "yarn run mocha -t 1000000 tests/" [features] -safety_checks = false diff --git a/tests/chat/package.json b/tests/chat/package.json index 91d383bf..bbfb9c0e 100644 --- a/tests/chat/package.json +++ b/tests/chat/package.json @@ -1,6 +1,6 @@ { "name": "chat", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/chat/programs/chat/src/lib.rs b/tests/chat/programs/chat/src/lib.rs index 7d9d8e02..be442852 100644 --- a/tests/chat/programs/chat/src/lib.rs +++ b/tests/chat/programs/chat/src/lib.rs @@ -107,7 +107,7 @@ pub struct Message { pub data: [u8; 280], } -#[error] +#[error_code] pub enum ErrorCode { Unknown, } diff --git a/tests/composite/package.json b/tests/composite/package.json index 8716571d..026ff5c7 100644 --- a/tests/composite/package.json +++ b/tests/composite/package.json @@ -1,6 +1,6 @@ { "name": "composite", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/composite/programs/composite/src/lib.rs b/tests/composite/programs/composite/src/lib.rs index 74a8459a..770b4308 100644 --- a/tests/composite/programs/composite/src/lib.rs +++ b/tests/composite/programs/composite/src/lib.rs @@ -8,7 +8,7 @@ declare_id!("EHthziFziNoac9LBGxEaVN47Y3uUiRoXvqAiR6oes4iU"); #[program] mod composite { use super::*; - pub fn initialize(_ctx: Context) -> ProgramResult { + pub fn initialize(_ctx: Context) -> Result<()> { Ok(()) } @@ -16,7 +16,7 @@ mod composite { ctx: Context, dummy_a: u64, dummy_b: u64, - ) -> ProgramResult { + ) -> Result<()> { let a = &mut ctx.accounts.foo.dummy_a; let b = &mut ctx.accounts.bar.dummy_b; diff --git a/tests/custom-coder/Anchor.toml b/tests/custom-coder/Anchor.toml index ff7a52de..6ea244ca 100644 --- a/tests/custom-coder/Anchor.toml +++ b/tests/custom-coder/Anchor.toml @@ -13,4 +13,3 @@ wallet = "~/.config/solana/id.json" test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" [features] -safety_checks = false diff --git a/tests/declare-id/package.json b/tests/declare-id/package.json index 0be35fc5..54659e92 100644 --- a/tests/declare-id/package.json +++ b/tests/declare-id/package.json @@ -1,6 +1,6 @@ { "name": "declare-id", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/declare-id/programs/declare-id/src/lib.rs b/tests/declare-id/programs/declare-id/src/lib.rs index 5e98ce2e..dba4a849 100644 --- a/tests/declare-id/programs/declare-id/src/lib.rs +++ b/tests/declare-id/programs/declare-id/src/lib.rs @@ -3,12 +3,11 @@ use anchor_lang::prelude::*; // Intentionally different program id than the one defined in Anchor.toml. declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); - #[program] mod declare_id { use super::*; - pub fn initialize(_ctx: Context) -> ProgramResult { + pub fn initialize(_ctx: Context) -> Result<()> { Ok(()) } } diff --git a/tests/errors/Anchor.toml b/tests/errors/Anchor.toml index 52f2897d..82be99c6 100644 --- a/tests/errors/Anchor.toml +++ b/tests/errors/Anchor.toml @@ -9,4 +9,3 @@ errors = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" test = "yarn run mocha -t 1000000 tests/" [features] -safety_checks = false diff --git a/tests/errors/package.json b/tests/errors/package.json index a8acd518..8be73d64 100644 --- a/tests/errors/package.json +++ b/tests/errors/package.json @@ -1,6 +1,6 @@ { "name": "errors", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/errors/programs/errors/src/lib.rs b/tests/errors/programs/errors/src/lib.rs index d0833751..1a37773a 100644 --- a/tests/errors/programs/errors/src/lib.rs +++ b/tests/errors/programs/errors/src/lib.rs @@ -10,15 +10,32 @@ mod errors { use super::*; pub fn hello(_ctx: Context) -> Result<()> { - Err(MyError::Hello.into()) + err!(MyError::Hello) } pub fn hello_no_msg(_ctx: Context) -> Result<()> { - Err(MyError::HelloNoMsg.into()) + err!(MyError::HelloNoMsg) } pub fn hello_next(_ctx: Context) -> Result<()> { - Err(MyError::HelloNext.into()) + err!(MyError::HelloNext) + } + + pub fn test_require(_ctx: Context) -> Result<()> { + require!(false, MyError::Hello); + Ok(()) + } + + pub fn test_err(_ctx: Context) -> Result<()> { + err!(MyError::Hello) + } + + pub fn test_program_error(_ctx: Context) -> Result<()> { + Err(ProgramError::InvalidAccountData.into()) + } + + pub fn test_program_error_with_source(_ctx: Context) -> Result<()> { + Err(Error::from(ProgramError::InvalidAccountData).with_source(source!())) } pub fn mut_error(_ctx: Context) -> Result<()> { @@ -82,7 +99,7 @@ pub struct AccountNotInitializedError<'info> { not_initialized_account: Account<'info, AnyAccount>, } -#[error] +#[error_code] pub enum MyError { #[msg("This is an error message clients will automatically display")] Hello, diff --git a/tests/errors/tests/errors.js b/tests/errors/tests/errors.js index be2ab972..9000f5b2 100644 --- a/tests/errors/tests/errors.js +++ b/tests/errors/tests/errors.js @@ -2,15 +2,62 @@ const assert = require("assert"); const anchor = require("@project-serum/anchor"); const { Account, Transaction, TransactionInstruction } = anchor.web3; +// sleep to allow logs to come in +const sleep = (ms) => + new Promise((resolve) => { + setTimeout(() => resolve(), ms); + }); + +const withLogTest = async (callback, expectedLog) => { + let logTestOk = false; + const listener = anchor.getProvider().connection.onLogs( + "all", + (logs) => { + if (logs.logs.some((logLine) => logLine === expectedLog)) { + logTestOk = true; + } else { + console.log(logs); + } + }, + "recent" + ); + try { + await callback(); + } catch (err) { + anchor.getProvider().connection.removeOnLogsListener(listener); + throw err; + } + await sleep(3000); + anchor.getProvider().connection.removeOnLogsListener(listener); + assert.ok(logTestOk); +}; + describe("errors", () => { // Configure the client to use the local cluster. - anchor.setProvider(anchor.Provider.local()); + const localProvider = anchor.Provider.local(); + localProvider.opts.skipPreflight = true; + anchor.setProvider(localProvider); const program = anchor.workspace.Errors; it("Emits a Hello error", async () => { + await withLogTest(async () => { + try { + const tx = await program.rpc.hello(); + assert.ok(false); + } catch (err) { + const errMsg = + "This is an error message clients will automatically display"; + assert.equal(err.toString(), errMsg); + assert.equal(err.msg, errMsg); + assert.equal(err.code, 6000); + } + }, "Program log: AnchorError thrown in programs/errors/src/lib.rs:13. Error Code: Hello. Error Number: 6000. Error Message: This is an error message clients will automatically display."); + }); + + it("Emits a Hello error via require!", async () => { try { - const tx = await program.rpc.hello(); + const tx = await program.rpc.testRequire(); assert.ok(false); } catch (err) { const errMsg = @@ -21,6 +68,41 @@ describe("errors", () => { } }); + it("Emits a Hello error via err!", async () => { + try { + const tx = await program.rpc.testErr(); + assert.ok(false); + } catch (err) { + const errMsg = + "This is an error message clients will automatically display"; + assert.equal(err.toString(), errMsg); + assert.equal(err.msg, errMsg); + assert.equal(err.code, 6000); + } + }); + + it("Logs a ProgramError", async () => { + await withLogTest(async () => { + try { + const tx = await program.rpc.testProgramError(); + assert.ok(false); + } catch (err) { + // No-op (withLogTest expects the callback to catch the initial tx error) + } + }, "Program log: ProgramError occurred. Error Code: InvalidAccountData. Error Number: 17179869184. Error Message: An account's data contents was invalid."); + }); + + it("Logs a ProgramError with source", async () => { + await withLogTest(async () => { + try { + const tx = await program.rpc.testProgramErrorWithSource(); + assert.ok(false); + } catch (err) { + // No-op (withLogTest expects the callback to catch the initial tx error) + } + }, "Program log: ProgramError thrown in programs/errors/src/lib.rs:38. Error Code: InvalidAccountData. Error Number: 17179869184. Error Message: An account's data contents was invalid."); + }); + it("Emits a HelloNoMsg error", async () => { try { const tx = await program.rpc.helloNoMsg(); @@ -46,19 +128,21 @@ describe("errors", () => { }); it("Emits a mut error", async () => { - try { - const tx = await program.rpc.mutError({ - accounts: { - myAccount: anchor.web3.SYSVAR_RENT_PUBKEY, - }, - }); - assert.ok(false); - } catch (err) { - const errMsg = "A mut constraint was violated"; - assert.equal(err.toString(), errMsg); - assert.equal(err.msg, errMsg); - assert.equal(err.code, 2000); - } + await withLogTest(async () => { + try { + const tx = await program.rpc.mutError({ + accounts: { + myAccount: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + }); + assert.ok(false); + } catch (err) { + const errMsg = "A mut constraint was violated"; + assert.equal(err.toString(), errMsg); + assert.equal(err.msg, errMsg); + assert.equal(err.code, 2000); + } + }, "Program log: AnchorError caused by account: my_account. Error Code: ConstraintMut. Error Number: 2000. Error Message: A mut constraint was violated."); }); it("Emits a has one error", async () => { @@ -88,8 +172,11 @@ describe("errors", () => { // instance since the client won't allow one to send a transaction // with an invalid signer account. it("Emits a signer error", async () => { + let signature; + const listener = anchor + .getProvider() + .connection.onLogs("all", (logs) => (signature = logs.signature)); try { - const account = new Account(); const tx = new Transaction(); tx.add( new TransactionInstruction({ @@ -107,9 +194,12 @@ describe("errors", () => { await program.provider.send(tx); assert.ok(false); } catch (err) { - const errMsg = - "Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0xbc2"; + await sleep(3000); + anchor.getProvider().connection.removeOnLogsListener(listener); + const errMsg = `Error: Raw transaction ${signature} failed ({"err":{"InstructionError":[0,{"Custom":3010}]}})`; assert.equal(err.toString(), errMsg); + } finally { + anchor.getProvider().connection.removeOnLogsListener(listener); } }); @@ -130,19 +220,21 @@ describe("errors", () => { }); it("Emits a account not initialized error", async () => { - try { - const tx = await program.rpc.accountNotInitializedError({ - accounts: { - notInitializedAccount: new anchor.web3.Keypair().publicKey, - }, - }); - assert.fail( - "Unexpected success in creating a transaction that should have fail with `AccountNotInitialized` error" - ); - } catch (err) { - const errMsg = - "The program expected this account to be already initialized"; - assert.equal(err.toString(), errMsg); - } + await withLogTest(async () => { + try { + const tx = await program.rpc.accountNotInitializedError({ + accounts: { + notInitializedAccount: new anchor.web3.Keypair().publicKey, + }, + }); + assert.fail( + "Unexpected success in creating a transaction that should have fail with `AccountNotInitialized` error" + ); + } catch (err) { + const errMsg = + "The program expected this account to be already initialized"; + assert.equal(err.toString(), errMsg); + } + }, "Program log: AnchorError caused by account: not_initialized_account. Error Code: AccountNotInitialized. Error Number: 3012. Error Message: The program expected this account to be already initialized."); }); }); diff --git a/tests/escrow/Anchor.toml b/tests/escrow/Anchor.toml index f5dab7ec..6511f7c0 100644 --- a/tests/escrow/Anchor.toml +++ b/tests/escrow/Anchor.toml @@ -9,4 +9,3 @@ escrow = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" test = "yarn run ts-mocha -t 1000000 tests/*.ts" [features] -safety_checks = false diff --git a/tests/escrow/package.json b/tests/escrow/package.json index fd85be5b..44d3f5cf 100644 --- a/tests/escrow/package.json +++ b/tests/escrow/package.json @@ -1,6 +1,6 @@ { "name": "escrow", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/escrow/programs/escrow/src/lib.rs b/tests/escrow/programs/escrow/src/lib.rs index 265c877d..2f3e80bc 100644 --- a/tests/escrow/programs/escrow/src/lib.rs +++ b/tests/escrow/programs/escrow/src/lib.rs @@ -31,7 +31,7 @@ pub mod escrow { ctx: Context, initializer_amount: u64, taker_amount: u64, - ) -> ProgramResult { + ) -> Result<()> { ctx.accounts.escrow_account.initializer_key = *ctx.accounts.initializer.key; ctx.accounts .escrow_account @@ -55,7 +55,7 @@ pub mod escrow { Ok(()) } - pub fn cancel_escrow(ctx: Context) -> ProgramResult { + pub fn cancel_escrow(ctx: Context) -> Result<()> { let (_pda, bump_seed) = Pubkey::find_program_address(&[ESCROW_PDA_SEED], ctx.program_id); let seeds = &[&ESCROW_PDA_SEED[..], &[bump_seed]]; @@ -70,7 +70,7 @@ pub mod escrow { Ok(()) } - pub fn exchange(ctx: Context) -> ProgramResult { + pub fn exchange(ctx: Context) -> Result<()> { // Transferring from initializer to taker let (_pda, bump_seed) = Pubkey::find_program_address(&[ESCROW_PDA_SEED], ctx.program_id); let seeds = &[&ESCROW_PDA_SEED[..], &[bump_seed]]; diff --git a/tests/events/package.json b/tests/events/package.json index f8c5bd24..bafd6d6e 100644 --- a/tests/events/package.json +++ b/tests/events/package.json @@ -1,6 +1,6 @@ { "name": "events", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/events/programs/events/src/lib.rs b/tests/events/programs/events/src/lib.rs index f614190c..ef17b7b4 100644 --- a/tests/events/programs/events/src/lib.rs +++ b/tests/events/programs/events/src/lib.rs @@ -8,7 +8,7 @@ declare_id!("2dhGsWUzy5YKUsjZdLHLmkNpUDAXkNa9MYWsPc4Ziqzy"); #[program] pub mod events { use super::*; - pub fn initialize(_ctx: Context) -> ProgramResult { + pub fn initialize(_ctx: Context) -> Result<()> { emit!(MyEvent { data: 5, label: "hello".to_string(), @@ -16,7 +16,7 @@ pub mod events { Ok(()) } - pub fn test_event(_ctx: Context) -> ProgramResult { + pub fn test_event(_ctx: Context) -> Result<()> { emit!(MyOtherEvent { data: 6, label: "bye".to_string(), diff --git a/tests/floats/package.json b/tests/floats/package.json index ddda9fe2..38ba52f2 100644 --- a/tests/floats/package.json +++ b/tests/floats/package.json @@ -1,6 +1,6 @@ { "name": "floats", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/floats/programs/floats/src/lib.rs b/tests/floats/programs/floats/src/lib.rs index d9e85fa7..22c64234 100644 --- a/tests/floats/programs/floats/src/lib.rs +++ b/tests/floats/programs/floats/src/lib.rs @@ -6,7 +6,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); pub mod floats { use super::*; - pub fn create(ctx: Context, data_f32: f32, data_f64: f64) -> ProgramResult { + pub fn create(ctx: Context, data_f32: f32, data_f64: f64) -> Result<()> { let account = &mut ctx.accounts.account; let authority = &mut ctx.accounts.authority; @@ -17,7 +17,7 @@ pub mod floats { Ok(()) } - pub fn update(ctx: Context, data_f32: f32, data_f64: f64) -> ProgramResult { + pub fn update(ctx: Context, data_f32: f32, data_f64: f64) -> Result<()> { let account = &mut ctx.accounts.account; account.data_f32 = data_f32; diff --git a/tests/ido-pool/Anchor.toml b/tests/ido-pool/Anchor.toml index bb192943..f38b98b0 100644 --- a/tests/ido-pool/Anchor.toml +++ b/tests/ido-pool/Anchor.toml @@ -9,4 +9,3 @@ ido_pool = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" test = "yarn run mocha -t 1000000 tests/" [features] -safety_checks = false diff --git a/tests/ido-pool/package.json b/tests/ido-pool/package.json index 2171f743..da2ff3bc 100644 --- a/tests/ido-pool/package.json +++ b/tests/ido-pool/package.json @@ -1,6 +1,6 @@ { "name": "ido-pool", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/ido-pool/programs/ido-pool/src/lib.rs b/tests/ido-pool/programs/ido-pool/src/lib.rs index e49fe0f2..2a2abfb7 100644 --- a/tests/ido-pool/programs/ido-pool/src/lib.rs +++ b/tests/ido-pool/programs/ido-pool/src/lib.rs @@ -22,7 +22,7 @@ pub mod ido_pool { _bumps: PoolBumps, // No longer used. num_ido_tokens: u64, ido_times: IdoTimes, - ) -> ProgramResult { + ) -> Result<()> { msg!("INITIALIZE POOL"); let ido_account = &mut ctx.accounts.ido_account; @@ -63,7 +63,7 @@ pub mod ido_pool { } #[access_control(unrestricted_phase(&ctx.accounts.ido_account))] - pub fn init_user_redeemable(ctx: Context) -> ProgramResult { + pub fn init_user_redeemable(ctx: Context) -> Result<()> { msg!("INIT USER REDEEMABLE"); Ok(()) } @@ -72,11 +72,11 @@ pub mod ido_pool { pub fn exchange_usdc_for_redeemable( ctx: Context, amount: u64, - ) -> ProgramResult { + ) -> Result<()> { msg!("EXCHANGE USDC FOR REDEEMABLE"); // While token::transfer will check this, we prefer a verbose err msg. if ctx.accounts.user_usdc.amount < amount { - return Err(ErrorCode::LowUsdc.into()); + return err!(ErrorCode::LowUsdc); } // Transfer user's USDC to pool USDC account. @@ -109,7 +109,7 @@ pub mod ido_pool { } #[access_control(withdraw_phase(&ctx.accounts.ido_account))] - pub fn init_escrow_usdc(ctx: Context) -> ProgramResult { + pub fn init_escrow_usdc(ctx: Context) -> Result<()> { msg!("INIT ESCROW USDC"); Ok(()) } @@ -118,11 +118,11 @@ pub mod ido_pool { pub fn exchange_redeemable_for_usdc( ctx: Context, amount: u64, - ) -> ProgramResult { + ) -> Result<()> { msg!("EXCHANGE REDEEMABLE FOR USDC"); // While token::burn will check this, we prefer a verbose err msg. if ctx.accounts.user_redeemable.amount < amount { - return Err(ErrorCode::LowRedeemable.into()); + return err!(ErrorCode::LowRedeemable); } let ido_name = ctx.accounts.ido_account.ido_name.as_ref(); @@ -159,11 +159,11 @@ pub mod ido_pool { pub fn exchange_redeemable_for_watermelon( ctx: Context, amount: u64, - ) -> ProgramResult { + ) -> Result<()> { msg!("EXCHANGE REDEEMABLE FOR WATERMELON"); // While token::burn will check this, we prefer a verbose err msg. if ctx.accounts.user_redeemable.amount < amount { - return Err(ErrorCode::LowRedeemable.into()); + return err!(ErrorCode::LowRedeemable); } // Calculate watermelon tokens due. @@ -217,7 +217,7 @@ pub mod ido_pool { } #[access_control(ido_over(&ctx.accounts.ido_account))] - pub fn withdraw_pool_usdc(ctx: Context) -> ProgramResult { + pub fn withdraw_pool_usdc(ctx: Context) -> Result<()> { msg!("WITHDRAW POOL USDC"); // Transfer total USDC from pool account to ido_authority account. let ido_name = ctx.accounts.ido_account.ido_name.as_ref(); @@ -239,11 +239,11 @@ pub mod ido_pool { } #[access_control(escrow_over(&ctx.accounts.ido_account))] - pub fn withdraw_from_escrow(ctx: Context, amount: u64) -> ProgramResult { + pub fn withdraw_from_escrow(ctx: Context, amount: u64) -> Result<()> { msg!("WITHDRAW FROM ESCROW"); // While token::transfer will check this, we prefer a verbose err msg. if ctx.accounts.escrow_usdc.amount < amount { - return Err(ErrorCode::LowUsdc.into()); + return err!(ErrorCode::LowUsdc); } let ido_name = ctx.accounts.ido_account.ido_name.as_ref(); @@ -573,7 +573,7 @@ pub struct PoolBumps { pub pool_usdc: u8, } -#[error] +#[error_code] pub enum ErrorCode { #[msg("IDO must start in the future")] IdoFuture, @@ -602,55 +602,55 @@ pub enum ErrorCode { // Access control modifiers. // Asserts the IDO starts in the future. -fn validate_ido_times(ido_times: IdoTimes) -> ProgramResult { +fn validate_ido_times(ido_times: IdoTimes) -> Result<()> { let clock = Clock::get()?; if ido_times.start_ido <= clock.unix_timestamp { - return Err(ErrorCode::IdoFuture.into()); + return err!(ErrorCode::IdoFuture); } if !(ido_times.start_ido < ido_times.end_deposits && ido_times.end_deposits < ido_times.end_ido && ido_times.end_ido < ido_times.end_escrow) { - return Err(ErrorCode::SeqTimes.into()); + return err!(ErrorCode::SeqTimes); } Ok(()) } // Asserts the IDO is still accepting deposits. -fn unrestricted_phase(ido_account: &IdoAccount) -> ProgramResult { +fn unrestricted_phase(ido_account: &IdoAccount) -> Result<()> { let clock = Clock::get()?; if clock.unix_timestamp <= ido_account.ido_times.start_ido { - return Err(ErrorCode::StartIdoTime.into()); + return err!(ErrorCode::StartIdoTime); } else if ido_account.ido_times.end_deposits <= clock.unix_timestamp { - return Err(ErrorCode::EndDepositsTime.into()); + return err!(ErrorCode::EndDepositsTime); } Ok(()) } // Asserts the IDO has started but not yet finished. -fn withdraw_phase(ido_account: &IdoAccount) -> ProgramResult { +fn withdraw_phase(ido_account: &IdoAccount) -> Result<()> { let clock = Clock::get()?; if clock.unix_timestamp <= ido_account.ido_times.start_ido { - return Err(ErrorCode::StartIdoTime.into()); + return err!(ErrorCode::StartIdoTime); } else if ido_account.ido_times.end_ido <= clock.unix_timestamp { - return Err(ErrorCode::EndIdoTime.into()); + return err!(ErrorCode::EndIdoTime); } Ok(()) } // Asserts the IDO sale period has ended. -fn ido_over(ido_account: &IdoAccount) -> ProgramResult { +fn ido_over(ido_account: &IdoAccount) -> Result<()> { let clock = Clock::get()?; if clock.unix_timestamp <= ido_account.ido_times.end_ido { - return Err(ErrorCode::IdoNotOver.into()); + return err!(ErrorCode::IdoNotOver); } Ok(()) } -fn escrow_over(ido_account: &IdoAccount) -> ProgramResult { +fn escrow_over(ido_account: &IdoAccount) -> Result<()> { let clock = Clock::get()?; if clock.unix_timestamp <= ido_account.ido_times.end_escrow { - return Err(ErrorCode::EscrowNotOver.into()); + return err!(ErrorCode::EscrowNotOver); } Ok(()) } diff --git a/tests/interface/Anchor.toml b/tests/interface/Anchor.toml index f77ab31f..3a313174 100644 --- a/tests/interface/Anchor.toml +++ b/tests/interface/Anchor.toml @@ -10,4 +10,3 @@ counter_auth = "Aws2XRVHjNqCUbMmaU245ojT2DBJFYX58KVo2YySEeeP" test = "yarn run mocha -t 1000000 tests/" [features] -safety_checks = false diff --git a/tests/interface/package.json b/tests/interface/package.json index e0c1c0fd..25fb02e5 100644 --- a/tests/interface/package.json +++ b/tests/interface/package.json @@ -1,6 +1,6 @@ { "name": "interface", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/interface/programs/counter-auth/src/lib.rs b/tests/interface/programs/counter-auth/src/lib.rs index 2281bd3a..544c74a0 100644 --- a/tests/interface/programs/counter-auth/src/lib.rs +++ b/tests/interface/programs/counter-auth/src/lib.rs @@ -16,14 +16,14 @@ pub mod counter_auth { pub struct CounterAuth; impl<'info> Auth<'info, Empty> for CounterAuth { - fn is_authorized(_ctx: Context, current: u64, new: u64) -> ProgramResult { + fn is_authorized(_ctx: Context, current: u64, new: u64) -> Result<()> { if current % 2 == 0 { if new % 2 == 0 { - return Err(ProgramError::Custom(15000)); // Arbitrary error code. + return Err(ProgramError::Custom(15000).into()); // Arbitrary error code. } } else { if new % 2 == 1 { - return Err(ProgramError::Custom(16000)); // Arbitrary error code. + return Err(ProgramError::Custom(16000).into()); // Arbitrary error code. } } Ok(()) diff --git a/tests/interface/programs/counter/src/lib.rs b/tests/interface/programs/counter/src/lib.rs index 8700d7da..b3f28b39 100644 --- a/tests/interface/programs/counter/src/lib.rs +++ b/tests/interface/programs/counter/src/lib.rs @@ -55,7 +55,7 @@ impl<'info> SetCount<'info> { // we separate it from the business logic of the instruction handler itself. pub fn accounts(counter: &Counter, ctx: &Context) -> Result<()> { if ctx.accounts.auth_program.key != &counter.auth_program { - return Err(ErrorCode::InvalidAuthProgram.into()); + return err!(ErrorCode::InvalidAuthProgram); } Ok(()) } @@ -63,10 +63,10 @@ impl<'info> SetCount<'info> { #[interface] pub trait Auth<'info, T: Accounts<'info>> { - fn is_authorized(ctx: Context, current: u64, new: u64) -> ProgramResult; + fn is_authorized(ctx: Context, current: u64, new: u64) -> Result<()>; } -#[error] +#[error_code] pub enum ErrorCode { #[msg("Invalid auth program.")] InvalidAuthProgram, diff --git a/tests/lockup/Anchor.toml b/tests/lockup/Anchor.toml index 4367e1dc..0e20f4ad 100644 --- a/tests/lockup/Anchor.toml +++ b/tests/lockup/Anchor.toml @@ -10,4 +10,3 @@ registry = "HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L" test = "yarn run mocha -t 1000000 tests/" [features] -safety_checks = false diff --git a/tests/lockup/package.json b/tests/lockup/package.json index 1e23c084..0d99b402 100644 --- a/tests/lockup/package.json +++ b/tests/lockup/package.json @@ -1,6 +1,6 @@ { "name": "lockup", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/lockup/programs/lockup/src/lib.rs b/tests/lockup/programs/lockup/src/lib.rs index d7013d86..55eb0237 100644 --- a/tests/lockup/programs/lockup/src/lib.rs +++ b/tests/lockup/programs/lockup/src/lib.rs @@ -39,10 +39,10 @@ pub mod lockup { #[access_control(whitelist_auth(self, &ctx))] pub fn whitelist_add(&mut self, ctx: Context, entry: WhitelistEntry) -> Result<()> { if self.whitelist.len() == Self::WHITELIST_SIZE { - return Err(ErrorCode::WhitelistFull.into()); + return err!(ErrorCode::WhitelistFull); } if self.whitelist.contains(&entry) { - return Err(ErrorCode::WhitelistEntryAlreadyExists.into()); + return err!(ErrorCode::WhitelistEntryAlreadyExists); } self.whitelist.push(entry); Ok(()) @@ -55,7 +55,7 @@ pub mod lockup { entry: WhitelistEntry, ) -> Result<()> { if !self.whitelist.contains(&entry) { - return Err(ErrorCode::WhitelistEntryNotFound.into()); + return err!(ErrorCode::WhitelistEntryNotFound); } self.whitelist.retain(|e| e != &entry); Ok(()) @@ -80,10 +80,10 @@ pub mod lockup { realizor: Option, ) -> Result<()> { if deposit_amount == 0 { - return Err(ErrorCode::InvalidDepositAmount.into()); + return err!(ErrorCode::InvalidDepositAmount); } if !is_valid_schedule(start_ts, end_ts, period_count) { - return Err(ErrorCode::InvalidSchedule.into()); + return err!(ErrorCode::InvalidSchedule); } let vesting = &mut ctx.accounts.vesting; vesting.beneficiary = beneficiary; @@ -114,7 +114,7 @@ pub mod lockup { ctx.accounts.clock.unix_timestamp, ) { - return Err(ErrorCode::InsufficientWithdrawalBalance.into()); + return err!(ErrorCode::InsufficientWithdrawalBalance); } // Transfer funds out. @@ -151,7 +151,7 @@ pub mod lockup { // CPI safety checks. let withdraw_amount = before_amount - after_amount; if withdraw_amount > amount { - return Err(ErrorCode::WhitelistWithdrawLimit)?; + return err!(ErrorCode::WhitelistWithdrawLimit); } // Bookeeping. @@ -177,10 +177,10 @@ pub mod lockup { // CPI safety checks. let deposit_amount = after_amount - before_amount; if deposit_amount <= 0 { - return Err(ErrorCode::InsufficientWhitelistDepositAmount)?; + return err!(ErrorCode::InsufficientWhitelistDepositAmount); } if deposit_amount > ctx.accounts.transfer.vesting.whitelist_owned { - return Err(ErrorCode::WhitelistDepositOverflow)?; + return err!(ErrorCode::WhitelistDepositOverflow)?; } // Bookkeeping. @@ -234,9 +234,9 @@ impl<'info> CreateVesting<'info> { ], ctx.program_id, ) - .map_err(|_| ErrorCode::InvalidProgramAddress)?; + .map_err(|_| error!(ErrorCode::InvalidProgramAddress))?; if ctx.accounts.vault.owner != vault_authority { - return Err(ErrorCode::InvalidVaultOwner)?; + return err!(ErrorCode::InvalidVaultOwner)?; } Ok(()) @@ -365,7 +365,7 @@ pub struct WhitelistEntry { pub program_id: Pubkey, } -#[error] +#[error_code] pub enum ErrorCode { #[msg("Vesting end must be greater than the current unix timestamp.")] InvalidTimestamp, @@ -484,14 +484,14 @@ pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<()> if !transfer.lockup.whitelist.contains(&WhitelistEntry { program_id: *transfer.whitelisted_program.key, }) { - return Err(ErrorCode::WhitelistEntryNotFound.into()); + return err!(ErrorCode::WhitelistEntryNotFound); } Ok(()) } fn whitelist_auth(lockup: &Lockup, ctx: &Context) -> Result<()> { if &lockup.authority != ctx.accounts.authority.key { - return Err(ErrorCode::Unauthorized.into()); + return err!(ErrorCode::Unauthorized); } Ok(()) } @@ -517,14 +517,15 @@ fn is_realized(ctx: &Context) -> Result<()> { let cpi_program = { let p = ctx.remaining_accounts[0].clone(); if p.key != &realizor.program { - return Err(ErrorCode::InvalidLockRealizor.into()); + return err!(ErrorCode::InvalidLockRealizor); } p }; let cpi_accounts = ctx.remaining_accounts.to_vec()[1..].to_vec(); let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); let vesting = (*ctx.accounts.vesting).clone(); - realize_lock::is_realized(cpi_ctx, vesting).map_err(|_| ErrorCode::UnrealizedVesting)?; + realize_lock::is_realized(cpi_ctx, vesting) + .map_err(|_| error!(ErrorCode::UnrealizedVesting))?; } Ok(()) } @@ -537,5 +538,5 @@ fn is_realized(ctx: &Context) -> Result<()> { /// until one has completely unstaked. #[interface] pub trait RealizeLock<'info, T: Accounts<'info>> { - fn is_realized(ctx: Context, v: Vesting) -> ProgramResult; + fn is_realized(ctx: Context, v: Vesting) -> Result<()>; } diff --git a/tests/lockup/programs/registry/src/lib.rs b/tests/lockup/programs/registry/src/lib.rs index d0577b34..e03cc1fc 100644 --- a/tests/lockup/programs/registry/src/lib.rs +++ b/tests/lockup/programs/registry/src/lib.rs @@ -41,7 +41,7 @@ mod registry { .parse() .unwrap(); if ctx.accounts.authority.key != &expected { - return Err(ErrorCode::InvalidProgramAuthority.into()); + return err!(ErrorCode::InvalidProgramAuthority); } self.lockup_program = lockup_program; @@ -51,16 +51,16 @@ mod registry { } impl<'info> RealizeLock<'info, IsRealized<'info>> for Registry { - fn is_realized(ctx: Context, v: Vesting) -> ProgramResult { + fn is_realized(ctx: Context, v: Vesting) -> Result<()> { if let Some(realizor) = &v.realizor { if &realizor.metadata != ctx.accounts.member.to_account_info().key { - return Err(ErrorCode::InvalidRealizorMetadata.into()); + return err!(ErrorCode::InvalidRealizorMetadata); } assert!(ctx.accounts.member.beneficiary == v.beneficiary); let total_staked = ctx.accounts.member_spt.amount + ctx.accounts.member_spt_locked.amount; if total_staked != 0 { - return Err(ErrorCode::UnrealizedReward.into()); + return err!(ErrorCode::UnrealizedReward); } } Ok(()) @@ -285,7 +285,7 @@ mod registry { pub fn end_unstake(ctx: Context) -> Result<()> { if ctx.accounts.pending_withdrawal.end_ts > ctx.accounts.clock.unix_timestamp { - return Err(ErrorCode::UnstakeTimelock.into()); + return err!(ErrorCode::UnstakeTimelock); } // Select which balance set this affects. @@ -298,10 +298,10 @@ mod registry { }; // Check the vaults given are corrrect. if &balances.vault != ctx.accounts.vault.key { - return Err(ErrorCode::InvalidVault.into()); + return err!(ErrorCode::InvalidVault); } if &balances.vault_pw != ctx.accounts.vault_pw.key { - return Err(ErrorCode::InvalidVault.into()); + return err!(ErrorCode::InvalidVault); } // Transfer tokens between vaults. @@ -377,10 +377,10 @@ mod registry { nonce: u8, ) -> Result<()> { if total < ctx.accounts.pool_mint.supply { - return Err(ErrorCode::InsufficientReward.into()); + return err!(ErrorCode::InsufficientReward); } if ctx.accounts.clock.unix_timestamp >= expiry_ts { - return Err(ErrorCode::InvalidExpiry.into()); + return err!(ErrorCode::InvalidExpiry); } if let RewardVendorKind::Locked { start_ts, @@ -389,7 +389,7 @@ mod registry { } = kind { if !lockup::is_valid_schedule(start_ts, end_ts, period_count) { - return Err(ErrorCode::InvalidVestingSchedule.into()); + return err!(ErrorCode::InvalidVestingSchedule); } } @@ -426,7 +426,7 @@ mod registry { #[access_control(reward_eligible(&ctx.accounts.cmn))] pub fn claim_reward(ctx: Context) -> Result<()> { if RewardVendorKind::Unlocked != ctx.accounts.cmn.vendor.kind { - return Err(ErrorCode::ExpectedUnlockedVendor.into()); + return err!(ErrorCode::ExpectedUnlockedVendor); } // Reward distribution. let spt_total = @@ -469,7 +469,7 @@ mod registry { nonce: u8, ) -> Result<()> { let (start_ts, end_ts, period_count) = match ctx.accounts.cmn.vendor.kind { - RewardVendorKind::Unlocked => return Err(ErrorCode::ExpectedLockedVendor.into()), + RewardVendorKind::Unlocked => return err!(ErrorCode::ExpectedLockedVendor), RewardVendorKind::Locked { start_ts, end_ts, @@ -535,7 +535,7 @@ mod registry { pub fn expire_reward(ctx: Context) -> Result<()> { if ctx.accounts.clock.unix_timestamp < ctx.accounts.vendor.expiry_ts { - return Err(ErrorCode::VendorNotYetExpired.into()); + return err!(ErrorCode::VendorNotYetExpired); } // Send all remaining funds to the expiry receiver's token. @@ -583,9 +583,9 @@ impl<'info> Initialize<'info> { ], ctx.program_id, ) - .map_err(|_| ErrorCode::InvalidNonce)?; + .map_err(|_| error!(ErrorCode::InvalidNonce))?; if ctx.accounts.pool_mint.mint_authority != COption::Some(registrar_signer) { - return Err(ErrorCode::InvalidPoolMintAuthority.into()); + return err!(ErrorCode::InvalidPoolMintAuthority); } assert!(ctx.accounts.pool_mint.supply == 0); Ok(()) @@ -633,9 +633,9 @@ impl<'info> CreateMember<'info> { &[nonce], ]; let member_signer = Pubkey::create_program_address(seeds, ctx.program_id) - .map_err(|_| ErrorCode::InvalidNonce)?; + .map_err(|_| error!(ErrorCode::InvalidNonce))?; if &member_signer != ctx.accounts.member_signer.to_account_info().key { - return Err(ErrorCode::InvalidMemberSigner.into()); + return err!(ErrorCode::InvalidMemberSigner); } Ok(()) @@ -931,9 +931,9 @@ impl<'info> DropReward<'info> { ], ctx.program_id, ) - .map_err(|_| ErrorCode::InvalidNonce)?; + .map_err(|_| error!(ErrorCode::InvalidNonce))?; if vendor_signer != ctx.accounts.vendor_vault.owner { - return Err(ErrorCode::InvalidVaultOwner.into()); + return err!(ErrorCode::InvalidVaultOwner); } Ok(()) @@ -960,7 +960,7 @@ pub struct ClaimRewardLocked<'info> { #[derive(Accounts)] pub struct ClaimRewardCommon<'info> { // Stake instance. - registrar: Account<'info, Registrar>, + registrar: Box>, // Member. #[account(mut, has_one = registrar, has_one = beneficiary)] member: Account<'info, Member>, @@ -1173,7 +1173,7 @@ pub enum RewardVendorKind { }, } -#[error] +#[error_code] pub enum ErrorCode { #[msg("The given reward queue has already been initialized.")] RewardQAlreadyInitialized, @@ -1284,13 +1284,13 @@ fn reward_eligible(cmn: &ClaimRewardCommon) -> Result<()> { let vendor = &cmn.vendor; let member = &cmn.member; if vendor.expired { - return Err(ErrorCode::VendorExpired.into()); + return err!(ErrorCode::VendorExpired); } if member.rewards_cursor > vendor.reward_event_q_cursor { - return Err(ErrorCode::CursorAlreadyProcessed.into()); + return err!(ErrorCode::CursorAlreadyProcessed); } if member.last_stake_ts > vendor.start_ts { - return Err(ErrorCode::NotStakedDuringDrop.into()); + return err!(ErrorCode::NotStakedDuringDrop); } Ok(()) } @@ -1316,7 +1316,7 @@ pub fn no_available_rewards<'info>( let r_event = reward_q.get(cursor); if member.last_stake_ts < r_event.ts { if balances.spt.amount > 0 || balances_locked.spt.amount > 0 { - return Err(ErrorCode::RewardsNeedsProcessing.into()); + return err!(ErrorCode::RewardsNeedsProcessing); } } cursor += 1; diff --git a/tests/misc/package.json b/tests/misc/package.json index bde5abb3..ff25b4e2 100644 --- a/tests/misc/package.json +++ b/tests/misc/package.json @@ -1,6 +1,6 @@ { "name": "misc", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/misc/programs/misc/src/lib.rs b/tests/misc/programs/misc/src/lib.rs index 0a0a0611..21a0c1c4 100644 --- a/tests/misc/programs/misc/src/lib.rs +++ b/tests/misc/programs/misc/src/lib.rs @@ -30,41 +30,41 @@ pub mod misc { } impl MyState { - pub fn new(_ctx: Context) -> Result { + pub fn new(_ctx: Context) -> Result { Ok(Self { v: vec![] }) } - pub fn remaining_accounts(&mut self, ctx: Context) -> ProgramResult { + pub fn remaining_accounts(&mut self, ctx: Context) -> Result<()> { if ctx.remaining_accounts.len() != 1 { - return Err(ProgramError::Custom(1)); // Arbitrary error. + return Err(ProgramError::Custom(1).into()); // Arbitrary error. } Ok(()) } } - pub fn initialize(ctx: Context, udata: u128, idata: i128) -> ProgramResult { + pub fn initialize(ctx: Context, udata: u128, idata: i128) -> Result<()> { ctx.accounts.data.udata = udata; ctx.accounts.data.idata = idata; Ok(()) } - pub fn initialize_no_rent_exempt(ctx: Context) -> ProgramResult { + pub fn initialize_no_rent_exempt(ctx: Context) -> Result<()> { Ok(()) } - pub fn initialize_skip_rent_exempt(ctx: Context) -> ProgramResult { + pub fn initialize_skip_rent_exempt(ctx: Context) -> Result<()> { Ok(()) } - pub fn test_owner(_ctx: Context) -> ProgramResult { + pub fn test_owner(_ctx: Context) -> Result<()> { Ok(()) } - pub fn test_executable(_ctx: Context) -> ProgramResult { + pub fn test_executable(_ctx: Context) -> Result<()> { Ok(()) } - pub fn test_state_cpi(ctx: Context, data: u64) -> ProgramResult { + pub fn test_state_cpi(ctx: Context, data: u64) -> Result<()> { let cpi_program = ctx.accounts.misc2_program.clone(); let cpi_accounts = Auth { authority: ctx.accounts.authority.clone(), @@ -73,41 +73,41 @@ pub mod misc { misc2::cpi::state::set_data(ctx, data) } - pub fn test_u16(ctx: Context, data: u16) -> ProgramResult { + pub fn test_u16(ctx: Context, data: u16) -> Result<()> { ctx.accounts.my_account.data = data; Ok(()) } - pub fn test_simulate(_ctx: Context, data: u32) -> ProgramResult { + pub fn test_simulate(_ctx: Context, data: u32) -> Result<()> { emit!(E1 { data }); emit!(E2 { data: 1234 }); emit!(E3 { data: 9 }); Ok(()) } - pub fn test_i8(ctx: Context, data: i8) -> ProgramResult { + pub fn test_i8(ctx: Context, data: i8) -> Result<()> { ctx.accounts.data.data = data; Ok(()) } - pub fn test_i16(ctx: Context, data: i16) -> ProgramResult { + 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) -> ProgramResult { + pub fn test_const_array_size(ctx: Context, data: u8) -> Result<()> { ctx.accounts.data.data[0] = data; Ok(()) } - pub fn test_close(_ctx: Context) -> ProgramResult { + pub fn test_close(_ctx: Context) -> Result<()> { Ok(()) } pub fn test_instruction_constraint( _ctx: Context, _nonce: u8, - ) -> ProgramResult { + ) -> Result<()> { Ok(()) } @@ -116,25 +116,25 @@ pub mod misc { _domain: String, _seed: Vec, _bump: u8, - ) -> ProgramResult { + ) -> Result<()> { ctx.accounts.my_pda.data = 6; Ok(()) } - pub fn test_pda_init_zero_copy(ctx: Context) -> ProgramResult { + pub fn test_pda_init_zero_copy(ctx: Context) -> Result<()> { let mut acc = ctx.accounts.my_pda.load_mut()?; acc.data = 9; acc.bump = *ctx.bumps.get("my_pda").unwrap(); Ok(()) } - pub fn test_pda_mut_zero_copy(ctx: Context) -> ProgramResult { + pub fn test_pda_mut_zero_copy(ctx: Context) -> Result<()> { let mut acc = ctx.accounts.my_pda.load_mut()?; acc.data = 1234; Ok(()) } - pub fn test_token_seeds_init(_ctx: Context) -> ProgramResult { + pub fn test_token_seeds_init(_ctx: Context) -> Result<()> { Ok(()) } @@ -142,120 +142,120 @@ pub mod misc { _program_id: &Pubkey, _accounts: &[AccountInfo<'info>], _data: &[u8], - ) -> ProgramResult { - Err(ProgramError::Custom(1234)) + ) -> Result<()> { + Err(ProgramError::Custom(1234).into()) } - pub fn test_init(ctx: Context) -> ProgramResult { + pub fn test_init(ctx: Context) -> Result<()> { ctx.accounts.data.data = 3; Ok(()) } - pub fn test_init_zero_copy(ctx: Context) -> ProgramResult { + pub fn test_init_zero_copy(ctx: Context) -> Result<()> { let mut data = ctx.accounts.data.load_mut()?; data.data = 10; data.bump = 2; Ok(()) } - pub fn test_init_mint(ctx: Context) -> ProgramResult { + pub fn test_init_mint(ctx: Context) -> Result<()> { assert!(ctx.accounts.mint.decimals == 6); Ok(()) } - pub fn test_init_token(ctx: Context) -> ProgramResult { + pub fn test_init_token(ctx: Context) -> Result<()> { assert!(ctx.accounts.token.mint == ctx.accounts.mint.key()); Ok(()) } - pub fn test_composite_payer(ctx: Context) -> ProgramResult { + pub fn test_composite_payer(ctx: Context) -> Result<()> { ctx.accounts.composite.data.data = 1; ctx.accounts.data.udata = 2; ctx.accounts.data.idata = 3; Ok(()) } - pub fn test_init_associated_token(ctx: Context) -> ProgramResult { + pub fn test_init_associated_token(ctx: Context) -> Result<()> { assert!(ctx.accounts.token.mint == ctx.accounts.mint.key()); Ok(()) } pub fn test_validate_associated_token( _ctx: Context, - ) -> ProgramResult { + ) -> Result<()> { Ok(()) } - pub fn test_fetch_all(ctx: Context, filterable: Pubkey) -> ProgramResult { + pub fn test_fetch_all(ctx: Context, filterable: Pubkey) -> Result<()> { ctx.accounts.data.authority = ctx.accounts.authority.key(); ctx.accounts.data.filterable = filterable; Ok(()) } - pub fn test_init_with_empty_seeds(ctx: Context) -> ProgramResult { + pub fn test_init_with_empty_seeds(ctx: Context) -> Result<()> { Ok(()) } - pub fn test_empty_seeds_constraint(ctx: Context) -> ProgramResult { + pub fn test_empty_seeds_constraint(ctx: Context) -> Result<()> { Ok(()) } - pub fn test_init_if_needed(ctx: Context, data: u16) -> ProgramResult { + pub fn test_init_if_needed(ctx: Context, data: u16) -> Result<()> { ctx.accounts.data.data = data; Ok(()) } pub fn test_init_if_needed_checks_owner( ctx: Context, - ) -> ProgramResult { + ) -> Result<()> { Ok(()) } pub fn test_init_if_needed_checks_seeds( ctx: Context, seed_data: String, - ) -> ProgramResult { + ) -> Result<()> { Ok(()) } pub fn test_init_mint_if_needed( ctx: Context, decimals: u8, - ) -> ProgramResult { + ) -> Result<()> { Ok(()) } - pub fn test_init_token_if_needed(ctx: Context) -> ProgramResult { + pub fn test_init_token_if_needed(ctx: Context) -> Result<()> { Ok(()) } pub fn test_init_associated_token_if_needed( ctx: Context, - ) -> ProgramResult { + ) -> Result<()> { Ok(()) } - pub fn init_with_space(ctx: Context, data: u16) -> ProgramResult { + pub fn init_with_space(ctx: Context, data: u16) -> Result<()> { Ok(()) } pub fn test_multidimensional_array( ctx: Context, data: [[u8; 10]; 10], - ) -> ProgramResult { + ) -> Result<()> { ctx.accounts.data.data = data; Ok(()) } - pub fn test_no_rent_exempt(ctx: Context) -> ProgramResult { + pub fn test_no_rent_exempt(ctx: Context) -> Result<()> { Ok(()) } - pub fn test_enforce_rent_exempt(ctx: Context) -> ProgramResult { + pub fn test_enforce_rent_exempt(ctx: Context) -> Result<()> { Ok(()) } - pub fn init_decrease_lamports(ctx: Context) -> ProgramResult { + pub fn init_decrease_lamports(ctx: Context) -> Result<()> { **ctx.accounts.data.try_borrow_mut_lamports()? -= 1; **ctx.accounts.user.try_borrow_mut_lamports()? += 1; Ok(()) @@ -263,7 +263,7 @@ pub mod misc { pub fn init_if_needed_checks_rent_exemption( _ctx: Context, - ) -> ProgramResult { + ) -> Result<()> { Ok(()) } @@ -271,13 +271,13 @@ pub mod misc { _ctx: Context, _bump: u8, _second_bump: u8, - ) -> ProgramResult { + ) -> Result<()> { Ok(()) } pub fn test_program_id_constraint_find_pda( _ctx: Context, - ) -> ProgramResult { + ) -> Result<()> { Ok(()) } } diff --git a/tests/misc/programs/misc2/src/lib.rs b/tests/misc/programs/misc2/src/lib.rs index 4ee2c9ae..d651a2c6 100644 --- a/tests/misc/programs/misc2/src/lib.rs +++ b/tests/misc/programs/misc2/src/lib.rs @@ -13,16 +13,16 @@ pub mod misc2 { } impl MyState { - pub fn new(ctx: Context) -> Result { + pub fn new(ctx: Context) -> Result { Ok(Self { data: 0, auth: *ctx.accounts.authority.key, }) } - pub fn set_data(&mut self, ctx: Context, data: u64) -> Result<(), ProgramError> { + pub fn set_data(&mut self, ctx: Context, data: u64) -> Result<()> { if self.auth != *ctx.accounts.authority.key { - return Err(ProgramError::Custom(1234)); // Arbitrary error code. + return Err(ProgramError::Custom(1234).into()); // Arbitrary error code. } self.data = data; Ok(()) diff --git a/tests/multisig/Anchor.toml b/tests/multisig/Anchor.toml index f8bc62e0..ee9f941d 100644 --- a/tests/multisig/Anchor.toml +++ b/tests/multisig/Anchor.toml @@ -9,4 +9,3 @@ multisig = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" test = "yarn run mocha -t 1000000 tests/" [features] -safety_checks = false diff --git a/tests/multisig/package.json b/tests/multisig/package.json index 56e6e797..4cc2b97c 100644 --- a/tests/multisig/package.json +++ b/tests/multisig/package.json @@ -1,6 +1,6 @@ { "name": "multisig", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/multisig/programs/multisig/src/lib.rs b/tests/multisig/programs/multisig/src/lib.rs index 18b5062b..91ac9ff7 100644 --- a/tests/multisig/programs/multisig/src/lib.rs +++ b/tests/multisig/programs/multisig/src/lib.rs @@ -17,8 +17,8 @@ //! the `execute_transaction`, once enough (i.e. `threshold`) of the owners have //! signed. -use anchor_lang::prelude::*; use anchor_lang::accounts::program_account::ProgramAccount; +use anchor_lang::prelude::*; use anchor_lang::solana_program; use anchor_lang::solana_program::instruction::Instruction; use std::convert::Into; @@ -57,7 +57,7 @@ pub mod multisig { .owners .iter() .position(|a| a == ctx.accounts.proposer.key) - .ok_or(ErrorCode::InvalidOwner)?; + .ok_or(error!(ErrorCode::InvalidOwner))?; let mut signers = Vec::new(); signers.resize(ctx.accounts.multisig.owners.len(), false); @@ -82,7 +82,7 @@ pub mod multisig { .owners .iter() .position(|a| a == ctx.accounts.owner.key) - .ok_or(ErrorCode::InvalidOwner)?; + .ok_or(error!(ErrorCode::InvalidOwner))?; ctx.accounts.transaction.signers[owner_index] = true; @@ -107,7 +107,7 @@ pub mod multisig { // change_threshold. pub fn change_threshold(ctx: Context, threshold: u64) -> Result<()> { if threshold > ctx.accounts.multisig.owners.len() as u64 { - return Err(ErrorCode::InvalidThreshold.into()); + return err!(ErrorCode::InvalidThreshold); } let multisig = &mut ctx.accounts.multisig; multisig.threshold = threshold; @@ -118,7 +118,7 @@ pub mod multisig { pub fn execute_transaction(ctx: Context) -> Result<()> { // Has this been executed already? if ctx.accounts.transaction.did_execute { - return Err(ErrorCode::AlreadyExecuted.into()); + return err!(ErrorCode::AlreadyExecuted); } // Do we have enough signers? @@ -134,7 +134,7 @@ pub mod multisig { .collect::>() .len() as u64; if sig_count < ctx.accounts.multisig.threshold { - return Err(ErrorCode::NotEnoughSigners.into()); + return err!(ErrorCode::NotEnoughSigners); } // Execute the transaction signed by the multisig. @@ -262,7 +262,7 @@ impl From for AccountMeta { } } -#[error] +#[error_code] pub enum ErrorCode { #[msg("The given owner is not part of this multisig.")] InvalidOwner, diff --git a/tests/package.json b/tests/package.json index 78240c72..a31b05b4 100644 --- a/tests/package.json +++ b/tests/package.json @@ -33,7 +33,7 @@ "declare-id" ], "dependencies": { - "@project-serum/anchor": "^0.21.0", + "@project-serum/anchor": "^0.22.0", "@project-serum/common": "^0.0.1-beta.3", "@project-serum/serum": "^0.13.60", "@solana/spl-token": "^0.1.8" diff --git a/tests/pda-derivation/Anchor.toml b/tests/pda-derivation/Anchor.toml index 2b17356f..78935651 100644 --- a/tests/pda-derivation/Anchor.toml +++ b/tests/pda-derivation/Anchor.toml @@ -1,6 +1,5 @@ [features] seeds = true -safety_checks = false [provider] cluster = "localnet" diff --git a/tests/pda-derivation/package.json b/tests/pda-derivation/package.json index 4f9b5276..b4558ec2 100644 --- a/tests/pda-derivation/package.json +++ b/tests/pda-derivation/package.json @@ -1,6 +1,6 @@ { "name": "pda-derivation", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/pda-derivation/programs/pda-derivation/src/lib.rs b/tests/pda-derivation/programs/pda-derivation/src/lib.rs index ce82597d..208ef1ca 100644 --- a/tests/pda-derivation/programs/pda-derivation/src/lib.rs +++ b/tests/pda-derivation/programs/pda-derivation/src/lib.rs @@ -15,14 +15,14 @@ pub const MY_SEED_U64: u64 = 3; pub mod pda_derivation { use super::*; - pub fn init_base(ctx: Context, data: u64, data_key: Pubkey) -> ProgramResult { + pub fn init_base(ctx: Context, data: u64, data_key: Pubkey) -> Result<()> { let base = &mut ctx.accounts.base; base.base_data = data; base.base_data_key = data_key; Ok(()) } - pub fn init_my_account(ctx: Context, seed_a: u8) -> ProgramResult { + pub fn init_my_account(ctx: Context, seed_a: u8) -> Result<()> { Ok(()) } } diff --git a/tests/pyth/Anchor.toml b/tests/pyth/Anchor.toml index 333e4721..68059115 100644 --- a/tests/pyth/Anchor.toml +++ b/tests/pyth/Anchor.toml @@ -9,4 +9,3 @@ pyth = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" [features] -safety_checks = false diff --git a/tests/pyth/package.json b/tests/pyth/package.json index 04c1e444..9631a374 100644 --- a/tests/pyth/package.json +++ b/tests/pyth/package.json @@ -1,6 +1,6 @@ { "name": "pyth", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/pyth/programs/pyth/src/lib.rs b/tests/pyth/programs/pyth/src/lib.rs index a25a0ffc..ff858042 100644 --- a/tests/pyth/programs/pyth/src/lib.rs +++ b/tests/pyth/programs/pyth/src/lib.rs @@ -8,7 +8,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); pub mod pyth { use super::*; - pub fn initialize(ctx: Context, price: i64, expo: i32, conf: u64) -> ProgramResult { + pub fn initialize(ctx: Context, price: i64, expo: i32, conf: u64) -> Result<()> { let oracle = &ctx.accounts.price; let mut price_oracle = Price::load(&oracle).unwrap(); @@ -20,7 +20,7 @@ pub mod pyth { Ok(()) } - pub fn set_price(ctx: Context, price: i64) -> ProgramResult { + pub fn set_price(ctx: Context, price: i64) -> Result<()> { let oracle = &ctx.accounts.price; let mut price_oracle = Price::load(&oracle).unwrap(); price_oracle.agg.price = price as i64; diff --git a/tests/pyth/programs/pyth/src/pc.rs b/tests/pyth/programs/pyth/src/pc.rs index d41e0ce5..9ee2ef3a 100644 --- a/tests/pyth/programs/pyth/src/pc.rs +++ b/tests/pyth/programs/pyth/src/pc.rs @@ -98,7 +98,7 @@ pub struct Price { impl Price { #[inline] - pub fn load<'a>(price_feed: &'a AccountInfo) -> Result, ProgramError> { + pub fn load<'a>(price_feed: &'a AccountInfo) -> Result> { let account_data: RefMut<'a, [u8]>; let state: RefMut<'a, Self>; diff --git a/tests/safety-checks/programs/account-info/Cargo.toml b/tests/safety-checks/programs/account-info/Cargo.toml index 032a7227..56dd899b 100644 --- a/tests/safety-checks/programs/account-info/Cargo.toml +++ b/tests/safety-checks/programs/account-info/Cargo.toml @@ -16,4 +16,4 @@ cpi = ["no-entrypoint"] default = [] [dependencies] -anchor-lang = "0.21.0" +anchor-lang = "0.22.0" diff --git a/tests/spl/token-proxy/Anchor.toml b/tests/spl/token-proxy/Anchor.toml index fea82118..ac6cd695 100644 --- a/tests/spl/token-proxy/Anchor.toml +++ b/tests/spl/token-proxy/Anchor.toml @@ -9,4 +9,3 @@ token_proxy = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" test = "yarn run mocha -t 1000000 tests/" [features] -safety_checks = false diff --git a/tests/spl/token-proxy/package.json b/tests/spl/token-proxy/package.json index 296d1fca..1493484c 100644 --- a/tests/spl/token-proxy/package.json +++ b/tests/spl/token-proxy/package.json @@ -1,6 +1,6 @@ { "name": "token-proxy", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/spl/token-proxy/programs/token-proxy/src/lib.rs b/tests/spl/token-proxy/programs/token-proxy/src/lib.rs index acb78fb5..9adafec8 100644 --- a/tests/spl/token-proxy/programs/token-proxy/src/lib.rs +++ b/tests/spl/token-proxy/programs/token-proxy/src/lib.rs @@ -9,15 +9,15 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); mod token_proxy { use super::*; - pub fn proxy_transfer(ctx: Context, amount: u64) -> ProgramResult { + pub fn proxy_transfer(ctx: Context, amount: u64) -> Result<()> { token::transfer(ctx.accounts.into(), amount) } - pub fn proxy_mint_to(ctx: Context, amount: u64) -> ProgramResult { + pub fn proxy_mint_to(ctx: Context, amount: u64) -> Result<()> { token::mint_to(ctx.accounts.into(), amount) } - pub fn proxy_burn(ctx: Context, amount: u64) -> ProgramResult { + pub fn proxy_burn(ctx: Context, amount: u64) -> Result<()> { token::burn(ctx.accounts.into(), amount) } @@ -25,7 +25,7 @@ mod token_proxy { ctx: Context, authority_type: AuthorityType, new_authority: Option, - ) -> ProgramResult { + ) -> Result<()> { token::set_authority(ctx.accounts.into(), authority_type.into(), new_authority) } } diff --git a/tests/swap/Anchor.toml b/tests/swap/Anchor.toml index 993c8f1a..fa7d2b91 100644 --- a/tests/swap/Anchor.toml +++ b/tests/swap/Anchor.toml @@ -13,4 +13,3 @@ program = "./deps/serum-dex/dex/target/deploy/serum_dex.so" test = "yarn run mocha -t 1000000 tests/" [features] -safety_checks = false diff --git a/tests/swap/package.json b/tests/swap/package.json index a8a86aa7..a9f43c5a 100644 --- a/tests/swap/package.json +++ b/tests/swap/package.json @@ -1,6 +1,6 @@ { "name": "swap", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/swap/programs/swap/src/lib.rs b/tests/swap/programs/swap/src/lib.rs index 55f07db5..1e4fa5f4 100644 --- a/tests/swap/programs/swap/src/lib.rs +++ b/tests/swap/programs/swap/src/lib.rs @@ -179,7 +179,7 @@ pub mod swap { fn apply_risk_checks(event: DidSwap) -> Result<()> { // Reject if the resulting amount is less than the client's expectation. if event.to_amount < event.min_expected_swap_amount { - return Err(ErrorCode::SlippageExceeded.into()); + return err!(ErrorCode::SlippageExceeded); } emit!(event); Ok(()) @@ -274,11 +274,12 @@ impl<'info> OrderbookClient<'info> { // // `base_amount` is the "native" amount of the base currency, i.e., token // amount including decimals. - fn sell(&self, base_amount: u64, referral: Option>) -> ProgramResult { + fn sell(&self, base_amount: u64, referral: Option>) -> Result<()> { let limit_price = 1; let max_coin_qty = { // The loaded market must be dropped before CPI. - let market = MarketState::load(&self.market.market, &dex::ID)?; + let market = MarketState::load(&self.market.market, &dex::ID) + .map_err(|de| ProgramError::from(de))?; coin_lots(&market, base_amount) }; let max_native_pc_qty = u64::MAX; @@ -296,7 +297,7 @@ impl<'info> OrderbookClient<'info> { // // `quote_amount` is the "native" amount of the quote currency, i.e., token // amount including decimals. - fn buy(&self, quote_amount: u64, referral: Option>) -> ProgramResult { + fn buy(&self, quote_amount: u64, referral: Option>) -> Result<()> { let limit_price = u64::MAX; let max_coin_qty = u64::MAX; let max_native_pc_qty = quote_amount; @@ -324,7 +325,7 @@ impl<'info> OrderbookClient<'info> { max_native_pc_qty: u64, side: Side, referral: Option>, - ) -> ProgramResult { + ) -> Result<()> { // Client order id is only used for cancels. Not used here so hardcode. let client_order_id = 0; // Limit is the dex's custom compute budge parameter, setting an upper @@ -363,7 +364,7 @@ impl<'info> OrderbookClient<'info> { ) } - fn settle(&self, referral: Option>) -> ProgramResult { + fn settle(&self, referral: Option>) -> Result<()> { let settle_accs = dex::SettleFunds { market: self.market.market.clone(), open_orders: self.market.open_orders.clone(), @@ -455,7 +456,7 @@ fn _is_valid_swap<'info>(from: &AccountInfo<'info>, to: &AccountInfo<'info>) -> let from_token_mint = token::accessor::mint(from)?; let to_token_mint = token::accessor::mint(to)?; if from_token_mint == to_token_mint { - return Err(ErrorCode::SwapTokensCannotMatch.into()); + return err!(ErrorCode::SwapTokensCannotMatch); } Ok(()) } @@ -486,7 +487,7 @@ pub struct DidSwap { pub authority: Pubkey, } -#[error] +#[error_code] pub enum ErrorCode { #[msg("The tokens being swapped must have different mints")] SwapTokensCannotMatch, diff --git a/tests/system-accounts/package.json b/tests/system-accounts/package.json index 79f2b45b..a9d4a5df 100644 --- a/tests/system-accounts/package.json +++ b/tests/system-accounts/package.json @@ -1,6 +1,6 @@ { "name": "system-accounts", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/system-accounts/programs/system-accounts/src/lib.rs b/tests/system-accounts/programs/system-accounts/src/lib.rs index 79127c92..36ffd160 100644 --- a/tests/system-accounts/programs/system-accounts/src/lib.rs +++ b/tests/system-accounts/programs/system-accounts/src/lib.rs @@ -6,7 +6,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); mod system_accounts { use super::*; - pub fn initialize(_ctx: Context) -> ProgramResult { + pub fn initialize(_ctx: Context) -> Result<()> { Ok(()) } } diff --git a/tests/sysvars/package.json b/tests/sysvars/package.json index d6eadac2..0892a899 100644 --- a/tests/sysvars/package.json +++ b/tests/sysvars/package.json @@ -1,6 +1,6 @@ { "name": "sysvars", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/sysvars/programs/sysvars/src/lib.rs b/tests/sysvars/programs/sysvars/src/lib.rs index 6a8b51ee..c6dca4e3 100644 --- a/tests/sysvars/programs/sysvars/src/lib.rs +++ b/tests/sysvars/programs/sysvars/src/lib.rs @@ -5,7 +5,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); #[program] mod sysvars { use super::*; - pub fn sysvars(_ctx: Context) -> ProgramResult { + pub fn sysvars(_ctx: Context) -> Result<()> { Ok(()) } } diff --git a/tests/tictactoe/package.json b/tests/tictactoe/package.json index 69b633a7..2ac71d44 100644 --- a/tests/tictactoe/package.json +++ b/tests/tictactoe/package.json @@ -1,6 +1,6 @@ { "name": "tictactoe", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/tictactoe/programs/tictactoe/src/lib.rs b/tests/tictactoe/programs/tictactoe/src/lib.rs index 9acef504..5df4cee9 100644 --- a/tests/tictactoe/programs/tictactoe/src/lib.rs +++ b/tests/tictactoe/programs/tictactoe/src/lib.rs @@ -18,14 +18,14 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); pub mod tictactoe { use super::*; - pub fn initialize_dashboard(ctx: Context) -> ProgramResult { + pub fn initialize_dashboard(ctx: Context) -> Result<()> { let dashboard = &mut ctx.accounts.dashboard; dashboard.game_count = 0; dashboard.address = *dashboard.to_account_info().key; Ok(()) } - pub fn initialize(ctx: Context) -> ProgramResult { + pub fn initialize(ctx: Context) -> Result<()> { let dashboard = &mut ctx.accounts.dashboard; let game = &mut ctx.accounts.game; dashboard.game_count = dashboard.game_count + 1; @@ -34,7 +34,7 @@ pub mod tictactoe { Ok(()) } - pub fn player_join(ctx: Context) -> ProgramResult { + pub fn player_join(ctx: Context) -> Result<()> { let game = &mut ctx.accounts.game; game.player_o = *ctx.accounts.player_o.key; game.game_state = 1; @@ -42,14 +42,14 @@ pub mod tictactoe { } #[access_control(Playermove::accounts(&ctx, x_or_o, player_move))] - pub fn player_move(ctx: Context, x_or_o: u8, player_move: u8) -> ProgramResult { + pub fn player_move(ctx: Context, x_or_o: u8, player_move: u8) -> Result<()> { let game = &mut ctx.accounts.game; game.board[player_move as usize] = x_or_o; game.status(x_or_o); Ok(()) } - pub fn status(ctx: Context) -> ProgramResult { + pub fn status(ctx: Context) -> Result<()> { Ok(()) } } diff --git a/tests/typescript/package.json b/tests/typescript/package.json index 5aac6048..b3559539 100644 --- a/tests/typescript/package.json +++ b/tests/typescript/package.json @@ -1,6 +1,6 @@ { "name": "typescript-example", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/typescript/programs/typescript/src/lib.rs b/tests/typescript/programs/typescript/src/lib.rs index 43a31238..63fd8e6e 100644 --- a/tests/typescript/programs/typescript/src/lib.rs +++ b/tests/typescript/programs/typescript/src/lib.rs @@ -8,7 +8,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); #[program] pub mod typescript { use super::*; - pub fn initialize(ctx: Context) -> ProgramResult { + pub fn initialize(ctx: Context) -> Result<()> { Ok(()) } } diff --git a/tests/zero-copy/Anchor.toml b/tests/zero-copy/Anchor.toml index a2c36232..ab1af598 100644 --- a/tests/zero-copy/Anchor.toml +++ b/tests/zero-copy/Anchor.toml @@ -13,4 +13,4 @@ zero_cpi = "ErjUjtqKE5AGWUsjseSJCVLtddM6rhaMbDqmhzraF9h6" zero_copy = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" [features] -safety_checks = false + diff --git a/tests/zero-copy/package.json b/tests/zero-copy/package.json index 67912653..81c2c320 100644 --- a/tests/zero-copy/package.json +++ b/tests/zero-copy/package.json @@ -1,6 +1,6 @@ { "name": "zero-copy", - "version": "0.21.0", + "version": "0.22.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://github.com/project-serum/anchor#readme", "bugs": { diff --git a/tests/zero-copy/programs/zero-copy/src/lib.rs b/tests/zero-copy/programs/zero-copy/src/lib.rs index bc3b8a65..5dae5f7a 100644 --- a/tests/zero-copy/programs/zero-copy/src/lib.rs +++ b/tests/zero-copy/programs/zero-copy/src/lib.rs @@ -13,38 +13,38 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); pub mod zero_copy { use super::*; - pub fn create_foo(ctx: Context) -> ProgramResult { + pub fn create_foo(ctx: Context) -> Result<()> { let foo = &mut ctx.accounts.foo.load_mut()?; foo.authority = *ctx.accounts.authority.key; foo.set_second_authority(ctx.accounts.authority.key); Ok(()) } - pub fn update_foo(ctx: Context, data: u64) -> ProgramResult { + pub fn update_foo(ctx: Context, data: u64) -> Result<()> { let mut foo = ctx.accounts.foo.load_mut()?; foo.data = data; Ok(()) } - pub fn update_foo_second(ctx: Context, second_data: u64) -> ProgramResult { + pub fn update_foo_second(ctx: Context, second_data: u64) -> Result<()> { let mut foo = ctx.accounts.foo.load_mut()?; foo.second_data = second_data; Ok(()) } - pub fn create_bar(ctx: Context) -> ProgramResult { + pub fn create_bar(ctx: Context) -> Result<()> { let bar = &mut ctx.accounts.bar.load_mut()?; bar.authority = *ctx.accounts.authority.key; Ok(()) } - pub fn update_bar(ctx: Context, data: u64) -> ProgramResult { + pub fn update_bar(ctx: Context, data: u64) -> Result<()> { let bar = &mut ctx.accounts.bar.load_mut()?; bar.data = data; Ok(()) } - pub fn create_large_account(_ctx: Context) -> ProgramResult { + pub fn create_large_account(_ctx: Context) -> Result<()> { Ok(()) } @@ -52,7 +52,7 @@ pub mod zero_copy { ctx: Context, idx: u32, data: u64, - ) -> ProgramResult { + ) -> Result<()> { let event_q = &mut ctx.accounts.event_q.load_mut()?; event_q.events[idx as usize] = Event { data, diff --git a/tests/zero-copy/programs/zero-cpi/src/lib.rs b/tests/zero-copy/programs/zero-cpi/src/lib.rs index 4ea23f18..c1d41c2e 100644 --- a/tests/zero-copy/programs/zero-cpi/src/lib.rs +++ b/tests/zero-copy/programs/zero-cpi/src/lib.rs @@ -8,7 +8,7 @@ declare_id!("ErjUjtqKE5AGWUsjseSJCVLtddM6rhaMbDqmhzraF9h6"); #[program] pub mod zero_cpi { use super::*; - pub fn check_cpi(ctx: Context, data: u64) -> ProgramResult { + pub fn check_cpi(ctx: Context, data: u64) -> Result<()> { let cpi_program = ctx.accounts.zero_copy_program.to_account_info(); let cpi_accounts = UpdateBar { authority: ctx.accounts.authority.clone(), diff --git a/ts/package.json b/ts/package.json index c5555de4..9622e394 100644 --- a/ts/package.json +++ b/ts/package.json @@ -1,6 +1,6 @@ { "name": "@project-serum/anchor", - "version": "0.21.0", + "version": "0.22.0", "description": "Anchor client", "module": "./dist/esm/index.js", "main": "./dist/cjs/index.js", diff --git a/ts/src/error.ts b/ts/src/error.ts index e9bdbc5f..a5084412 100644 --- a/ts/src/error.ts +++ b/ts/src/error.ts @@ -15,16 +15,28 @@ export class ProgramError extends Error { err: any, idlErrors: Map ): ProgramError | null { + const errString: string = err.toString(); // TODO: don't rely on the error string. web3.js should preserve the error // code information instead of giving us an untyped string. - let components = err.toString().split("custom program error: "); - if (components.length !== 2) { - return null; + let unparsedErrorCode: string; + if (errString.includes("custom program error:")) { + let components = errString.split("custom program error: "); + if (components.length !== 2) { + return null; + } else { + unparsedErrorCode = components[1]; + } + } else { + const matches = errString.match(/"Custom":([0-9]+)}/g); + if (!matches || matches.length > 1) { + return null; + } + unparsedErrorCode = matches[0].match(/([0-9]+)/g)![0]; } let errorCode: number; try { - errorCode = parseInt(components[1]); + errorCode = parseInt(unparsedErrorCode); } catch (parseErr) { return null; }