anchor_spl crate and example (#24)
This commit is contained in:
parent
b5ca210696
commit
11272e3677
|
@ -16,6 +16,9 @@ _examples: &examples
|
|||
- nvm install $NODE_VERSION
|
||||
- npm install -g mocha
|
||||
- npm install -g @project-serum/anchor
|
||||
- npm install -g @project-serum/serum
|
||||
- npm install -g @project-serum/common
|
||||
- npm install -g @solana/spl-token
|
||||
- sudo apt-get install -y pkg-config build-essential libudev-dev
|
||||
- sh -c "$(curl -sSfL https://release.solana.com/v1.5.0/install)"
|
||||
- export PATH="/home/travis/.local/share/solana/install/active_release/bin:$PATH"
|
||||
|
@ -42,6 +45,7 @@ jobs:
|
|||
script:
|
||||
- pushd examples/sysvars && anchor test && popd
|
||||
- pushd examples/composite && anchor test && popd
|
||||
- pushd examples/spl/token-proxy && anchor test && popd
|
||||
- pushd examples/tutorial/basic-0 && anchor test && popd
|
||||
- pushd examples/tutorial/basic-1 && anchor test && popd
|
||||
- pushd examples/tutorial/basic-2 && anchor test && popd
|
||||
|
|
|
@ -121,6 +121,15 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-spl"
|
||||
version = "0.0.0-alpha.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"solana-program",
|
||||
"spl-token 3.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-syn"
|
||||
version = "0.0.0-alpha.0"
|
||||
|
|
|
@ -27,4 +27,5 @@ members = [
|
|||
"syn",
|
||||
"attribute/*",
|
||||
"derive/*",
|
||||
"spl",
|
||||
]
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -0,0 +1,4 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "token-proxy"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "token_proxy"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
|
||||
anchor-spl = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
|
||||
serum-borsh = { version = "0.8.0-serum.1", features = ["serum-program"] }
|
||||
solana-program = "1.4.3"
|
||||
solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,96 @@
|
|||
//! This example demonstrates the use of the `anchor_spl::token` CPI client.
|
||||
|
||||
#![feature(proc_macro_hygiene)]
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{self, Burn, MintTo, Transfer};
|
||||
|
||||
#[program]
|
||||
mod token_proxy {
|
||||
use super::*;
|
||||
|
||||
pub fn proxy_transfer(ctx: Context<ProxyTransfer>, amount: u64) -> ProgramResult {
|
||||
token::transfer(ctx.accounts.into(), amount)
|
||||
}
|
||||
|
||||
pub fn proxy_mint_to(ctx: Context<ProxyMintTo>, amount: u64) -> ProgramResult {
|
||||
token::mint_to(ctx.accounts.into(), amount)
|
||||
}
|
||||
|
||||
pub fn proxy_burn(ctx: Context<ProxyBurn>, amount: u64) -> ProgramResult {
|
||||
token::burn(ctx.accounts.into(), amount)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ProxyTransfer<'info> {
|
||||
#[account(signer)]
|
||||
pub authority: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub from: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub to: AccountInfo<'info>,
|
||||
pub token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ProxyMintTo<'info> {
|
||||
#[account(signer)]
|
||||
pub authority: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub mint: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub to: AccountInfo<'info>,
|
||||
pub token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ProxyBurn<'info> {
|
||||
#[account(signer)]
|
||||
pub authority: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub mint: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub to: AccountInfo<'info>,
|
||||
pub token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, 'info> From<&mut ProxyTransfer<'info>>
|
||||
for CpiContext<'a, 'b, 'c, 'info, Transfer<'info>>
|
||||
{
|
||||
fn from(accounts: &mut ProxyTransfer<'info>) -> CpiContext<'a, 'b, 'c, 'info, Transfer<'info>> {
|
||||
let cpi_accounts = Transfer {
|
||||
from: accounts.from.clone(),
|
||||
to: accounts.to.clone(),
|
||||
authority: accounts.authority.clone(),
|
||||
};
|
||||
let cpi_program = accounts.token_program.clone();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, 'info> From<&mut ProxyMintTo<'info>>
|
||||
for CpiContext<'a, 'b, 'c, 'info, MintTo<'info>>
|
||||
{
|
||||
fn from(accounts: &mut ProxyMintTo<'info>) -> CpiContext<'a, 'b, 'c, 'info, MintTo<'info>> {
|
||||
let cpi_accounts = MintTo {
|
||||
mint: accounts.mint.clone(),
|
||||
to: accounts.to.clone(),
|
||||
authority: accounts.authority.clone(),
|
||||
};
|
||||
let cpi_program = accounts.token_program.clone();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, 'info> From<&mut ProxyBurn<'info>> for CpiContext<'a, 'b, 'c, 'info, Burn<'info>> {
|
||||
fn from(accounts: &mut ProxyBurn<'info>) -> CpiContext<'a, 'b, 'c, 'info, Burn<'info>> {
|
||||
let cpi_accounts = Burn {
|
||||
mint: accounts.mint.clone(),
|
||||
to: accounts.to.clone(),
|
||||
authority: accounts.authority.clone(),
|
||||
};
|
||||
let cpi_program = accounts.token_program.clone();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
const anchor = require("@project-serum/anchor");
|
||||
const assert = require("assert");
|
||||
|
||||
describe("token", () => {
|
||||
const provider = anchor.Provider.local();
|
||||
|
||||
// Configure the client to use the local cluster.
|
||||
anchor.setProvider(provider);
|
||||
|
||||
const program = anchor.workspace.TokenProxy;
|
||||
|
||||
let mint = null;
|
||||
let from = null;
|
||||
let to = null;
|
||||
|
||||
it("Initializes test state", async () => {
|
||||
mint = await createMint(provider);
|
||||
from = await createTokenAccount(provider, mint, provider.wallet.publicKey);
|
||||
to = await createTokenAccount(provider, mint, provider.wallet.publicKey);
|
||||
});
|
||||
|
||||
it("Mints a token", async () => {
|
||||
await program.rpc.proxyMintTo(new anchor.BN(1000), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
mint,
|
||||
to: from,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const fromAccount = await getTokenAccount(provider, from);
|
||||
|
||||
assert.ok(fromAccount.amount.eq(new anchor.BN(1000)));
|
||||
});
|
||||
|
||||
it("Transfers a token", async () => {
|
||||
await program.rpc.proxyTransfer(new anchor.BN(500), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
to,
|
||||
from,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const fromAccount = await getTokenAccount(provider, from);
|
||||
const toAccount = await getTokenAccount(provider, to);
|
||||
|
||||
assert.ok(fromAccount.amount.eq(new anchor.BN(500)));
|
||||
assert.ok(fromAccount.amount.eq(new anchor.BN(500)));
|
||||
});
|
||||
|
||||
it("Burns a token", async () => {
|
||||
await program.rpc.proxyBurn(new anchor.BN(499), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
mint,
|
||||
to,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const toAccount = await getTokenAccount(provider, to);
|
||||
assert.ok(toAccount.amount.eq(new anchor.BN(1)));
|
||||
});
|
||||
});
|
||||
|
||||
// SPL token client boilerplate for test initialization. Everything below here is
|
||||
// mostly irrelevant to the point of the example.
|
||||
|
||||
const serumCmn = require("@project-serum/common");
|
||||
const TokenInstructions = require("@project-serum/serum").TokenInstructions;
|
||||
|
||||
async function getTokenAccount(provider, addr) {
|
||||
return await serumCmn.getTokenAccount(provider, addr);
|
||||
}
|
||||
|
||||
async function createMint(provider, authority) {
|
||||
if (authority === undefined) {
|
||||
authority = provider.wallet.publicKey;
|
||||
}
|
||||
const mint = new anchor.web3.Account();
|
||||
const instructions = await createMintInstructions(
|
||||
provider,
|
||||
authority,
|
||||
mint.publicKey
|
||||
);
|
||||
|
||||
const tx = new anchor.web3.Transaction();
|
||||
tx.add(...instructions);
|
||||
|
||||
await provider.send(tx, [mint]);
|
||||
|
||||
return mint.publicKey;
|
||||
}
|
||||
|
||||
async function createMintInstructions(provider, authority, mint) {
|
||||
let instructions = [
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
newAccountPubkey: mint,
|
||||
space: 82,
|
||||
lamports: await provider.connection.getMinimumBalanceForRentExemption(82),
|
||||
programId: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
TokenInstructions.initializeMint({
|
||||
mint,
|
||||
decimals: 0,
|
||||
mintAuthority: authority,
|
||||
}),
|
||||
];
|
||||
return instructions;
|
||||
}
|
||||
|
||||
async function createTokenAccount(provider, mint, owner) {
|
||||
const vault = new anchor.web3.Account();
|
||||
const tx = new anchor.web3.Transaction();
|
||||
tx.add(
|
||||
...(await createTokenAccountInstrs(provider, vault.publicKey, mint, owner))
|
||||
);
|
||||
await provider.send(tx, [vault]);
|
||||
return vault.publicKey;
|
||||
}
|
||||
|
||||
async function createTokenAccountInstrs(
|
||||
provider,
|
||||
newAccountPubkey,
|
||||
mint,
|
||||
owner,
|
||||
lamports
|
||||
) {
|
||||
if (lamports === undefined) {
|
||||
lamports = await provider.connection.getMinimumBalanceForRentExemption(165);
|
||||
}
|
||||
return [
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
newAccountPubkey,
|
||||
space: 165,
|
||||
lamports,
|
||||
programId: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
TokenInstructions.initializeAccount({
|
||||
account: newAccountPubkey,
|
||||
mint,
|
||||
owner,
|
||||
}),
|
||||
];
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "anchor-spl"
|
||||
version = "0.0.0-alpha.0"
|
||||
authors = ["Armani Ferrante <armaniferrante@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../", features = ["derive"] }
|
||||
solana-program = "1.4.3"
|
||||
spl-token = { version = "3.0.1", features = ["no-entrypoint"] }
|
|
@ -0,0 +1 @@
|
|||
pub mod token;
|
|
@ -0,0 +1,96 @@
|
|||
use anchor_lang::{Accounts, CpiContext};
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::entrypoint::ProgramResult;
|
||||
|
||||
pub fn transfer<'a, 'b, 'c, 'info>(
|
||||
ctx: CpiContext<'a, 'b, 'c, 'info, Transfer<'info>>,
|
||||
amount: u64,
|
||||
) -> ProgramResult {
|
||||
let ix = spl_token::instruction::transfer(
|
||||
&spl_token::ID,
|
||||
ctx.accounts.from.key,
|
||||
ctx.accounts.to.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[],
|
||||
amount,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.from.clone(),
|
||||
ctx.accounts.to.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
ctx.program.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mint_to<'a, 'b, 'c, 'info>(
|
||||
ctx: CpiContext<'a, 'b, 'c, 'info, MintTo<'info>>,
|
||||
amount: u64,
|
||||
) -> ProgramResult {
|
||||
let ix = spl_token::instruction::mint_to(
|
||||
&spl_token::ID,
|
||||
ctx.accounts.mint.key,
|
||||
ctx.accounts.to.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[],
|
||||
amount,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.mint.clone(),
|
||||
ctx.accounts.to.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
ctx.program.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn burn<'a, 'b, 'c, 'info>(
|
||||
ctx: CpiContext<'a, 'b, 'c, 'info, Burn<'info>>,
|
||||
amount: u64,
|
||||
) -> ProgramResult {
|
||||
let ix = spl_token::instruction::burn(
|
||||
&spl_token::ID,
|
||||
ctx.accounts.to.key,
|
||||
ctx.accounts.mint.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[],
|
||||
amount,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.to.clone(),
|
||||
ctx.accounts.mint.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
ctx.program.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Transfer<'info> {
|
||||
pub from: AccountInfo<'info>,
|
||||
pub to: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct MintTo<'info> {
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub to: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Burn<'info> {
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub to: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
|
@ -76,7 +76,7 @@ where
|
|||
T: AccountSerialize + AccountDeserialize + Clone,
|
||||
{
|
||||
fn try_accounts_init(
|
||||
program_id: &Pubkey,
|
||||
_program_id: &Pubkey,
|
||||
accounts: &mut &[AccountInfo<'info>],
|
||||
) -> Result<Self, ProgramError> {
|
||||
if accounts.len() == 0 {
|
||||
|
|
|
@ -81,22 +81,23 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
AccountField::Field(f) => {
|
||||
let ident = &f.ident;
|
||||
let info = match f.ty {
|
||||
Ty::AccountInfo => quote! { #ident },
|
||||
// Only ProgramAccounts are automatically saved (when
|
||||
// marked `#[account(mut)]`).
|
||||
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
|
||||
_ => return quote! {},
|
||||
};
|
||||
match f.is_mut {
|
||||
false => quote! {},
|
||||
true => quote! {
|
||||
// Only persist the change if the account is owned by the
|
||||
// current program.
|
||||
if program_id == self.#info.owner {
|
||||
let info = self.#info;
|
||||
let mut data = info.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
let mut cursor = std::io::Cursor::new(dst);
|
||||
self.#ident.try_serialize(&mut cursor)?;
|
||||
}
|
||||
// Only persist the change if the account is owned by the
|
||||
// current program.
|
||||
if program_id == self.#info.owner {
|
||||
let info = self.#info;
|
||||
let mut data = info.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
let mut cursor = std::io::Cursor::new(dst);
|
||||
self.#ident.try_serialize(&mut cursor)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -144,8 +145,8 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
};
|
||||
|
||||
quote! {
|
||||
impl#combined_generics Accounts#trait_generics for #name#strct_generics {
|
||||
fn try_accounts(program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
|
||||
impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
|
||||
fn try_accounts(program_id: &solana_program::pubkey::Pubkey, accounts: &mut &[solana_program::account_info::AccountInfo<'info>]) -> Result<Self, solana_program::program_error::ProgramError> {
|
||||
// Deserialize each account.
|
||||
#(#deser_fields)*
|
||||
|
||||
|
@ -159,8 +160,8 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
impl#combined_generics ToAccountInfos#trait_generics for #name#strct_generics {
|
||||
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
|
||||
impl#combined_generics anchor_lang::ToAccountInfos#trait_generics for #name#strct_generics {
|
||||
fn to_account_infos(&self) -> Vec<solana_program::account_info::AccountInfo<'info>> {
|
||||
let mut account_infos = vec![];
|
||||
|
||||
#(#to_acc_infos)*
|
||||
|
@ -169,8 +170,8 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
impl#combined_generics ToAccountMetas for #name#strct_generics {
|
||||
fn to_account_metas(&self) -> Vec<AccountMeta> {
|
||||
impl#combined_generics anchor_lang::ToAccountMetas for #name#strct_generics {
|
||||
fn to_account_metas(&self) -> Vec<solana_program::instruction::AccountMeta> {
|
||||
let mut account_metas = vec![];
|
||||
|
||||
#(#to_acc_metas)*
|
||||
|
@ -181,7 +182,7 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
}
|
||||
|
||||
impl#strct_generics #name#strct_generics {
|
||||
pub fn exit(&self, program_id: &Pubkey) -> ProgramResult {
|
||||
pub fn exit(&self, program_id: &solana_program::pubkey::Pubkey) -> solana_program::entrypoint::ProgramResult {
|
||||
#(#on_save)*
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue