Browse Source

lang: Error on undocumented unsafe account types (#1452)

Tom Linton 3 years ago
parent
commit
5ff9947ba2
40 changed files with 342 additions and 7 deletions
  1. 2 0
      .github/workflows/tests.yaml
  2. 1 0
      CHANGELOG.md
  3. 19 1
      cli/src/config.rs
  4. 6 1
      cli/src/lib.rs
  5. 1 1
      lang/syn/Cargo.toml
  6. 4 0
      lang/syn/src/idl/file.rs
  7. 51 1
      lang/syn/src/parser/context.rs
  8. 1 1
      tests/auction-house
  9. 3 0
      tests/cashiers-check/Anchor.toml
  10. 3 0
      tests/cfo/Anchor.toml
  11. 1 1
      tests/cfo/deps/stake
  12. 1 1
      tests/cfo/deps/swap
  13. 3 0
      tests/chat/Anchor.toml
  14. 3 0
      tests/custom-coder/Anchor.toml
  15. 3 0
      tests/errors/Anchor.toml
  16. 3 0
      tests/escrow/Anchor.toml
  17. 3 0
      tests/ido-pool/Anchor.toml
  18. 3 0
      tests/interface/Anchor.toml
  19. 3 0
      tests/lockup/Anchor.toml
  20. 49 0
      tests/misc/programs/misc/src/context.rs
  21. 1 0
      tests/misc/programs/misc2/src/lib.rs
  22. 3 0
      tests/multisig/Anchor.toml
  23. 1 0
      tests/pda-derivation/Anchor.toml
  24. 3 0
      tests/pyth/Anchor.toml
  25. 6 0
      tests/safety-checks/.gitignore
  26. 13 0
      tests/safety-checks/Anchor.toml
  27. 4 0
      tests/safety-checks/Cargo.toml
  28. 12 0
      tests/safety-checks/migrations/deploy.ts
  29. 19 0
      tests/safety-checks/programs/account-info/Cargo.toml
  30. 2 0
      tests/safety-checks/programs/account-info/Xargo.toml
  31. 16 0
      tests/safety-checks/programs/account-info/src/lib.rs
  32. 19 0
      tests/safety-checks/programs/unchecked-account/Cargo.toml
  33. 2 0
      tests/safety-checks/programs/unchecked-account/Xargo.toml
  34. 16 0
      tests/safety-checks/programs/unchecked-account/src/lib.rs
  35. 27 0
      tests/safety-checks/test.sh
  36. 16 0
      tests/safety-checks/tests/safety-checks.ts
  37. 10 0
      tests/safety-checks/tsconfig.json
  38. 3 0
      tests/spl/token-proxy/Anchor.toml
  39. 3 0
      tests/swap/Anchor.toml
  40. 3 0
      tests/zero-copy/Anchor.toml

+ 2 - 0
.github/workflows/tests.yaml

@@ -278,6 +278,8 @@ jobs:
             path: tests/auction-house
             path: tests/auction-house
           - cmd: cd tests/floats && yarn && anchor test
           - cmd: cd tests/floats && yarn && anchor test
             path: tests/floats
             path: tests/floats
+          - cmd: cd tests/safety-checks && ./test.sh
+            path: tests/safety-checks
     steps:
     steps:
       - uses: actions/checkout@v2
       - uses: actions/checkout@v2
       - uses: ./.github/actions/setup/
       - uses: ./.github/actions/setup/

+ 1 - 0
CHANGELOG.md

@@ -24,6 +24,7 @@ incremented for features.
 
 
 * lang: Enforce that the payer for an init-ed account be marked `mut` ([#1271](https://github.com/project-serum/anchor/pull/1271)).
 * lang: Enforce that the payer for an init-ed account be marked `mut` ([#1271](https://github.com/project-serum/anchor/pull/1271)).
 * lang: All error-related code is now in the error module ([#1426](https://github.com/project-serum/anchor/pull/1426)).
 * 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)).
 
 
 ## [0.21.0] - 2022-02-07
 ## [0.21.0] - 2022-02-07
 
 

+ 19 - 1
cli/src/config.rs

@@ -166,6 +166,7 @@ impl WithPath<Config> {
                 path.join("src/lib.rs"),
                 path.join("src/lib.rs"),
                 version,
                 version,
                 self.features.seeds,
                 self.features.seeds,
+                false,
             )?;
             )?;
             r.push(Program {
             r.push(Program {
                 lib_name,
                 lib_name,
@@ -256,9 +257,26 @@ pub struct Config {
     pub test: Option<Test>,
     pub test: Option<Test>,
 }
 }
 
 
-#[derive(Default, Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct FeaturesConfig {
 pub struct FeaturesConfig {
+    #[serde(default)]
     pub seeds: bool,
     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)]
 #[derive(Clone, Debug, Serialize, Deserialize)]

+ 6 - 1
cli/src/lib.rs

@@ -1388,7 +1388,12 @@ fn extract_idl(cfg: &WithPath<Config>, file: &str) -> Result<Option<Idl>> {
     let manifest_from_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap());
     let manifest_from_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap());
     let cargo = Manifest::discover_from_path(manifest_from_path)?
     let cargo = Manifest::discover_from_path(manifest_from_path)?
         .ok_or_else(|| anyhow!("Cargo.toml not found"))?;
         .ok_or_else(|| anyhow!("Cargo.toml not found"))?;
-    anchor_syn::idl::file::parse(&*file, cargo.version(), cfg.features.seeds)
+    anchor_syn::idl::file::parse(
+        &*file,
+        cargo.version(),
+        cfg.features.seeds,
+        cfg.features.safety_checks,
+    )
 }
 }
 
 
 fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
 fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {

+ 1 - 1
lang/syn/Cargo.toml

@@ -16,7 +16,7 @@ anchor-debug = []
 seeds = []
 seeds = []
 
 
 [dependencies]
 [dependencies]
-proc-macro2 = "1.0"
+proc-macro2 = { version = "1.0", features=["span-locations"]}
 proc-macro2-diagnostics = "0.9"
 proc-macro2-diagnostics = "0.9"
 quote = "1.0"
 quote = "1.0"
 syn = { version = "1.0.60", features = ["full", "extra-traits", "parsing"] }
 syn = { version = "1.0.60", features = ["full", "extra-traits", "parsing"] }

+ 4 - 0
lang/syn/src/idl/file.rs

@@ -18,8 +18,12 @@ pub fn parse(
     filename: impl AsRef<Path>,
     filename: impl AsRef<Path>,
     version: String,
     version: String,
     seeds_feature: bool,
     seeds_feature: bool,
+    safety_checks: bool,
 ) -> Result<Option<Idl>> {
 ) -> Result<Option<Idl>> {
     let ctx = CrateContext::parse(filename)?;
     let ctx = CrateContext::parse(filename)?;
+    if safety_checks {
+        ctx.safety_checks()?;
+    }
 
 
     let program_mod = match parse_program_mod(&ctx) {
     let program_mod = match parse_program_mod(&ctx) {
         None => return Ok(None),
         None => return Ok(None),

+ 51 - 1
lang/syn/src/parser/context.rs

@@ -1,6 +1,6 @@
+use anyhow::anyhow;
 use std::collections::BTreeMap;
 use std::collections::BTreeMap;
 use std::path::{Path, PathBuf};
 use std::path::{Path, PathBuf};
-
 use syn::parse::{Error as ParseError, Result as ParseResult};
 use syn::parse::{Error as ParseError, Result as ParseResult};
 
 
 /// Crate parse context
 /// Crate parse context
@@ -40,6 +40,41 @@ impl CrateContext {
             modules: ParsedModule::parse_recursive(root.as_ref())?,
             modules: ParsedModule::parse_recursive(root.as_ref())?,
         })
         })
     }
     }
+
+    // Perform Anchor safety checks on the parsed create
+    pub fn safety_checks(&self) -> Result<(), anyhow::Error> {
+        // Check all structs for unsafe field types, i.e. AccountInfo and UncheckedAccount.
+        for (_, ctx) in self.modules.iter() {
+            for unsafe_field in ctx.unsafe_struct_fields() {
+                // Check if unsafe field type has been documented with a /// SAFETY: doc string.
+                let is_documented = unsafe_field.attrs.iter().any(|attr| {
+                    attr.tokens.clone().into_iter().any(|token| match token {
+                        // Check for doc comments containing CHECK
+                        proc_macro2::TokenTree::Literal(s) => s.to_string().contains("CHECK"),
+                        _ => false,
+                    })
+                });
+                if !is_documented {
+                    let ident = unsafe_field.ident.as_ref().unwrap();
+                    let span = ident.span();
+                    // Error if undocumented.
+                    return Err(anyhow!(
+                        r#"
+        {}:{}:{}
+        Struct field "{}" is unsafe, but is not documented.
+        Please add a `/// CHECK:` doc comment explaining why no checks through types are necessary.
+        See https://book.anchor-lang.com/chapter_3/the_accounts_struct.html#safety-checks for more information.
+                    "#,
+                        ctx.file.canonicalize().unwrap().display(),
+                        span.start().line,
+                        span.start().column,
+                        ident.to_string()
+                    ));
+                };
+            }
+        }
+        Ok(())
+    }
 }
 }
 
 
 /// Module parse context
 /// Module parse context
@@ -181,6 +216,21 @@ impl ParsedModule {
         })
         })
     }
     }
 
 
+    fn unsafe_struct_fields(&self) -> impl Iterator<Item = &syn::Field> {
+        self.structs()
+            .flat_map(|s| &s.fields)
+            .filter(|f| match &f.ty {
+                syn::Type::Path(syn::TypePath {
+                    path: syn::Path { segments, .. },
+                    ..
+                }) => {
+                    segments.len() == 1 && segments[0].ident == "UncheckedAccount"
+                        || segments[0].ident == "AccountInfo"
+                }
+                _ => false,
+            })
+    }
+
     fn enums(&self) -> impl Iterator<Item = &syn::ItemEnum> {
     fn enums(&self) -> impl Iterator<Item = &syn::ItemEnum> {
         self.items.iter().filter_map(|i| match i {
         self.items.iter().filter_map(|i| match i {
             syn::Item::Enum(item) => Some(item),
             syn::Item::Enum(item) => Some(item),

+ 1 - 1
tests/auction-house

@@ -1 +1 @@
-Subproject commit 2bfe49bdac2333d0e413a1e452c0ab7b502266fa
+Subproject commit 2b1b1e04986106715ab53794bcb63d3641673f64

+ 3 - 0
tests/cashiers-check/Anchor.toml

@@ -7,3 +7,6 @@ cashiers_check = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 
 
 [scripts]
 [scripts]
 test = "yarn run mocha -t 1000000 tests/"
 test = "yarn run mocha -t 1000000 tests/"
+
+[features]
+safety_checks = false

+ 3 - 0
tests/cfo/Anchor.toml

@@ -41,3 +41,6 @@ program = "./deps/stake/target/deploy/registry.so"
 [[test.genesis]]
 [[test.genesis]]
 address = "6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks"
 address = "6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks"
 program = "./deps/stake/target/deploy/lockup.so"
 program = "./deps/stake/target/deploy/lockup.so"
+
+[features]
+safety_checks = false

+ 1 - 1
tests/cfo/deps/stake

@@ -1 +1 @@
-Subproject commit f04b2aaf8817dac4c8d89e75eaaa0c099dfbf166
+Subproject commit 990eaa7944c6682838fdaa6c14cc07ed680007f0

+ 1 - 1
tests/cfo/deps/swap

@@ -1 +1 @@
-Subproject commit b3021f1444280a372721133bb2e5acca2d271283
+Subproject commit 96e3b1e2a53a95ef56e6ec2da68348ffd6a5c091

+ 3 - 0
tests/chat/Anchor.toml

@@ -7,3 +7,6 @@ chat = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 
 
 [scripts]
 [scripts]
 test = "yarn run mocha -t 1000000 tests/"
 test = "yarn run mocha -t 1000000 tests/"
+
+[features]
+safety_checks = false

+ 3 - 0
tests/custom-coder/Anchor.toml

@@ -11,3 +11,6 @@ wallet = "~/.config/solana/id.json"
 
 
 [scripts]
 [scripts]
 test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
 test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+
+[features]
+safety_checks = false

+ 3 - 0
tests/errors/Anchor.toml

@@ -7,3 +7,6 @@ errors = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 
 
 [scripts]
 [scripts]
 test = "yarn run mocha -t 1000000 tests/"
 test = "yarn run mocha -t 1000000 tests/"
+
+[features]
+safety_checks = false

+ 3 - 0
tests/escrow/Anchor.toml

@@ -7,3 +7,6 @@ escrow = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 
 
 [scripts]
 [scripts]
 test = "yarn run ts-mocha -t 1000000 tests/*.ts"
 test = "yarn run ts-mocha -t 1000000 tests/*.ts"
+
+[features]
+safety_checks = false

+ 3 - 0
tests/ido-pool/Anchor.toml

@@ -7,3 +7,6 @@ ido_pool = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 
 
 [scripts]
 [scripts]
 test = "yarn run mocha -t 1000000 tests/"
 test = "yarn run mocha -t 1000000 tests/"
+
+[features]
+safety_checks = false

+ 3 - 0
tests/interface/Anchor.toml

@@ -8,3 +8,6 @@ counter_auth = "Aws2XRVHjNqCUbMmaU245ojT2DBJFYX58KVo2YySEeeP"
 
 
 [scripts]
 [scripts]
 test = "yarn run mocha -t 1000000 tests/"
 test = "yarn run mocha -t 1000000 tests/"
+
+[features]
+safety_checks = false

+ 3 - 0
tests/lockup/Anchor.toml

@@ -8,3 +8,6 @@ registry = "HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L"
 
 
 [scripts]
 [scripts]
 test = "yarn run mocha -t 1000000 tests/"
 test = "yarn run mocha -t 1000000 tests/"
+
+[features]
+safety_checks = false

+ 49 - 0
tests/misc/programs/misc/src/context.rs

@@ -28,6 +28,7 @@ pub struct TestTokenSeedsInit<'info> {
     )]
     )]
     pub my_pda: Account<'info, TokenAccount>,
     pub my_pda: Account<'info, TokenAccount>,
     #[account(mut)]
     #[account(mut)]
+    /// CHECK:
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
     pub system_program: Program<'info, System>,
     pub system_program: Program<'info, System>,
     pub rent: Sysvar<'info, Rent>,
     pub rent: Sysvar<'info, Rent>,
@@ -60,6 +61,7 @@ pub struct TestValidateAssociatedToken<'info> {
     )]
     )]
     pub token: Account<'info, TokenAccount>,
     pub token: Account<'info, TokenAccount>,
     pub mint: Account<'info, Mint>,
     pub mint: Account<'info, Mint>,
+    /// CHECK:
     pub wallet: AccountInfo<'info>,
     pub wallet: AccountInfo<'info>,
 }
 }
 
 
@@ -70,7 +72,9 @@ pub struct TestInstructionConstraint<'info> {
         seeds = [b"my-seed", my_account.key.as_ref()],
         seeds = [b"my-seed", my_account.key.as_ref()],
         bump = nonce,
         bump = nonce,
     )]
     )]
+    /// CHECK:
     pub my_pda: AccountInfo<'info>,
     pub my_pda: AccountInfo<'info>,
+    /// CHECK:
     pub my_account: AccountInfo<'info>,
     pub my_account: AccountInfo<'info>,
 }
 }
 
 
@@ -86,6 +90,7 @@ pub struct TestPdaInit<'info> {
     pub my_pda: Account<'info, DataU16>,
     pub my_pda: Account<'info, DataU16>,
     #[account(mut)]
     #[account(mut)]
     pub my_payer: Signer<'info>,
     pub my_payer: Signer<'info>,
+    /// CHECK:
     pub foo: AccountInfo<'info>,
     pub foo: AccountInfo<'info>,
     pub system_program: Program<'info, System>,
     pub system_program: Program<'info, System>,
 }
 }
@@ -112,6 +117,7 @@ pub struct TestPdaMutZeroCopy<'info> {
         bump = my_pda.load()?.bump,
         bump = my_pda.load()?.bump,
     )]
     )]
     pub my_pda: Loader<'info, DataZeroCopy>,
     pub my_pda: Loader<'info, DataZeroCopy>,
+    /// CHECK:
     pub my_payer: AccountInfo<'info>,
     pub my_payer: AccountInfo<'info>,
 }
 }
 
 
@@ -135,29 +141,35 @@ pub struct InitializeSkipRentExempt<'info> {
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct InitializeNoRentExempt<'info> {
 pub struct InitializeNoRentExempt<'info> {
+    /// CHECK:
     pub data: AccountInfo<'info>,
     pub data: AccountInfo<'info>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct TestOwner<'info> {
 pub struct TestOwner<'info> {
     #[account(owner = *misc.key)]
     #[account(owner = *misc.key)]
+    /// CHECK:
     pub data: AccountInfo<'info>,
     pub data: AccountInfo<'info>,
+    /// CHECK:
     pub misc: AccountInfo<'info>,
     pub misc: AccountInfo<'info>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct TestExecutable<'info> {
 pub struct TestExecutable<'info> {
     #[account(executable)]
     #[account(executable)]
+    /// CHECK:
     pub program: AccountInfo<'info>,
     pub program: AccountInfo<'info>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct TestStateCpi<'info> {
 pub struct TestStateCpi<'info> {
     #[account(signer)]
     #[account(signer)]
+    /// CHECK:
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
     #[account(mut, state = misc2_program)]
     #[account(mut, state = misc2_program)]
     pub cpi_state: CpiState<'info, Misc2State>,
     pub cpi_state: CpiState<'info, Misc2State>,
     #[account(executable)]
     #[account(executable)]
+    /// CHECK:
     pub misc2_program: AccountInfo<'info>,
     pub misc2_program: AccountInfo<'info>,
 }
 }
 
 
@@ -165,6 +177,7 @@ pub struct TestStateCpi<'info> {
 pub struct TestClose<'info> {
 pub struct TestClose<'info> {
     #[account(mut, close = sol_dest)]
     #[account(mut, close = sol_dest)]
     pub data: Account<'info, Data>,
     pub data: Account<'info, Data>,
+    /// CHECK:
     sol_dest: AccountInfo<'info>,
     sol_dest: AccountInfo<'info>,
 }
 }
 
 
@@ -259,6 +272,7 @@ pub struct TestInitWithEmptySeeds<'info> {
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct TestEmptySeedsConstraint<'info> {
 pub struct TestEmptySeedsConstraint<'info> {
     #[account(seeds = [], bump)]
     #[account(seeds = [], bump)]
+    /// CHECK:
     pub pda: AccountInfo<'info>,
     pub pda: AccountInfo<'info>,
 }
 }
 
 
@@ -283,10 +297,12 @@ pub struct TestInitIfNeeded<'info> {
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct TestInitIfNeededChecksOwner<'info> {
 pub struct TestInitIfNeededChecksOwner<'info> {
     #[account(init_if_needed, payer = payer, space = 100, owner = *owner.key, seeds = [b"hello"], bump)]
     #[account(init_if_needed, payer = payer, space = 100, owner = *owner.key, seeds = [b"hello"], bump)]
+    /// CHECK:
     pub data: UncheckedAccount<'info>,
     pub data: UncheckedAccount<'info>,
     #[account(mut)]
     #[account(mut)]
     pub payer: Signer<'info>,
     pub payer: Signer<'info>,
     pub system_program: Program<'info, System>,
     pub system_program: Program<'info, System>,
+    /// CHECK:
     pub owner: AccountInfo<'info>,
     pub owner: AccountInfo<'info>,
 }
 }
 
 
@@ -294,6 +310,7 @@ pub struct TestInitIfNeededChecksOwner<'info> {
 #[instruction(seed_data: String)]
 #[instruction(seed_data: String)]
 pub struct TestInitIfNeededChecksSeeds<'info> {
 pub struct TestInitIfNeededChecksSeeds<'info> {
     #[account(init_if_needed, payer = payer, space = 100, seeds = [seed_data.as_bytes()], bump)]
     #[account(init_if_needed, payer = payer, space = 100, seeds = [seed_data.as_bytes()], bump)]
+    /// CHECK:
     pub data: UncheckedAccount<'info>,
     pub data: UncheckedAccount<'info>,
     #[account(mut)]
     #[account(mut)]
     pub payer: Signer<'info>,
     pub payer: Signer<'info>,
@@ -310,7 +327,9 @@ pub struct TestInitMintIfNeeded<'info> {
     pub rent: Sysvar<'info, Rent>,
     pub rent: Sysvar<'info, Rent>,
     pub system_program: Program<'info, System>,
     pub system_program: Program<'info, System>,
     pub token_program: Program<'info, Token>,
     pub token_program: Program<'info, Token>,
+    /// CHECK:
     pub mint_authority: AccountInfo<'info>,
     pub mint_authority: AccountInfo<'info>,
+    /// CHECK:
     pub freeze_authority: AccountInfo<'info>,
     pub freeze_authority: AccountInfo<'info>,
 }
 }
 
 
@@ -324,6 +343,7 @@ pub struct TestInitTokenIfNeeded<'info> {
     pub rent: Sysvar<'info, Rent>,
     pub rent: Sysvar<'info, Rent>,
     pub system_program: Program<'info, System>,
     pub system_program: Program<'info, System>,
     pub token_program: Program<'info, Token>,
     pub token_program: Program<'info, Token>,
+    /// CHECK:
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
 }
 }
 
 
@@ -343,6 +363,7 @@ pub struct TestInitAssociatedTokenIfNeeded<'info> {
     pub system_program: Program<'info, System>,
     pub system_program: Program<'info, System>,
     pub token_program: Program<'info, Token>,
     pub token_program: Program<'info, Token>,
     pub associated_token_program: Program<'info, AssociatedToken>,
     pub associated_token_program: Program<'info, AssociatedToken>,
+    /// CHECK:
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
 }
 }
 
 
@@ -360,18 +381,21 @@ pub struct TestConstArraySize<'info> {
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct NoRentExempt<'info> {
 pub struct NoRentExempt<'info> {
+    /// CHECK:
     pub data: AccountInfo<'info>,
     pub data: AccountInfo<'info>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct EnforceRentExempt<'info> {
 pub struct EnforceRentExempt<'info> {
     #[account(rent_exempt = enforce)]
     #[account(rent_exempt = enforce)]
+    /// CHECK:
     pub data: AccountInfo<'info>,
     pub data: AccountInfo<'info>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct InitDecreaseLamports<'info> {
 pub struct InitDecreaseLamports<'info> {
     #[account(init, payer = user, space = 1000)]
     #[account(init, payer = user, space = 1000)]
+    /// CHECK:
     pub data: AccountInfo<'info>,
     pub data: AccountInfo<'info>,
     #[account(mut)]
     #[account(mut)]
     pub user: Signer<'info>,
     pub user: Signer<'info>,
@@ -381,6 +405,7 @@ pub struct InitDecreaseLamports<'info> {
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct InitIfNeededChecksRentExemption<'info> {
 pub struct InitIfNeededChecksRentExemption<'info> {
     #[account(init_if_needed, payer = user, space = 1000)]
     #[account(init_if_needed, payer = user, space = 1000)]
+    /// CHECK:
     pub data: AccountInfo<'info>,
     pub data: AccountInfo<'info>,
     #[account(mut)]
     #[account(mut)]
     pub user: Signer<'info>,
     pub user: Signer<'info>,
@@ -393,9 +418,11 @@ pub struct TestProgramIdConstraint<'info> {
     // not a real associated token account
     // not a real associated token account
     // just deriving like this for testing purposes
     // just deriving like this for testing purposes
     #[account(seeds = [b"seed"], bump = bump, seeds::program = anchor_spl::associated_token::ID)]
     #[account(seeds = [b"seed"], bump = bump, seeds::program = anchor_spl::associated_token::ID)]
+    /// CHECK:
     first: AccountInfo<'info>,
     first: AccountInfo<'info>,
 
 
     #[account(seeds = [b"seed"], bump = second_bump, seeds::program = crate::ID)]
     #[account(seeds = [b"seed"], bump = second_bump, seeds::program = crate::ID)]
+    /// CHECK:
     second: AccountInfo<'info>,
     second: AccountInfo<'info>,
 }
 }
 
 
@@ -404,8 +431,30 @@ pub struct TestProgramIdConstraintUsingFindPda<'info> {
     // not a real associated token account
     // not a real associated token account
     // just deriving like this for testing purposes
     // just deriving like this for testing purposes
     #[account(seeds = [b"seed"], bump, seeds::program = anchor_spl::associated_token::ID)]
     #[account(seeds = [b"seed"], bump, seeds::program = anchor_spl::associated_token::ID)]
+    /// CHECK:
     first: AccountInfo<'info>,
     first: AccountInfo<'info>,
 
 
     #[account(seeds = [b"seed"], bump, seeds::program = crate::ID)]
     #[account(seeds = [b"seed"], bump, seeds::program = crate::ID)]
+    /// CHECK:
     second: AccountInfo<'info>,
     second: AccountInfo<'info>,
 }
 }
+
+#[derive(Accounts)]
+pub struct TestUnsafeFieldSafetyErrors<'info> {
+    #[doc = "test"]
+    /// CHECK:
+    pub data: UncheckedAccount<'info>,
+    #[account(mut)]
+    /// CHECK:
+    pub data_two: UncheckedAccount<'info>,
+    #[account(
+        seeds = [b"my-seed", signer.key.as_ref()],
+        bump
+    )]
+    /// CHECK:
+    pub data_three: UncheckedAccount<'info>,
+    /// CHECK:
+    pub data_four: UncheckedAccount<'info>,
+    pub signer: Signer<'info>,
+    pub system_program: Program<'info, System>,
+}

+ 1 - 0
tests/misc/programs/misc2/src/lib.rs

@@ -33,5 +33,6 @@ pub mod misc2 {
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct Auth<'info> {
 pub struct Auth<'info> {
     #[account(signer)]
     #[account(signer)]
+    /// CHECK:
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
 }
 }

+ 3 - 0
tests/multisig/Anchor.toml

@@ -7,3 +7,6 @@ multisig = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 
 
 [scripts]
 [scripts]
 test = "yarn run mocha -t 1000000 tests/"
 test = "yarn run mocha -t 1000000 tests/"
+
+[features]
+safety_checks = false

+ 1 - 0
tests/pda-derivation/Anchor.toml

@@ -1,5 +1,6 @@
 [features]
 [features]
 seeds = true
 seeds = true
+safety_checks = false
 
 
 [provider]
 [provider]
 cluster = "localnet"
 cluster = "localnet"

+ 3 - 0
tests/pyth/Anchor.toml

@@ -7,3 +7,6 @@ pyth = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 
 
 [scripts]
 [scripts]
 test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
 test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
+
+[features]
+safety_checks = false

+ 6 - 0
tests/safety-checks/.gitignore

@@ -0,0 +1,6 @@
+
+.anchor
+.DS_Store
+target
+**/*.rs.bk
+node_modules

+ 13 - 0
tests/safety-checks/Anchor.toml

@@ -0,0 +1,13 @@
+[programs.localnet]
+unchecked_account = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+account_info = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
+[registry]
+url = "https://anchor.projectserum.com"
+
+[provider]
+cluster = "localnet"
+wallet = "/home/armaniferrante/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 4 - 0
tests/safety-checks/Cargo.toml

@@ -0,0 +1,4 @@
+[workspace]
+members = [
+    "programs/*"
+]

+ 12 - 0
tests/safety-checks/migrations/deploy.ts

@@ -0,0 +1,12 @@
+// Migrations are an early feature. Currently, they're nothing more than this
+// single deploy script that's invoked from the CLI, injecting a provider
+// configured from the workspace's Anchor.toml.
+
+const anchor = require("@project-serum/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+};

+ 19 - 0
tests/safety-checks/programs/account-info/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "account-info"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "account_info"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.21.0"

+ 2 - 0
tests/safety-checks/programs/account-info/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 16 - 0
tests/safety-checks/programs/account-info/src/lib.rs

@@ -0,0 +1,16 @@
+use anchor_lang::prelude::*;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[program]
+pub mod account_info {
+    use super::*;
+    pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+    unchecked: AccountInfo<'info>,
+}

+ 19 - 0
tests/safety-checks/programs/unchecked-account/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "unchecked-account"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "safety_checks"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }

+ 2 - 0
tests/safety-checks/programs/unchecked-account/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 16 - 0
tests/safety-checks/programs/unchecked-account/src/lib.rs

@@ -0,0 +1,16 @@
+use anchor_lang::prelude::*;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[program]
+pub mod unchecked_account {
+    use super::*;
+    pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+    unchecked: UncheckedAccount<'info>,
+}

+ 27 - 0
tests/safety-checks/test.sh

@@ -0,0 +1,27 @@
+#!/bin/bash
+
+echo "Building programs"
+
+#
+# Build the UncheckedAccount variant.
+#
+pushd programs/unchecked-account/
+anchor build
+if [ $? -eq 0 ]; then
+   echo "Error: expected failure"
+   exit 1
+fi
+popd
+
+#
+# Build the AccountInfo variant.
+#
+pushd programs/account-info/
+anchor build
+if [ $? -eq 0 ]; then
+   echo "Error: expected failure"
+   exit 1
+fi
+popd
+
+echo "Success. All builds failed."

+ 16 - 0
tests/safety-checks/tests/safety-checks.ts

@@ -0,0 +1,16 @@
+import * as anchor from "@project-serum/anchor";
+import { Program } from "@project-serum/anchor";
+import { SafetyChecks } from "../target/types/safety_checks";
+
+describe("safety-checks", () => {
+  // Configure the client to use the local cluster.
+  anchor.setProvider(anchor.Provider.env());
+
+  const program = anchor.workspace.SafetyChecks as Program<SafetyChecks>;
+
+  it("Is initialized!", async () => {
+    // Add your test here.
+    const tx = await program.rpc.initialize({});
+    console.log("Your transaction signature", tx);
+  });
+});

+ 10 - 0
tests/safety-checks/tsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "types": ["mocha", "chai"],
+    "typeRoots": ["./node_modules/@types"],
+    "lib": ["es2015"],
+    "module": "commonjs",
+    "target": "es6",
+    "esModuleInterop": true
+  }
+}

+ 3 - 0
tests/spl/token-proxy/Anchor.toml

@@ -7,3 +7,6 @@ token_proxy = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 
 
 [scripts]
 [scripts]
 test = "yarn run mocha -t 1000000 tests/"
 test = "yarn run mocha -t 1000000 tests/"
+
+[features]
+safety_checks = false

+ 3 - 0
tests/swap/Anchor.toml

@@ -11,3 +11,6 @@ program = "./deps/serum-dex/dex/target/deploy/serum_dex.so"
 
 
 [scripts]
 [scripts]
 test = "yarn run mocha -t 1000000 tests/"
 test = "yarn run mocha -t 1000000 tests/"
+
+[features]
+safety_checks = false

+ 3 - 0
tests/zero-copy/Anchor.toml

@@ -11,3 +11,6 @@ test = "yarn run mocha -t 1000000 tests/"
 [programs.localnet]
 [programs.localnet]
 zero_cpi = "ErjUjtqKE5AGWUsjseSJCVLtddM6rhaMbDqmhzraF9h6"
 zero_cpi = "ErjUjtqKE5AGWUsjseSJCVLtddM6rhaMbDqmhzraF9h6"
 zero_copy = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
 zero_copy = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
+[features]
+safety_checks = false