Remove tests. When BPF SDK: /home/clarkeni/.local/share/solana/install/releases/1.10.29/solana-release/bin/sdk/bpf
cargo-build-bpf child: rustup toolchain list -v cargo-build-bpf child: cargo +bpf build --target bpfel-unknown-unknown --release cargo-build-bpf child: /home/clarkeni/.local/share/solana/install/releases/1.10.29/solana-release/bin/sdk/bpf/scripts/strip.sh /home/clarkeni/code/mango-v4/target/bpfel-unknown-unknown/release/mango_v4.so /home/clarkeni/code/mango-v4/target/deploy/mango_v4.so cargo-build-bpf child: /home/clarkeni/.local/share/solana/install/releases/1.10.29/solana-release/bin/sdk/bpf/dependencies/bpf-tools/llvm/bin/llvm-readelf --dyn-symbols /home/clarkeni/code/mango-v4/target/deploy/mango_v4.so To deploy this program: $ solana program deploy /home/clarkeni/code/mango-v4/target/deploy/mango_v4.so The program address will default to this keypair (override with --program-id): /home/clarkeni/code/mango-v4/target/deploy/mango_v4-keypair.json is ran with anchor as a submodule it incorrectly tries to run all submodule tests as well
This commit is contained in:
parent
b52f236146
commit
9e546f52d9
|
@ -1,3 +0,0 @@
|
|||
**/target/types/*.ts
|
||||
cfo/deps/
|
||||
auction-house/deps/
|
|
@ -1,7 +0,0 @@
|
|||
# Tests
|
||||
|
||||
No program here is guaranteed to be safe or secure in any way. In most cases, they are
|
||||
simply just simple integration tests to illustrate some particular functionality
|
||||
of the framework. If used, one should audit any programs used and take full
|
||||
responsibility for the consequences that occur due to any outstanding bugs
|
||||
or security vulnerabilities that exist.
|
|
@ -1,16 +0,0 @@
|
|||
[features]
|
||||
seeds = false
|
||||
|
||||
[programs.localnet]
|
||||
idl_commands_one = "2uA3amp95zsEHUpo8qnLMhcFAUsiKVEcKHXS1JetFjU5"
|
||||
idl_commands_two = "DE4UbHnAcT6Kfh1fVTPRPwpiA3vipmQ4xR3gcLwX3wwS"
|
||||
|
||||
[registry]
|
||||
url = "https://anchor.projectserum.com"
|
||||
|
||||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "./keypairs/deployer-keypair.json"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
|
@ -1,7 +0,0 @@
|
|||
[profile.release]
|
||||
overflow-checks = true
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -1 +0,0 @@
|
|||
[164,152,29,200,163,125,1,132,86,139,100,111,64,167,29,222,179,216,47,111,217,86,2,178,218,42,72,47,0,153,173,236,13,61,210,33,74,223,99,110,68,220,145,123,246,228,31,59,49,171,192,81,98,88,48,234,139,75,103,103,37,74,182,34]
|
|
@ -1 +0,0 @@
|
|||
[94,216,182,223,16,41,49,136,185,3,66,213,109,246,240,174,89,197,124,116,127,169,30,24,121,73,68,181,56,13,95,164,28,56,29,156,175,177,255,55,3,211,19,248,245,194,166,177,104,58,249,124,97,88,18,11,252,61,240,174,10,218,5,202]
|
|
@ -1 +0,0 @@
|
|||
[97,131,36,142,66,37,137,233,196,84,65,128,4,46,96,251,52,1,254,169,72,193,88,186,252,19,204,232,29,55,2,86,181,164,185,63,247,168,27,24,156,83,149,184,17,138,206,99,52,0,13,42,107,119,126,35,251,52,134,231,178,93,7,29]
|
|
@ -1,12 +0,0 @@
|
|||
// 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.
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"name": "anchor-cli-idl",
|
||||
"version": "0.25.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./test.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"mocha": "^9.1.3"
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "idl-commands-one"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "idl_commands_one"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,15 +0,0 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("2uA3amp95zsEHUpo8qnLMhcFAUsiKVEcKHXS1JetFjU5");
|
||||
|
||||
#[program]
|
||||
pub mod idl_commands_one {
|
||||
use super::*;
|
||||
|
||||
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize {}
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "idl-commands-two"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "idl_commands_two"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,15 +0,0 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("DE4UbHnAcT6Kfh1fVTPRPwpiA3vipmQ4xR3gcLwX3wwS");
|
||||
|
||||
#[program]
|
||||
pub mod idl_commands_two {
|
||||
use super::*;
|
||||
|
||||
pub fn uninitialize(_ctx: Context<Initialize>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize {}
|
|
@ -1,26 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Write a keypair for program deploy
|
||||
mkdir -p target/deploy
|
||||
cp keypairs/idl_commands_one-keypair.json target/deploy
|
||||
|
||||
echo "Building programs"
|
||||
|
||||
anchor build
|
||||
|
||||
echo "Starting local validator for test"
|
||||
|
||||
solana-test-validator --reset \
|
||||
-q \
|
||||
--mint tgyXxAhCkpgtKCEi4W6xWJSzqwVGs3uk2RodbZP2J49 \
|
||||
--bpf-program 2uA3amp95zsEHUpo8qnLMhcFAUsiKVEcKHXS1JetFjU5 target/deploy/idl_commands_one.so \
|
||||
--bpf-program DE4UbHnAcT6Kfh1fVTPRPwpiA3vipmQ4xR3gcLwX3wwS target/deploy/idl_commands_one.so \
|
||||
&
|
||||
|
||||
sleep 10
|
||||
|
||||
echo "Running tests"
|
||||
|
||||
anchor test --skip-deploy --skip-local-validator
|
||||
|
||||
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
|
|
@ -1,65 +0,0 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { IdlCommandsOne } from "../target/types/idl_commands_one";
|
||||
import { IdlCommandsTwo } from "../target/types/idl_commands_two";
|
||||
import { assert } from "chai";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
describe("Test CLI IDL commands", () => {
|
||||
// Configure the client to use the local cluster.
|
||||
const provider = anchor.AnchorProvider.env();
|
||||
|
||||
anchor.setProvider(provider);
|
||||
|
||||
const programOne = anchor.workspace.IdlCommandsOne as Program<IdlCommandsOne>;
|
||||
const programTwo = anchor.workspace.IdlCommandsTwo as Program<IdlCommandsTwo>;
|
||||
|
||||
it("Can initialize IDL account", async () => {
|
||||
execSync(
|
||||
`anchor idl init --filepath target/idl/idl_commands_one.json ${programOne.programId}`,
|
||||
{ stdio: "inherit" }
|
||||
);
|
||||
});
|
||||
|
||||
it("Can fetch an IDL using the TypeScript client", async () => {
|
||||
const idl = await anchor.Program.fetchIdl(programOne.programId, provider);
|
||||
assert.deepEqual(idl, programOne.idl);
|
||||
});
|
||||
|
||||
it("Can fetch an IDL via the CLI", async () => {
|
||||
const idl = execSync(`anchor idl fetch ${programOne.programId}`).toString();
|
||||
assert.deepEqual(JSON.parse(idl), programOne.idl);
|
||||
});
|
||||
|
||||
it("Can write a new IDL using the upgrade command", async () => {
|
||||
// Upgrade the IDL of program one to the IDL of program two to test upgrade
|
||||
execSync(
|
||||
`anchor idl upgrade --filepath target/idl/idl_commands_two.json ${programOne.programId}`,
|
||||
{ stdio: "inherit" }
|
||||
);
|
||||
const idl = await anchor.Program.fetchIdl(programOne.programId, provider);
|
||||
assert.deepEqual(idl, programTwo.idl);
|
||||
});
|
||||
|
||||
it("Can write a new IDL using write-buffer and set-buffer", async () => {
|
||||
// "Upgrade" back to program one via write-buffer set-buffer
|
||||
let buffer = execSync(
|
||||
`anchor idl write-buffer --filepath target/idl/idl_commands_one.json ${programOne.programId}`
|
||||
).toString();
|
||||
buffer = buffer.replace("Idl buffer created: ", "").trim();
|
||||
execSync(
|
||||
`anchor idl set-buffer --buffer ${buffer} ${programOne.programId}`,
|
||||
{ stdio: "inherit" }
|
||||
);
|
||||
const idl = await anchor.Program.fetchIdl(programOne.programId, provider);
|
||||
assert.deepEqual(idl, programOne.idl);
|
||||
});
|
||||
|
||||
it("Can fetch an IDL authority via the CLI", async () => {
|
||||
const authority = execSync(`anchor idl authority ${programOne.programId}`)
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
assert.equal(authority, provider.wallet.publicKey.toString());
|
||||
});
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"lib": ["es2015"],
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 9b0c81e5972f2466d47c20f705fc5fd1ca16476c
|
|
@ -1 +0,0 @@
|
|||
yarn.lock
|
|
@ -1,12 +0,0 @@
|
|||
[programs.localnet]
|
||||
bpf_upgradeable_state = "Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e"
|
||||
|
||||
[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"
|
|
@ -1,4 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -1 +0,0 @@
|
|||
[114,99,192,17,48,208,90,184,231,46,220,91,47,115,132,253,218,163,228,101,8,121,220,138,41,140,176,127,254,91,51,28,176,244,174,182,223,57,57,125,117,201,31,213,9,39,207,212,100,173,88,252,61,235,89,156,53,86,4,90,16,251,191,219]
|
|
@ -1,12 +0,0 @@
|
|||
// 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.
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"name": "bpf-upgradeable-state",
|
||||
"version": "0.24.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11"
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
[86,234,116,86,82,140,116,250,254,32,75,217,35,39,9,238,39,98,242,254,25,216,201,66,1,239,93,12,81,19,34,108,219,67,158,98,245,234,81,126,228,157,205,206,130,5,14,54,1,21,88,246,128,124,240,93,157,49,102,19,253,19,205,178]
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "bpf-upgradeable-state"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.56"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "bpf_upgradeable_state"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,80 +0,0 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e");
|
||||
|
||||
#[program]
|
||||
pub mod bpf_upgradeable_state {
|
||||
use super::*;
|
||||
pub fn set_admin_settings(ctx: Context<SetAdminSettings>, admin_data: u64) -> Result<()> {
|
||||
match *ctx.accounts.program {
|
||||
UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
} => {
|
||||
if programdata_address != ctx.accounts.program_data.key() {
|
||||
return err!(CustomError::InvalidProgramDataAddress);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return err!(CustomError::AccountNotProgram);
|
||||
}
|
||||
};
|
||||
ctx.accounts.settings.admin_data = admin_data;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_admin_settings_use_program_state(
|
||||
ctx: Context<SetAdminSettingsUseProgramState>,
|
||||
admin_data: u64,
|
||||
) -> Result<()> {
|
||||
ctx.accounts.settings.admin_data = admin_data;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct Settings {
|
||||
admin_data: u64,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub const LEN: usize = 8;
|
||||
}
|
||||
|
||||
#[error_code]
|
||||
pub enum CustomError {
|
||||
InvalidProgramDataAddress,
|
||||
AccountNotProgram,
|
||||
AccountNotBpfUpgradableProgram,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SetAdminSettings<'info> {
|
||||
// In a real program, this should be a PDA,
|
||||
// so the authority cannot create multiple settings accounts.
|
||||
// Not done here for easier testing
|
||||
#[account(init, payer = authority, space = Settings::LEN + 8)]
|
||||
pub settings: Account<'info, Settings>,
|
||||
#[account(mut)]
|
||||
pub authority: Signer<'info>,
|
||||
#[account(address = crate::ID)]
|
||||
pub program: Account<'info, UpgradeableLoaderState>,
|
||||
#[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))]
|
||||
pub program_data: Account<'info, ProgramData>,
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SetAdminSettingsUseProgramState<'info> {
|
||||
// In a real program, this should be a PDA,
|
||||
// so the authority cannot create multiple settings accounts.
|
||||
// Not done here for easier testing
|
||||
#[account(init, payer = authority, space = Settings::LEN + 8)]
|
||||
pub settings: Account<'info, Settings>,
|
||||
#[account(mut)]
|
||||
pub authority: Signer<'info>,
|
||||
#[account(constraint = program.programdata_address()? == Some(program_data.key()))]
|
||||
pub program: Program<'info, crate::program::BpfUpgradeableState>,
|
||||
#[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))]
|
||||
pub program_data: Account<'info, ProgramData>,
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
import * as anchor from "@project-serum/anchor";
|
||||
import { AnchorError, Program } from "@project-serum/anchor";
|
||||
import { findProgramAddressSync } from "@project-serum/anchor/dist/cjs/utils/pubkey";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { assert } from "chai";
|
||||
import { BpfUpgradeableState } from "../target/types/bpf_upgradeable_state";
|
||||
|
||||
describe("bpf_upgradeable_state", () => {
|
||||
const provider = anchor.AnchorProvider.env();
|
||||
// Configure the client to use the local cluster.
|
||||
anchor.setProvider(provider);
|
||||
|
||||
const program = anchor.workspace
|
||||
.BpfUpgradeableState as Program<BpfUpgradeableState>;
|
||||
const programDataAddress = findProgramAddressSync(
|
||||
[program.programId.toBytes()],
|
||||
new anchor.web3.PublicKey("BPFLoaderUpgradeab1e11111111111111111111111")
|
||||
)[0];
|
||||
|
||||
it("Reads ProgramData and sets field", async () => {
|
||||
const settings = anchor.web3.Keypair.generate();
|
||||
const tx = await program.rpc.setAdminSettings(new anchor.BN(500), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
programData: programDataAddress,
|
||||
program: program.programId,
|
||||
settings: settings.publicKey,
|
||||
},
|
||||
signers: [settings],
|
||||
});
|
||||
assert.strictEqual(
|
||||
(
|
||||
await program.account.settings.fetch(settings.publicKey)
|
||||
).adminData.toNumber(),
|
||||
500
|
||||
);
|
||||
});
|
||||
|
||||
it("Reads ProgramData and sets field, uses program state", async () => {
|
||||
const settings = anchor.web3.Keypair.generate();
|
||||
const tx = await program.rpc.setAdminSettingsUseProgramState(
|
||||
new anchor.BN(500),
|
||||
{
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
programData: programDataAddress,
|
||||
program: program.programId,
|
||||
settings: settings.publicKey,
|
||||
},
|
||||
signers: [settings],
|
||||
}
|
||||
);
|
||||
assert.strictEqual(
|
||||
(
|
||||
await program.account.settings.fetch(settings.publicKey)
|
||||
).adminData.toNumber(),
|
||||
500
|
||||
);
|
||||
});
|
||||
|
||||
it("Validates constraint on ProgramData", async () => {
|
||||
const settings = anchor.web3.Keypair.generate();
|
||||
try {
|
||||
const authority = anchor.web3.Keypair.generate();
|
||||
await provider.connection.confirmTransaction(
|
||||
await provider.connection.requestAirdrop(
|
||||
authority.publicKey,
|
||||
10000000000
|
||||
),
|
||||
"confirmed"
|
||||
);
|
||||
await program.rpc.setAdminSettings(new anchor.BN(500), {
|
||||
accounts: {
|
||||
authority: authority.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
programData: programDataAddress,
|
||||
settings: settings.publicKey,
|
||||
program: program.programId,
|
||||
},
|
||||
signers: [settings, authority],
|
||||
});
|
||||
assert.ok(false);
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 2003);
|
||||
assert.strictEqual(
|
||||
err.error.errorMessage,
|
||||
"A raw constraint was violated"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Validates that account is ProgramData", async () => {
|
||||
const settings = anchor.web3.Keypair.generate();
|
||||
try {
|
||||
await program.rpc.setAdminSettings(new anchor.BN(500), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
programData: program.programId,
|
||||
settings: settings.publicKey,
|
||||
program: program.programId,
|
||||
},
|
||||
signers: [settings],
|
||||
});
|
||||
assert.ok(false);
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 3013);
|
||||
assert.strictEqual(
|
||||
err.error.errorMessage,
|
||||
"The given account is not a program data account"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Validates that account is owned by the upgradeable bpf loader", async () => {
|
||||
const settings = anchor.web3.Keypair.generate();
|
||||
try {
|
||||
await program.rpc.setAdminSettings(new anchor.BN(500), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
programData: provider.wallet.publicKey,
|
||||
settings: settings.publicKey,
|
||||
program: program.programId,
|
||||
},
|
||||
signers: [settings],
|
||||
});
|
||||
assert.ok(false);
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 3007);
|
||||
assert.strictEqual(
|
||||
err.error.errorMessage,
|
||||
"The given account is owned by a different program than expected"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Deserializes UpgradableLoaderState and validates that programData is the expected account", async () => {
|
||||
const secondProgramAddress = new PublicKey(
|
||||
"Fkv67TwmbakfZw2PoW57wYPbqNexAH6vuxpyT8vmrc3B"
|
||||
);
|
||||
const secondProgramProgramDataAddress = findProgramAddressSync(
|
||||
[secondProgramAddress.toBytes()],
|
||||
new anchor.web3.PublicKey("BPFLoaderUpgradeab1e11111111111111111111111")
|
||||
)[0];
|
||||
|
||||
const settings = anchor.web3.Keypair.generate();
|
||||
try {
|
||||
await program.rpc.setAdminSettings(new anchor.BN(500), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
programData: secondProgramProgramDataAddress,
|
||||
settings: settings.publicKey,
|
||||
program: program.programId,
|
||||
},
|
||||
signers: [settings],
|
||||
});
|
||||
assert.ok(false);
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 6000);
|
||||
}
|
||||
});
|
||||
|
||||
it("Deserializes Program and validates that programData is the expected account", async () => {
|
||||
const secondProgramAddress = new PublicKey(
|
||||
"Fkv67TwmbakfZw2PoW57wYPbqNexAH6vuxpyT8vmrc3B"
|
||||
);
|
||||
const secondProgramProgramDataAddress = findProgramAddressSync(
|
||||
[secondProgramAddress.toBytes()],
|
||||
new anchor.web3.PublicKey("BPFLoaderUpgradeab1e11111111111111111111111")
|
||||
)[0];
|
||||
|
||||
const settings = anchor.web3.Keypair.generate();
|
||||
try {
|
||||
await program.rpc.setAdminSettingsUseProgramState(new anchor.BN(500), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
programData: secondProgramProgramDataAddress,
|
||||
settings: settings.publicKey,
|
||||
program: program.programId,
|
||||
},
|
||||
signers: [settings],
|
||||
});
|
||||
assert.ok(false);
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 2003);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"lib": ["es2015"],
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.localnet]
|
||||
cashiers_check = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run mocha -t 1000000 tests/"
|
||||
|
||||
[features]
|
|
@ -1,4 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -1,12 +0,0 @@
|
|||
// 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.
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "cashiers-check",
|
||||
"version": "0.25.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "anchor test"
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "cashiers-check"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.56"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "cashiers_check"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
anchor-spl = { path = "../../../../spl" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,180 +0,0 @@
|
|||
//! A cashiers check example. The funds are immediately withdrawn from a user's
|
||||
//! account and sent to a program controlled `Check` account, where the funds
|
||||
//! reside until they are "cashed" by the intended recipient. The creator of
|
||||
//! the check can cancel the check at any time to get back the funds.
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{self, TokenAccount, Transfer};
|
||||
use std::convert::Into;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
#[program]
|
||||
pub mod cashiers_check {
|
||||
use super::*;
|
||||
|
||||
#[access_control(CreateCheck::accounts(&ctx, nonce))]
|
||||
pub fn create_check(
|
||||
ctx: Context<CreateCheck>,
|
||||
amount: u64,
|
||||
memo: Option<String>,
|
||||
nonce: u8,
|
||||
) -> Result<()> {
|
||||
// Transfer funds to the check.
|
||||
let cpi_accounts = Transfer {
|
||||
from: ctx.accounts.from.to_account_info().clone(),
|
||||
to: ctx.accounts.vault.to_account_info().clone(),
|
||||
authority: ctx.accounts.owner.clone(),
|
||||
};
|
||||
let cpi_program = ctx.accounts.token_program.clone();
|
||||
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
|
||||
token::transfer(cpi_ctx, amount)?;
|
||||
|
||||
// Print the check.
|
||||
let check = &mut ctx.accounts.check;
|
||||
check.amount = amount;
|
||||
check.from = *ctx.accounts.from.to_account_info().key;
|
||||
check.to = *ctx.accounts.to.to_account_info().key;
|
||||
check.vault = *ctx.accounts.vault.to_account_info().key;
|
||||
check.nonce = nonce;
|
||||
check.memo = memo;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[access_control(not_burned(&ctx.accounts.check))]
|
||||
pub fn cash_check(ctx: Context<CashCheck>) -> Result<()> {
|
||||
let seeds = &[
|
||||
ctx.accounts.check.to_account_info().key.as_ref(),
|
||||
&[ctx.accounts.check.nonce],
|
||||
];
|
||||
let signer = &[&seeds[..]];
|
||||
let cpi_accounts = Transfer {
|
||||
from: ctx.accounts.vault.to_account_info().clone(),
|
||||
to: ctx.accounts.to.to_account_info().clone(),
|
||||
authority: ctx.accounts.check_signer.clone(),
|
||||
};
|
||||
let cpi_program = ctx.accounts.token_program.clone();
|
||||
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
|
||||
token::transfer(cpi_ctx, ctx.accounts.check.amount)?;
|
||||
// Burn the check for one time use.
|
||||
ctx.accounts.check.burned = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[access_control(not_burned(&ctx.accounts.check))]
|
||||
pub fn cancel_check(ctx: Context<CancelCheck>) -> Result<()> {
|
||||
let seeds = &[
|
||||
ctx.accounts.check.to_account_info().key.as_ref(),
|
||||
&[ctx.accounts.check.nonce],
|
||||
];
|
||||
let signer = &[&seeds[..]];
|
||||
let cpi_accounts = Transfer {
|
||||
from: ctx.accounts.vault.to_account_info().clone(),
|
||||
to: ctx.accounts.from.to_account_info().clone(),
|
||||
authority: ctx.accounts.check_signer.clone(),
|
||||
};
|
||||
let cpi_program = ctx.accounts.token_program.clone();
|
||||
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
|
||||
token::transfer(cpi_ctx, ctx.accounts.check.amount)?;
|
||||
ctx.accounts.check.burned = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CreateCheck<'info> {
|
||||
// Check being created.
|
||||
#[account(zero)]
|
||||
check: Account<'info, Check>,
|
||||
// Check's token vault.
|
||||
#[account(mut, constraint = &vault.owner == check_signer.key)]
|
||||
vault: Account<'info, TokenAccount>,
|
||||
// Program derived address for the check.
|
||||
check_signer: AccountInfo<'info>,
|
||||
// Token account the check is made from.
|
||||
#[account(mut, has_one = owner)]
|
||||
from: Account<'info, TokenAccount>,
|
||||
// Token account the check is made to.
|
||||
#[account(constraint = from.mint == to.mint)]
|
||||
to: Account<'info, TokenAccount>,
|
||||
// Owner of the `from` token account.
|
||||
owner: AccountInfo<'info>,
|
||||
token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
impl<'info> CreateCheck<'info> {
|
||||
pub fn accounts(ctx: &Context<CreateCheck>, nonce: u8) -> Result<()> {
|
||||
let signer = Pubkey::create_program_address(
|
||||
&[ctx.accounts.check.to_account_info().key.as_ref(), &[nonce]],
|
||||
ctx.program_id,
|
||||
)
|
||||
.map_err(|_| error!(ErrorCode::InvalidCheckNonce))?;
|
||||
if &signer != ctx.accounts.check_signer.to_account_info().key {
|
||||
return err!(ErrorCode::InvalidCheckSigner);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CashCheck<'info> {
|
||||
#[account(mut, has_one = vault, has_one = to)]
|
||||
check: Account<'info, Check>,
|
||||
#[account(mut)]
|
||||
vault: AccountInfo<'info>,
|
||||
#[account(
|
||||
seeds = [check.to_account_info().key.as_ref()],
|
||||
bump = check.nonce,
|
||||
)]
|
||||
check_signer: AccountInfo<'info>,
|
||||
#[account(mut, has_one = owner)]
|
||||
to: Account<'info, TokenAccount>,
|
||||
owner: Signer<'info>,
|
||||
token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CancelCheck<'info> {
|
||||
#[account(mut, has_one = vault, has_one = from)]
|
||||
check: Account<'info, Check>,
|
||||
#[account(mut)]
|
||||
vault: AccountInfo<'info>,
|
||||
#[account(
|
||||
seeds = [check.to_account_info().key.as_ref()],
|
||||
bump = check.nonce,
|
||||
)]
|
||||
check_signer: AccountInfo<'info>,
|
||||
#[account(mut, has_one = owner)]
|
||||
from: Account<'info, TokenAccount>,
|
||||
owner: Signer<'info>,
|
||||
token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct Check {
|
||||
from: Pubkey,
|
||||
to: Pubkey,
|
||||
amount: u64,
|
||||
memo: Option<String>,
|
||||
vault: Pubkey,
|
||||
nonce: u8,
|
||||
burned: bool,
|
||||
}
|
||||
|
||||
#[error_code]
|
||||
pub enum ErrorCode {
|
||||
#[msg("The given nonce does not create a valid program derived address.")]
|
||||
InvalidCheckNonce,
|
||||
#[msg("The derived check signer does not match that which was given.")]
|
||||
InvalidCheckSigner,
|
||||
#[msg("The given check has already been burned.")]
|
||||
AlreadyBurned,
|
||||
}
|
||||
|
||||
fn not_burned(check: &Check) -> Result<()> {
|
||||
if check.burned {
|
||||
return err!(ErrorCode::AlreadyBurned);
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
const anchor = require("@project-serum/anchor");
|
||||
const serumCmn = require("@project-serum/common");
|
||||
const { assert } = require("chai");
|
||||
const { TOKEN_PROGRAM_ID } = require("@solana/spl-token");
|
||||
|
||||
describe("cashiers-check", () => {
|
||||
// Configure the client to use the local cluster.
|
||||
const provider = anchor.AnchorProvider.env();
|
||||
// hack so we don't have to update serum-common library
|
||||
// to the new AnchorProvider class and Provider interface
|
||||
provider.send = provider.sendAndConfirm;
|
||||
anchor.setProvider(provider);
|
||||
|
||||
const program = anchor.workspace.CashiersCheck;
|
||||
|
||||
let mint = null;
|
||||
let god = null;
|
||||
let receiver = null;
|
||||
|
||||
it("Sets up initial test state", async () => {
|
||||
const [_mint, _god] = await serumCmn.createMintAndVault(
|
||||
program.provider,
|
||||
new anchor.BN(1000000)
|
||||
);
|
||||
mint = _mint;
|
||||
god = _god;
|
||||
|
||||
receiver = await serumCmn.createTokenAccount(
|
||||
program.provider,
|
||||
mint,
|
||||
program.provider.wallet.publicKey
|
||||
);
|
||||
});
|
||||
|
||||
const check = anchor.web3.Keypair.generate();
|
||||
const vault = anchor.web3.Keypair.generate();
|
||||
|
||||
let checkSigner = null;
|
||||
|
||||
it("Creates a check!", async () => {
|
||||
let [_checkSigner, nonce] = await anchor.web3.PublicKey.findProgramAddress(
|
||||
[check.publicKey.toBuffer()],
|
||||
program.programId
|
||||
);
|
||||
checkSigner = _checkSigner;
|
||||
|
||||
await program.rpc.createCheck(new anchor.BN(100), "Hello world", nonce, {
|
||||
accounts: {
|
||||
check: check.publicKey,
|
||||
vault: vault.publicKey,
|
||||
checkSigner,
|
||||
from: god,
|
||||
to: receiver,
|
||||
owner: program.provider.wallet.publicKey,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
signers: [check, vault],
|
||||
instructions: [
|
||||
await program.account.check.createInstruction(check, 300),
|
||||
...(await serumCmn.createTokenAccountInstrs(
|
||||
program.provider,
|
||||
vault.publicKey,
|
||||
mint,
|
||||
checkSigner
|
||||
)),
|
||||
],
|
||||
});
|
||||
|
||||
const checkAccount = await program.account.check.fetch(check.publicKey);
|
||||
assert.isTrue(checkAccount.from.equals(god));
|
||||
assert.isTrue(checkAccount.to.equals(receiver));
|
||||
assert.isTrue(checkAccount.amount.eq(new anchor.BN(100)));
|
||||
assert.strictEqual(checkAccount.memo, "Hello world");
|
||||
assert.isTrue(checkAccount.vault.equals(vault.publicKey));
|
||||
assert.strictEqual(checkAccount.nonce, nonce);
|
||||
assert.isFalse(checkAccount.burned);
|
||||
|
||||
let vaultAccount = await serumCmn.getTokenAccount(
|
||||
program.provider,
|
||||
checkAccount.vault
|
||||
);
|
||||
assert.isTrue(vaultAccount.amount.eq(new anchor.BN(100)));
|
||||
});
|
||||
|
||||
it("Cashes a check", async () => {
|
||||
await program.rpc.cashCheck({
|
||||
accounts: {
|
||||
check: check.publicKey,
|
||||
vault: vault.publicKey,
|
||||
checkSigner: checkSigner,
|
||||
to: receiver,
|
||||
owner: program.provider.wallet.publicKey,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const checkAccount = await program.account.check.fetch(check.publicKey);
|
||||
assert.isTrue(checkAccount.burned);
|
||||
|
||||
let vaultAccount = await serumCmn.getTokenAccount(
|
||||
program.provider,
|
||||
checkAccount.vault
|
||||
);
|
||||
assert.isTrue(vaultAccount.amount.eq(new anchor.BN(0)));
|
||||
|
||||
let receiverAccount = await serumCmn.getTokenAccount(
|
||||
program.provider,
|
||||
receiver
|
||||
);
|
||||
assert.isTrue(receiverAccount.amount.eq(new anchor.BN(100)));
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.localnet]
|
||||
cfo = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||
registry = { address = "GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv", idl = "./deps/stake/target/idl/registry.json" }
|
||||
lockup = { address = "6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks", idl = "./deps/stake/target/idl/lockup.json" }
|
||||
|
||||
[scripts]
|
||||
#
|
||||
# Testing.
|
||||
#
|
||||
test = "yarn run mocha -t 1000000 tests/"
|
||||
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 --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 --skip-lint && cd ../../"
|
||||
build-stake = "pushd deps/stake && anchor build --skip-lint && popd"
|
||||
#
|
||||
# Runs a localnet with all the programs deployed.
|
||||
#
|
||||
localnet = "./scripts/localnet.sh"
|
||||
|
||||
[[test.genesis]]
|
||||
address = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
|
||||
program = "./deps/serum-dex/dex/target/deploy/serum_dex.so"
|
||||
|
||||
[[test.genesis]]
|
||||
address = "22Y43yTVxuUkoRKdm9thyRhQ3SdgQS7c7kB6UNCiaczD"
|
||||
program = "./deps/swap/target/deploy/swap.so"
|
||||
|
||||
[[test.genesis]]
|
||||
address = "GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv"
|
||||
program = "./deps/stake/target/deploy/registry.so"
|
||||
|
||||
[[test.genesis]]
|
||||
address = "6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks"
|
||||
program = "./deps/stake/target/deploy/lockup.so"
|
||||
|
||||
[features]
|
|
@ -1,9 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
||||
exclude = [
|
||||
"deps/serum-dex",
|
||||
"deps/stake",
|
||||
"deps/swap"
|
||||
]
|
|
@ -1 +0,0 @@
|
|||
Subproject commit ed9d54a717bec01de2924f6e6ca465f942b072aa
|
|
@ -1 +0,0 @@
|
|||
Subproject commit fd78344ab5f34c36a91bdaf8b9edf2fbd8a93510
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 9c6bd407eb96ac77f4aef0d4387ca5b122f1c20c
|
|
@ -1,12 +0,0 @@
|
|||
// 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.
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "cfo",
|
||||
"version": "0.25.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "anchor run test-with-build"
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
[package]
|
||||
name = "cfo"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.56"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "cfo"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = ["test"]
|
||||
test = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
anchor-spl = { path = "../../../../spl" }
|
||||
spl-token = { version = "~3.3.0", features = ["no-entrypoint"] }
|
||||
swap = { path = "../../deps/swap/programs/swap", features = ["cpi"] }
|
||||
serum_dex = { path = "../../deps/serum-dex/dex", features = ["no-entrypoint"] }
|
||||
registry = { path = "../../deps/stake/programs/registry", features = ["cpi"] }
|
||||
lockup = { path = "../../deps/stake/programs/lockup", features = ["cpi"] }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,995 +0,0 @@
|
|||
// WIP. This program has been checkpointed and is not production ready.
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
|
||||
use anchor_spl::dex::{self, Dex};
|
||||
use anchor_spl::token::{self, Mint, Token, TokenAccount};
|
||||
use lockup::program::Lockup;
|
||||
use registry::program::Registry;
|
||||
use registry::{Registrar, RewardVendorKind};
|
||||
use serum_dex::state::OpenOrders;
|
||||
use std::convert::TryInto;
|
||||
use std::mem::size_of;
|
||||
use swap::program::Swap;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
/// CFO is the program representing the Serum chief financial officer. It is
|
||||
/// the program responsible for collecting and distributing fees from the Serum
|
||||
/// DEX.
|
||||
#[program]
|
||||
pub mod cfo {
|
||||
use super::*;
|
||||
|
||||
/// Creates a financial officer account associated with a DEX program ID.
|
||||
#[access_control(is_distribution_valid(&d))]
|
||||
pub fn create_officer(
|
||||
ctx: Context<CreateOfficer>,
|
||||
bumps: OfficerBumps,
|
||||
d: Distribution,
|
||||
registrar: Pubkey,
|
||||
msrm_registrar: Pubkey,
|
||||
) -> Result<()> {
|
||||
let officer = &mut ctx.accounts.officer;
|
||||
officer.authority = *ctx.accounts.authority.key;
|
||||
officer.swap_program = *ctx.accounts.swap_program.key;
|
||||
officer.dex_program = ctx.accounts.dex_program.key();
|
||||
officer.distribution = d;
|
||||
officer.registrar = registrar;
|
||||
officer.msrm_registrar = msrm_registrar;
|
||||
officer.stake = *ctx.accounts.stake.to_account_info().key;
|
||||
officer.treasury = *ctx.accounts.treasury.to_account_info().key;
|
||||
officer.srm_vault = *ctx.accounts.srm_vault.to_account_info().key;
|
||||
officer.bumps = bumps;
|
||||
emit!(OfficerDidCreate {
|
||||
pubkey: *officer.to_account_info().key,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a market authorization token.
|
||||
pub fn authorize_market(ctx: Context<AuthorizeMarket>, bump: u8) -> Result<()> {
|
||||
ctx.accounts.market_auth.bump = bump;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Revokes a market authorization token.
|
||||
pub fn revoke_market(_ctx: Context<RevokeMarket>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a deterministic token account owned by the CFO.
|
||||
/// This should be used when a new mint is used for collecting fees.
|
||||
/// Can only be called once per token CFO and token mint.
|
||||
pub fn create_officer_token(_ctx: Context<CreateOfficerToken>, _bump: u8) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates an open orders account for the given market.
|
||||
pub fn create_officer_open_orders(
|
||||
ctx: Context<CreateOfficerOpenOrders>,
|
||||
_bump: u8,
|
||||
) -> Result<()> {
|
||||
let seeds = [
|
||||
ctx.accounts.dex_program.key.as_ref(),
|
||||
&[ctx.accounts.officer.bumps.bump],
|
||||
];
|
||||
let cpi_ctx = CpiContext::from(&*ctx.accounts);
|
||||
dex::init_open_orders(cpi_ctx.with_signer(&[&seeds])).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Updates the cfo's fee distribution.
|
||||
#[access_control(is_distribution_valid(&d))]
|
||||
pub fn set_distribution(ctx: Context<SetDistribution>, d: Distribution) -> Result<()> {
|
||||
ctx.accounts.officer.distribution = d.clone();
|
||||
emit!(DistributionDidChange { distribution: d });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transfers fees from the dex to the CFO.
|
||||
pub fn sweep_fees<'info>(ctx: Context<'_, '_, '_, 'info, SweepFees<'info>>) -> Result<()> {
|
||||
let cpi_ctx = CpiContext::from(&*ctx.accounts);
|
||||
let seeds = [
|
||||
ctx.accounts.dex.dex_program.key.as_ref(),
|
||||
&[ctx.accounts.officer.bumps.bump],
|
||||
];
|
||||
dex::sweep_fees(cpi_ctx.with_signer(&[&seeds])).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Convert the CFO's entire non-SRM token balance into USDC.
|
||||
/// Assumes USDC is the quote currency.
|
||||
#[access_control(is_not_trading(&ctx.accounts.instructions))]
|
||||
pub fn swap_to_usdc<'info>(
|
||||
ctx: Context<'_, '_, '_, 'info, SwapToUsdc<'info>>,
|
||||
min_exchange_rate: ExchangeRate,
|
||||
) -> Result<()> {
|
||||
let seeds = [
|
||||
ctx.accounts.dex_program.key.as_ref(),
|
||||
&[ctx.accounts.officer.bumps.bump],
|
||||
];
|
||||
let cpi_ctx = CpiContext::from(&*ctx.accounts);
|
||||
swap::cpi::swap(
|
||||
cpi_ctx.with_signer(&[&seeds]),
|
||||
swap::Side::Ask,
|
||||
ctx.accounts.from_vault.amount,
|
||||
min_exchange_rate.into(),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Convert the CFO's entire token balance into SRM.
|
||||
/// Assumes SRM is the base currency.
|
||||
#[access_control(is_not_trading(&ctx.accounts.instructions))]
|
||||
pub fn swap_to_srm<'info>(
|
||||
ctx: Context<'_, '_, '_, 'info, SwapToSrm<'info>>,
|
||||
min_exchange_rate: ExchangeRate,
|
||||
) -> Result<()> {
|
||||
let seeds = [
|
||||
ctx.accounts.dex_program.key.as_ref(),
|
||||
&[ctx.accounts.officer.bumps.bump],
|
||||
];
|
||||
let cpi_ctx = CpiContext::from(&*ctx.accounts);
|
||||
swap::cpi::swap(
|
||||
cpi_ctx.with_signer(&[&seeds]),
|
||||
swap::Side::Bid,
|
||||
ctx.accounts.usdc_vault.amount,
|
||||
min_exchange_rate.into(),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Distributes srm tokens to the various categories. Before calling this,
|
||||
/// one must convert the fees into SRM via the swap APIs.
|
||||
#[access_control(is_distribution_ready(&ctx.accounts))]
|
||||
pub fn distribute<'info>(ctx: Context<'_, '_, '_, 'info, Distribute<'info>>) -> Result<()> {
|
||||
let total_fees = ctx.accounts.srm_vault.amount;
|
||||
let seeds = [
|
||||
ctx.accounts.dex_program.key.as_ref(),
|
||||
&[ctx.accounts.officer.bumps.bump],
|
||||
];
|
||||
|
||||
// Burn.
|
||||
let burn_amount: u64 = u128::from(total_fees)
|
||||
.checked_mul(ctx.accounts.officer.distribution.burn.into())
|
||||
.unwrap()
|
||||
.checked_div(100)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.map_err(|_| error!(ErrorCode::U128CannotConvert))?;
|
||||
token::burn(ctx.accounts.into_burn().with_signer(&[&seeds]), burn_amount)?;
|
||||
|
||||
// Stake.
|
||||
let stake_amount: u64 = u128::from(total_fees)
|
||||
.checked_mul(ctx.accounts.officer.distribution.stake.into())
|
||||
.unwrap()
|
||||
.checked_div(100)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.map_err(|_| error!(ErrorCode::U128CannotConvert))?;
|
||||
token::transfer(
|
||||
ctx.accounts.into_stake_transfer().with_signer(&[&seeds]),
|
||||
stake_amount,
|
||||
)?;
|
||||
|
||||
// Treasury.
|
||||
let treasury_amount: u64 = u128::from(total_fees)
|
||||
.checked_mul(ctx.accounts.officer.distribution.treasury.into())
|
||||
.unwrap()
|
||||
.checked_div(100)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.map_err(|_| error!(ErrorCode::U128CannotConvert))?;
|
||||
token::transfer(
|
||||
ctx.accounts.into_treasury_transfer().with_signer(&[&seeds]),
|
||||
treasury_amount,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[access_control(is_stake_reward_ready(&ctx.accounts))]
|
||||
pub fn drop_stake_reward<'info>(
|
||||
ctx: Context<'_, '_, '_, 'info, DropStakeReward<'info>>,
|
||||
) -> Result<()> {
|
||||
// Common reward parameters.
|
||||
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.25.0.
|
||||
let end_ts = 1822320000; // 9/30/2027.
|
||||
let period_count = 2191;
|
||||
RewardVendorKind::Locked {
|
||||
start_ts,
|
||||
end_ts,
|
||||
period_count,
|
||||
}
|
||||
};
|
||||
let seeds = [
|
||||
ctx.accounts.dex_program.key.as_ref(),
|
||||
&[ctx.accounts.officer.bumps.bump],
|
||||
];
|
||||
|
||||
// Total amount staked denominated in SRM (i.e. MSRM is converted to
|
||||
// SRM)
|
||||
let total_pool_value = u128::from(ctx.accounts.srm.pool_mint.supply)
|
||||
.checked_mul(500)
|
||||
.unwrap()
|
||||
.checked_add(
|
||||
u128::from(ctx.accounts.msrm.pool_mint.supply)
|
||||
.checked_mul(1_000_000)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Total reward split between both the SRM and MSRM stake pools.
|
||||
let total_reward_amount = u128::from(ctx.accounts.stake.amount);
|
||||
|
||||
// Proportion of the reward going to the srm pool.
|
||||
//
|
||||
// total_reward_amount * (srm_pool_value / total_pool_value)
|
||||
//
|
||||
let srm_amount: u64 = u128::from(ctx.accounts.srm.pool_mint.supply)
|
||||
.checked_mul(500)
|
||||
.unwrap()
|
||||
.checked_mul(total_reward_amount)
|
||||
.unwrap()
|
||||
.checked_div(total_pool_value)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.map_err(|_| error!(ErrorCode::U128CannotConvert))?;
|
||||
|
||||
// Proportion of the reward going to the msrm pool.
|
||||
//
|
||||
// total_reward_amount * (msrm_pool_value / total_pool_value)
|
||||
//
|
||||
let msrm_amount = u128::from(ctx.accounts.msrm.pool_mint.supply)
|
||||
.checked_mul(total_reward_amount)
|
||||
.unwrap()
|
||||
.checked_div(total_pool_value)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.map_err(|_| error!(ErrorCode::U128CannotConvert))?;
|
||||
|
||||
// SRM drop.
|
||||
{
|
||||
// Drop locked reward.
|
||||
let (_, nonce) = Pubkey::find_program_address(
|
||||
&[
|
||||
ctx.accounts.srm.registrar.to_account_info().key.as_ref(),
|
||||
ctx.accounts.srm.vendor.to_account_info().key.as_ref(),
|
||||
],
|
||||
ctx.accounts.token_program.key,
|
||||
);
|
||||
registry::cpi::drop_reward(
|
||||
ctx.accounts.into_srm_reward().with_signer(&[&seeds[..]]),
|
||||
locked_kind.clone(),
|
||||
srm_amount.try_into().unwrap(),
|
||||
expiry_ts,
|
||||
expiry_receiver,
|
||||
nonce,
|
||||
)?;
|
||||
|
||||
// Drop unlocked reward.
|
||||
registry::cpi::drop_reward(
|
||||
ctx.accounts.into_srm_reward().with_signer(&[&seeds[..]]),
|
||||
RewardVendorKind::Unlocked,
|
||||
srm_amount,
|
||||
expiry_ts,
|
||||
expiry_receiver,
|
||||
nonce,
|
||||
)?;
|
||||
}
|
||||
|
||||
// MSRM drop.
|
||||
{
|
||||
// Drop locked reward.
|
||||
let (_, nonce) = Pubkey::find_program_address(
|
||||
&[
|
||||
ctx.accounts.msrm.registrar.to_account_info().key.as_ref(),
|
||||
ctx.accounts.msrm.vendor.to_account_info().key.as_ref(),
|
||||
],
|
||||
ctx.accounts.token_program.key,
|
||||
);
|
||||
registry::cpi::drop_reward(
|
||||
ctx.accounts.into_msrm_reward().with_signer(&[&seeds[..]]),
|
||||
locked_kind,
|
||||
msrm_amount,
|
||||
expiry_ts,
|
||||
expiry_receiver,
|
||||
nonce,
|
||||
)?;
|
||||
|
||||
// Drop unlocked reward.
|
||||
registry::cpi::drop_reward(
|
||||
ctx.accounts.into_msrm_reward().with_signer(&[&seeds[..]]),
|
||||
RewardVendorKind::Unlocked,
|
||||
msrm_amount,
|
||||
expiry_ts,
|
||||
expiry_receiver,
|
||||
nonce,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Context accounts.
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(bumps: OfficerBumps)]
|
||||
pub struct CreateOfficer<'info> {
|
||||
#[account(
|
||||
init,
|
||||
seeds = [dex_program.key.as_ref()],
|
||||
bump,
|
||||
payer = authority,
|
||||
space = Officer::LEN + 8
|
||||
)]
|
||||
officer: Box<Account<'info, Officer>>,
|
||||
#[account(
|
||||
init,
|
||||
seeds = [b"token", officer.key().as_ref(), srm_mint.key().as_ref()],
|
||||
bump,
|
||||
payer = authority,
|
||||
token::mint = srm_mint,
|
||||
token::authority = officer
|
||||
)]
|
||||
srm_vault: Box<Account<'info, TokenAccount>>,
|
||||
#[account(
|
||||
init,
|
||||
seeds = [b"token", officer.key().as_ref(), usdc_mint.key().as_ref()],
|
||||
bump,
|
||||
payer = authority,
|
||||
token::mint = usdc_mint,
|
||||
token::authority = officer
|
||||
)]
|
||||
usdc_vault: Box<Account<'info, TokenAccount>>,
|
||||
#[account(
|
||||
init,
|
||||
seeds = [b"stake", officer.key().as_ref()],
|
||||
bump,
|
||||
payer = authority,
|
||||
token::mint = srm_mint,
|
||||
token::authority = officer
|
||||
)]
|
||||
stake: Box<Account<'info, TokenAccount>>,
|
||||
#[account(
|
||||
init,
|
||||
seeds = [b"treasury", officer.key().as_ref()],
|
||||
bump,
|
||||
payer = authority,
|
||||
token::mint = srm_mint,
|
||||
token::authority = officer
|
||||
)]
|
||||
treasury: Box<Account<'info, TokenAccount>>,
|
||||
#[account(mut)]
|
||||
authority: Signer<'info>,
|
||||
#[cfg_attr(
|
||||
not(feature = "test"),
|
||||
account(address = mint::SRM),
|
||||
)]
|
||||
srm_mint: Box<Account<'info, Mint>>,
|
||||
#[cfg_attr(
|
||||
not(feature = "test"),
|
||||
account(address = mint::USDC),
|
||||
)]
|
||||
usdc_mint: Box<Account<'info, Mint>>,
|
||||
dex_program: Program<'info, Dex>,
|
||||
swap_program: Program<'info, Swap>,
|
||||
system_program: Program<'info, System>,
|
||||
token_program: Program<'info, Token>,
|
||||
rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(bump: u8)]
|
||||
pub struct AuthorizeMarket<'info> {
|
||||
#[account(has_one = authority)]
|
||||
officer: Account<'info, Officer>,
|
||||
authority: Signer<'info>,
|
||||
#[account(
|
||||
init,
|
||||
payer = payer,
|
||||
seeds = [b"market-auth", officer.key().as_ref(), market.key.as_ref()],
|
||||
bump,
|
||||
space = MarketAuth::LEN + 8
|
||||
)]
|
||||
market_auth: Account<'info, MarketAuth>,
|
||||
#[account(mut)]
|
||||
payer: Signer<'info>,
|
||||
// Not read or written to so not validated.
|
||||
market: UncheckedAccount<'info>,
|
||||
system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct RevokeMarket<'info> {
|
||||
#[account(has_one = authority)]
|
||||
pub officer: Account<'info, Officer>,
|
||||
pub authority: Signer<'info>,
|
||||
#[account(mut, close = payer)]
|
||||
pub auth: Account<'info, MarketAuth>,
|
||||
pub payer: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(bump: u8)]
|
||||
pub struct CreateOfficerToken<'info> {
|
||||
officer: Account<'info, Officer>,
|
||||
#[account(
|
||||
init,
|
||||
seeds = [b"token", officer.key().as_ref(), mint.key().as_ref()],
|
||||
bump,
|
||||
token::mint = mint,
|
||||
token::authority = officer,
|
||||
payer = payer
|
||||
)]
|
||||
token: Account<'info, TokenAccount>,
|
||||
mint: Account<'info, Mint>,
|
||||
#[account(mut)]
|
||||
payer: Signer<'info>,
|
||||
system_program: Program<'info, System>,
|
||||
token_program: Program<'info, Token>,
|
||||
rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(bump: u8)]
|
||||
pub struct CreateOfficerOpenOrders<'info> {
|
||||
officer: Account<'info, Officer>,
|
||||
#[account(
|
||||
init,
|
||||
seeds = [b"open-orders", officer.key().as_ref(), market.key.as_ref()],
|
||||
bump,
|
||||
space = 12 + size_of::<OpenOrders>(),
|
||||
payer = payer,
|
||||
owner = dex::ID,
|
||||
)]
|
||||
open_orders: UncheckedAccount<'info>,
|
||||
#[account(mut)]
|
||||
payer: Signer<'info>,
|
||||
dex_program: Program<'info, Dex>,
|
||||
system_program: Program<'info, System>,
|
||||
rent: Sysvar<'info, Rent>,
|
||||
// Used for CPI. Not read or written so not validated.
|
||||
market: UncheckedAccount<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SetDistribution<'info> {
|
||||
#[account(has_one = authority)]
|
||||
officer: Account<'info, Officer>,
|
||||
authority: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SweepFees<'info> {
|
||||
#[account(
|
||||
seeds = [dex.dex_program.key.as_ref()],
|
||||
bump = officer.bumps.bump,
|
||||
)]
|
||||
officer: Account<'info, Officer>,
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [b"token", officer.key().as_ref(), mint.key().as_ref()],
|
||||
bump,
|
||||
)]
|
||||
sweep_vault: Account<'info, TokenAccount>,
|
||||
mint: Account<'info, Mint>,
|
||||
dex: DexAccounts<'info>,
|
||||
}
|
||||
|
||||
// DexAccounts are safe because they are used for CPI only.
|
||||
// They are not read or written and so are not validated.
|
||||
#[derive(Accounts)]
|
||||
pub struct DexAccounts<'info> {
|
||||
#[account(mut)]
|
||||
market: UncheckedAccount<'info>,
|
||||
#[account(mut)]
|
||||
pc_vault: UncheckedAccount<'info>,
|
||||
sweep_authority: UncheckedAccount<'info>,
|
||||
vault_signer: UncheckedAccount<'info>,
|
||||
dex_program: Program<'info, Dex>,
|
||||
token_program: Program<'info, Token>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SwapToUsdc<'info> {
|
||||
#[account(
|
||||
seeds = [dex_program.key.as_ref()],
|
||||
bump = officer.bumps.bump,
|
||||
)]
|
||||
officer: Box<Account<'info, Officer>>,
|
||||
market: DexMarketAccounts<'info>,
|
||||
#[account(
|
||||
seeds = [b"market-auth", officer.key().as_ref(), market.market.key.as_ref()],
|
||||
bump = market_auth.bump,
|
||||
)]
|
||||
market_auth: Account<'info, MarketAuth>,
|
||||
#[account(
|
||||
mut,
|
||||
constraint = &officer.treasury != &from_vault.key(),
|
||||
constraint = &officer.stake != &from_vault.key(),
|
||||
)]
|
||||
from_vault: Box<Account<'info, TokenAccount>>,
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [b"token", officer.key().as_ref(), usdc_mint.key().as_ref()],
|
||||
bump,
|
||||
)]
|
||||
usdc_vault: Box<Account<'info, TokenAccount>>,
|
||||
#[cfg_attr(not(feature = "test"), account(address = mint::USDC))]
|
||||
usdc_mint: Box<Account<'info, Mint>>,
|
||||
swap_program: Program<'info, Swap>,
|
||||
dex_program: Program<'info, Dex>,
|
||||
token_program: Program<'info, Token>,
|
||||
#[account(address = tx_instructions::ID)]
|
||||
instructions: UncheckedAccount<'info>,
|
||||
rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(bump: u8)]
|
||||
pub struct SwapToSrm<'info> {
|
||||
#[account(
|
||||
seeds = [dex_program.key.as_ref()],
|
||||
bump = officer.bumps.bump,
|
||||
)]
|
||||
officer: Box<Account<'info, Officer>>,
|
||||
market: DexMarketAccounts<'info>,
|
||||
#[account(
|
||||
seeds = [b"market-auth", officer.key().as_ref(), market.market.key.as_ref()],
|
||||
bump = market_auth.bump,
|
||||
)]
|
||||
market_auth: Account<'info, MarketAuth>,
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [b"token", officer.key().as_ref(), usdc_mint.key().as_ref()],
|
||||
bump,
|
||||
)]
|
||||
usdc_vault: Box<Account<'info, TokenAccount>>,
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [b"token", officer.key().as_ref(), srm_mint.key().as_ref()],
|
||||
bump,
|
||||
)]
|
||||
srm_vault: Box<Account<'info, TokenAccount>>,
|
||||
#[cfg_attr(not(feature = "test"), account(address = mint::SRM))]
|
||||
srm_mint: Box<Account<'info, Mint>>,
|
||||
#[cfg_attr(not(feature = "test"), account(address = mint::USDC))]
|
||||
usdc_mint: Box<Account<'info, Mint>>,
|
||||
swap_program: Program<'info, Swap>,
|
||||
dex_program: Program<'info, Dex>,
|
||||
token_program: Program<'info, Token>,
|
||||
#[account(address = tx_instructions::ID)]
|
||||
instructions: UncheckedAccount<'info>,
|
||||
rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
// Dex accounts are used for CPI only.
|
||||
// They are not read or written and so are not validated.
|
||||
#[derive(Accounts)]
|
||||
pub struct DexMarketAccounts<'info> {
|
||||
#[account(mut)]
|
||||
market: UncheckedAccount<'info>,
|
||||
#[account(mut)]
|
||||
open_orders: UncheckedAccount<'info>,
|
||||
#[account(mut)]
|
||||
request_queue: UncheckedAccount<'info>,
|
||||
#[account(mut)]
|
||||
event_queue: UncheckedAccount<'info>,
|
||||
#[account(mut)]
|
||||
bids: UncheckedAccount<'info>,
|
||||
#[account(mut)]
|
||||
asks: UncheckedAccount<'info>,
|
||||
// The `spl_token::Account` that funds will be taken from, i.e., transferred
|
||||
// from the user into the market's vault.
|
||||
//
|
||||
// For bids, this is the base currency. For asks, the quote.
|
||||
#[account(mut)]
|
||||
order_payer_token_account: UncheckedAccount<'info>,
|
||||
// Also known as the "base" currency. For a given A/B market,
|
||||
// this is the vault for the A mint.
|
||||
#[account(mut)]
|
||||
coin_vault: UncheckedAccount<'info>,
|
||||
// Also known as the "quote" currency. For a given A/B market,
|
||||
// this is the vault for the B mint.
|
||||
#[account(mut)]
|
||||
pc_vault: UncheckedAccount<'info>,
|
||||
// PDA owner of the DEX's token accounts for base + quote currencies.
|
||||
vault_signer: UncheckedAccount<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Distribute<'info> {
|
||||
#[account(
|
||||
has_one = srm_vault,
|
||||
has_one = treasury,
|
||||
has_one = stake,
|
||||
)]
|
||||
officer: Box<Account<'info, Officer>>,
|
||||
#[account(mut)]
|
||||
treasury: Box<Account<'info, TokenAccount>>,
|
||||
#[account(mut)]
|
||||
stake: Account<'info, TokenAccount>,
|
||||
#[account(mut)]
|
||||
srm_vault: Account<'info, TokenAccount>,
|
||||
#[account(mut)]
|
||||
srm_mint: Account<'info, Mint>,
|
||||
token_program: Program<'info, Token>,
|
||||
dex_program: Program<'info, Dex>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct DropStakeReward<'info> {
|
||||
#[account(
|
||||
has_one = stake,
|
||||
constraint = srm.registrar.key == &officer.registrar,
|
||||
constraint = msrm.registrar.key == &officer.msrm_registrar,
|
||||
)]
|
||||
officer: Box<Account<'info, Officer>>,
|
||||
#[account(
|
||||
seeds = [b"stake", officer.key().as_ref()],
|
||||
bump = officer.bumps.stake,
|
||||
)]
|
||||
stake: Box<Account<'info, TokenAccount>>,
|
||||
#[cfg_attr(
|
||||
not(feature = "test"),
|
||||
account(address = mint::SRM),
|
||||
)]
|
||||
mint: UncheckedAccount<'info>,
|
||||
srm: DropStakeRewardPool<'info>,
|
||||
msrm: DropStakeRewardPool<'info>,
|
||||
msrm_registrar: Box<Account<'info, Registrar>>,
|
||||
token_program: Program<'info, Token>,
|
||||
registry_program: Program<'info, Registry>,
|
||||
lockup_program: Program<'info, Lockup>,
|
||||
dex_program: Program<'info, Dex>,
|
||||
clock: Sysvar<'info, Clock>,
|
||||
rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
// Don't bother doing validation on the individual accounts. Allow the stake
|
||||
// program to handle it.
|
||||
#[derive(Accounts)]
|
||||
pub struct DropStakeRewardPool<'info> {
|
||||
registrar: UncheckedAccount<'info>,
|
||||
reward_event_q: UncheckedAccount<'info>,
|
||||
pool_mint: Account<'info, Mint>,
|
||||
vendor: UncheckedAccount<'info>,
|
||||
vendor_vault: UncheckedAccount<'info>,
|
||||
}
|
||||
|
||||
// Accounts.
|
||||
|
||||
/// Officer represents a deployed instance of the CFO mechanism. It is tied
|
||||
/// to a single deployment of the dex program.
|
||||
///
|
||||
/// PDA - [dex_program_id].
|
||||
#[account]
|
||||
pub struct Officer {
|
||||
// Priviledged account.
|
||||
pub authority: Pubkey, // 32
|
||||
// Vault holding the officer's SRM tokens prior to distribution.
|
||||
pub srm_vault: Pubkey, // 32
|
||||
// Escrow SRM vault holding tokens which are dropped onto stakers.
|
||||
pub stake: Pubkey, // 32
|
||||
// SRM token account to send treasury earned tokens to.
|
||||
pub treasury: Pubkey, // 32
|
||||
// Defines the fee distribution, i.e., what percent each fee category gets.
|
||||
pub distribution: Distribution, // Distribution::LEN
|
||||
// Swap frontend for the dex.
|
||||
pub swap_program: Pubkey, // 32
|
||||
// Dex program the officer is associated with.
|
||||
pub dex_program: Pubkey, // 32
|
||||
// SRM stake pool address
|
||||
pub registrar: Pubkey, // 32
|
||||
// MSRM stake pool address.
|
||||
pub msrm_registrar: Pubkey, // 32
|
||||
// Bump seeds for pdas.
|
||||
pub bumps: OfficerBumps, // OfficerBumps::LEN
|
||||
}
|
||||
|
||||
impl Officer {
|
||||
pub const LEN: usize = 8 * 32 + Distribution::LEN + OfficerBumps::LEN;
|
||||
}
|
||||
|
||||
/// MarketAuth represents an authorization token created by the Officer
|
||||
/// authority. This is used as an authorization token which allows one to
|
||||
/// permissionlessly invoke the swap instructions on the market. Without this
|
||||
/// one would be able to create their own market with prices unfavorable
|
||||
/// to the smart contract (and subsequently swap on it).
|
||||
///
|
||||
/// Because a PDA is used here, the account existing (without a tombstone) is
|
||||
/// proof of the validity of a given market, which means that anyone can use
|
||||
/// the vault here to swap.
|
||||
///
|
||||
/// PDA - [b"market-auth", officer, market_address]
|
||||
#[account]
|
||||
pub struct MarketAuth {
|
||||
// Bump seed for this account's PDA.
|
||||
pub bump: u8, // 1
|
||||
}
|
||||
|
||||
impl MarketAuth {
|
||||
pub const LEN: usize = 1;
|
||||
}
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
|
||||
pub struct OfficerBumps {
|
||||
pub bump: u8, // 1
|
||||
pub srm: u8, // 1
|
||||
pub usdc: u8, // 1
|
||||
pub stake: u8, // 1
|
||||
pub treasury: u8, // 1
|
||||
}
|
||||
|
||||
impl OfficerBumps {
|
||||
pub const LEN: usize = 5;
|
||||
}
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Default, Clone)]
|
||||
pub struct Distribution {
|
||||
burn: u8, // 1
|
||||
stake: u8, // 1
|
||||
treasury: u8, // 1
|
||||
}
|
||||
|
||||
impl Distribution {
|
||||
pub const LEN: usize = 3;
|
||||
}
|
||||
|
||||
// CpiContext transformations.
|
||||
|
||||
impl<'info> From<&CreateOfficerOpenOrders<'info>>
|
||||
for CpiContext<'_, '_, '_, 'info, dex::InitOpenOrders<'info>>
|
||||
{
|
||||
fn from(accs: &CreateOfficerOpenOrders<'info>) -> Self {
|
||||
let program = accs.dex_program.to_account_info();
|
||||
let accounts = dex::InitOpenOrders {
|
||||
open_orders: accs.open_orders.to_account_info(),
|
||||
authority: accs.officer.to_account_info(),
|
||||
market: accs.market.to_account_info(),
|
||||
rent: accs.rent.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program, accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> From<&SweepFees<'info>> for CpiContext<'_, '_, '_, 'info, dex::SweepFees<'info>> {
|
||||
fn from(sweep: &SweepFees<'info>) -> Self {
|
||||
let program = sweep.dex.dex_program.to_account_info();
|
||||
let accounts = dex::SweepFees {
|
||||
market: sweep.dex.market.to_account_info(),
|
||||
pc_vault: sweep.dex.pc_vault.to_account_info(),
|
||||
sweep_authority: sweep.dex.sweep_authority.to_account_info(),
|
||||
sweep_receiver: sweep.sweep_vault.to_account_info(),
|
||||
vault_signer: sweep.dex.vault_signer.to_account_info(),
|
||||
token_program: sweep.dex.token_program.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program.to_account_info(), accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> From<&SwapToSrm<'info>>
|
||||
for CpiContext<'_, '_, '_, 'info, swap::cpi::accounts::Swap<'info>>
|
||||
{
|
||||
fn from(accs: &SwapToSrm<'info>) -> Self {
|
||||
let program = accs.swap_program.to_account_info();
|
||||
let accounts = swap::cpi::accounts::Swap {
|
||||
market: swap::cpi::accounts::MarketAccounts {
|
||||
market: accs.market.market.to_account_info(),
|
||||
open_orders: accs.market.open_orders.to_account_info(),
|
||||
request_queue: accs.market.request_queue.to_account_info(),
|
||||
event_queue: accs.market.event_queue.to_account_info(),
|
||||
bids: accs.market.bids.to_account_info(),
|
||||
asks: accs.market.asks.to_account_info(),
|
||||
order_payer_token_account: accs.market.order_payer_token_account.to_account_info(),
|
||||
coin_vault: accs.market.coin_vault.to_account_info(),
|
||||
pc_vault: accs.market.pc_vault.to_account_info(),
|
||||
vault_signer: accs.market.vault_signer.to_account_info(),
|
||||
coin_wallet: accs.srm_vault.to_account_info(),
|
||||
},
|
||||
authority: accs.officer.to_account_info(),
|
||||
pc_wallet: accs.usdc_vault.to_account_info(),
|
||||
dex_program: accs.dex_program.to_account_info(),
|
||||
token_program: accs.token_program.to_account_info(),
|
||||
rent: accs.rent.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program.to_account_info(), accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> From<&SwapToUsdc<'info>>
|
||||
for CpiContext<'_, '_, '_, 'info, swap::cpi::accounts::Swap<'info>>
|
||||
{
|
||||
fn from(accs: &SwapToUsdc<'info>) -> Self {
|
||||
let program = accs.swap_program.to_account_info();
|
||||
let accounts = swap::cpi::accounts::Swap {
|
||||
market: swap::cpi::accounts::MarketAccounts {
|
||||
market: accs.market.market.to_account_info(),
|
||||
open_orders: accs.market.open_orders.to_account_info(),
|
||||
request_queue: accs.market.request_queue.to_account_info(),
|
||||
event_queue: accs.market.event_queue.to_account_info(),
|
||||
bids: accs.market.bids.to_account_info(),
|
||||
asks: accs.market.asks.to_account_info(),
|
||||
order_payer_token_account: accs.market.order_payer_token_account.to_account_info(),
|
||||
coin_vault: accs.market.coin_vault.to_account_info(),
|
||||
pc_vault: accs.market.pc_vault.to_account_info(),
|
||||
vault_signer: accs.market.vault_signer.to_account_info(),
|
||||
coin_wallet: accs.from_vault.to_account_info(),
|
||||
},
|
||||
authority: accs.officer.to_account_info(),
|
||||
pc_wallet: accs.usdc_vault.to_account_info(),
|
||||
dex_program: accs.dex_program.to_account_info(),
|
||||
token_program: accs.token_program.to_account_info(),
|
||||
rent: accs.rent.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program.to_account_info(), accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> Distribute<'info> {
|
||||
fn into_burn(&self) -> CpiContext<'_, '_, '_, 'info, token::Burn<'info>> {
|
||||
let program = self.token_program.to_account_info();
|
||||
let accounts = token::Burn {
|
||||
mint: self.srm_mint.to_account_info(),
|
||||
from: self.srm_vault.to_account_info(),
|
||||
authority: self.officer.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program, accounts)
|
||||
}
|
||||
|
||||
fn into_stake_transfer(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
|
||||
let program = self.token_program.to_account_info();
|
||||
let accounts = token::Transfer {
|
||||
from: self.srm_vault.to_account_info(),
|
||||
to: self.stake.to_account_info(),
|
||||
authority: self.officer.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program, accounts)
|
||||
}
|
||||
|
||||
fn into_treasury_transfer(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
|
||||
let program = self.token_program.to_account_info();
|
||||
let accounts = token::Transfer {
|
||||
from: self.srm_vault.to_account_info(),
|
||||
to: self.treasury.to_account_info(),
|
||||
authority: self.officer.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program, accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> DropStakeReward<'info> {
|
||||
fn into_srm_reward(
|
||||
&self,
|
||||
) -> CpiContext<'_, '_, '_, 'info, registry::cpi::accounts::DropReward<'info>> {
|
||||
let program = self.registry_program.clone();
|
||||
let accounts = registry::cpi::accounts::DropReward {
|
||||
registrar: self.srm.registrar.to_account_info(),
|
||||
reward_event_q: self.srm.reward_event_q.to_account_info(),
|
||||
pool_mint: self.srm.pool_mint.to_account_info(),
|
||||
vendor: self.srm.vendor.to_account_info(),
|
||||
vendor_vault: self.srm.vendor_vault.to_account_info(),
|
||||
depositor: self.stake.to_account_info(),
|
||||
depositor_authority: self.officer.to_account_info(),
|
||||
token_program: self.token_program.to_account_info(),
|
||||
clock: self.clock.to_account_info(),
|
||||
rent: self.rent.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program.to_account_info(), accounts)
|
||||
}
|
||||
|
||||
fn into_msrm_reward(
|
||||
&self,
|
||||
) -> CpiContext<'_, '_, '_, 'info, registry::cpi::accounts::DropReward<'info>> {
|
||||
let program = self.registry_program.clone();
|
||||
let accounts = registry::cpi::accounts::DropReward {
|
||||
registrar: self.msrm.registrar.to_account_info(),
|
||||
reward_event_q: self.msrm.reward_event_q.to_account_info(),
|
||||
pool_mint: self.msrm.pool_mint.to_account_info(),
|
||||
vendor: self.msrm.vendor.to_account_info(),
|
||||
vendor_vault: self.msrm.vendor_vault.to_account_info(),
|
||||
depositor: self.stake.to_account_info(),
|
||||
depositor_authority: self.officer.to_account_info(),
|
||||
token_program: self.token_program.to_account_info(),
|
||||
clock: self.clock.to_account_info(),
|
||||
rent: self.rent.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program.to_account_info(), accounts)
|
||||
}
|
||||
}
|
||||
|
||||
// Events.
|
||||
|
||||
#[event]
|
||||
pub struct DistributionDidChange {
|
||||
distribution: Distribution,
|
||||
}
|
||||
|
||||
#[event]
|
||||
pub struct OfficerDidCreate {
|
||||
pubkey: Pubkey,
|
||||
}
|
||||
|
||||
// Error.
|
||||
|
||||
#[error_code]
|
||||
pub enum ErrorCode {
|
||||
#[msg("Distribution does not add to 100")]
|
||||
InvalidDistribution,
|
||||
#[msg("u128 cannot be converted into u64")]
|
||||
U128CannotConvert,
|
||||
#[msg("Only one instruction is allowed for this transaction")]
|
||||
TooManyInstructions,
|
||||
#[msg("Not enough SRM has been accumulated to distribute")]
|
||||
InsufficientDistributionAmount,
|
||||
#[msg("Must drop more SRM onto the stake pool")]
|
||||
InsufficientStakeReward,
|
||||
}
|
||||
|
||||
// Access control.
|
||||
|
||||
fn is_distribution_valid(d: &Distribution) -> Result<()> {
|
||||
if d.burn + d.stake + d.treasury != 100 {
|
||||
return err!(ErrorCode::InvalidDistribution);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_distribution_ready(accounts: &Distribute) -> Result<()> {
|
||||
if accounts.srm_vault.amount < 1_000_000 {
|
||||
return err!(ErrorCode::InsufficientDistributionAmount);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// `ixs` must be the Instructions sysvar.
|
||||
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),
|
||||
Err(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Redefintions.
|
||||
//
|
||||
// The following types are redefined so that they can be parsed into the IDL,
|
||||
// since Anchor doesn't yet support idl parsing across multiple crates.
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct ExchangeRate {
|
||||
rate: u64,
|
||||
from_decimals: u8,
|
||||
quote_decimals: u8,
|
||||
strict: bool,
|
||||
}
|
||||
|
||||
impl From<ExchangeRate> for swap::ExchangeRate {
|
||||
fn from(e: ExchangeRate) -> Self {
|
||||
let ExchangeRate {
|
||||
rate,
|
||||
from_decimals,
|
||||
quote_decimals,
|
||||
strict,
|
||||
} = e;
|
||||
Self {
|
||||
rate,
|
||||
from_decimals,
|
||||
quote_decimals,
|
||||
strict,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
cleanup() {
|
||||
pkill -P $$ || true
|
||||
wait || true
|
||||
}
|
||||
|
||||
trap_add() {
|
||||
trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error"
|
||||
for trap_add_name in "$@"; do
|
||||
trap -- "$(
|
||||
extract_trap_cmd() { printf '%s\n' "${3:-}"; }
|
||||
eval "extract_trap_cmd $(trap -p "${trap_add_name}")"
|
||||
printf '%s\n' "${trap_add_cmd}"
|
||||
)" "${trap_add_name}" \
|
||||
|| fatal "unable to add to trap ${trap_add_name}"
|
||||
done
|
||||
}
|
||||
|
||||
declare -f -t trap_add
|
||||
trap_add 'cleanup' EXIT
|
|
@ -1,34 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const process = require("process");
|
||||
const fs = require("fs");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const { Market, OpenOrders } = require("@project-serum/serum");
|
||||
const Account = anchor.web3.Account;
|
||||
const Program = anchor.Program;
|
||||
const provider = anchor.AnchorProvider.local();
|
||||
const secret = JSON.parse(fs.readFileSync("./scripts/market-maker.json"));
|
||||
const MARKET_MAKER = new Account(secret);
|
||||
const PublicKey = anchor.web3.PublicKey;
|
||||
|
||||
const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
|
||||
|
||||
async function main() {
|
||||
const market = new PublicKey(process.argv[2]);
|
||||
while (true) {
|
||||
let marketClient = await Market.load(
|
||||
provider.connection,
|
||||
market,
|
||||
{ commitment: "processed" },
|
||||
DEX_PID
|
||||
);
|
||||
console.log("Fees: ", marketClient._decoded.quoteFeesAccrued.toString());
|
||||
await sleep(3000);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Script to list a market, logging the address to stdout.
|
||||
|
||||
const utils = require("../tests/utils");
|
||||
const fs = require("fs");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const provider = anchor.AnchorProvider.local();
|
||||
// hack so we don't have to update serum-common library
|
||||
// to the new AnchorProvider class and Provider interface
|
||||
provider.send = provider.sendAndConfirm;
|
||||
|
||||
async function main() {
|
||||
ORDERBOOK_ENV = await utils.initMarket({
|
||||
provider,
|
||||
});
|
||||
const out = {
|
||||
market: ORDERBOOK_ENV.marketA._decoded.ownAddress.toString(),
|
||||
};
|
||||
console.log(JSON.stringify(out));
|
||||
}
|
||||
|
||||
main();
|
|
@ -1,58 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source scripts/common.sh
|
||||
|
||||
DEX_PID="9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
|
||||
PAYER_FILEPATH="$HOME/.config/solana/id.json"
|
||||
CRANK="/home/armaniferrante/Documents/code/src/github.com/project-serum/serum-dex/target/debug/crank"
|
||||
VALIDATOR_OUT="./validator-stdout.txt"
|
||||
CRANK_LOGS="crank-logs.txt"
|
||||
CRANK_STDOUT="crank-stdout.txt"
|
||||
TRADE_BOT_STDOUT="trade-bot-stdout.txt"
|
||||
FEES_STDOUT="fees.txt"
|
||||
|
||||
main () {
|
||||
echo "Cleaning old output files..."
|
||||
rm -rf test-ledger
|
||||
rm -f $TRADE_BOT_STDOUT
|
||||
rm -f $FEES_STDOUT
|
||||
rm -f $VALIDATOR_OUT
|
||||
rm -f $CRANK_LOGS && touch $CRANK_LOGS
|
||||
|
||||
echo "Starting local network..."
|
||||
solana-test-validator \
|
||||
--bpf-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin ./deps/serum-dex/dex/target/deploy/serum_dex.so \
|
||||
--bpf-program 22Y43yTVxuUkoRKdm9thyRhQ3SdgQS7c7kB6UNCiaczD ./deps/swap/target/deploy/swap.so \
|
||||
--bpf-program GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv ./deps/stake/target/deploy/registry.so \
|
||||
--bpf-program 6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks ./deps/stake/target/deploy/lockup.so \
|
||||
--bpf-program 5CHQcwNhkFiFXXM8HakHi8cB7AKP3M3GPdEBDeRJBWQq ./target/deploy/cfo.so > $VALIDATOR_OUT &
|
||||
sleep 2
|
||||
|
||||
echo "Listing market..."
|
||||
market=$(./scripts/list-market.js | jq -r .market)
|
||||
sleep 2
|
||||
echo "Market listed $market"
|
||||
|
||||
echo "Running crank..."
|
||||
$CRANK localnet consume-events \
|
||||
-c $market \
|
||||
-d $DEX_PID -e 5 \
|
||||
--log-directory $CRANK_LOGS \
|
||||
--market $market \
|
||||
--num-workers 1 \
|
||||
--payer $PAYER_FILEPATH \
|
||||
--pc-wallet $market > $CRANK_STDOUT &
|
||||
echo "Running trade bot..."
|
||||
./scripts/trade-bot.js $market > $TRADE_BOT_STDOUT &
|
||||
|
||||
echo "Running fees listener..."
|
||||
./scripts/fees.js $market > $FEES_STDOUT &
|
||||
|
||||
echo "Localnet running..."
|
||||
echo "Ctl-c to exit."
|
||||
wait
|
||||
}
|
||||
|
||||
main
|
|
@ -1 +0,0 @@
|
|||
[13,174,53,150,78,228,12,98,170,254,212,211,125,193,2,241,97,137,49,209,189,199,27,215,220,65,57,203,215,93,105,203,217,32,5,194,157,118,162,47,102,126,235,65,99,80,56,231,217,114,25,225,239,140,169,92,150,146,211,218,183,139,9,104]
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Script to infinitely post orders that are immediately filled.
|
||||
|
||||
const process = require("process");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const PublicKey = anchor.web3.PublicKey;
|
||||
const { runTradeBot } = require("../tests/utils");
|
||||
|
||||
async function main() {
|
||||
const market = new PublicKey(process.argv[2]);
|
||||
const provider = anchor.AnchorProvider.local();
|
||||
runTradeBot(market, provider);
|
||||
}
|
||||
|
||||
main();
|
|
@ -1,515 +0,0 @@
|
|||
const { assert } = require("chai");
|
||||
const { Token } = require("@solana/spl-token");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const serumCmn = require("@project-serum/common");
|
||||
const { Market } = require("@project-serum/serum");
|
||||
const utf8 = anchor.utils.bytes.utf8;
|
||||
const { PublicKey, SystemProgram, Keypair, SYSVAR_RENT_PUBKEY } = anchor.web3;
|
||||
const utils = require("./utils");
|
||||
const { setupStakePool } = require("./utils/stake");
|
||||
|
||||
const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
|
||||
const SWAP_PID = new PublicKey("22Y43yTVxuUkoRKdm9thyRhQ3SdgQS7c7kB6UNCiaczD");
|
||||
const TOKEN_PID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
||||
const REGISTRY_PID = new PublicKey(
|
||||
"GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv"
|
||||
);
|
||||
const LOCKUP_PID = new PublicKey(
|
||||
"6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks"
|
||||
);
|
||||
const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey(
|
||||
"Sysvar1nstructions1111111111111111111111111"
|
||||
);
|
||||
const FEES = "6160355581";
|
||||
|
||||
describe("cfo", () => {
|
||||
const provider = anchor.AnchorProvider.env();
|
||||
anchor.setProvider(provider);
|
||||
|
||||
const program = anchor.workspace.Cfo;
|
||||
// hack so we don't have to update serum-common library
|
||||
// to the new AnchorProvider class and Provider interface
|
||||
program.provider.send = provider.sendAndConfirm;
|
||||
const sweepAuthority = program.provider.wallet.publicKey;
|
||||
let officer, srmVault, usdcVault, bVault, stake, treasury;
|
||||
let officerBump, srmBump, usdcBump, bBump, stakeBump, treasuryBump;
|
||||
let openOrders, openOrdersBump;
|
||||
let openOrdersB, openOrdersBumpB;
|
||||
let USDC_TOKEN_CLIENT, A_TOKEN_CLIENT, B_TOKEN_CLIENT;
|
||||
let officerAccount;
|
||||
let marketAClient, marketBClient;
|
||||
let marketAuth, marketAuthBump;
|
||||
let marketAuthB, marketAuthBumpB;
|
||||
let distribution;
|
||||
|
||||
// Accounts used to setup the orderbook.
|
||||
let ORDERBOOK_ENV,
|
||||
// Accounts used for A -> USDC swap transactions.
|
||||
SWAP_A_USDC_ACCOUNTS,
|
||||
// Accounts used for USDC -> A swap transactions.
|
||||
SWAP_USDC_A_ACCOUNTS,
|
||||
// Serum DEX vault PDA for market A/USDC.
|
||||
marketAVaultSigner,
|
||||
// Serum DEX vault PDA for market B/USDC.
|
||||
marketBVaultSigner;
|
||||
|
||||
let registrar, msrmRegistrar;
|
||||
|
||||
it("BOILERPLATE: Sets up a market with funded fees", async () => {
|
||||
ORDERBOOK_ENV = await utils.initMarket({
|
||||
provider: program.provider,
|
||||
});
|
||||
console.log("Token A: ", ORDERBOOK_ENV.marketA.baseMintAddress.toString());
|
||||
console.log(
|
||||
"Token USDC: ",
|
||||
ORDERBOOK_ENV.marketA.quoteMintAddress.toString()
|
||||
);
|
||||
USDC_TOKEN_CLIENT = new Token(
|
||||
program.provider.connection,
|
||||
ORDERBOOK_ENV.usdc,
|
||||
TOKEN_PID,
|
||||
program.provider.wallet.payer
|
||||
);
|
||||
SRM_TOKEN_CLIENT = new Token(
|
||||
program.provider.connection,
|
||||
ORDERBOOK_ENV.mintA,
|
||||
TOKEN_PID,
|
||||
program.provider.wallet.payer
|
||||
);
|
||||
B_TOKEN_CLIENT = new Token(
|
||||
program.provider.connection,
|
||||
ORDERBOOK_ENV.mintB,
|
||||
TOKEN_PID,
|
||||
program.provider.wallet.payer
|
||||
);
|
||||
|
||||
await USDC_TOKEN_CLIENT.transfer(
|
||||
ORDERBOOK_ENV.godUsdc,
|
||||
ORDERBOOK_ENV.marketA._decoded.quoteVault,
|
||||
program.provider.wallet.payer,
|
||||
[],
|
||||
10000000000000
|
||||
);
|
||||
|
||||
const tokenAccount = await USDC_TOKEN_CLIENT.getAccountInfo(
|
||||
ORDERBOOK_ENV.marketA._decoded.quoteVault
|
||||
);
|
||||
assert.strictEqual(tokenAccount.amount.toString(), "10000902263700");
|
||||
});
|
||||
|
||||
it("BOILERPLATE: Executes trades to generate fees", async () => {
|
||||
await utils.runTradeBot(
|
||||
ORDERBOOK_ENV.marketA._decoded.ownAddress,
|
||||
program.provider,
|
||||
1
|
||||
);
|
||||
marketAClient = await Market.load(
|
||||
program.provider.connection,
|
||||
ORDERBOOK_ENV.marketA.address,
|
||||
{ commitment: "processed" },
|
||||
DEX_PID
|
||||
);
|
||||
marketBClient = await Market.load(
|
||||
program.provider.connection,
|
||||
ORDERBOOK_ENV.marketB.address,
|
||||
{ commitment: "processed" },
|
||||
DEX_PID
|
||||
);
|
||||
assert.strictEqual(
|
||||
marketAClient._decoded.quoteFeesAccrued.toString(),
|
||||
FEES
|
||||
);
|
||||
});
|
||||
|
||||
it("BOILERPLATE: Sets up the staking pools", async () => {
|
||||
await setupStakePool(ORDERBOOK_ENV.mintA, ORDERBOOK_ENV.godA);
|
||||
registrar = ORDERBOOK_ENV.usdc;
|
||||
msrmRegistrar = registrar;
|
||||
});
|
||||
|
||||
it("BOILERPLATE: Finds PDA addresses", async () => {
|
||||
const [_officer, _officerBump] = await PublicKey.findProgramAddress(
|
||||
[DEX_PID.toBuffer()],
|
||||
program.programId
|
||||
);
|
||||
const [_openOrders, _openOrdersBump] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
utf8.encode("open-orders"),
|
||||
_officer.toBuffer(),
|
||||
ORDERBOOK_ENV.marketA.address.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
const [_openOrdersB, _openOrdersBumpB] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
utf8.encode("open-orders"),
|
||||
_officer.toBuffer(),
|
||||
ORDERBOOK_ENV.marketB.address.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
const [_srmVault, _srmBump] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
utf8.encode("token"),
|
||||
_officer.toBuffer(),
|
||||
ORDERBOOK_ENV.mintA.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
const [_bVault, _bBump] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
utf8.encode("token"),
|
||||
_officer.toBuffer(),
|
||||
ORDERBOOK_ENV.mintB.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
const [_usdcVault, _usdcBump] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
utf8.encode("token"),
|
||||
_officer.toBuffer(),
|
||||
ORDERBOOK_ENV.usdc.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
const [_stake, _stakeBump] = await PublicKey.findProgramAddress(
|
||||
[utf8.encode("stake"), _officer.toBuffer()],
|
||||
program.programId
|
||||
);
|
||||
const [_treasury, _treasuryBump] = await PublicKey.findProgramAddress(
|
||||
[utf8.encode("treasury"), _officer.toBuffer()],
|
||||
program.programId
|
||||
);
|
||||
const [_marketAuth, _marketAuthBump] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
utf8.encode("market-auth"),
|
||||
_officer.toBuffer(),
|
||||
ORDERBOOK_ENV.marketA.address.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
const [_marketAuthB, _marketAuthBumpB] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
utf8.encode("market-auth"),
|
||||
_officer.toBuffer(),
|
||||
ORDERBOOK_ENV.marketB.address.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
|
||||
officer = _officer;
|
||||
officerBump = _officerBump;
|
||||
openOrders = _openOrders;
|
||||
openOrdersBump = _openOrdersBump;
|
||||
openOrdersB = _openOrdersB;
|
||||
openOrdersBumpB = _openOrdersBumpB;
|
||||
srmVault = _srmVault;
|
||||
srmBump = _srmBump;
|
||||
usdcVault = _usdcVault;
|
||||
usdcBump = _usdcBump;
|
||||
bVault = _bVault;
|
||||
bBump = _bBump;
|
||||
stake = _stake;
|
||||
stakeBump = _stakeBump;
|
||||
treasury = _treasury;
|
||||
treasuryBump = _treasuryBump;
|
||||
marketAuth = _marketAuth;
|
||||
marketAuthBump = _marketAuthBump;
|
||||
marketAuthB = _marketAuthB;
|
||||
marketAuthBumpB = _marketAuthBumpB;
|
||||
});
|
||||
|
||||
it("Creates a CFO!", async () => {
|
||||
distribution = {
|
||||
burn: 80,
|
||||
stake: 20,
|
||||
treasury: 0,
|
||||
};
|
||||
const bumps = {
|
||||
bump: officerBump,
|
||||
srm: srmBump,
|
||||
usdc: usdcBump,
|
||||
stake: stakeBump,
|
||||
treasury: treasuryBump,
|
||||
};
|
||||
await program.methods
|
||||
.createOfficer(bumps, distribution, registrar, msrmRegistrar)
|
||||
.accounts({
|
||||
officer,
|
||||
srmVault,
|
||||
usdcVault,
|
||||
stake,
|
||||
treasury,
|
||||
srmMint: ORDERBOOK_ENV.mintA,
|
||||
usdcMint: ORDERBOOK_ENV.usdc,
|
||||
authority: program.provider.wallet.publicKey,
|
||||
dexProgram: DEX_PID,
|
||||
swapProgram: SWAP_PID,
|
||||
tokenProgram: TOKEN_PID,
|
||||
systemProgram: SystemProgram.programId,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
})
|
||||
.rpc();
|
||||
|
||||
officerAccount = await program.account.officer.fetch(officer);
|
||||
assert.isTrue(
|
||||
officerAccount.authority.equals(program.provider.wallet.publicKey)
|
||||
);
|
||||
assert.strictEqual(
|
||||
JSON.stringify(officerAccount.distribution),
|
||||
JSON.stringify(distribution)
|
||||
);
|
||||
});
|
||||
|
||||
it("Creates a token account for the officer associated with the market", async () => {
|
||||
await program.methods
|
||||
.createOfficerToken(bBump)
|
||||
.accounts({
|
||||
officer,
|
||||
token: bVault,
|
||||
mint: ORDERBOOK_ENV.mintB,
|
||||
payer: program.provider.wallet.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PID,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
})
|
||||
.rpc();
|
||||
const tokenAccount = await B_TOKEN_CLIENT.getAccountInfo(bVault);
|
||||
assert.strictEqual(tokenAccount.state, 1);
|
||||
assert.isTrue(tokenAccount.isInitialized);
|
||||
});
|
||||
|
||||
it("Creates an open orders account for the officer", async () => {
|
||||
await program.methods
|
||||
.createOfficerOpenOrders(openOrdersBump)
|
||||
.accounts({
|
||||
officer,
|
||||
openOrders,
|
||||
payer: program.provider.wallet.publicKey,
|
||||
dexProgram: DEX_PID,
|
||||
systemProgram: SystemProgram.programId,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
market: ORDERBOOK_ENV.marketA.address,
|
||||
})
|
||||
.rpc();
|
||||
await program.rpc.createOfficerOpenOrders(openOrdersBumpB, {
|
||||
accounts: {
|
||||
officer,
|
||||
openOrders: openOrdersB,
|
||||
payer: program.provider.wallet.publicKey,
|
||||
dexProgram: DEX_PID,
|
||||
systemProgram: SystemProgram.programId,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
market: ORDERBOOK_ENV.marketB.address,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Sweeps fees", async () => {
|
||||
const [sweepVault, bump] = await PublicKey.findProgramAddress(
|
||||
[utf8.encode("token"), officer.toBuffer(), ORDERBOOK_ENV.usdc.toBuffer()],
|
||||
program.programId
|
||||
);
|
||||
const beforeTokenAccount = await serumCmn.getTokenAccount(
|
||||
program.provider,
|
||||
sweepVault
|
||||
);
|
||||
await program.methods
|
||||
.sweepFees()
|
||||
.accounts({
|
||||
officer,
|
||||
sweepVault,
|
||||
mint: ORDERBOOK_ENV.usdc,
|
||||
dex: {
|
||||
market: ORDERBOOK_ENV.marketA._decoded.ownAddress,
|
||||
pcVault: ORDERBOOK_ENV.marketA._decoded.quoteVault,
|
||||
sweepAuthority,
|
||||
vaultSigner: ORDERBOOK_ENV.marketAVaultSigner,
|
||||
dexProgram: DEX_PID,
|
||||
tokenProgram: TOKEN_PID,
|
||||
},
|
||||
})
|
||||
.rpc();
|
||||
const afterTokenAccount = await serumCmn.getTokenAccount(
|
||||
program.provider,
|
||||
sweepVault
|
||||
);
|
||||
assert.strictEqual(
|
||||
afterTokenAccount.amount.sub(beforeTokenAccount.amount).toString(),
|
||||
FEES
|
||||
);
|
||||
});
|
||||
|
||||
it("Creates a market auth token", async () => {
|
||||
await program.methods
|
||||
.authorizeMarket(marketAuthBump)
|
||||
.accounts({
|
||||
officer,
|
||||
authority: program.provider.wallet.publicKey,
|
||||
marketAuth,
|
||||
payer: program.provider.wallet.publicKey,
|
||||
market: ORDERBOOK_ENV.marketA.address,
|
||||
systemProgram: SystemProgram.programId,
|
||||
})
|
||||
.rpc();
|
||||
await program.methods
|
||||
.authorizeMarket(marketAuthBumpB)
|
||||
.accounts({
|
||||
officer,
|
||||
authority: program.provider.wallet.publicKey,
|
||||
marketAuth: marketAuthB,
|
||||
payer: program.provider.wallet.publicKey,
|
||||
market: ORDERBOOK_ENV.marketB.address,
|
||||
systemProgram: SystemProgram.programId,
|
||||
})
|
||||
.rpc();
|
||||
});
|
||||
|
||||
it("Transfers into the mintB vault", async () => {
|
||||
await B_TOKEN_CLIENT.transfer(
|
||||
ORDERBOOK_ENV.godB,
|
||||
bVault,
|
||||
program.provider.wallet.payer,
|
||||
[],
|
||||
616035558100
|
||||
);
|
||||
});
|
||||
|
||||
it("Swaps from B token to USDC", async () => {
|
||||
const bVaultBefore = await B_TOKEN_CLIENT.getAccountInfo(bVault);
|
||||
const usdcVaultBefore = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
|
||||
|
||||
const minExchangeRate = {
|
||||
rate: new anchor.BN(0),
|
||||
fromDecimals: 6,
|
||||
quoteDecimals: 6,
|
||||
strict: false,
|
||||
};
|
||||
await program.methods
|
||||
.swapToUsdc(minExchangeRate)
|
||||
.accounts({
|
||||
officer,
|
||||
market: {
|
||||
market: marketBClient.address,
|
||||
openOrders: openOrdersB,
|
||||
requestQueue: marketBClient.decoded.requestQueue,
|
||||
eventQueue: marketBClient.decoded.eventQueue,
|
||||
bids: marketBClient.decoded.bids,
|
||||
asks: marketBClient.decoded.asks,
|
||||
orderPayerTokenAccount: bVault,
|
||||
coinVault: marketBClient.decoded.baseVault,
|
||||
pcVault: marketBClient.decoded.quoteVault,
|
||||
vaultSigner: ORDERBOOK_ENV.marketBVaultSigner,
|
||||
},
|
||||
marketAuth: marketAuthB,
|
||||
usdcVault,
|
||||
fromVault: bVault,
|
||||
usdcMint: ORDERBOOK_ENV.usdc,
|
||||
swapProgram: SWAP_PID,
|
||||
dexProgram: DEX_PID,
|
||||
tokenProgram: TOKEN_PID,
|
||||
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
})
|
||||
.rpc();
|
||||
|
||||
const bVaultAfter = await B_TOKEN_CLIENT.getAccountInfo(bVault);
|
||||
const usdcVaultAfter = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
|
||||
|
||||
assert.strictEqual(bVaultBefore.amount.toNumber(), 616035558100);
|
||||
assert.strictEqual(usdcVaultBefore.amount.toNumber(), 6160355581);
|
||||
assert.strictEqual(bVaultAfter.amount.toNumber(), 615884458100);
|
||||
assert.strictEqual(usdcVaultAfter.amount.toNumber(), 7060634298);
|
||||
});
|
||||
|
||||
it("Swaps to SRM", async () => {
|
||||
const srmVaultBefore = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
|
||||
const usdcVaultBefore = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
|
||||
|
||||
const minExchangeRate = {
|
||||
rate: new anchor.BN(0),
|
||||
fromDecimals: 6,
|
||||
quoteDecimals: 6,
|
||||
strict: false,
|
||||
};
|
||||
await program.methods
|
||||
.swapToSrm(minExchangeRate)
|
||||
.accounts({
|
||||
officer,
|
||||
market: {
|
||||
market: marketAClient.address,
|
||||
openOrders,
|
||||
requestQueue: marketAClient.decoded.requestQueue,
|
||||
eventQueue: marketAClient.decoded.eventQueue,
|
||||
bids: marketAClient.decoded.bids,
|
||||
asks: marketAClient.decoded.asks,
|
||||
orderPayerTokenAccount: usdcVault,
|
||||
coinVault: marketAClient.decoded.baseVault,
|
||||
pcVault: marketAClient.decoded.quoteVault,
|
||||
vaultSigner: ORDERBOOK_ENV.marketAVaultSigner,
|
||||
},
|
||||
marketAuth,
|
||||
usdcVault,
|
||||
srmVault,
|
||||
usdcMint: ORDERBOOK_ENV.usdc,
|
||||
srmMint: ORDERBOOK_ENV.mintA,
|
||||
swapProgram: SWAP_PID,
|
||||
dexProgram: DEX_PID,
|
||||
tokenProgram: TOKEN_PID,
|
||||
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
rent: SYSVAR_RENT_PUBKEY,
|
||||
})
|
||||
.rpc();
|
||||
|
||||
const srmVaultAfter = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
|
||||
const usdcVaultAfter = await USDC_TOKEN_CLIENT.getAccountInfo(usdcVault);
|
||||
|
||||
assert.strictEqual(srmVaultBefore.amount.toNumber(), 0);
|
||||
assert.strictEqual(srmVaultAfter.amount.toNumber(), 1152000000);
|
||||
assert.strictEqual(usdcVaultBefore.amount.toNumber(), 7060634298);
|
||||
assert.strictEqual(usdcVaultAfter.amount.toNumber(), 530863);
|
||||
});
|
||||
|
||||
it("Distributes the tokens to categories", async () => {
|
||||
const srmVaultBefore = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
|
||||
const treasuryBefore = await SRM_TOKEN_CLIENT.getAccountInfo(treasury);
|
||||
const stakeBefore = await SRM_TOKEN_CLIENT.getAccountInfo(stake);
|
||||
const mintInfoBefore = await SRM_TOKEN_CLIENT.getMintInfo();
|
||||
|
||||
await program.methods
|
||||
.distribute()
|
||||
.accounts({
|
||||
officer,
|
||||
treasury,
|
||||
stake,
|
||||
srmVault,
|
||||
srmMint: ORDERBOOK_ENV.mintA,
|
||||
tokenProgram: TOKEN_PID,
|
||||
dexProgram: DEX_PID,
|
||||
})
|
||||
.rpc();
|
||||
|
||||
const srmVaultAfter = await SRM_TOKEN_CLIENT.getAccountInfo(srmVault);
|
||||
const treasuryAfter = await SRM_TOKEN_CLIENT.getAccountInfo(treasury);
|
||||
const stakeAfter = await SRM_TOKEN_CLIENT.getAccountInfo(stake);
|
||||
const mintInfoAfter = await SRM_TOKEN_CLIENT.getMintInfo();
|
||||
|
||||
const beforeAmount = 1152000000;
|
||||
assert.strictEqual(srmVaultBefore.amount.toNumber(), beforeAmount);
|
||||
assert.strictEqual(srmVaultAfter.amount.toNumber(), 0); // Fully distributed.
|
||||
assert.strictEqual(
|
||||
stakeAfter.amount.toNumber(),
|
||||
beforeAmount * (distribution.stake / 100.0)
|
||||
);
|
||||
assert.strictEqual(
|
||||
treasuryAfter.amount.toNumber(),
|
||||
beforeAmount * (distribution.treasury / 100.0)
|
||||
);
|
||||
// Check burn amount.
|
||||
assert.strictEqual(mintInfoBefore.supply.toString(), "1000000000000000000");
|
||||
assert.strictEqual(
|
||||
mintInfoBefore.supply.sub(mintInfoAfter.supply).toNumber(),
|
||||
beforeAmount * (distribution.burn / 100.0)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,599 +0,0 @@
|
|||
// Boilerplate utils to bootstrap an orderbook for testing on a localnet.
|
||||
// not super relevant to the point of the example, though may be useful to
|
||||
// include into your own workspace for testing.
|
||||
//
|
||||
// TODO: Modernize all these apis. This is all quite clunky.
|
||||
|
||||
const Token = require("@solana/spl-token").Token;
|
||||
const TOKEN_PROGRAM_ID = require("@solana/spl-token").TOKEN_PROGRAM_ID;
|
||||
const TokenInstructions = require("@project-serum/serum").TokenInstructions;
|
||||
const { Market, OpenOrders } = require("@project-serum/serum");
|
||||
const DexInstructions = require("@project-serum/serum").DexInstructions;
|
||||
const web3 = require("@project-serum/anchor").web3;
|
||||
const Connection = web3.Connection;
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const BN = anchor.BN;
|
||||
const serumCmn = require("@project-serum/common");
|
||||
const Account = web3.Account;
|
||||
const Transaction = web3.Transaction;
|
||||
const PublicKey = web3.PublicKey;
|
||||
const SystemProgram = web3.SystemProgram;
|
||||
const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
|
||||
const secret = JSON.parse(
|
||||
require("fs").readFileSync("./scripts/market-maker.json")
|
||||
);
|
||||
const MARKET_MAKER = new Account(secret);
|
||||
|
||||
async function initMarket({ provider }) {
|
||||
// Setup mints with initial tokens owned by the provider.
|
||||
const decimals = 6;
|
||||
const [MINT_A, GOD_A] = await serumCmn.createMintAndVault(
|
||||
provider,
|
||||
new BN("1000000000000000000"),
|
||||
undefined,
|
||||
decimals
|
||||
);
|
||||
const [MINT_B, GOD_B] = await serumCmn.createMintAndVault(
|
||||
provider,
|
||||
new BN("1000000000000000000"),
|
||||
undefined,
|
||||
decimals
|
||||
);
|
||||
const [USDC, GOD_USDC] = await serumCmn.createMintAndVault(
|
||||
provider,
|
||||
new BN("1000000000000000000"),
|
||||
undefined,
|
||||
decimals
|
||||
);
|
||||
|
||||
// Create a funded account to act as market maker.
|
||||
const amount = new BN("10000000000000").muln(10 ** decimals);
|
||||
const marketMaker = await fundAccount({
|
||||
provider,
|
||||
mints: [
|
||||
{ god: GOD_A, mint: MINT_A, amount, decimals },
|
||||
{ god: GOD_B, mint: MINT_B, amount, decimals },
|
||||
{ god: GOD_USDC, mint: USDC, amount, decimals },
|
||||
],
|
||||
});
|
||||
|
||||
// Setup A/USDC with resting orders.
|
||||
const asks = [
|
||||
[6.041, 7.8],
|
||||
[6.051, 72.3],
|
||||
[6.055, 5.4],
|
||||
[6.067, 15.7],
|
||||
[6.077, 390.0],
|
||||
[6.09, 24.0],
|
||||
[6.11, 36.3],
|
||||
[6.133, 300.0],
|
||||
[6.167, 687.8],
|
||||
];
|
||||
const bids = [
|
||||
[6.004, 8.5],
|
||||
[5.995, 12.9],
|
||||
[5.987, 6.2],
|
||||
[5.978, 15.3],
|
||||
[5.965, 82.8],
|
||||
[5.961, 25.4],
|
||||
];
|
||||
|
||||
[MARKET_A_USDC, marketAVaultSigner] = await setupMarket({
|
||||
baseMint: MINT_A,
|
||||
quoteMint: USDC,
|
||||
marketMaker: {
|
||||
account: marketMaker.account,
|
||||
baseToken: marketMaker.tokens[MINT_A.toString()],
|
||||
quoteToken: marketMaker.tokens[USDC.toString()],
|
||||
},
|
||||
bids,
|
||||
asks,
|
||||
provider,
|
||||
});
|
||||
[MARKET_B_USDC, marketBVaultSigner] = await setupMarket({
|
||||
baseMint: MINT_B,
|
||||
quoteMint: USDC,
|
||||
marketMaker: {
|
||||
account: marketMaker.account,
|
||||
baseToken: marketMaker.tokens[MINT_B.toString()],
|
||||
quoteToken: marketMaker.tokens[USDC.toString()],
|
||||
},
|
||||
bids,
|
||||
asks,
|
||||
provider,
|
||||
});
|
||||
|
||||
return {
|
||||
marketA: MARKET_A_USDC,
|
||||
marketAVaultSigner,
|
||||
marketB: MARKET_B_USDC,
|
||||
marketBVaultSigner,
|
||||
marketMaker,
|
||||
mintA: MINT_A,
|
||||
mintB: MINT_B,
|
||||
usdc: USDC,
|
||||
godA: GOD_A,
|
||||
godB: GOD_B,
|
||||
godUsdc: GOD_USDC,
|
||||
};
|
||||
}
|
||||
|
||||
async function fundAccount({ provider, mints }) {
|
||||
const marketMaker = {
|
||||
tokens: {},
|
||||
account: MARKET_MAKER,
|
||||
};
|
||||
|
||||
// Transfer lamports to market maker.
|
||||
await provider.sendAndConfirm(
|
||||
(() => {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
SystemProgram.transfer({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
toPubkey: MARKET_MAKER.publicKey,
|
||||
lamports: 100000000000,
|
||||
})
|
||||
);
|
||||
return tx;
|
||||
})()
|
||||
);
|
||||
|
||||
// Transfer SPL tokens to the market maker.
|
||||
for (let k = 0; k < mints.length; k += 1) {
|
||||
const { mint, god, amount, decimals } = mints[k];
|
||||
let MINT_A = mint;
|
||||
let GOD_A = god;
|
||||
// Setup token accounts owned by the market maker.
|
||||
const mintAClient = new Token(
|
||||
provider.connection,
|
||||
MINT_A,
|
||||
TOKEN_PROGRAM_ID,
|
||||
provider.wallet.payer // node only
|
||||
);
|
||||
const marketMakerTokenA = await mintAClient.createAccount(
|
||||
MARKET_MAKER.publicKey
|
||||
);
|
||||
|
||||
await provider.sendAndConfirm(
|
||||
(() => {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
Token.createTransferCheckedInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
GOD_A,
|
||||
MINT_A,
|
||||
marketMakerTokenA,
|
||||
provider.wallet.publicKey,
|
||||
[],
|
||||
amount,
|
||||
decimals
|
||||
)
|
||||
);
|
||||
return tx;
|
||||
})()
|
||||
);
|
||||
|
||||
marketMaker.tokens[mint.toString()] = marketMakerTokenA;
|
||||
}
|
||||
|
||||
return marketMaker;
|
||||
}
|
||||
|
||||
async function setupMarket({
|
||||
provider,
|
||||
marketMaker,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
bids,
|
||||
asks,
|
||||
}) {
|
||||
const [marketAPublicKey, vaultOwner] = await listMarket({
|
||||
connection: provider.connection,
|
||||
wallet: provider.wallet,
|
||||
baseMint: baseMint,
|
||||
quoteMint: quoteMint,
|
||||
baseLotSize: 100000,
|
||||
quoteLotSize: 100,
|
||||
dexProgramId: DEX_PID,
|
||||
feeRateBps: 0,
|
||||
});
|
||||
const MARKET_A_USDC = await Market.load(
|
||||
provider.connection,
|
||||
marketAPublicKey,
|
||||
{ commitment: "processed" },
|
||||
DEX_PID
|
||||
);
|
||||
for (let k = 0; k < asks.length; k += 1) {
|
||||
let ask = asks[k];
|
||||
const { transaction, signers } =
|
||||
await MARKET_A_USDC.makePlaceOrderTransaction(provider.connection, {
|
||||
owner: marketMaker.account,
|
||||
payer: marketMaker.baseToken,
|
||||
side: "sell",
|
||||
price: ask[0],
|
||||
size: ask[1],
|
||||
orderType: "postOnly",
|
||||
clientId: undefined,
|
||||
openOrdersAddressKey: undefined,
|
||||
openOrdersAccount: undefined,
|
||||
feeDiscountPubkey: null,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
});
|
||||
await provider.sendAndConfirm(
|
||||
transaction,
|
||||
signers.concat(marketMaker.account)
|
||||
);
|
||||
}
|
||||
|
||||
for (let k = 0; k < bids.length; k += 1) {
|
||||
let bid = bids[k];
|
||||
const { transaction, signers } =
|
||||
await MARKET_A_USDC.makePlaceOrderTransaction(provider.connection, {
|
||||
owner: marketMaker.account,
|
||||
payer: marketMaker.quoteToken,
|
||||
side: "buy",
|
||||
price: bid[0],
|
||||
size: bid[1],
|
||||
orderType: "postOnly",
|
||||
clientId: undefined,
|
||||
openOrdersAddressKey: undefined,
|
||||
openOrdersAccount: undefined,
|
||||
feeDiscountPubkey: null,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
});
|
||||
await provider.sendAndConfirm(
|
||||
transaction,
|
||||
signers.concat(marketMaker.account)
|
||||
);
|
||||
}
|
||||
|
||||
return [MARKET_A_USDC, vaultOwner];
|
||||
}
|
||||
|
||||
async function listMarket({
|
||||
connection,
|
||||
wallet,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
baseLotSize,
|
||||
quoteLotSize,
|
||||
dexProgramId,
|
||||
feeRateBps,
|
||||
}) {
|
||||
const market = new Account();
|
||||
const requestQueue = new Account();
|
||||
const eventQueue = new Account();
|
||||
const bids = new Account();
|
||||
const asks = new Account();
|
||||
const baseVault = new Account();
|
||||
const quoteVault = new Account();
|
||||
const quoteDustThreshold = new BN(100);
|
||||
|
||||
const [vaultOwner, vaultSignerNonce] = await getVaultOwnerAndNonce(
|
||||
market.publicKey,
|
||||
dexProgramId
|
||||
);
|
||||
|
||||
const tx1 = new Transaction();
|
||||
tx1.add(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: baseVault.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(165),
|
||||
space: 165,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: quoteVault.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(165),
|
||||
space: 165,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
TokenInstructions.initializeAccount({
|
||||
account: baseVault.publicKey,
|
||||
mint: baseMint,
|
||||
owner: vaultOwner,
|
||||
}),
|
||||
TokenInstructions.initializeAccount({
|
||||
account: quoteVault.publicKey,
|
||||
mint: quoteMint,
|
||||
owner: vaultOwner,
|
||||
})
|
||||
);
|
||||
|
||||
const tx2 = new Transaction();
|
||||
tx2.add(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: market.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(
|
||||
Market.getLayout(dexProgramId).span
|
||||
),
|
||||
space: Market.getLayout(dexProgramId).span,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: requestQueue.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(5120 + 12),
|
||||
space: 5120 + 12,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: eventQueue.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(262144 + 12),
|
||||
space: 262144 + 12,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: bids.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
|
||||
space: 65536 + 12,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: asks.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
|
||||
space: 65536 + 12,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
DexInstructions.initializeMarket({
|
||||
market: market.publicKey,
|
||||
requestQueue: requestQueue.publicKey,
|
||||
eventQueue: eventQueue.publicKey,
|
||||
bids: bids.publicKey,
|
||||
asks: asks.publicKey,
|
||||
baseVault: baseVault.publicKey,
|
||||
quoteVault: quoteVault.publicKey,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
baseLotSize: new BN(baseLotSize),
|
||||
quoteLotSize: new BN(quoteLotSize),
|
||||
feeRateBps,
|
||||
vaultSignerNonce,
|
||||
quoteDustThreshold,
|
||||
programId: dexProgramId,
|
||||
})
|
||||
);
|
||||
|
||||
const signedTransactions = await signTransactions({
|
||||
transactionsAndSigners: [
|
||||
{ transaction: tx1, signers: [baseVault, quoteVault] },
|
||||
{
|
||||
transaction: tx2,
|
||||
signers: [market, requestQueue, eventQueue, bids, asks],
|
||||
},
|
||||
],
|
||||
wallet,
|
||||
connection,
|
||||
});
|
||||
for (let signedTransaction of signedTransactions) {
|
||||
await sendAndConfirmRawTransaction(
|
||||
connection,
|
||||
signedTransaction.serialize()
|
||||
);
|
||||
}
|
||||
const acc = await connection.getAccountInfo(market.publicKey);
|
||||
|
||||
return [market.publicKey, vaultOwner];
|
||||
}
|
||||
|
||||
async function signTransactions({
|
||||
transactionsAndSigners,
|
||||
wallet,
|
||||
connection,
|
||||
}) {
|
||||
const blockhash = (await connection.getRecentBlockhash("finalized"))
|
||||
.blockhash;
|
||||
transactionsAndSigners.forEach(({ transaction, signers = [] }) => {
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.setSigners(
|
||||
wallet.publicKey,
|
||||
...signers.map((s) => s.publicKey)
|
||||
);
|
||||
if (signers?.length > 0) {
|
||||
transaction.partialSign(...signers);
|
||||
}
|
||||
});
|
||||
return await wallet.signAllTransactions(
|
||||
transactionsAndSigners.map(({ transaction }) => transaction)
|
||||
);
|
||||
}
|
||||
|
||||
async function sendAndConfirmRawTransaction(
|
||||
connection,
|
||||
raw,
|
||||
commitment = "processed"
|
||||
) {
|
||||
let tx = await connection.sendRawTransaction(raw, {
|
||||
skipPreflight: true,
|
||||
});
|
||||
return await connection.confirmTransaction(tx, commitment);
|
||||
}
|
||||
|
||||
async function getVaultOwnerAndNonce(marketPublicKey, dexProgramId = DEX_PID) {
|
||||
const nonce = new BN(0);
|
||||
while (nonce.toNumber() < 255) {
|
||||
try {
|
||||
const vaultOwner = await PublicKey.createProgramAddress(
|
||||
[marketPublicKey.toBuffer(), nonce.toArrayLike(Buffer, "le", 8)],
|
||||
dexProgramId
|
||||
);
|
||||
return [vaultOwner, nonce];
|
||||
} catch (e) {
|
||||
nonce.iaddn(1);
|
||||
}
|
||||
}
|
||||
throw new Error("Unable to find nonce");
|
||||
}
|
||||
|
||||
async function runTradeBot(market, provider, iterations = undefined) {
|
||||
let marketClient = await Market.load(
|
||||
provider.connection,
|
||||
market,
|
||||
{ commitment: "processed" },
|
||||
DEX_PID
|
||||
);
|
||||
const baseTokenUser1 = (
|
||||
await marketClient.getTokenAccountsByOwnerForMint(
|
||||
provider.connection,
|
||||
MARKET_MAKER.publicKey,
|
||||
marketClient.baseMintAddress
|
||||
)
|
||||
)[0].pubkey;
|
||||
const quoteTokenUser1 = (
|
||||
await marketClient.getTokenAccountsByOwnerForMint(
|
||||
provider.connection,
|
||||
MARKET_MAKER.publicKey,
|
||||
marketClient.quoteMintAddress
|
||||
)
|
||||
)[0].pubkey;
|
||||
|
||||
const baseTokenUser2 = (
|
||||
await marketClient.getTokenAccountsByOwnerForMint(
|
||||
provider.connection,
|
||||
provider.wallet.publicKey,
|
||||
marketClient.baseMintAddress
|
||||
)
|
||||
)[0].pubkey;
|
||||
const quoteTokenUser2 = (
|
||||
await marketClient.getTokenAccountsByOwnerForMint(
|
||||
provider.connection,
|
||||
provider.wallet.publicKey,
|
||||
marketClient.quoteMintAddress
|
||||
)
|
||||
)[0].pubkey;
|
||||
|
||||
const makerOpenOrdersUser1 = (
|
||||
await OpenOrders.findForMarketAndOwner(
|
||||
provider.connection,
|
||||
market,
|
||||
MARKET_MAKER.publicKey,
|
||||
DEX_PID
|
||||
)
|
||||
)[0];
|
||||
makerOpenOrdersUser2 = (
|
||||
await OpenOrders.findForMarketAndOwner(
|
||||
provider.connection,
|
||||
market,
|
||||
provider.wallet.publicKey,
|
||||
DEX_PID
|
||||
)
|
||||
)[0];
|
||||
|
||||
const price = 6.041;
|
||||
const size = 700000.8;
|
||||
|
||||
let maker = MARKET_MAKER;
|
||||
let taker = provider.wallet.payer;
|
||||
let baseToken = baseTokenUser1;
|
||||
let quoteToken = quoteTokenUser2;
|
||||
let makerOpenOrders = makerOpenOrdersUser1;
|
||||
|
||||
let k = 1;
|
||||
|
||||
while (true) {
|
||||
if (iterations && k > iterations) {
|
||||
break;
|
||||
}
|
||||
const clientId = new anchor.BN(k);
|
||||
if (k % 5 === 0) {
|
||||
if (maker.publicKey.equals(MARKET_MAKER.publicKey)) {
|
||||
maker = provider.wallet.payer;
|
||||
makerOpenOrders = makerOpenOrdersUser2;
|
||||
taker = MARKET_MAKER;
|
||||
baseToken = baseTokenUser2;
|
||||
quoteToken = quoteTokenUser1;
|
||||
} else {
|
||||
maker = MARKET_MAKER;
|
||||
makerOpenOrders = makerOpenOrdersUser1;
|
||||
taker = provider.wallet.payer;
|
||||
baseToken = baseTokenUser1;
|
||||
quoteToken = quoteTokenUser2;
|
||||
}
|
||||
}
|
||||
|
||||
// Post ask.
|
||||
const { transaction: tx_ask, signers: sigs_ask } =
|
||||
await marketClient.makePlaceOrderTransaction(provider.connection, {
|
||||
owner: maker,
|
||||
payer: baseToken,
|
||||
side: "sell",
|
||||
price,
|
||||
size,
|
||||
orderType: "postOnly",
|
||||
clientId,
|
||||
openOrdersAddressKey: undefined,
|
||||
openOrdersAccount: undefined,
|
||||
feeDiscountPubkey: null,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
});
|
||||
let txSig = await provider.sendAndConfirm(tx_ask, sigs_ask.concat(maker));
|
||||
console.log("Ask", txSig);
|
||||
|
||||
// Take.
|
||||
const { transaction: tx_bid, signers: sigs_bid } =
|
||||
await marketClient.makePlaceOrderTransaction(provider.connection, {
|
||||
owner: taker,
|
||||
payer: quoteToken,
|
||||
side: "buy",
|
||||
price,
|
||||
size,
|
||||
orderType: "ioc",
|
||||
clientId: undefined,
|
||||
openOrdersAddressKey: undefined,
|
||||
openOrdersAccount: undefined,
|
||||
feeDiscountPubkey: null,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
});
|
||||
txSig = await provider.sendAndConfirm(tx_bid, sigs_bid.concat(taker));
|
||||
console.log("Bid", txSig);
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
// Cancel anything remaining.
|
||||
try {
|
||||
txSig = await marketClient.cancelOrderByClientId(
|
||||
provider.connection,
|
||||
maker,
|
||||
makerOpenOrders.address,
|
||||
clientId
|
||||
);
|
||||
console.log("Cancelled the rest", txSig);
|
||||
await sleep(1000);
|
||||
} catch (e) {
|
||||
console.log("Unable to cancel order", e);
|
||||
}
|
||||
k += 1;
|
||||
|
||||
// If the open orders account wasn't previously initialized, it is now.
|
||||
if (makerOpenOrdersUser2 === undefined) {
|
||||
makerOpenOrdersUser2 = (
|
||||
await OpenOrders.findForMarketAndOwner(
|
||||
provider.connection,
|
||||
market,
|
||||
provider.wallet.publicKey,
|
||||
DEX_PID
|
||||
)
|
||||
)[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fundAccount,
|
||||
initMarket,
|
||||
setupMarket,
|
||||
DEX_PID,
|
||||
getVaultOwnerAndNonce,
|
||||
runTradeBot,
|
||||
};
|
|
@ -1,187 +0,0 @@
|
|||
const anchor = require("@project-serum/anchor");
|
||||
const serumCmn = require("@project-serum/common");
|
||||
const TokenInstructions = require("@project-serum/serum").TokenInstructions;
|
||||
const utils = require("../../deps/stake/tests/utils");
|
||||
|
||||
const lockup = anchor.workspace.Lockup;
|
||||
const registry = anchor.workspace.Registry;
|
||||
const provider = anchor.AnchorProvider.env();
|
||||
// hack so we don't have to update serum-common library
|
||||
// to the new AnchorProvider class and Provider interface
|
||||
provider.send = provider.sendAndConfirm;
|
||||
|
||||
let lockupAddress = null;
|
||||
let mint = null;
|
||||
let god = null;
|
||||
|
||||
let registrarAccount = null;
|
||||
let registrarSigner = null;
|
||||
let nonce = null;
|
||||
let poolMint = null;
|
||||
|
||||
const registrar = new anchor.web3.Account();
|
||||
const rewardQ = new anchor.web3.Account();
|
||||
const withdrawalTimelock = new anchor.BN(4);
|
||||
const stakeRate = new anchor.BN(2);
|
||||
const rewardQLen = 170;
|
||||
let member = null;
|
||||
|
||||
let memberAccount = null;
|
||||
let memberSigner = null;
|
||||
let balances = null;
|
||||
let balancesLocked = null;
|
||||
|
||||
const WHITELIST_SIZE = 10;
|
||||
|
||||
async function setupStakePool(mint, god) {
|
||||
// Registry genesis.
|
||||
const [_registrarSigner, _nonce] =
|
||||
await anchor.web3.PublicKey.findProgramAddress(
|
||||
[registrar.publicKey.toBuffer()],
|
||||
registry.programId
|
||||
);
|
||||
registrarSigner = _registrarSigner;
|
||||
nonce = _nonce;
|
||||
poolMint = await serumCmn.createMint(provider, registrarSigner);
|
||||
|
||||
try {
|
||||
// Init registry.
|
||||
await registry.state.rpc.new({
|
||||
accounts: { lockupProgram: lockup.programId },
|
||||
});
|
||||
|
||||
// Init lockup.
|
||||
await lockup.state.rpc.new({
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
// Skip errors for convenience when developing locally,
|
||||
// since the state constructors can only be called once.
|
||||
}
|
||||
|
||||
// Initialize stake pool.
|
||||
await registry.rpc.initialize(
|
||||
mint,
|
||||
provider.wallet.publicKey,
|
||||
nonce,
|
||||
withdrawalTimelock,
|
||||
stakeRate,
|
||||
rewardQLen,
|
||||
{
|
||||
accounts: {
|
||||
registrar: registrar.publicKey,
|
||||
poolMint,
|
||||
rewardEventQ: rewardQ.publicKey,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
signers: [registrar, rewardQ],
|
||||
instructions: [
|
||||
await registry.account.registrar.createInstruction(registrar),
|
||||
await registry.account.rewardQueue.createInstruction(rewardQ, 8250),
|
||||
],
|
||||
}
|
||||
);
|
||||
registrarAccount = await registry.account.registrar.fetch(
|
||||
registrar.publicKey
|
||||
);
|
||||
console.log("Registrar", registrar.publicKey.toString());
|
||||
console.log("Wallet", registry.provider.wallet.publicKey.toString());
|
||||
// Create account for staker.
|
||||
const seed = anchor.utils.sha256
|
||||
.hash(`${registrar.publicKey.toString()}:Member`)
|
||||
.slice(0, 32);
|
||||
member = await anchor.web3.PublicKey.createWithSeed(
|
||||
registry.provider.wallet.publicKey,
|
||||
seed,
|
||||
registry.programId
|
||||
);
|
||||
const [_memberSigner, nonce2] =
|
||||
await anchor.web3.PublicKey.findProgramAddress(
|
||||
[registrar.publicKey.toBuffer(), member.toBuffer()],
|
||||
registry.programId
|
||||
);
|
||||
memberSigner = _memberSigner;
|
||||
const [mainTx, _balances] = await utils.createBalanceSandbox(
|
||||
provider,
|
||||
registrarAccount,
|
||||
memberSigner
|
||||
);
|
||||
const [lockedTx, _balancesLocked] = await utils.createBalanceSandbox(
|
||||
provider,
|
||||
registrarAccount,
|
||||
memberSigner
|
||||
);
|
||||
balances = _balances;
|
||||
balancesLocked = _balancesLocked;
|
||||
const tx = registry.transaction.createMember(nonce2, {
|
||||
accounts: {
|
||||
registrar: registrar.publicKey,
|
||||
member: member,
|
||||
beneficiary: provider.wallet.publicKey,
|
||||
memberSigner,
|
||||
balances,
|
||||
balancesLocked,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
instructions: [
|
||||
anchor.web3.SystemProgram.createAccountWithSeed({
|
||||
fromPubkey: registry.provider.wallet.publicKey,
|
||||
newAccountPubkey: member,
|
||||
basePubkey: registry.provider.wallet.publicKey,
|
||||
seed,
|
||||
lamports:
|
||||
await registry.provider.connection.getMinimumBalanceForRentExemption(
|
||||
registry.account.member.size
|
||||
),
|
||||
space: registry.account.member.size,
|
||||
programId: registry.programId,
|
||||
}),
|
||||
],
|
||||
});
|
||||
const signers = [provider.wallet.payer];
|
||||
const allTxs = [mainTx, lockedTx, { tx, signers }];
|
||||
await provider.sendAll(allTxs);
|
||||
memberAccount = await registry.account.member.fetch(member);
|
||||
|
||||
// Deposit into stake program.
|
||||
const depositAmount = new anchor.BN(120);
|
||||
await registry.rpc.deposit(depositAmount, {
|
||||
accounts: {
|
||||
depositor: god,
|
||||
depositorAuthority: provider.wallet.publicKey,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
vault: memberAccount.balances.vault,
|
||||
beneficiary: provider.wallet.publicKey,
|
||||
member: member,
|
||||
},
|
||||
});
|
||||
|
||||
// Stake.
|
||||
const stakeAmount = new anchor.BN(10);
|
||||
await registry.rpc.stake(stakeAmount, false, {
|
||||
accounts: {
|
||||
// Stake instance.
|
||||
registrar: registrar.publicKey,
|
||||
rewardEventQ: rewardQ.publicKey,
|
||||
poolMint,
|
||||
// Member.
|
||||
member: member,
|
||||
beneficiary: provider.wallet.publicKey,
|
||||
balances,
|
||||
balancesLocked,
|
||||
// Program signers.
|
||||
memberSigner,
|
||||
registrarSigner,
|
||||
// Misc.
|
||||
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupStakePool,
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.localnet]
|
||||
chat = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run mocha -t 1000000 tests/"
|
||||
|
||||
[features]
|
|
@ -1,4 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -1,12 +0,0 @@
|
|||
// 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.
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "chat",
|
||||
"version": "0.25.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "anchor test"
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "chat"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.56"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "chat"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,113 +0,0 @@
|
|||
//! A simple chat program using a ring buffer to store messages.
|
||||
|
||||
use anchor_lang::accounts::loader::Loader;
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
#[program]
|
||||
pub mod chat {
|
||||
use super::*;
|
||||
|
||||
pub fn create_user(ctx: Context<CreateUser>, name: String) -> Result<()> {
|
||||
ctx.accounts.user.name = name;
|
||||
ctx.accounts.user.authority = *ctx.accounts.authority.key;
|
||||
ctx.accounts.user.bump = *ctx.bumps.get("user").unwrap();
|
||||
Ok(())
|
||||
}
|
||||
pub fn create_chat_room(ctx: Context<CreateChatRoom>, name: String) -> Result<()> {
|
||||
let given_name = name.as_bytes();
|
||||
let mut name = [0u8; 280];
|
||||
name[..given_name.len()].copy_from_slice(given_name);
|
||||
let mut chat = ctx.accounts.chat_room.load_init()?;
|
||||
chat.name = name;
|
||||
Ok(())
|
||||
}
|
||||
pub fn send_message(ctx: Context<SendMessage>, msg: String) -> Result<()> {
|
||||
let mut chat = ctx.accounts.chat_room.load_mut()?;
|
||||
chat.append({
|
||||
let src = msg.as_bytes();
|
||||
let mut data = [0u8; 280];
|
||||
data[..src.len()].copy_from_slice(src);
|
||||
Message {
|
||||
from: *ctx.accounts.user.to_account_info().key,
|
||||
data,
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(name: String)]
|
||||
pub struct CreateUser<'info> {
|
||||
#[account(
|
||||
init,
|
||||
seeds = [authority.key().as_ref()],
|
||||
bump,
|
||||
payer = authority,
|
||||
space = 320,
|
||||
)]
|
||||
user: Account<'info, User>,
|
||||
#[account(mut)]
|
||||
authority: Signer<'info>,
|
||||
system_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CreateChatRoom<'info> {
|
||||
#[account(zero)]
|
||||
chat_room: Loader<'info, ChatRoom>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SendMessage<'info> {
|
||||
#[account(
|
||||
seeds = [authority.key().as_ref()],
|
||||
bump = user.bump,
|
||||
has_one = authority,
|
||||
)]
|
||||
user: Account<'info, User>,
|
||||
authority: Signer<'info>,
|
||||
#[account(mut)]
|
||||
chat_room: Loader<'info, ChatRoom>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct User {
|
||||
name: String,
|
||||
authority: Pubkey,
|
||||
bump: u8,
|
||||
}
|
||||
|
||||
#[account(zero_copy)]
|
||||
pub struct ChatRoom {
|
||||
head: u64,
|
||||
tail: u64,
|
||||
name: [u8; 280], // Human readable name (char bytes).
|
||||
messages: [Message; 33607], // Leaves the account at 10,485,680 bytes.
|
||||
}
|
||||
|
||||
impl ChatRoom {
|
||||
fn append(&mut self, msg: Message) {
|
||||
self.messages[ChatRoom::index_of(self.head)] = msg;
|
||||
if ChatRoom::index_of(self.head + 1) == ChatRoom::index_of(self.tail) {
|
||||
self.tail += 1;
|
||||
}
|
||||
self.head += 1;
|
||||
}
|
||||
fn index_of(counter: u64) -> usize {
|
||||
std::convert::TryInto::try_into(counter % 33607).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[zero_copy]
|
||||
pub struct Message {
|
||||
pub from: Pubkey,
|
||||
pub data: [u8; 280],
|
||||
}
|
||||
|
||||
#[error_code]
|
||||
pub enum ErrorCode {
|
||||
Unknown,
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
const anchor = require("@project-serum/anchor");
|
||||
const { assert } = require("chai");
|
||||
const { PublicKey } = anchor.web3;
|
||||
|
||||
describe("chat", () => {
|
||||
// Configure the client to use the local cluster.
|
||||
anchor.setProvider(anchor.AnchorProvider.env());
|
||||
|
||||
// Program client handle.
|
||||
const program = anchor.workspace.Chat;
|
||||
|
||||
// Chat room account.
|
||||
const chatRoom = anchor.web3.Keypair.generate();
|
||||
|
||||
it("Creates a chat room", async () => {
|
||||
await program.rpc.createChatRoom("Test Chat", {
|
||||
accounts: {
|
||||
chatRoom: chatRoom.publicKey,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
instructions: [
|
||||
await program.account.chatRoom.createInstruction(chatRoom),
|
||||
],
|
||||
signers: [chatRoom],
|
||||
});
|
||||
|
||||
const chat = await program.account.chatRoom.fetch(chatRoom.publicKey);
|
||||
const name = new TextDecoder("utf-8").decode(new Uint8Array(chat.name));
|
||||
assert.isTrue(name.startsWith("Test Chat")); // [u8; 280] => trailing zeros.
|
||||
assert.lengthOf(chat.messages, 33607);
|
||||
assert.strictEqual(chat.head.toNumber(), 0);
|
||||
assert.strictEqual(chat.tail.toNumber(), 0);
|
||||
});
|
||||
|
||||
it("Creates a user", async () => {
|
||||
const authority = program.provider.wallet.publicKey;
|
||||
const [user, bump] = await PublicKey.findProgramAddress(
|
||||
[authority.toBuffer()],
|
||||
program.programId
|
||||
);
|
||||
await program.rpc.createUser("My User", {
|
||||
accounts: {
|
||||
user,
|
||||
authority,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
},
|
||||
});
|
||||
const account = await program.account.user.fetch(user);
|
||||
assert.strictEqual(account.name, "My User");
|
||||
assert.isTrue(account.authority.equals(authority));
|
||||
});
|
||||
|
||||
it("Sends messages", async () => {
|
||||
const authority = program.provider.wallet.publicKey;
|
||||
const user = (
|
||||
await PublicKey.findProgramAddress(
|
||||
[authority.toBuffer()],
|
||||
program.programId
|
||||
)
|
||||
)[0];
|
||||
|
||||
// Only send a couple messages so the test doesn't take an eternity.
|
||||
const numMessages = 10;
|
||||
|
||||
// Generate random message strings.
|
||||
const messages = new Array(numMessages).fill("").map((msg) => {
|
||||
return (
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15)
|
||||
);
|
||||
});
|
||||
|
||||
// Send each message.
|
||||
for (let k = 0; k < numMessages; k += 1) {
|
||||
console.log("Sending message " + k);
|
||||
await program.rpc.sendMessage(messages[k], {
|
||||
accounts: {
|
||||
user,
|
||||
authority,
|
||||
chatRoom: chatRoom.publicKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Check the chat room state is as expected.
|
||||
const chat = await program.account.chatRoom.fetch(chatRoom.publicKey);
|
||||
const name = new TextDecoder("utf-8").decode(new Uint8Array(chat.name));
|
||||
assert.isTrue(name.startsWith("Test Chat")); // [u8; 280] => trailing zeros.
|
||||
assert.lengthOf(chat.messages, 33607);
|
||||
assert.strictEqual(chat.head.toNumber(), numMessages);
|
||||
assert.strictEqual(chat.tail.toNumber(), 0);
|
||||
chat.messages.forEach((msg, idx) => {
|
||||
if (idx < 10) {
|
||||
const data = new TextDecoder("utf-8").decode(new Uint8Array(msg.data));
|
||||
console.log("Message", data);
|
||||
assert.isTrue(msg.from.equals(user));
|
||||
assert.isTrue(data.startsWith(messages[idx]));
|
||||
} else {
|
||||
assert.strictEqual(
|
||||
JSON.stringify(msg.data),
|
||||
JSON.stringify(new Array(280).fill(0))
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.localnet]
|
||||
composite = "EHthziFziNoac9LBGxEaVN47Y3uUiRoXvqAiR6oes4iU"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run mocha -t 1000000 tests/"
|
|
@ -1,4 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "composite",
|
||||
"version": "0.25.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "anchor test"
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
[package]
|
||||
name = "composite"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.56"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "composite"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,64 +0,0 @@
|
|||
//! This example demonstrates the ability to compose together multiple
|
||||
//! structs deriving `Accounts`. See `CompositeUpdate`, below.
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("EHthziFziNoac9LBGxEaVN47Y3uUiRoXvqAiR6oes4iU");
|
||||
|
||||
#[program]
|
||||
mod composite {
|
||||
use super::*;
|
||||
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn composite_update(
|
||||
ctx: Context<CompositeUpdate>,
|
||||
dummy_a: u64,
|
||||
dummy_b: u64,
|
||||
) -> Result<()> {
|
||||
let a = &mut ctx.accounts.foo.dummy_a;
|
||||
let b = &mut ctx.accounts.bar.dummy_b;
|
||||
|
||||
a.data = dummy_a;
|
||||
b.data = dummy_b;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize<'info> {
|
||||
#[account(zero)]
|
||||
pub dummy_a: Account<'info, DummyA>,
|
||||
#[account(zero)]
|
||||
pub dummy_b: Account<'info, DummyB>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CompositeUpdate<'info> {
|
||||
foo: Foo<'info>,
|
||||
bar: Bar<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Foo<'info> {
|
||||
#[account(mut)]
|
||||
pub dummy_a: Account<'info, DummyA>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Bar<'info> {
|
||||
#[account(mut)]
|
||||
pub dummy_b: Account<'info, DummyB>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct DummyA {
|
||||
pub data: u64,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct DummyB {
|
||||
pub data: u64,
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
const { assert } = require("chai");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
|
||||
describe("composite", () => {
|
||||
const provider = anchor.AnchorProvider.local();
|
||||
|
||||
// Configure the client to use the local cluster.
|
||||
anchor.setProvider(provider);
|
||||
|
||||
it("Is initialized!", async () => {
|
||||
const program = anchor.workspace.Composite;
|
||||
|
||||
const dummyA = anchor.web3.Keypair.generate();
|
||||
const dummyB = anchor.web3.Keypair.generate();
|
||||
|
||||
const tx = await program.rpc.initialize({
|
||||
accounts: {
|
||||
dummyA: dummyA.publicKey,
|
||||
dummyB: dummyB.publicKey,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
signers: [dummyA, dummyB],
|
||||
instructions: [
|
||||
await program.account.dummyA.createInstruction(dummyA),
|
||||
await program.account.dummyB.createInstruction(dummyB),
|
||||
],
|
||||
});
|
||||
|
||||
await program.rpc.compositeUpdate(
|
||||
new anchor.BN(1234),
|
||||
new anchor.BN(4321),
|
||||
{
|
||||
accounts: {
|
||||
foo: {
|
||||
dummyA: dummyA.publicKey,
|
||||
},
|
||||
bar: {
|
||||
dummyB: dummyB.publicKey,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const dummyAAccount = await program.account.dummyA.fetch(dummyA.publicKey);
|
||||
const dummyBAccount = await program.account.dummyB.fetch(dummyB.publicKey);
|
||||
|
||||
assert.isTrue(dummyAAccount.data.eq(new anchor.BN(1234)));
|
||||
assert.isTrue(dummyBAccount.data.eq(new anchor.BN(4321)));
|
||||
});
|
||||
});
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
**/*.rs.bk
|
||||
node_modules
|
|
@ -1,16 +0,0 @@
|
|||
[features]
|
||||
seeds = false
|
||||
|
||||
[programs.localnet]
|
||||
callee = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||
caller = "HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L"
|
||||
|
||||
[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"
|
|
@ -1,4 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -1,12 +0,0 @@
|
|||
// 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.
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "cpi-returns",
|
||||
"version": "0.25.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "anchor run test-with-build"
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "callee"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "callee"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,57 +0,0 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
#[program]
|
||||
pub mod callee {
|
||||
use super::*;
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct StructReturn {
|
||||
pub value: u64,
|
||||
}
|
||||
|
||||
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
|
||||
let account = &mut ctx.accounts.account;
|
||||
account.value = 10;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn return_u64(_ctx: Context<CpiReturn>) -> Result<u64> {
|
||||
Ok(10)
|
||||
}
|
||||
|
||||
pub fn return_struct(_ctx: Context<CpiReturn>) -> Result<StructReturn> {
|
||||
let s = StructReturn { value: 11 };
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn return_vec(_ctx: Context<CpiReturn>) -> Result<Vec<u8>> {
|
||||
Ok(vec![12, 13, 14, 100])
|
||||
}
|
||||
|
||||
// Used for testing views
|
||||
pub fn return_u64_from_account(ctx: Context<CpiReturn>) -> Result<u64> {
|
||||
let account = &ctx.accounts.account;
|
||||
Ok(account.value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize<'info> {
|
||||
#[account(init, payer = user, space = 8 + 8)]
|
||||
pub account: Account<'info, CpiReturnAccount>,
|
||||
#[account(mut)]
|
||||
pub user: Signer<'info>,
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CpiReturn<'info> {
|
||||
pub account: Account<'info, CpiReturnAccount>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct CpiReturnAccount {
|
||||
pub value: u64,
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "caller"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "caller"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] }
|
||||
callee = { path = "../callee", features = ["cpi"] }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,75 +0,0 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use callee::cpi::accounts::CpiReturn;
|
||||
use callee::program::Callee;
|
||||
use callee::{self, CpiReturnAccount};
|
||||
|
||||
declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L");
|
||||
|
||||
#[program]
|
||||
pub mod caller {
|
||||
use super::*;
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct Struct {
|
||||
pub a: u64,
|
||||
pub b: u64,
|
||||
}
|
||||
|
||||
pub fn cpi_call_return_u64(ctx: Context<CpiReturnContext>) -> Result<()> {
|
||||
let cpi_program = ctx.accounts.cpi_return_program.to_account_info();
|
||||
let cpi_accounts = CpiReturn {
|
||||
account: ctx.accounts.cpi_return.to_account_info(),
|
||||
};
|
||||
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
|
||||
let result = callee::cpi::return_u64(cpi_ctx)?;
|
||||
let solana_return = result.get();
|
||||
anchor_lang::solana_program::log::sol_log_data(&[&solana_return.try_to_vec().unwrap()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cpi_call_return_struct(ctx: Context<CpiReturnContext>) -> Result<()> {
|
||||
let cpi_program = ctx.accounts.cpi_return_program.to_account_info();
|
||||
let cpi_accounts = CpiReturn {
|
||||
account: ctx.accounts.cpi_return.to_account_info(),
|
||||
};
|
||||
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
|
||||
let result = callee::cpi::return_struct(cpi_ctx)?;
|
||||
let solana_return = result.get();
|
||||
anchor_lang::solana_program::log::sol_log_data(&[&solana_return.try_to_vec().unwrap()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cpi_call_return_vec(ctx: Context<CpiReturnContext>) -> Result<()> {
|
||||
let cpi_program = ctx.accounts.cpi_return_program.to_account_info();
|
||||
let cpi_accounts = CpiReturn {
|
||||
account: ctx.accounts.cpi_return.to_account_info(),
|
||||
};
|
||||
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
|
||||
let result = callee::cpi::return_vec(cpi_ctx)?;
|
||||
let solana_return = result.get();
|
||||
anchor_lang::solana_program::log::sol_log_data(&[&solana_return.try_to_vec().unwrap()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn return_u64(ctx: Context<ReturnContext>) -> Result<u64> {
|
||||
Ok(99)
|
||||
}
|
||||
|
||||
pub fn return_struct(ctx: Context<ReturnContext>) -> Result<Struct> {
|
||||
Ok(Struct { a: 1, b: 2 })
|
||||
}
|
||||
|
||||
pub fn return_vec(ctx: Context<ReturnContext>) -> Result<Vec<u64>> {
|
||||
Ok(vec![1, 2, 3])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CpiReturnContext<'info> {
|
||||
#[account(mut)]
|
||||
pub cpi_return: Account<'info, CpiReturnAccount>,
|
||||
pub cpi_return_program: Program<'info, Callee>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ReturnContext {}
|
|
@ -1,226 +0,0 @@
|
|||
import assert from "assert";
|
||||
import * as anchor from "@project-serum/anchor";
|
||||
import * as borsh from "borsh";
|
||||
import { Program } from "@project-serum/anchor";
|
||||
import { Callee } from "../target/types/callee";
|
||||
import { Caller } from "../target/types/caller";
|
||||
import { ConfirmOptions } from "@solana/web3.js";
|
||||
|
||||
const { SystemProgram } = anchor.web3;
|
||||
|
||||
describe("CPI return", () => {
|
||||
const provider = anchor.AnchorProvider.env();
|
||||
anchor.setProvider(provider);
|
||||
|
||||
const callerProgram = anchor.workspace.Caller as Program<Caller>;
|
||||
const calleeProgram = anchor.workspace.Callee as Program<Callee>;
|
||||
|
||||
const getReturnLog = (confirmedTransaction) => {
|
||||
const prefix = "Program return: ";
|
||||
let log = confirmedTransaction.meta.logMessages.find((log) =>
|
||||
log.startsWith(prefix)
|
||||
);
|
||||
log = log.slice(prefix.length);
|
||||
const [key, data] = log.split(" ", 2);
|
||||
const buffer = Buffer.from(data, "base64");
|
||||
return [key, data, buffer];
|
||||
};
|
||||
|
||||
const cpiReturn = anchor.web3.Keypair.generate();
|
||||
|
||||
const confirmOptions: ConfirmOptions = { commitment: "confirmed" };
|
||||
|
||||
it("can initialize", async () => {
|
||||
await calleeProgram.methods
|
||||
.initialize()
|
||||
.accounts({
|
||||
account: cpiReturn.publicKey,
|
||||
user: provider.wallet.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
})
|
||||
.signers([cpiReturn])
|
||||
.rpc();
|
||||
});
|
||||
|
||||
it("can return u64 from a cpi", async () => {
|
||||
const tx = await callerProgram.methods
|
||||
.cpiCallReturnU64()
|
||||
.accounts({
|
||||
cpiReturn: cpiReturn.publicKey,
|
||||
cpiReturnProgram: calleeProgram.programId,
|
||||
})
|
||||
.rpc(confirmOptions);
|
||||
let t = await provider.connection.getTransaction(tx, {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
const [key, data, buffer] = getReturnLog(t);
|
||||
assert.equal(key, calleeProgram.programId);
|
||||
|
||||
// Check for matching log on receive side
|
||||
let receiveLog = t.meta.logMessages.find(
|
||||
(log) => log == `Program data: ${data}`
|
||||
);
|
||||
assert(receiveLog !== undefined);
|
||||
|
||||
const reader = new borsh.BinaryReader(buffer);
|
||||
assert.equal(reader.readU64().toNumber(), 10);
|
||||
});
|
||||
|
||||
it("can make a non-cpi call to a function that returns a u64", async () => {
|
||||
const tx = await calleeProgram.methods
|
||||
.returnU64()
|
||||
.accounts({
|
||||
account: cpiReturn.publicKey,
|
||||
})
|
||||
.rpc(confirmOptions);
|
||||
let t = await provider.connection.getTransaction(tx, {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
const [key, , buffer] = getReturnLog(t);
|
||||
assert.equal(key, calleeProgram.programId);
|
||||
const reader = new borsh.BinaryReader(buffer);
|
||||
assert.equal(reader.readU64().toNumber(), 10);
|
||||
});
|
||||
|
||||
it("can return a struct from a cpi", async () => {
|
||||
const tx = await callerProgram.methods
|
||||
.cpiCallReturnStruct()
|
||||
.accounts({
|
||||
cpiReturn: cpiReturn.publicKey,
|
||||
cpiReturnProgram: calleeProgram.programId,
|
||||
})
|
||||
.rpc(confirmOptions);
|
||||
let t = await provider.connection.getTransaction(tx, {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
const [key, data, buffer] = getReturnLog(t);
|
||||
assert.equal(key, calleeProgram.programId);
|
||||
|
||||
// Check for matching log on receive side
|
||||
let receiveLog = t.meta.logMessages.find(
|
||||
(log) => log == `Program data: ${data}`
|
||||
);
|
||||
assert(receiveLog !== undefined);
|
||||
|
||||
// Deserialize the struct and validate
|
||||
class Assignable {
|
||||
constructor(properties) {
|
||||
Object.keys(properties).map((key) => {
|
||||
this[key] = properties[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
class Data extends Assignable {}
|
||||
const schema = new Map([
|
||||
[Data, { kind: "struct", fields: [["value", "u64"]] }],
|
||||
]);
|
||||
const deserialized = borsh.deserialize(schema, Data, buffer);
|
||||
assert(deserialized.value.toNumber() === 11);
|
||||
});
|
||||
|
||||
it("can return a vec from a cpi", async () => {
|
||||
const tx = await callerProgram.methods
|
||||
.cpiCallReturnVec()
|
||||
.accounts({
|
||||
cpiReturn: cpiReturn.publicKey,
|
||||
cpiReturnProgram: calleeProgram.programId,
|
||||
})
|
||||
.rpc(confirmOptions);
|
||||
let t = await provider.connection.getTransaction(tx, {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
const [key, data, buffer] = getReturnLog(t);
|
||||
assert.equal(key, calleeProgram.programId);
|
||||
|
||||
// Check for matching log on receive side
|
||||
let receiveLog = t.meta.logMessages.find(
|
||||
(log) => log == `Program data: ${data}`
|
||||
);
|
||||
assert(receiveLog !== undefined);
|
||||
|
||||
const reader = new borsh.BinaryReader(buffer);
|
||||
const array = reader.readArray(() => reader.readU8());
|
||||
assert.deepStrictEqual(array, [12, 13, 14, 100]);
|
||||
});
|
||||
|
||||
it("sets a return value in idl", async () => {
|
||||
// @ts-expect-error
|
||||
const returnu64Instruction = calleeProgram._idl.instructions.find(
|
||||
(f) => f.name == "returnU64"
|
||||
);
|
||||
assert.equal(returnu64Instruction.returns, "u64");
|
||||
|
||||
// @ts-expect-error
|
||||
const returnStructInstruction = calleeProgram._idl.instructions.find(
|
||||
(f) => f.name == "returnStruct"
|
||||
);
|
||||
assert.deepStrictEqual(returnStructInstruction.returns, {
|
||||
defined: "StructReturn",
|
||||
});
|
||||
});
|
||||
|
||||
it("can return a u64 via view", async () => {
|
||||
// @ts-expect-error
|
||||
assert(new anchor.BN(99).eq(await callerProgram.views.returnU64()));
|
||||
// Via methods API
|
||||
assert(
|
||||
new anchor.BN(99).eq(await callerProgram.methods.returnU64().view())
|
||||
);
|
||||
});
|
||||
|
||||
it("can return a struct via view", async () => {
|
||||
// @ts-expect-error
|
||||
const struct = await callerProgram.views.returnStruct();
|
||||
assert(struct.a.eq(new anchor.BN(1)));
|
||||
assert(struct.b.eq(new anchor.BN(2)));
|
||||
// Via methods API
|
||||
const struct2 = await callerProgram.methods.returnStruct().view();
|
||||
assert(struct2.a.eq(new anchor.BN(1)));
|
||||
assert(struct2.b.eq(new anchor.BN(2)));
|
||||
});
|
||||
|
||||
it("can return a vec via view", async () => {
|
||||
// @ts-expect-error
|
||||
const vec = await callerProgram.views.returnVec();
|
||||
assert(vec[0].eq(new anchor.BN(1)));
|
||||
assert(vec[1].eq(new anchor.BN(2)));
|
||||
assert(vec[2].eq(new anchor.BN(3)));
|
||||
// Via methods API
|
||||
const vec2 = await callerProgram.methods.returnVec().view();
|
||||
assert(vec2[0].eq(new anchor.BN(1)));
|
||||
assert(vec2[1].eq(new anchor.BN(2)));
|
||||
assert(vec2[2].eq(new anchor.BN(3)));
|
||||
});
|
||||
|
||||
it("can return a u64 from an account via view", async () => {
|
||||
const value = new anchor.BN(10);
|
||||
assert(
|
||||
value.eq(
|
||||
await calleeProgram.methods
|
||||
.returnU64FromAccount()
|
||||
.accounts({ account: cpiReturn.publicKey })
|
||||
.view()
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("cant call view on mutable instruction", async () => {
|
||||
assert.equal(calleeProgram.views.initialize, undefined);
|
||||
try {
|
||||
await calleeProgram.methods
|
||||
.initialize()
|
||||
.accounts({
|
||||
account: cpiReturn.publicKey,
|
||||
user: provider.wallet.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
})
|
||||
.signers([cpiReturn])
|
||||
.view();
|
||||
} catch (e) {
|
||||
assert(e.message.includes("Method does not support views"));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"lib": ["es2015"],
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
[programs.localnet]
|
||||
custom_coder = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||
spl_token = "FmpfPa1LHEYRbueNMnwNVd2JvyQ89GXGWdyZEXNNKV8w"
|
||||
native_system = "9NxAd91hhJ3ZBTHytYP894y4ESRKG7n8VbLgdyYGJFLB"
|
||||
spl_associated_token = "4dUGnkre6uBhX1abB4ofkoecGN4aDXdiWSaWLUjVw6bh"
|
||||
|
||||
[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"
|
||||
|
||||
[features]
|
|
@ -1,4 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -1,12 +0,0 @@
|
|||
// 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.
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "custom-coder",
|
||||
"version": "0.20.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "anchor test"
|
||||
},
|
||||
"dependencies": {
|
||||
"mocha": "^10.0.0",
|
||||
"ts-mocha": "^10.0.0"
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "custom-coder"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.56"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "custom_coder"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,14 +0,0 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
#[program]
|
||||
pub mod custom_coder {
|
||||
use super::*;
|
||||
pub fn initialize(_ctx: Context<Initialize>, a: Option<u8>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize {}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "native-system"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.56"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "native_system"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,214 +0,0 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("9NxAd91hhJ3ZBTHytYP894y4ESRKG7n8VbLgdyYGJFLB");
|
||||
|
||||
#[program]
|
||||
pub mod native_system {
|
||||
use super::*;
|
||||
|
||||
pub fn create_account(
|
||||
ctx: Context<CreateAccount>,
|
||||
lamports: u64,
|
||||
space: u64,
|
||||
owner: Pubkey,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn assign(ctx: Context<Assign>, owner: Pubkey) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn transfer(ctx: Context<Transfer>, lamports: u64) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_account_with_seed(
|
||||
ctx: Context<CreateAccountWithSeed>,
|
||||
base: Pubkey,
|
||||
seed: String,
|
||||
lamports: u64,
|
||||
space: u64,
|
||||
owner: Pubkey,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn advance_nonce_account(
|
||||
ctx: Context<AdvanceNonceAccount>,
|
||||
authorized: Pubkey,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn withdraw_nonce_account(ctx: Context<WithdrawNonceAccount>, lamports: u64) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn initialize_nonce_account(
|
||||
ctx: Context<InitializeNonceAccount>,
|
||||
authorized: Pubkey,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn authorize_nonce_account(
|
||||
ctx: Context<AuthorizeNonceAccount>,
|
||||
authorized: Pubkey,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn allocate(ctx: Context<Allocate>, space: u64) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn allocate_with_seed(
|
||||
ctx: Context<AllocateWithSeed>,
|
||||
base: Pubkey,
|
||||
seed: String,
|
||||
space: u64,
|
||||
owner: Pubkey,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn assign_with_seed(
|
||||
ctx: Context<AssignWithSeed>,
|
||||
base: Pubkey,
|
||||
seed: String,
|
||||
owner: Pubkey,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn transfer_with_seed(
|
||||
ctx: Context<TransferWithSeed>,
|
||||
lamports: u64,
|
||||
seed: String,
|
||||
owner: Pubkey,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CreateAccount<'info> {
|
||||
#[account(mut)]
|
||||
from: Signer<'info>,
|
||||
#[account(mut)]
|
||||
to: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Assign<'info> {
|
||||
#[account(mut)]
|
||||
pubkey: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Transfer<'info> {
|
||||
#[account(mut)]
|
||||
from: Signer<'info>,
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
to: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CreateAccountWithSeed<'info> {
|
||||
#[account(mut)]
|
||||
from: Signer<'info>,
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
to: AccountInfo<'info>,
|
||||
base: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct AdvanceNonceAccount<'info> {
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
nonce: AccountInfo<'info>,
|
||||
/// CHECK:
|
||||
recent_blockhashes: AccountInfo<'info>,
|
||||
authorized: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct WithdrawNonceAccount<'info> {
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
nonce: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
to: AccountInfo<'info>,
|
||||
/// CHECK:
|
||||
recent_blockhashes: AccountInfo<'info>,
|
||||
rent: Sysvar<'info, Rent>,
|
||||
authorized: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeNonceAccount<'info> {
|
||||
#[account(mut)]
|
||||
nonce: Signer<'info>,
|
||||
/// CHECK:
|
||||
recent_blockhashes: AccountInfo<'info>,
|
||||
rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct AuthorizeNonceAccount<'info> {
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
nonce: AccountInfo<'info>,
|
||||
authorized: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Allocate<'info> {
|
||||
#[account(mut)]
|
||||
pubkey: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct AllocateWithSeed<'info> {
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
account: AccountInfo<'info>,
|
||||
base: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct AssignWithSeed<'info> {
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
account: AccountInfo<'info>,
|
||||
base: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TransferWithSeed<'info> {
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
from: AccountInfo<'info>,
|
||||
base: Signer<'info>,
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
to: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
||||
pub struct FeeCalculator {
|
||||
pub lamports_per_signature: u64,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct Nonce {
|
||||
pub version: u32,
|
||||
pub state: u32,
|
||||
pub authorized_pubkey: Pubkey,
|
||||
pub nonce: Pubkey,
|
||||
pub fee_calculator: FeeCalculator,
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
[package]
|
||||
name = "spl-associated-token"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "spl_associated_token"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[profile.release]
|
||||
overflow-checks = true
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,33 +0,0 @@
|
|||
// This file is autogenerated with https://github.com/acheroncrypto/native-to-anchor
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("4dUGnkre6uBhX1abB4ofkoecGN4aDXdiWSaWLUjVw6bh");
|
||||
|
||||
#[program]
|
||||
pub mod spl_associated_token {
|
||||
use super::*;
|
||||
|
||||
pub fn create(ctx: Context<Create>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Create<'info> {
|
||||
#[account(mut)]
|
||||
authority: Signer<'info>,
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
associated_account: AccountInfo<'info>,
|
||||
/// CHECK:
|
||||
owner: AccountInfo<'info>,
|
||||
/// CHECK:
|
||||
mint: AccountInfo<'info>,
|
||||
/// CHECK:
|
||||
system_program: AccountInfo<'info>,
|
||||
/// CHECK:
|
||||
token_program: AccountInfo<'info>,
|
||||
/// CHECK:
|
||||
rent: AccountInfo<'info>,
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "spl-token"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.56"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "spl_token"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue