Skip to content

Commit

Permalink
anchor_spl crate and example (coral-xyz#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante authored Jan 16, 2021
1 parent b5ca210 commit 11272e3
Show file tree
Hide file tree
Showing 14 changed files with 414 additions and 18 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ members = [
"syn",
"attribute/*",
"derive/*",
"spl",
]
2 changes: 2 additions & 0 deletions examples/spl/token-proxy/Anchor.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cluster = "localnet"
wallet = "~/.config/solana/id.json"
4 changes: 4 additions & 0 deletions examples/spl/token-proxy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[workspace]
members = [
"programs/*"
]
20 changes: 20 additions & 0 deletions examples/spl/token-proxy/programs/token-proxy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"] }
2 changes: 2 additions & 0 deletions examples/spl/token-proxy/programs/token-proxy/Xargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []
96 changes: 96 additions & 0 deletions examples/spl/token-proxy/programs/token-proxy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
150 changes: 150 additions & 0 deletions examples/spl/token-proxy/tests/token-proxy.js
Original file line number Diff line number Diff line change
@@ -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,
}),
];
}
10 changes: 10 additions & 0 deletions spl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "anchor-spl"
version = "0.0.0-alpha.0"
authors = ["Armani Ferrante <[email protected]>"]
edition = "2018"

[dependencies]
anchor-lang = { path = "../", features = ["derive"] }
solana-program = "1.4.3"
spl-token = { version = "3.0.1", features = ["no-entrypoint"] }
1 change: 1 addition & 0 deletions spl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod token;
Loading

0 comments on commit 11272e3

Please sign in to comment.