Merge branch 'master' into armani/versioning

This commit is contained in:
Armani Ferrante 2022-02-18 12:07:09 -05:00 committed by GitHub
commit 2dd6caa2e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 761 additions and 47 deletions

View File

@ -262,6 +262,8 @@ jobs:
path: tests/events
- cmd: cd tests/cashiers-check && anchor test
path: tests/cashiers-check
- cmd: cd tests/declare-id && anchor test
path: tests/declare-id
- cmd: cd tests/typescript && anchor test
path: tests/typescript
- cmd: cd tests/zero-copy && anchor test && cd programs/zero-copy && cargo test-bpf
@ -274,6 +276,10 @@ jobs:
path: tests/cfo
- cmd: cd tests/auction-house && yarn && anchor test
path: tests/auction-house
- cmd: cd tests/floats && yarn && anchor test
path: tests/floats
- cmd: cd tests/safety-checks && ./test.sh
path: tests/safety-checks
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/setup/

View File

@ -11,6 +11,11 @@ incremented for features.
## [Unreleased]
### Features
* 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)).
### Fixes
* ts: Allow nullable types for `Option<T>` mapped types ([#1428](https://github.com/project-serum/anchor/pull/1428)).
@ -21,6 +26,7 @@ incremented for features.
* lang, client, ts: 8 byte account discriminator has been replaced with a versioned account header. This is a state breaking change. If you have existing Anchor programs, previously deployed, one can continue to use the old 8 byte account discriminator by using the `deprecated-layout` feature flag ([#1415](https://github.com/project-serum/anchor/pull/1415)).
* 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)).
* 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

View File

@ -10,7 +10,7 @@
<p>
<a href="https://github.com/project-serum/anchor/actions"><img alt="Build Status" src="https://github.com/project-serum/anchor/actions/workflows/tests.yaml/badge.svg" /></a>
<a href="https://project-serum.github.io/anchor/"><img alt="Tutorials" src="https://img.shields.io/badge/docs-tutorials-blueviolet" /></a>
<a href="https://discord.com/channels/889577356681945098"><img alt="Discord Chat" src="https://img.shields.io/discord/889577356681945098?color=blueviolet" /></a>
<a href="https://discord.gg/PDeRXyVURd"><img alt="Discord Chat" src="https://img.shields.io/discord/889577356681945098?color=blueviolet" /></a>
<a href="https://opensource.org/licenses/Apache-2.0"><img alt="License" src="https://img.shields.io/github/license/project-serum/anchor?color=blueviolet" /></a>
</p>
</div>
@ -97,6 +97,14 @@ pub struct Counter {
For more, see the [examples](https://github.com/project-serum/anchor/tree/master/examples)
and [tests](https://github.com/project-serum/anchor/tree/master/tests) directories.
## License
Anchor is licensed under [Apache 2.0](./LICENSE).
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Anchor by you, as defined in the Apache-2.0 license, shall be
licensed as above, without any additional terms or conditions.
## Contribution
Thank you for your interest in contributing to Anchor! All contributions are welcome no
@ -113,10 +121,10 @@ For simple documentation changes, feel free to just open a pull request.
If you're considering larger changes or self motivated features, please file an issue
and engage with the maintainers in [Discord](https://discord.gg/sxy4zxBckh).
## License
### Thanks ❤️
Anchor is licensed under [Apache 2.0](./LICENSE).
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Anchor by you, as defined in the Apache-2.0 license, shall be
licensed as above, without any additional terms or conditions.
<div align="center">
<a href="https://github.com/project-serum/anchor/graphs/contributors">
<img src="https://contrib.rocks/image?repo=project-serum/anchor" width="100%" />
</a>
</div>

View File

@ -261,6 +261,9 @@ mod tests {
let mut current_version_file =
fs::File::create(current_version_file_path().as_path()).unwrap();
current_version_file.write_all("0.18.2".as_bytes()).unwrap();
// Sync the file to disk before the read in current_version() to
// mitigate the read not seeing the written version bytes.
current_version_file.sync_all().unwrap();
assert!(current_version().unwrap() == Version::parse("0.18.2").unwrap());
}
@ -278,6 +281,9 @@ mod tests {
let mut current_version_file =
fs::File::create(current_version_file_path().as_path()).unwrap();
current_version_file.write_all("0.18.2".as_bytes()).unwrap();
// Sync the file to disk before the read in current_version() to
// mitigate the read not seeing the written version bytes.
current_version_file.sync_all().unwrap();
// Create a fake binary for anchor-0.18.2 in the bin directory
fs::File::create(version_binary_path(&version)).unwrap();
uninstall_version(&version).unwrap();

View File

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

View File

@ -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 cargo = Manifest::discover_from_path(manifest_from_path)?
.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
anchor_syn::idl::file::parse(&*file, cargo.version(), cfg.features.seeds)
anchor_syn::idl::file::parse(
&*file,
cargo.version(),
cfg.features.seeds,
cfg.features.safety_checks,
)
}
fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {

View File

@ -141,7 +141,7 @@ module.exports = async function (provider) {
anchor.setProvider(provider);
// Add your deploy script here.
}
};
"#
}
@ -157,7 +157,7 @@ module.exports = async function (provider) {
anchor.setProvider(provider);
// Add your deploy script here.
}
};
"#
}
@ -191,14 +191,13 @@ pub struct Initialize {{}}
pub fn mocha(name: &str) -> String {
format!(
r#"const anchor = require('@project-serum/anchor');
describe('{}', () => {{
r#"const anchor = require("@project-serum/anchor");
describe("{}", () => {{
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
it('Is initialized!', async () => {{
it("Is initialized!", async () => {{
// Add your test here.
const program = anchor.workspace.{};
const tx = await program.rpc.initialize();
@ -248,18 +247,17 @@ pub fn ts_package_json() -> String {
pub fn ts_mocha(name: &str) -> String {
format!(
r#"import * as anchor from '@project-serum/anchor';
import {{ Program }} from '@project-serum/anchor';
import {{ {} }} from '../target/types/{}';
describe('{}', () => {{
r#"import * as anchor from "@project-serum/anchor";
import {{ Program }} from "@project-serum/anchor";
import {{ {} }} from "../target/types/{}";
describe("{}", () => {{
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
const program = anchor.workspace.{} as Program<{}>;
it('Is initialized!', async () => {{
it("Is initialized!", async () => {{
// Add your test here.
const tx = await program.rpc.initialize({{}});
console.log("Your transaction signature", tx);

View File

@ -10,6 +10,7 @@ pub const ERROR_CODE_OFFSET: u32 = 6000;
/// - &gt;= 2000 constraint error codes
/// - &gt;= 3000 account error codes
/// - = 4000 state error code
/// - &gt;= 4100 misc error codes
/// - = 5000 deprecated error code
///
/// The starting point for user-defined errors is defined
@ -154,6 +155,11 @@ pub enum ErrorCode {
#[msg("The given state account does not have the correct address")]
StateInvalidAddress = 4000,
// Miscellaneous
/// 4100 - The declared program id does not match actual program id
#[msg("The declared program id does not match the actual program id")]
DeclaredProgramIdMismatch = 4100,
// Deprecated
/// 5000 - The API being used is deprecated and should no longer be used
#[msg("The API being used is deprecated and should no longer be used")]

View File

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

View File

@ -56,6 +56,9 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
{
msg!("anchor-debug is active");
}
if *program_id != ID {
return Err(anchor_lang::error::ErrorCode::DeclaredProgramIdMismatch.into());
}
if data.len() < 8 {
return #fallback_maybe
}

View File

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

View File

@ -178,8 +178,10 @@ pub enum IdlType {
I16,
U32,
I32,
F32,
U64,
I64,
F64,
U128,
I128,
Bytes,
@ -217,8 +219,10 @@ impl std::str::FromStr for IdlType {
"i16" => IdlType::I16,
"u32" => IdlType::U32,
"i32" => IdlType::I32,
"f32" => IdlType::F32,
"u64" => IdlType::U64,
"i64" => IdlType::I64,
"f64" => IdlType::F64,
"u128" => IdlType::U128,
"i128" => IdlType::I128,
"Vec<u8>" => IdlType::Bytes,

View File

@ -1,6 +1,6 @@
use anyhow::anyhow;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use syn::parse::{Error as ParseError, Result as ParseResult};
/// Crate parse context
@ -40,6 +40,41 @@ impl CrateContext {
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
@ -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> {
self.items.iter().filter_map(|i| match i {
syn::Item::Enum(item) => Some(item),

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[programs.localnet]
declare_id = "FJcF5c8HncdfAgjPjTH49GAEypkJCG2ZADh2xhduNi5B"
[scripts]
test = "yarn run mocha -t 1000000 tests/"

View File

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

View File

@ -0,0 +1,19 @@
{
"name": "declare-id",
"version": "0.21.0",
"license": "(MIT OR Apache-2.0)",
"homepage": "https://github.com/project-serum/anchor#readme",
"bugs": {
"url": "https://github.com/project-serum/anchor/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/project-serum/anchor.git"
},
"engines": {
"node": ">=11"
},
"scripts": {
"test": "anchor test"
}
}

View File

@ -0,0 +1,16 @@
[package]
name = "declare-id"
version = "0.1.0"
description = "Created with Anchor"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "declare_id"
[features]
no-entrypoint = []
cpi = ["no-entrypoint"]
[dependencies]
anchor-lang = { path = "../../../../lang" }

View File

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

View File

@ -0,0 +1,18 @@
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<Initialize>) -> ProgramResult {
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {
}

View File

@ -0,0 +1,17 @@
const anchor = require("@project-serum/anchor");
const splToken = require("@solana/spl-token");
const assert = require("assert");
describe("declare_id", () => {
anchor.setProvider(anchor.Provider.local());
const program = anchor.workspace.DeclareId;
it("throws error!", async () => {
try {
await program.rpc.initialize();
assert.ok(false);
} catch (err) {
assert.equal(err.code, 4100);
}
});
});

View File

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

View File

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

12
tests/floats/Anchor.toml Normal file
View File

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

4
tests/floats/Cargo.toml Normal file
View File

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

View File

@ -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
tests/floats/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "floats",
"version": "0.21.0",
"license": "(MIT OR Apache-2.0)",
"homepage": "https://github.com/project-serum/anchor#readme",
"bugs": {
"url": "https://github.com/project-serum/anchor/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/project-serum/anchor.git"
},
"engines": {
"node": ">=11"
},
"scripts": {
"test": "anchor test"
}
}

View File

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

View File

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

View File

@ -0,0 +1,51 @@
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod floats {
use super::*;
pub fn create(ctx: Context<Create>, data_f32: f32, data_f64: f64) -> ProgramResult {
let account = &mut ctx.accounts.account;
let authority = &mut ctx.accounts.authority;
account.data_f32 = data_f32;
account.data_f64 = data_f64;
account.authority = authority.key();
Ok(())
}
pub fn update(ctx: Context<Update>, data_f32: f32, data_f64: f64) -> ProgramResult {
let account = &mut ctx.accounts.account;
account.data_f32 = data_f32;
account.data_f64 = data_f64;
Ok(())
}
}
#[derive(Accounts)]
pub struct Create<'info> {
#[account(init, payer = authority, space = 8 + 8 + 4 + 32)]
pub account: Account<'info, FloatDataAccount>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut, has_one = authority)]
pub account: Account<'info, FloatDataAccount>,
pub authority: Signer<'info>,
}
#[account]
pub struct FloatDataAccount {
pub data_f64: f64,
pub data_f32: f32,
pub authority: Pubkey,
}

View File

@ -0,0 +1,67 @@
import * as anchor from "@project-serum/anchor";
import { Program, getProvider } from "@project-serum/anchor";
import { Keypair, SystemProgram } from "@solana/web3.js";
import { Floats } from "../target/types/floats";
import assert from "assert";
describe("floats", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
const program = anchor.workspace.Floats as Program<Floats>;
it("Creates an account with float data", async () => {
const accountKeypair = Keypair.generate();
await program.methods
.create(1.0, 2.0)
.accounts({
account: accountKeypair.publicKey,
authority: getProvider().wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.signers([accountKeypair])
.rpc();
const account = await program.account.floatDataAccount.fetch(
accountKeypair.publicKey
);
assert.strictEqual(account.dataF32, 1.0);
assert.strictEqual(account.dataF64, 2.0);
});
it("Updates an account with float data", async () => {
const accountKeypair = Keypair.generate();
const authorityPublicKey = getProvider().wallet.publicKey;
await program.methods
.create(1.0, 2.0)
.accounts({
account: accountKeypair.publicKey,
authority: authorityPublicKey,
systemProgram: SystemProgram.programId,
})
.signers([accountKeypair])
.rpc();
let account = await program.account.floatDataAccount.fetch(
accountKeypair.publicKey
);
await program.methods
.update(3.0, 4.0)
.accounts({
account: accountKeypair.publicKey,
authority: authorityPublicKey,
})
.rpc();
account = await program.account.floatDataAccount.fetch(
accountKeypair.publicKey
);
assert.strictEqual(account.dataF32, 3.0);
assert.strictEqual(account.dataF64, 4.0);
});
});

View File

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

View File

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

View File

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

View File

@ -19,11 +19,11 @@ pub mod counter_auth {
fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> ProgramResult {
if current % 2 == 0 {
if new % 2 == 0 {
return Err(ProgramError::Custom(50)); // Arbitrary error code.
return Err(ProgramError::Custom(15000)); // Arbitrary error code.
}
} else {
if new % 2 == 1 {
return Err(ProgramError::Custom(60)); // Arbitrary error code.
return Err(ProgramError::Custom(16000)); // Arbitrary error code.
}
}
Ok(())

View File

@ -25,7 +25,7 @@ describe("interface", () => {
});
},
(err) => {
if (err.toString().split("custom program error: 0x32").length !== 2) {
if (err.toString().split("custom program error: 0x3a98").length !== 2) {
return false;
}
return true;
@ -33,7 +33,7 @@ describe("interface", () => {
);
});
it("Shold succeed to go from even to odd", async () => {
it("Should succeed to go from even to odd", async () => {
await counter.state.rpc.setCount(new anchor.BN(3), {
accounts: {
authProgram: counterAuth.programId,

View File

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

View File

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

View File

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

View File

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

View File

@ -14,6 +14,7 @@
"errors",
"escrow",
"events",
"floats",
"ido-pool",
"interface",
"lockup",
@ -28,7 +29,8 @@
"sysvars",
"tictactoe",
"typescript",
"zero-copy"
"zero-copy",
"declare-id"
],
"dependencies": {
"@project-serum/anchor": "^0.21.0",

View File

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

View File

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

6
tests/safety-checks/.gitignore vendored Normal file
View File

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

View File

@ -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"

View File

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

View File

@ -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.
};

View File

@ -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"

View File

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

View File

@ -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>,
}

View File

@ -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" }

View File

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

View File

@ -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
tests/safety-checks/test.sh Executable file
View File

@ -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."

View File

@ -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);
});
});

View File

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

View File

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

View File

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

View File

@ -50,18 +50,19 @@
snake-case "^3.0.4"
toml "^3.0.0"
"@project-serum/anchor@^0.20.0":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.20.0.tgz#547f5c0ff7e66809fa7118b2e3abd8087b5ec519"
integrity sha512-p1KOiqGBIbNsopMrSVoPwgxR1iPffsdjMNCOysahTPL9whX2CLX9HQCdopHjYaGl7+SdHRuXml6Wahk/wUmC8g==
"@project-serum/anchor@^0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.21.0.tgz#ad5fb33744991ec1900cdb2fd22707c908b12b5f"
integrity sha512-flRuW/F+iC8mitNokx82LOXyND7Dyk6n5UUPJpQv/+NfySFrNFlzuQZaBZJ4CG5g9s8HS/uaaIz1nVkDR8V/QA==
dependencies:
"@project-serum/borsh" "^0.2.2"
"@project-serum/borsh" "^0.2.4"
"@solana/web3.js" "^1.17.0"
base64-js "^1.5.1"
bn.js "^5.1.2"
bs58 "^4.0.1"
buffer-layout "^1.2.2"
camelcase "^5.3.1"
cross-fetch "^3.1.5"
crypto-hash "^1.3.0"
eventemitter3 "^4.0.7"
find "^0.3.0"
@ -78,6 +79,14 @@
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@project-serum/borsh@^0.2.4":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.5.tgz#6059287aa624ecebbfc0edd35e4c28ff987d8663"
integrity sha512-UmeUkUoKdQ7rhx6Leve1SssMR/Ghv8qrEiyywyxSWg7ooV7StdpPBhciiy5eB3T0qU1BXvdRNC8TdrkxK7WC5Q==
dependencies:
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@project-serum/common@^0.0.1-beta.3":
version "0.0.1-beta.3"
resolved "https://registry.yarnpkg.com/@project-serum/common/-/common-0.0.1-beta.3.tgz#53586eaff9d9fd7e8938b1e12080c935b8b6ad07"
@ -455,6 +464,13 @@ cross-fetch@^3.1.4:
dependencies:
node-fetch "2.6.1"
cross-fetch@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
dependencies:
node-fetch "2.6.7"
crypto-hash@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247"
@ -906,6 +922,13 @@ node-fetch@2.6.1:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-gyp-build@^4.2.0, node-gyp-build@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
@ -1127,6 +1150,11 @@ toml@^3.0.0:
resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee"
integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
traverse-chain@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1"
@ -1202,6 +1230,19 @@ uuid@^8.3.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
which@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"

View File

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

View File

@ -33,7 +33,7 @@
"test": "jest tests --detectOpenHandles"
},
"dependencies": {
"@project-serum/borsh": "^0.2.4",
"@project-serum/borsh": "^0.2.5",
"@solana/web3.js": "^1.17.0",
"base64-js": "^1.5.1",
"bn.js": "^5.1.2",

View File

@ -33,12 +33,18 @@ export class IdlCoder {
case "i32": {
return borsh.i32(fieldName);
}
case "f32": {
return borsh.f32(fieldName);
}
case "u64": {
return borsh.u64(fieldName);
}
case "i64": {
return borsh.i64(fieldName);
}
case "f64": {
return borsh.f64(fieldName);
}
case "u128": {
return borsh.u128(fieldName);
}

View File

@ -46,10 +46,14 @@ function typeSize(idl: Idl, ty: IdlType): number {
return 4;
case "i32":
return 4;
case "f32":
return 4;
case "u64":
return 8;
case "i64":
return 8;
case "f64":
return 8;
case "u128":
return 16;
case "i128":

View File

@ -102,6 +102,9 @@ const LangErrorCode = {
// State.
StateInvalidAddress: 4000,
// Miscellaneous
DeclaredProgramIdMismatch: 4100,
// Used for APIs that shouldn't be used anymore.
Deprecated: 5000,
};
@ -219,7 +222,13 @@ const LangErrorMessage = new Map([
"The given state account does not have the correct address",
],
// Misc.
// Miscellaneous
[
LangErrorCode.DeclaredProgramIdMismatch,
"The declared program id does not match the actual program id",
],
// Deprecated
[
LangErrorCode.Deprecated,
"The API being used is deprecated and should no longer be used",

View File

@ -102,8 +102,10 @@ export type IdlType =
| "i16"
| "u32"
| "i32"
| "f32"
| "u64"
| "i64"
| "f64"
| "u128"
| "i128"
| "bytes"

View File

@ -94,7 +94,7 @@ type TypeMap = {
bool: boolean;
string: string;
} & {
[K in "u8" | "i8" | "u16" | "i16" | "u32" | "i32"]: number;
[K in "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "f32" | "f64"]: number;
} &
{
[K in "u64" | "i64" | "u128" | "i128"]: BN;

View File

@ -544,7 +544,7 @@ export type SplToken = {
];
accounts: [
{
name: "Mint";
name: "mint";
type: {
kind: "struct";
fields: [
@ -576,7 +576,7 @@ export type SplToken = {
};
},
{
name: "Token";
name: "token";
type: {
kind: "struct";
fields: [
@ -1150,7 +1150,7 @@ export const IDL: SplToken = {
],
accounts: [
{
name: "Mint",
name: "mint",
type: {
kind: "struct",
fields: [
@ -1182,7 +1182,7 @@ export const IDL: SplToken = {
},
},
{
name: "Token",
name: "token",
type: {
kind: "struct",
fields: [

View File

@ -855,10 +855,10 @@
"@nodelib/fs.scandir" "2.1.4"
fastq "^1.6.0"
"@project-serum/borsh@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.4.tgz#8884c3a759984a39d54bf5b7390bd1ee0b579f16"
integrity sha512-tQPc1ktAp1Jtn9D72DmObAfhAic9ivfYBOS5b+T4H7MvkQ84uML88LY1LfvGep30mCy+ua5rf+X9ocPfg6u9MA==
"@project-serum/borsh@^0.2.5":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.5.tgz#6059287aa624ecebbfc0edd35e4c28ff987d8663"
integrity sha512-UmeUkUoKdQ7rhx6Leve1SssMR/Ghv8qrEiyywyxSWg7ooV7StdpPBhciiy5eB3T0qU1BXvdRNC8TdrkxK7WC5Q==
dependencies:
bn.js "^5.1.2"
buffer-layout "^1.2.0"