Init
This commit is contained in:
commit
5ab454649c
|
@ -0,0 +1,5 @@
|
|||
node_modules/
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
**/*.rs.bk
|
|
@ -0,0 +1,9 @@
|
|||
[registry]
|
||||
url = "https://anchor.projectserum.com"
|
||||
|
||||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "/home/armaniferrante/.config/solana/id.json"
|
||||
|
||||
[scripts]
|
||||
test = "ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,4 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
// 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.
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@project-serum/anchor": "^0.13.2",
|
||||
"bn.js": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^9.0.0",
|
||||
"chai": "^4.3.4",
|
||||
"mocha": "^9.0.3",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typescript": "^4.3.5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "assert-balances"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "assert_balances"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = "0.13.2"
|
||||
anchor-spl = "0.13.2"
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,82 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{self, TokenAccount};
|
||||
|
||||
#[program]
|
||||
pub mod assert_balances {
|
||||
use super::*;
|
||||
|
||||
/// Asserts the balances are greater than or equal to the amounts given.
|
||||
///
|
||||
/// This can be used as an additional safety check by wallets and other apps
|
||||
/// to ensure the outcome of a given transaction is as expected. If a
|
||||
/// transaction ends up changing balances more than expected, then it will
|
||||
/// abort.
|
||||
///
|
||||
/// Each item in `expected_token_balances` corresponds with an account in
|
||||
/// `ctx.remaining_accounts`.
|
||||
pub fn assert(
|
||||
ctx: Context<AssertBalances>,
|
||||
expected_sol_balance: u64,
|
||||
expected_token_balances: Vec<u64>,
|
||||
) -> Result<()> {
|
||||
let mut expected_token_balances = expected_token_balances;
|
||||
let mut accs = ctx.remaining_accounts;
|
||||
|
||||
// There must be a token account for each balance given.
|
||||
if expected_token_balances.len() != accs.len() {
|
||||
return Err(ErrorCode::InvalidBalanceLen.into());
|
||||
}
|
||||
|
||||
// Assert SOL balance.
|
||||
if **ctx.accounts.user.lamports.borrow() < expected_sol_balance {
|
||||
return Err(ErrorCode::InvalidSolBalance.into());
|
||||
}
|
||||
|
||||
// Assert token balances.
|
||||
loop {
|
||||
// Have we iterated through all tokens?
|
||||
if accs.len() == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Deserialize the token account.
|
||||
let token = CpiAccount::<TokenAccount>::try_accounts(ctx.program_id, &mut accs, &[])?;
|
||||
|
||||
// Is it owned by the SPL token program.
|
||||
if token.to_account_info().owner != &token::ID {
|
||||
return Err(ErrorCode::InvalidOwner.into());
|
||||
}
|
||||
|
||||
// Is it owned by the user?
|
||||
if &token.owner != ctx.accounts.user.key {
|
||||
return Err(ErrorCode::InvalidOwner.into());
|
||||
}
|
||||
|
||||
// Is the post balance at least the amount expected?
|
||||
let balance = expected_token_balances.remove(0);
|
||||
if token.amount < balance {
|
||||
return Err(ErrorCode::InvalidTokenBalance.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct AssertBalances<'info> {
|
||||
#[account(signer)]
|
||||
user: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[error]
|
||||
pub enum ErrorCode {
|
||||
#[msg("Owner doesn't match the SPL token program")]
|
||||
InvalidOwner,
|
||||
#[msg("Authority doesn't match the user")]
|
||||
InvalidAuthority,
|
||||
#[msg("Accounts length must match the given instruction balance length.")]
|
||||
InvalidBalanceLen,
|
||||
#[msg("Unexpected SOL balance")]
|
||||
InvalidSolBalance,
|
||||
#[msg("Unexpected token balance")]
|
||||
InvalidTokenBalance,
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
import * as assert from 'assert';
|
||||
import * as anchor from '@project-serum/anchor';
|
||||
import { Token, TOKEN_PROGRAM_ID, Token as MintClient, Account as TokenAccount } from '@solana/spl-token';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
|
||||
describe('assert-balances', () => {
|
||||
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.AssertBalances;
|
||||
const mints: Array<MintAccounts> = [];
|
||||
let ctx;
|
||||
|
||||
it('BOILERPLATE: Setup token accounts', async () => {
|
||||
// Iniitialize each mint and token for the test suite.
|
||||
for (let k = 0; k < 5; k += 1) {
|
||||
// Create mint.
|
||||
const client = await Token.createMint(
|
||||
program.provider.connection,
|
||||
program.provider.wallet.payer,
|
||||
program.provider.wallet.publicKey,
|
||||
null,
|
||||
6,
|
||||
TOKEN_PROGRAM_ID
|
||||
);
|
||||
|
||||
// Create token account.
|
||||
const tokenAddress = await client.createAccount(program.provider.wallet.publicKey);
|
||||
|
||||
// Mint to the account.
|
||||
await client.mintTo(
|
||||
tokenAddress,
|
||||
program.provider.wallet.payer,
|
||||
[],
|
||||
1000000,
|
||||
);
|
||||
|
||||
// Get the account data.
|
||||
const tokenAccount = await client.getAccountInfo(tokenAddress);
|
||||
|
||||
// Save the mints.
|
||||
mints.push({
|
||||
client,
|
||||
tokenAddress,
|
||||
tokenAccount,
|
||||
});
|
||||
}
|
||||
|
||||
// Shared context for subsequent instructions.
|
||||
ctx = {
|
||||
accounts: {
|
||||
user: program.provider.wallet.publicKey,
|
||||
},
|
||||
remainingAccounts: mints.map(m => {
|
||||
return {
|
||||
pubkey: m.tokenAddress, isWritable: false, isSigner: false,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
it('Succeeds when balances are equal', async () => {
|
||||
const solBalance = new BN('499999999982490600');
|
||||
const tokenBalances = mints.map(mint => mint.tokenAccount.amount);
|
||||
await program.rpc.assert(solBalance, tokenBalances, ctx);
|
||||
});
|
||||
|
||||
it('Aborts balances when token accounts are not provided', async () => {
|
||||
const solBalance = new BN('499999999982490600');
|
||||
const tokenBalances = mints.map(mint => mint.tokenAccount.amount);
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await program.rpc.assert(solBalance, tokenBalances, {...ctx, remainingAccounts:[]});
|
||||
}, (err) => {
|
||||
// @ts-ignore
|
||||
assert.ok(err.code, 302);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('Fails to assert sol balance', async () => {
|
||||
const solBalance = new BN('499999999982490599');
|
||||
const tokenBalances = mints.map(mint => mint.tokenAccount.amount);
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await program.rpc.assert(solBalance, tokenBalances, ctx);
|
||||
}, (err) => {
|
||||
// @ts-ignore
|
||||
assert.ok(err.code, 303);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('Fails to assert token balance', async () => {
|
||||
const solBalance = new BN('399999999982490600');
|
||||
const tokenBalances = mints.map(mint => mint.tokenAccount.amount.addn(1));
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await program.rpc.assert(solBalance, tokenBalances, ctx);
|
||||
}, (err) => {
|
||||
// @ts-ignore
|
||||
assert.ok(err.code, 304);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
type MintAccounts = {
|
||||
client: MintClient;
|
||||
tokenAddress: PublicKey;
|
||||
tokenAccount: TokenAccount;
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"lib": ["es2015"],
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue