Merge branch 'master' into types

This commit is contained in:
armaniferrante 2021-09-25 10:50:10 -07:00
commit 26b7337481
No known key found for this signature in database
GPG Key ID: 58BEF301E91F7828
346 changed files with 10689 additions and 6027 deletions

2
.gitignore vendored
View File

@ -10,6 +10,8 @@ target/
test-ledger
examples/*/Cargo.lock
examples/**/Cargo.lock
tests/*/Cargo.lock
tests/**/Cargo.lock
.DS_Store
docs/yarn.lock
ts/docs/

10
.gitmodules vendored
View File

@ -1,15 +1,15 @@
[submodule "examples/swap/deps/serum-dex"]
path = examples/swap/deps/serum-dex
path = tests/swap/deps/serum-dex
url = https://github.com/project-serum/serum-dex
[submodule "examples/cfo/deps/serum-dex"]
path = examples/cfo/deps/serum-dex
path = tests/cfo/deps/serum-dex
url = https://github.com/project-serum/serum-dex
[submodule "examples/cfo/deps/swap"]
path = examples/cfo/deps/swap
path = tests/cfo/deps/swap
url = https://github.com/project-serum/swap.git
[submodule "examples/cfo/deps/stake"]
path = examples/cfo/deps/stake
path = tests/cfo/deps/stake
url = https://github.com/project-serum/stake.git
[submodule "examples/permissioned-markets/deps/serum-dex"]
path = examples/permissioned-markets/deps/serum-dex
path = tests/permissioned-markets/deps/serum-dex
url = https://github.com/project-serum/serum-dex

View File

@ -6,7 +6,7 @@ cache: cargo
env:
global:
- NODE_VERSION="14.7.0"
- SOLANA_CLI_VERSION="1.7.4"
- SOLANA_CLI_VERSION="1.7.11"
git:
submodules: true
@ -16,7 +16,7 @@ _defaults: &defaults
- nvm install $NODE_VERSION
- sudo apt-get install -y pkg-config build-essential libudev-dev
_examples: &examples
_tests: &tests
before_install:
- nvm install $NODE_VERSION
- npm install -g mocha
@ -47,39 +47,40 @@ jobs:
- pushd ts && yarn && popd
- pushd ts && yarn test && popd
- pushd ts && yarn lint && popd
- <<: *examples
name: Runs the examples 1
- <<: *tests
name: Runs the e2e tests 1
script:
- pushd client/example && ./run-test.sh && popd
- pushd examples/sysvars && anchor test && popd
- pushd examples/composite && anchor test && popd
- pushd examples/errors && anchor test && popd
- pushd examples/spl/token-proxy && anchor test && popd
- pushd examples/multisig && anchor test && popd
- pushd examples/interface && anchor test && popd
- pushd examples/lockup && anchor test && popd
- <<: *examples
name: Runs the examples 2
- pushd tests/sysvars && anchor test && popd
- pushd tests/composite && anchor test && popd
- pushd tests/errors && anchor test && popd
- pushd tests/spl/token-proxy && anchor test && popd
- pushd tests/multisig && anchor test && popd
- pushd tests/interface && anchor test && popd
- pushd tests/lockup && anchor test && popd
- pushd tests/permissioned-markets/deps/serum-dex/dex && cargo build-bpf && cd ../../../ && anchor test && popd
- <<: *tests
name: Runs the e2e tests 2
script:
- pushd examples/misc && anchor test && popd
- pushd examples/events && anchor test && popd
- pushd examples/cashiers-check && anchor test && popd
- pushd examples/typescript && yarn && anchor test && popd
- pushd examples/zero-copy && yarn && anchor test && popd
- pushd examples/chat && yarn && anchor test && popd
- pushd examples/ido-pool && yarn && anchor test && popd
- pushd examples/swap/deps/serum-dex/dex && cargo build-bpf && cd ../../../ && anchor test && popd
- pushd examples/cfo && anchor run test && popd
- <<: *examples
name: Runs the examples 3
- pushd tests/misc && anchor test && popd
- pushd tests/events && anchor test && popd
- pushd tests/cashiers-check && anchor test && popd
- pushd tests/typescript && yarn && anchor test && popd
- pushd tests/zero-copy && yarn && anchor test && popd
- pushd tests/chat && yarn && anchor test && popd
- pushd tests/ido-pool && yarn && anchor test && popd
- pushd tests/swap/deps/serum-dex/dex && cargo build-bpf && cd ../../../ && anchor test && popd
- pushd tests/cfo && anchor run test-with-build && popd
- <<: *tests
name: Runs the e2e tests 3
script:
- pushd ts && yarn && yarn build && npm link && popd
- pushd examples/escrow && npm link @project-serum/anchor && yarn && popd
- pushd examples/escrow && anchor build && npx ts-node createIDLType.ts && anchor test --skip-build && popd
- pushd examples/pyth && yarn && anchor test && popd
- pushd tests/escrow && yarn && anchor test && popd
- pushd tests/pyth && yarn && 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
- pushd examples/tutorial/basic-3 && anchor test && popd
- pushd examples/tutorial/basic-4 && anchor test && popd
- pushd examples/tutorial/basic-5 && anchor test && popd

View File

@ -11,6 +11,110 @@ incremented for features.
## [Unreleased]
### Features
* lang: Add `--detach` flag to `anchor test` ([#770](https://github.com/project-serum/anchor/pull/770)).
* lang: Add `associated_token` keyword for initializing associated token accounts within `#[derive(Accounts)]` ([#790](https://github.com/project-serum/anchor/pull/790)).
## [0.16.1] - 2021-09-17
### Fixes
* lang: `Signer` type now sets isSigner to true in the IDL ([#750](https://github.com/project-serum/anchor/pull/750)).
## [0.16.0] - 2021-09-16
### Features
* lang: `Program` type introduced for executable accounts ([#705](https://github.com/project-serum/anchor/pull/705)).
* lang: `Signer` type introduced for signing accounts where data is not used ([#705](https://github.com/project-serum/anchor/pull/705)).
* lang: `UncheckedAccount` type introduced as a preferred alias for `AccountInfo` ([#745](https://github.com/project-serum/anchor/pull/745)).
### Breaking Changes
* lang: `#[account(owner = <pubkey>)]` now requires a `Pubkey` instead of an account ([#691](https://github.com/project-serum/anchor/pull/691)).
## [0.15.0] - 2021-09-07
### Features
* lang: Add new `Account` type to replace `ProgramAccount` and `CpiAccount`, both of which are deprecated ([#686](https://github.com/project-serum/anchor/pull/686)).
* lang: `Box` can be used with `Account` types to reduce stack usage ([#686](https://github.com/project-serum/anchor/pull/686)).
* lang: Add `Owner` trait, which is automatically implemented by all `#[account]` structs ([#686](https://github.com/project-serum/anchor/pull/686)).
* lang: Check that ProgramAccount writable before mut borrow (`anchor-debug` only) ([#681](https://github.com/project-serum/anchor/pull/681)).
### Breaking Changes
* lang: All programs must now define their program id in source via `declare_id!` ([#686](https://github.com/project-serum/anchor/pull/686)).
## [0.14.0] - 2021-09-02
### Features
* lang: Ignore `Unnamed` structs instead of panic ([#605](https://github.com/project-serum/anchor/pull/605)).
* lang: Add constraints for initializing mint accounts as pdas, `#[account(init, seeds = [...], mint::decimals = <expr>, mint::authority = <expr>)]` ([#562](https://github.com/project-serum/anchor/pull/562)).
* lang: Add `AsRef<AccountInfo>` for `AccountInfo` wrappers ([#652](https://github.com/project-serum/anchor/pull/652)).
* lang: Optimize `trait Key` by removing `AccountInfo` cloning ([#652](https://github.com/project-serum/anchor/pull/652)).
* cli, client, lang: Update solana toolchain to v1.7.11 ([#653](https://github.com/project-serum/anchor/pull/653)).
### Breaking Changes
* lang: Change `#[account(init, seeds = [...], token = <expr>, authority = <expr>)]` to `#[account(init, token::mint = <expr> token::authority = <expr>)]` ([#562](https://github.com/project-serum/anchor/pull/562)).
* lang: `#[associated]` and `#[account(associated = <target>, with = <target>)]` are both removed ([#612](https://github.com/project-serum/anchor/pull/612)).
* cli: Removed `anchor launch` command ([#634](https://github.com/project-serum/anchor/pull/634)).
* lang: `#[account(init)]` now creates the account inside the same instruction to be consistent with initializing PDAs. To maintain the old behavior of `init`, replace it with `#[account(zero)]` ([#641](https://github.com/project-serum/anchor/pull/641)).
* lang: `bump` must be provided when using the `seeds` constraint. This has been added as an extra safety constraint to ensure that whenever a PDA is initialized via a constraint the bump used is the one created by `Pubkey::find_program_address` ([#641](https://github.com/project-serum/anchor/pull/641)).
* lang: `try_from_init` has been removed from `Loader`, `ProgramAccount`, and `CpiAccount` and replaced with `try_from_unchecked` ([#641](https://github.com/project-serum/anchor/pull/641)).
* lang: Remove `AccountsInit` trait ([#641](https://github.com/project-serum/anchor/pull/641)).
* lang: `try_from` methods for `ProgramAccount`, `Loader`, and `ProgramState` now take in an additional `program_id: &Pubkey` parameter ([#660](https://github.com/project-serum/anchor/pull/660)).
## [0.13.2] - 2021-08-11
### Fixes
* cli: Fix `anchor init` command "Workspace not found" regression ([#598](https://github.com/project-serum/anchor/pull/598)).
## [0.13.1] - 2021-08-10
### Features
* cli: Programs embedded into genesis during tests will produce program logs ([#594](https://github.com/project-serum/anchor/pull/594)).
### Fixes
* cli: Allows Cargo.lock to exist in workspace subdirectories when publishing ([#593](https://github.com/project-serum/anchor/pull/593)).
## [0.13.0] - 2021-08-08
### Features
* cli: Adds a `[registry]` section in the Anchor toml ([#570](https://github.com/project-serum/anchor/pull/570)).
* cli: Adds the `anchor login <api-token>` command ([#570](https://github.com/project-serum/anchor/pull/570)).
* cli: Adds the `anchor publish <package>` command ([#570](https://github.com/project-serum/anchor/pull/570)).
* cli: Adds a root level `anchor_version` field to the Anchor.toml for specifying the anchor docker image to use for verifiable builds ([#570](https://github.com/project-serum/anchor/pull/570)).
* cli: Adds a root level `solana_version` field to the Anchor.toml for specifying the solana toolchain to use for verifiable builds ([#570](https://github.com/project-serum/anchor/pull/570)).
* lang: Dynamically fetch rent sysvar for when using `init` ([#587](https://github.com/project-serum/anchor/pull/587)).
### Breaking
* cli: `[clusters.<network>]` Anchor.toml section has been renamed to `[programs.<network>]` ([#570](https://github.com/project-serum/anchor/pull/570)).
* cli: `[workspace]` member and exclude arrays must now be filepaths relative to the workpsace root ([#570](https://github.com/project-serum/anchor/pull/570)).
## [0.12.0] - 2021-08-03
### Features
* cli: Add keys `members` / `exclude` in config `programs` section ([#546](https://github.com/project-serum/anchor/pull/546)).
* cli: Allow program address configuration for test command through `clusters.localnet` ([#554](https://github.com/project-serum/anchor/pull/554)).
* lang: IDLs are now parsed from the entire crate ([#517](https://github.com/project-serum/anchor/pull/517)).
* spl: Dex permissioned markets proxy ([#519](https://github.com/project-serum/anchor/pull/519), [#543](https://github.com/project-serum/anchor/pull/543)).
### Breaking Changes
* ts: Use `hex` by default for decoding Instruction ([#547](https://github.com/project-serum/anchor/pull/547)).
* lang: `CpiAccount::reload` mutates the existing struct instead of returning a new one ([#526](https://github.com/project-serum/anchor/pull/526)).
* cli: Anchor.toml now requires an explicit `[scripts]` test command ([#550](https://github.com/project-serum/anchor/pull/550)).
## [0.11.1] - 2021-07-09
### Features
@ -18,7 +122,6 @@ incremented for features.
* lang: Adds `require` macro for specifying assertions that return error codes on failure ([#483](https://github.com/project-serum/anchor/pull/483)).
* lang: Allow one to specify arbitrary programs as the owner when creating PDA ([#483](https://github.com/project-serum/anchor/pull/483)).
* lang: A new `bump` keyword is added to the accounts constraints, which is used to add an optional bump seed to the end of a `seeds` array. When used in conjunction with *both* `init` and `seeds`, then the program executes `find_program_address` to assert that the given bump is the canonical bump ([#483](https://github.com/project-serum/anchor/pull/483)).
* lang: IDLs are now parsed from the entire crate ([#517](https://github.com/project-serum/anchor/pull/517)).
### Fixes

1705
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ members = [
"spl",
]
exclude = [
"examples/swap/deps/serum-dex",
"examples/cfo/deps/serum-dex",
"examples/permissioned-markets/deps/serum-dex",
"tests/swap/deps/serum-dex",
"tests/cfo/deps/serum-dex",
"tests/permissioned-markets/deps/serum-dex",
]

View File

@ -2,3 +2,50 @@
build-cli:
cargo build -p anchor-cli --release
cp target/release/anchor cli/npm-package/anchor
.PHONY: build-example-bpf-%
build-example-bpf-%: export NAME=$(subst _,/,$($(strip @):build-example-bpf-%=%))
build-example-bpf-%:
cd examples/${NAME} && cargo build-bpf
.PHONY: build-example-bpf-permissioned-markets
build-example-bpf-permissioned-markets:
cd examples/permissioned-markets/deps/serum-dex/dex && cargo build-bpf
cd examples/permissioned-markets && cargo build-bpf
.PHONY: build-example-bpf-swap
build-example-bpf-swap:
cd examples/swap/deps/serum-dex/dex && cargo build-bpf
cd examples/swap && cargo build-bpf
.PHONY: build-example-bpf-all
build-example-bpf-all: build-example-bpf-cashiers-check
build-example-bpf-all: build-example-bpf-cfo
build-example-bpf-all: build-example-bpf-chat
build-example-bpf-all: build-example-bpf-composite
build-example-bpf-all: build-example-bpf-errors
build-example-bpf-all: build-example-bpf-escrow
build-example-bpf-all: build-example-bpf-events
build-example-bpf-all: build-example-bpf-ido-pool
build-example-bpf-all: build-example-bpf-interface
build-example-bpf-all: build-example-bpf-lockup
build-example-bpf-all: build-example-bpf-misc
build-example-bpf-all: build-example-bpf-multisig
build-example-bpf-all: build-example-bpf-permissioned-markets
build-example-bpf-all: build-example-bpf-pyth
build-example-bpf-all: build-example-bpf-spl_token-proxy
build-example-bpf-all: build-example-bpf-swap
build-example-bpf-all: build-example-bpf-sysvars
build-example-bpf-all: build-example-bpf-tutorial_basic-0
build-example-bpf-all: build-example-bpf-tutorial_basic-1
build-example-bpf-all: build-example-bpf-tutorial_basic-2
build-example-bpf-all: build-example-bpf-tutorial_basic-3
build-example-bpf-all: build-example-bpf-tutorial_basic-4
build-example-bpf-all: build-example-bpf-tutorial_basic-5
build-example-bpf-all: build-example-bpf-typescript
build-example-bpf-all: build-example-bpf-zero-copy
.PHONY: clean
clean:
find . -type d -name .anchor -print0 | xargs -0 rm -rf
find . -type d -name target -print0 | xargs -0 rm -rf

View File

@ -1,18 +1,28 @@
# Anchor ⚓
<div align="center">
<img height="170x" src="https://media.discordapp.net/attachments/813444514949103658/890278520553603092/export.png?width=746&height=746" />
[![Build Status](https://travis-ci.com/project-serum/anchor.svg?branch=master)](https://travis-ci.com/project-serum/anchor)
[![Docs](https://img.shields.io/badge/docs-tutorials-orange)](https://project-serum.github.io/anchor/)
[![Chat](https://img.shields.io/discord/739225212658122886?color=blueviolet)](https://discord.com/channels/739225212658122886)
[![License](https://img.shields.io/github/license/project-serum/anchor?color=ff69b4)](https://opensource.org/licenses/Apache-2.0)
<h1>Anchor</h1>
Anchor is a framework for Solana's [Sealevel](https://medium.com/solana-labs/sealevel-parallel-processing-thousands-of-smart-contracts-d814b378192) runtime providing several convenient developer tools.
<p>
<strong>Solana Sealevel Framework</strong>
</p>
<p>
<a href="https://travis-ci.com/project-serum/anchor"><img alt="Build Status" src="https://travis-ci.com/project-serum/anchor.svg?branch=master&color=blueviolet" /></a>
<a href="https://project-serum.github.io/anchor/"><img alt="Tutorials" src="https://img.shields.io/badge/docs-tutorials-blueviolet" /></a>
<a href="https://discord.com/channels/889577356681945098"><img alt="Discord Chat" src="https://img.shields.io/discord/889577356681945098?color=blueviolet" /></a>
<a href="https://opensource.org/licenses/Apache-2.0"><img alt="License" src="https://img.shields.io/github/license/project-serum/anchor?color=blueviolet" /></a>
</p>
</div>
Anchor is a framework for Solana's [Sealevel](https://medium.com/solana-labs/sealevel-parallel-processing-thousands-of-smart-contracts-d814b378192) runtime providing several convenient developer tools for writing smart contracts.
- Rust eDSL for writing Solana programs
- [IDL](https://en.wikipedia.org/wiki/Interface_description_language) specification
- TypeScript package for generating clients from IDL
- CLI and workspace management for developing complete applications
If you're familiar with developing in Ethereum's [Solidity](https://docs.soliditylang.org/en/v0.7.4/), [Truffle](https://www.trufflesuite.com/), [web3.js](https://github.com/ethereum/web3.js) or Parity's [Ink!](https://github.com/paritytech/ink), then the experience will be familiar. Although the DSL syntax and semantics are targeted at Solana, the high level flow of writing RPC request handlers, emitting an IDL, and generating clients from IDL is the same.
If you're familiar with developing in Ethereum's [Solidity](https://docs.soliditylang.org/en/v0.7.4/), [Truffle](https://www.trufflesuite.com/), [web3.js](https://github.com/ethereum/web3.js), then the experience will be familiar. Although the DSL syntax and semantics are targeted at Solana, the high level flow of writing RPC request handlers, emitting an IDL, and generating clients from IDL is the same.
## Getting Started
@ -39,43 +49,41 @@ Here's a counter program, where only the designated `authority`
can increment the count.
```rust
use anchor::prelude::*;
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod counter {
use super::*;
pub fn initialize(ctx: Context<Initialize>, authority: Pubkey) -> Result<()> {
pub fn initialize(ctx: Context<Initialize>, start: u64) -> ProgramResult {
let counter = &mut ctx.accounts.counter;
counter.authority = authority;
counter.count = 0;
counter.authority = *ctx.accounts.authority.key;
counter.count = start;
Ok(())
}
pub fn increment(ctx: Context<Increment>) -> Result<()> {
pub fn increment(ctx: Context<Increment>) -> ProgramResult {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init)]
pub counter: ProgramAccount<'info, Counter>,
pub rent: Sysvar<'info, Rent>,
#[account(init, payer = authority, space = 48)]
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: ProgramAccount<'info, Counter>,
#[account(signer)]
pub authority: AccountInfo<'info>,
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
#[account]
@ -83,21 +91,15 @@ pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}
#[error]
pub enum ErrorCode {
#[msg("You are not authorized to perform this action.")]
Unauthorized,
}
```
For more, see the [examples](https://github.com/project-serum/anchor/tree/master/examples)
directory.
and [tests](https://github.com/project-serum/anchor/tree/master/tests) directories.
## Contribution
Thank you for your interest in contributing to Anchor! All contributions are welcome no
matter how big or small. This includes includes (but is not limited to) filing issues,
matter how big or small. This includes (but is not limited to) filing issues,
adding documentation, fixing bugs, creating examples, and implementing features.
If you'd like to contribute, please claim an issue by commenting, forking, and
@ -108,15 +110,7 @@ or issues where [help is wanted](https://github.com/project-serum/anchor/issues?
For simple documentation changes, feel free to just open a pull request.
If you're considering larger changes or self motivated features, please file an issue
and engage with the maintainers in [Discord](https://discord.com/channels/739225212658122886).
When contributing, please make sure your code adheres to some basic coding guidlines:
* Code must be formatted with the configured formatters (e.g. rustfmt and prettier).
* Comment lines should be no longer than 80 characters and written with proper grammar and punctuation.
* Commit messages should be prefixed with the package(s) they modify. Changes affecting multiple
packages should list all packages. In rare cases, changes may omit the package name prefix.
* All notable changes should be documented in the [Change Log](https://github.com/project-serum/anchor/blob/master/CHANGELOG.md).
and engage with the maintainers in [Discord](https://discord.com/channels/889577356681945098).
## License

View File

@ -1,12 +1,12 @@
[package]
name = "anchor-cli"
version = "0.11.1"
version = "0.16.1"
authors = ["armaniferrante <armaniferrante@gmail.com>"]
edition = "2018"
[[bin]]
name = "anchor"
path = "src/main.rs"
path = "src/bin/main.rs"
[features]
dev = []
@ -23,11 +23,17 @@ serde_json = "1.0"
shellexpand = "2.1.0"
toml = "0.5.8"
serde = { version = "1.0.122", features = ["derive"] }
solana-sdk = "1.7.4"
solana-program = "1.7.4"
solana-client = "1.7.4"
solana-sdk = "=1.7.11"
solana-program = "=1.7.11"
solana-client = "=1.7.11"
serum-common = { git = "https://github.com/project-serum/serum-dex", features = ["client"] }
dirs = "3.0"
heck = "0.3.1"
flate2 = "1.0.19"
rand = "0.7.3"
tar = "0.4.35"
reqwest = { version = "0.11.4", features = ["multipart", "blocking"] }
tokio = "1.0"
pathdiff = "0.2.0"
cargo_toml = "0.9.2"
walkdir = "2"

View File

@ -1,6 +1,6 @@
{
"name": "@project-serum/anchor-cli",
"version": "0.11.1",
"version": "0.16.0",
"description": "Anchor CLI tool",
"homepage": "https://github.com/project-serum/anchor#readme",
"bugs": {

7
cli/src/bin/main.rs Normal file
View File

@ -0,0 +1,7 @@
use anchor_cli::Opts;
use anyhow::Result;
use clap::Clap;
fn main() -> Result<()> {
anchor_cli::entry(Opts::parse())
}

View File

@ -1,26 +1,252 @@
use crate::ConfigOverride;
use anchor_client::Cluster;
use anchor_syn::idl::Idl;
use anyhow::{anyhow, Error, Result};
use clap::Clap;
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use solana_sdk::signature::{Keypair, Signer};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fs::{self, File};
use std::io::prelude::*;
use std::ops::Deref;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Default, Debug, Clap)]
pub struct ConfigOverride {
/// Cluster override.
#[clap(global = true, long = "provider.cluster")]
pub cluster: Option<Cluster>,
/// Wallet override.
#[clap(global = true, long = "provider.wallet")]
pub wallet: Option<WalletPath>,
}
pub struct WithPath<T> {
inner: T,
path: PathBuf,
}
impl<T> WithPath<T> {
pub fn new(inner: T, path: PathBuf) -> Self {
Self { inner, path }
}
pub fn path(&self) -> &PathBuf {
&self.path
}
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T> std::convert::AsRef<T> for WithPath<T> {
fn as_ref(&self) -> &T {
&self.inner
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Manifest(cargo_toml::Manifest);
impl Manifest {
pub fn from_path(p: impl AsRef<Path>) -> Result<Self> {
cargo_toml::Manifest::from_path(p)
.map(Manifest)
.map_err(Into::into)
}
pub fn lib_name(&self) -> Result<String> {
if self.lib.is_some() && self.lib.as_ref().unwrap().name.is_some() {
Ok(self
.lib
.as_ref()
.unwrap()
.name
.as_ref()
.unwrap()
.to_string())
} else {
Ok(self
.package
.as_ref()
.ok_or_else(|| anyhow!("package section not provided"))?
.name
.to_string())
}
}
// Climbs each parent directory until we find a Cargo.toml.
pub fn discover() -> Result<Option<WithPath<Manifest>>> {
let _cwd = std::env::current_dir()?;
let mut cwd_opt = Some(_cwd.as_path());
while let Some(cwd) = cwd_opt {
for f in fs::read_dir(cwd)? {
let p = f?.path();
if let Some(filename) = p.file_name() {
if filename.to_str() == Some("Cargo.toml") {
let m = WithPath::new(Manifest::from_path(&p)?, p);
return Ok(Some(m));
}
}
}
// Not found. Go up a directory level.
cwd_opt = cwd.parent();
}
Ok(None)
}
}
impl Deref for Manifest {
type Target = cargo_toml::Manifest;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl WithPath<Config> {
pub fn get_program_list(&self) -> Result<Vec<PathBuf>> {
// Canonicalize the workspace filepaths to compare with relative paths.
let (members, exclude) = self.canonicalize_workspace()?;
// Get all candidate programs.
//
// If [workspace.members] exists, then use that.
// Otherwise, default to `programs/*`.
let program_paths: Vec<PathBuf> = {
if members.is_empty() {
let path = self.path().parent().unwrap().join("programs");
fs::read_dir(path)?
.map(|dir| dir.map(|d| d.path().canonicalize().unwrap()))
.collect::<Vec<Result<PathBuf, std::io::Error>>>()
.into_iter()
.collect::<Result<Vec<PathBuf>, std::io::Error>>()?
} else {
members
}
};
// Filter out everything part of the exclude array.
Ok(program_paths
.into_iter()
.filter(|m| !exclude.contains(m))
.collect())
}
// TODO: this should read idl dir instead of parsing source.
pub fn read_all_programs(&self) -> Result<Vec<Program>> {
let mut r = vec![];
for path in self.get_program_list()? {
let idl = anchor_syn::idl::file::parse(path.join("src/lib.rs"))?;
let lib_name = Manifest::from_path(&path.join("Cargo.toml"))?.lib_name()?;
r.push(Program {
lib_name,
path,
idl,
});
}
Ok(r)
}
pub fn canonicalize_workspace(&self) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
let members = self
.workspace
.members
.iter()
.map(|m| {
self.path()
.parent()
.unwrap()
.join(m)
.canonicalize()
.unwrap()
})
.collect();
let exclude = self
.workspace
.exclude
.iter()
.map(|m| {
self.path()
.parent()
.unwrap()
.join(m)
.canonicalize()
.unwrap()
})
.collect();
Ok((members, exclude))
}
pub fn get_program(&self, name: &str) -> Result<Option<WithPath<Program>>> {
for program in self.read_all_programs()? {
let cargo_toml = program.path.join("Cargo.toml");
if !cargo_toml.exists() {
return Err(anyhow!(
"Did not find Cargo.toml at the path: {}",
program.path.display()
));
}
let p_lib_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
if name == p_lib_name {
let path = self
.path()
.parent()
.unwrap()
.canonicalize()?
.join(&program.path);
return Ok(Some(WithPath::new(program, path)));
}
}
Ok(None)
}
}
impl<T> std::ops::Deref for WithPath<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> std::ops::DerefMut for WithPath<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[derive(Debug, Default)]
pub struct Config {
pub anchor_version: Option<String>,
pub solana_version: Option<String>,
pub registry: RegistryConfig,
pub provider: ProviderConfig,
pub clusters: ClustersConfig,
pub programs: ProgramsConfig,
pub scripts: ScriptsConfig,
pub workspace: WorkspaceConfig,
pub test: Option<Test>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RegistryConfig {
pub url: String,
}
impl Default for RegistryConfig {
fn default() -> Self {
Self {
url: "https://anchor.projectserum.com".to_string(),
}
}
}
#[derive(Debug, Default)]
pub struct ProviderConfig {
pub cluster: Cluster,
@ -29,83 +255,86 @@ pub struct ProviderConfig {
pub type ScriptsConfig = BTreeMap<String, String>;
pub type ClustersConfig = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;
pub type ProgramsConfig = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct WorkspaceConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub members: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exclude: Vec<String>,
}
impl Config {
pub fn discover(
cfg_override: &ConfigOverride,
) -> Result<Option<(Self, PathBuf, Option<PathBuf>)>> {
pub fn docker(&self) -> String {
let ver = self
.anchor_version
.clone()
.unwrap_or_else(|| crate::DOCKER_BUILDER_VERSION.to_string());
format!("projectserum/build:v{}", ver)
}
pub fn discover(cfg_override: &ConfigOverride) -> Result<Option<WithPath<Config>>> {
Config::_discover().map(|opt| {
opt.map(|(mut cfg, cfg_path, cargo_toml)| {
opt.map(|mut cfg| {
if let Some(cluster) = cfg_override.cluster.clone() {
cfg.provider.cluster = cluster;
}
if let Some(wallet) = cfg_override.wallet.clone() {
cfg.provider.wallet = wallet;
}
(cfg, cfg_path, cargo_toml)
cfg
})
})
}
// Searches all parent directories for an Anchor.toml file.
fn _discover() -> Result<Option<(Self, PathBuf, Option<PathBuf>)>> {
// Set to true if we ever see a Cargo.toml file when traversing the
// parent directories.
let mut cargo_toml = None;
// Climbs each parent directory until we find an Anchor.toml.
fn _discover() -> Result<Option<WithPath<Config>>> {
let _cwd = std::env::current_dir()?;
let mut cwd_opt = Some(_cwd.as_path());
while let Some(cwd) = cwd_opt {
let files = fs::read_dir(cwd)?;
// Cargo.toml file for this directory level.
let mut cargo_toml_level = None;
let mut anchor_toml = None;
for f in files {
for f in fs::read_dir(cwd)? {
let p = f?.path();
if let Some(filename) = p.file_name() {
if filename.to_str() == Some("Cargo.toml") {
cargo_toml_level = Some(p);
} else if filename.to_str() == Some("Anchor.toml") {
let mut cfg_file = File::open(&p)?;
let mut cfg_contents = String::new();
cfg_file.read_to_string(&mut cfg_contents)?;
let cfg = cfg_contents.parse()?;
anchor_toml = Some((cfg, p));
if filename.to_str() == Some("Anchor.toml") {
let cfg = Config::from_path(&p)?;
return Ok(Some(WithPath::new(cfg, p)));
}
}
}
if let Some((cfg, parent)) = anchor_toml {
return Ok(Some((cfg, parent, cargo_toml)));
}
if cargo_toml.is_none() {
cargo_toml = cargo_toml_level;
}
cwd_opt = cwd.parent();
}
Ok(None)
}
fn from_path(p: impl AsRef<Path>) -> Result<Self> {
let mut cfg_file = File::open(&p)?;
let mut cfg_contents = String::new();
cfg_file.read_to_string(&mut cfg_contents)?;
let cfg = cfg_contents.parse()?;
Ok(cfg)
}
pub fn wallet_kp(&self) -> Result<Keypair> {
solana_sdk::signature::read_keypair_file(&self.provider.wallet.to_string())
.map_err(|_| anyhow!("Unable to read keypair file"))
}
}
// Pubkey serializes as a byte array so use this type a hack to serialize
// into base 58 strings.
#[derive(Debug, Serialize, Deserialize)]
struct _Config {
anchor_version: Option<String>,
solana_version: Option<String>,
programs: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
registry: Option<RegistryConfig>,
provider: Provider,
test: Option<Test>,
workspace: Option<WorkspaceConfig>,
scripts: Option<ScriptsConfig>,
clusters: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
test: Option<Test>,
}
#[derive(Debug, Serialize, Deserialize)]
@ -116,8 +345,8 @@ struct Provider {
impl ToString for Config {
fn to_string(&self) -> String {
let clusters = {
let c = ser_clusters(&self.clusters);
let programs = {
let c = ser_programs(&self.programs);
if c.is_empty() {
None
} else {
@ -125,6 +354,9 @@ impl ToString for Config {
}
};
let cfg = _Config {
anchor_version: self.anchor_version.clone(),
solana_version: self.solana_version.clone(),
registry: Some(self.registry.clone()),
provider: Provider {
cluster: format!("{}", self.provider.cluster),
wallet: self.provider.wallet.to_string(),
@ -134,7 +366,9 @@ impl ToString for Config {
true => None,
false => Some(self.scripts.clone()),
},
clusters,
programs,
workspace: (!self.workspace.members.is_empty() || !self.workspace.exclude.is_empty())
.then(|| self.workspace.clone()),
};
toml::to_string(&cfg).expect("Must be well formed")
@ -148,21 +382,25 @@ impl FromStr for Config {
let cfg: _Config = toml::from_str(s)
.map_err(|e| anyhow::format_err!("Unable to deserialize config: {}", e.to_string()))?;
Ok(Config {
anchor_version: cfg.anchor_version,
solana_version: cfg.solana_version,
registry: cfg.registry.unwrap_or_default(),
provider: ProviderConfig {
cluster: cfg.provider.cluster.parse()?,
wallet: shellexpand::tilde(&cfg.provider.wallet).parse()?,
},
scripts: cfg.scripts.unwrap_or_else(BTreeMap::new),
test: cfg.test,
clusters: cfg.clusters.map_or(Ok(BTreeMap::new()), deser_clusters)?,
programs: cfg.programs.map_or(Ok(BTreeMap::new()), deser_programs)?,
workspace: cfg.workspace.unwrap_or_default(),
})
}
}
fn ser_clusters(
clusters: &BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>,
fn ser_programs(
programs: &BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>,
) -> BTreeMap<String, BTreeMap<String, serde_json::Value>> {
clusters
programs
.iter()
.map(|(cluster, programs)| {
let cluster = cluster.to_string();
@ -171,7 +409,7 @@ fn ser_clusters(
.map(|(name, deployment)| {
(
name.clone(),
serde_json::to_value(&_ProgramDeployment::from(deployment)).unwrap(),
to_value(&_ProgramDeployment::from(deployment)),
)
})
.collect::<BTreeMap<String, serde_json::Value>>();
@ -180,10 +418,17 @@ fn ser_clusters(
.collect::<BTreeMap<String, BTreeMap<String, serde_json::Value>>>()
}
fn deser_clusters(
clusters: BTreeMap<String, BTreeMap<String, serde_json::Value>>,
fn to_value(dep: &_ProgramDeployment) -> serde_json::Value {
if dep.path.is_none() && dep.idl.is_none() {
return serde_json::Value::String(dep.address.to_string());
}
serde_json::to_value(dep).unwrap()
}
fn deser_programs(
programs: BTreeMap<String, BTreeMap<String, serde_json::Value>>,
) -> Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>> {
clusters
programs
.iter()
.map(|(cluster, programs)| {
let cluster: Cluster = cluster.parse()?;
@ -195,6 +440,7 @@ fn deser_clusters(
ProgramDeployment::try_from(match &program_id {
serde_json::Value::String(address) => _ProgramDeployment {
address: address.parse()?,
path: None,
idl: None,
},
serde_json::Value::Object(_) => {
@ -224,60 +470,38 @@ pub struct GenesisEntry {
pub program: String,
}
// TODO: this should read idl dir instead of parsing source.
pub fn read_all_programs() -> Result<Vec<Program>> {
let files = fs::read_dir("programs")?;
let mut r = vec![];
for f in files {
let path = f?.path();
let idl = anchor_syn::idl::file::parse(path.join("src/lib.rs"))?;
let lib_name = extract_lib_name(&path.join("Cargo.toml"))?;
r.push(Program {
lib_name,
path,
idl,
});
}
Ok(r)
}
pub fn extract_lib_name(path: impl AsRef<Path>) -> Result<String> {
let mut toml = File::open(path)?;
let mut contents = String::new();
toml.read_to_string(&mut contents)?;
let cargo_toml: toml::Value = contents.parse()?;
match cargo_toml {
toml::Value::Table(t) => match t.get("lib") {
None => Err(anyhow!("lib not found in Cargo.toml")),
Some(lib) => match lib
.get("name")
.ok_or_else(|| anyhow!("lib name not found in Cargo.toml"))?
{
toml::Value::String(n) => Ok(n.to_string()),
_ => Err(anyhow!("lib name must be a string")),
},
},
_ => Err(anyhow!("Invalid Cargo.toml")),
}
}
#[derive(Debug, Clone)]
pub struct Program {
pub lib_name: String,
// Canonicalized path to the program directory.
pub path: PathBuf,
pub idl: Idl,
pub idl: Option<Idl>,
}
impl Program {
pub fn anchor_keypair_path(&self) -> PathBuf {
std::env::current_dir()
pub fn pubkey(&self) -> Result<Pubkey> {
self.keypair().map(|kp| kp.pubkey())
}
pub fn keypair(&self) -> Result<Keypair> {
let file = self.keypair_file()?;
solana_sdk::signature::read_keypair_file(file.path())
.map_err(|_| anyhow!("failed to read keypair for program: {}", self.lib_name))
}
// Lazily initializes the keypair file with a new key if it doesn't exist.
pub fn keypair_file(&self) -> Result<WithPath<File>> {
fs::create_dir_all("target/deploy/")?;
let path = std::env::current_dir()
.expect("Must have current dir")
.join(format!(
"target/deploy/anchor-{}-keypair.json",
self.lib_name
))
.join(format!("target/deploy/{}-keypair.json", self.lib_name));
if path.exists() {
return Ok(WithPath::new(File::open(&path)?, path));
}
let program_kp = Keypair::generate(&mut rand::rngs::OsRng);
let mut file = File::create(&path)?;
file.write_all(format!("{:?}", &program_kp.to_bytes()).as_bytes())?;
Ok(WithPath::new(file, path))
}
pub fn binary_path(&self) -> PathBuf {
@ -290,6 +514,7 @@ impl Program {
#[derive(Debug, Default)]
pub struct ProgramDeployment {
pub address: Pubkey,
pub path: Option<String>,
pub idl: Option<String>,
}
@ -298,6 +523,7 @@ impl TryFrom<_ProgramDeployment> for ProgramDeployment {
fn try_from(pd: _ProgramDeployment) -> Result<Self, Self::Error> {
Ok(ProgramDeployment {
address: pd.address.parse()?,
path: pd.path,
idl: pd.idl,
})
}
@ -306,6 +532,7 @@ impl TryFrom<_ProgramDeployment> for ProgramDeployment {
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct _ProgramDeployment {
pub address: String,
pub path: Option<String>,
pub idl: Option<String>,
}
@ -313,6 +540,7 @@ impl From<&ProgramDeployment> for _ProgramDeployment {
fn from(pd: &ProgramDeployment) -> Self {
Self {
address: pd.address.to_string(),
path: pd.path.clone(),
idl: pd.idl.clone(),
}
}
@ -324,4 +552,29 @@ pub struct ProgramWorkspace {
pub idl: Idl,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AnchorPackage {
pub name: String,
pub address: String,
pub idl: Option<String>,
}
impl AnchorPackage {
pub fn from(name: String, cfg: &WithPath<Config>) -> Result<Self> {
let cluster = &cfg.provider.cluster;
if cluster != &Cluster::Mainnet {
return Err(anyhow!("Publishing requires the mainnet cluster"));
}
let program_details = cfg
.programs
.get(cluster)
.ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?
.get(&name)
.ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?;
let idl = program_details.idl.clone();
let address = program_details.address.to_string();
Ok(Self { name, address, idl })
}
}
serum_common::home_path!(WalletPath, ".config/solana/id.json");

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,13 @@ use crate::config::ProgramWorkspace;
use crate::VERSION;
use anyhow::Result;
use heck::{CamelCase, SnakeCase};
use solana_sdk::pubkey::Pubkey;
pub fn default_program_id() -> Pubkey {
"Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
.parse()
.unwrap()
}
pub fn virtual_manifest() -> &'static str {
r#"[workspace]
@ -11,6 +18,15 @@ members = [
"#
}
pub fn credentials(token: &str) -> String {
format!(
r#"[registry]
token = "{}"
"#,
token
)
}
pub fn cargo_toml(name: &str) -> String {
format!(
r#"[package]
@ -68,8 +84,7 @@ main();
pub fn deploy_ts_script_host(cluster_url: &str, script_path: &str) -> String {
format!(
r#"
import * as anchor from '@project-serum/anchor';
r#"import * as anchor from '@project-serum/anchor';
// Deploy script defined by the user.
const userScript = require("{0}");
@ -95,8 +110,7 @@ main();
}
pub fn deploy_script() -> &'static str {
r#"
// Migrations are an early feature. Currently, they're nothing more than this
r#"// 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.
@ -112,8 +126,7 @@ module.exports = async function (provider) {
}
pub fn ts_deploy_script() -> &'static str {
r#"
// Migrations are an early feature. Currently, they're nothing more than this
r#"// 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.
@ -138,6 +151,8 @@ pub fn lib_rs(name: &str) -> String {
format!(
r#"use anchor_lang::prelude::*;
declare_id!("{}");
#[program]
pub mod {} {{
use super::*;
@ -149,6 +164,7 @@ pub mod {} {{
#[derive(Accounts)]
pub struct Initialize {{}}
"#,
default_program_id(),
name.to_snake_case(),
)
}
@ -175,6 +191,41 @@ describe('{}', () => {{
)
}
pub fn package_json() -> String {
format!(
r#"{{
"dependencies": {{
"@project-serum/anchor": "^{0}"
}},
"devDependencies": {{
"chai": "^4.3.4",
"mocha": "^9.0.3"
}}
}}
"#,
VERSION
)
}
pub fn ts_package_json() -> String {
format!(
r#"{{
"dependencies": {{
"@project-serum/anchor": "^{0}"
}},
"devDependencies": {{
"chai": "^4.3.4",
"mocha": "^9.0.3",
"ts-mocha": "^8.0.0",
"@types/mocha": "^9.0.0",
"typescript": "^4.3.5"
}}
}}
"#,
VERSION
)
}
pub fn ts_mocha(name: &str) -> String {
format!(
r#"import * as anchor from '@project-serum/anchor';
@ -217,6 +268,7 @@ pub fn git_ignore() -> &'static str {
.DS_Store
target
**/*.rs.bk
node_modules
"#
}
@ -230,15 +282,18 @@ pub fn node_shell(
const anchor = require('@project-serum/anchor');
const web3 = anchor.web3;
const PublicKey = anchor.web3.PublicKey;
const Keypair = anchor.web3.Keypair;
const __wallet = new anchor.Wallet(
Buffer.from(
JSON.parse(
require('fs').readFileSync(
"{}",
{{
encoding: "utf-8",
}},
Keypair.fromSecretKey(
Buffer.from(
JSON.parse(
require('fs').readFileSync(
"{}",
{{
encoding: "utf-8",
}},
),
),
),
),

View File

@ -1,13 +1,13 @@
[package]
name = "anchor-client"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
edition = "2018"
license = "Apache-2.0"
description = "Rust client for Anchor programs"
[dependencies]
anchor-lang = { path = "../lang", version = "0.11.1" }
anchor-lang = { path = "../lang", version = "0.16.1" }
anyhow = "1.0.32"
regex = "1.4.5"
serde = { version = "1.0.122", features = ["derive"] }

View File

@ -10,9 +10,10 @@ edition = "2018"
anchor-client = { path = "../" }
basic-2 = { path = "../../examples/tutorial/basic-2/programs/basic-2", features = ["no-entrypoint"] }
basic-4 = { path = "../../examples/tutorial/basic-4/programs/basic-4", features = ["no-entrypoint"] }
composite = { path = "../../examples/composite/programs/composite", features = ["no-entrypoint"] }
events = { path = "../../examples/events/programs/events", features = ["no-entrypoint"] }
composite = { path = "../../tests/composite/programs/composite", features = ["no-entrypoint"] }
events = { path = "../../tests/events/programs/events", features = ["no-entrypoint"] }
shellexpand = "2.1.0"
anyhow = "1.0.32"
rand = "0.7.3"
clap = "3.0.0-beta.2"
solana-sdk = "1.7.11"

View File

@ -20,35 +20,36 @@ set -euox pipefail
main() {
#
# Bootup validator.
# Build programs.
#
solana-test-validator > test-validator.log &
sleep 5
#
# Deploy programs.
#
pushd ../../examples/composite/
pushd ../../tests/composite/
anchor build
anchor deploy
local composite_pid=$(cat target/idl/composite.json | jq -r .metadata.address)
local composite_pid="EHthziFziNoac9LBGxEaVN47Y3uUiRoXvqAiR6oes4iU"
popd
pushd ../../examples/tutorial/basic-2/
anchor build
anchor deploy
local basic_2_pid=$(cat target/idl/basic_2.json | jq -r .metadata.address)
local basic_2_pid="Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
popd
pushd ../../examples/tutorial/basic-4/
anchor build
anchor deploy
local basic_4_pid=$(cat target/idl/basic_4.json | jq -r .metadata.address)
local basic_4_pid="CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr"
popd
pushd ../../examples/events
pushd ../../tests/events
anchor build
anchor deploy
local events_pid=$(cat target/idl/events.json | jq -r .metadata.address)
local events_pid="2dhGsWUzy5YKUsjZdLHLmkNpUDAXkNa9MYWsPc4Ziqzy"
popd
#
# Bootup validator.
#
solana-test-validator \
--bpf-program $composite_pid ../../tests/composite/target/deploy/composite.so \
--bpf-program $basic_2_pid ../../examples/tutorial/basic-2/target/deploy/basic_2.so \
--bpf-program $basic_4_pid ../../examples/tutorial/basic-4/target/deploy/basic_4.so \
--bpf-program $events_pid ../../tests/events/target/deploy/events.so \
> test-validator.log &
sleep 5
#
# Run Test.
#

View File

@ -3,9 +3,9 @@ use anchor_client::solana_sdk::pubkey::Pubkey;
use anchor_client::solana_sdk::signature::read_keypair_file;
use anchor_client::solana_sdk::signature::{Keypair, Signer};
use anchor_client::solana_sdk::system_instruction;
use anchor_client::solana_sdk::sysvar;
use anchor_client::{Client, Cluster, EventContext};
use anyhow::Result;
use solana_sdk::system_program;
// The `accounts` and `instructions` modules are generated by the framework.
use basic_2::accounts as basic_2_accounts;
use basic_2::instruction as basic_2_instruction;
@ -96,7 +96,6 @@ fn composite(client: &Client, pid: Pubkey) -> Result<()> {
.accounts(Initialize {
dummy_a: dummy_a.pubkey(),
dummy_b: dummy_b.pubkey(),
rent: sysvar::rent::ID,
})
.args(composite_instruction::Initialize)
.send()?;
@ -148,17 +147,11 @@ fn basic_2(client: &Client, pid: Pubkey) -> Result<()> {
// Build and send a transaction.
program
.request()
.instruction(system_instruction::create_account(
&authority,
&counter.pubkey(),
program.rpc().get_minimum_balance_for_rent_exemption(500)?,
500,
&pid,
))
.signer(&counter)
.accounts(basic_2_accounts::Create {
counter: counter.pubkey(),
rent: sysvar::rent::ID,
user: authority,
system_program: system_program::ID,
})
.args(basic_2_instruction::Create { authority })
.send()?;

View File

@ -40,6 +40,14 @@ impl FromStr for Cluster {
ws_url.set_port(Some(8900))
.map_err(|_| anyhow!("Unable to set port"))?;
}
if ws_url.scheme() == "https" {
ws_url.set_scheme("wss")
.map_err(|_| anyhow!("Unable to set scheme"))?;
} else {
ws_url.set_scheme("ws")
.map_err(|_| anyhow!("Unable to set scheme"))?;
}
Ok(Cluster::Custom(http_url.to_string(), ws_url.to_string()))
}
@ -116,7 +124,7 @@ mod tests {
let url = "http://my-url.com:7000/";
let cluster = Cluster::from_str(url).unwrap();
assert_eq!(
Cluster::Custom(url.to_string(), "http://my-url.com:7001/".to_string()),
Cluster::Custom(url.to_string(), "ws://my-url.com:7001/".to_string()),
cluster
);
}
@ -126,7 +134,26 @@ mod tests {
let url = "http://my-url.com/";
let cluster = Cluster::from_str(url).unwrap();
assert_eq!(
Cluster::Custom(url.to_string(), "http://my-url.com:8900/".to_string()),
Cluster::Custom(url.to_string(), "ws://my-url.com:8900/".to_string()),
cluster
);
}
#[test]
fn test_https_port() {
let url = "https://my-url.com:7000/";
let cluster = Cluster::from_str(url).unwrap();
assert_eq!(
Cluster::Custom(url.to_string(), "wss://my-url.com:7001/".to_string()),
cluster
);
}
#[test]
fn test_https_no_port() {
let url = "https://my-url.com/";
let cluster = Cluster::from_str(url).unwrap();
assert_eq!(
Cluster::Custom(url.to_string(), "wss://my-url.com:8900/".to_string()),
cluster
);
}

View File

@ -5,7 +5,6 @@ use anchor_lang::solana_program::instruction::{AccountMeta, Instruction};
use anchor_lang::solana_program::program_error::ProgramError;
use anchor_lang::solana_program::pubkey::Pubkey;
use anchor_lang::solana_program::system_program;
use anchor_lang::solana_program::sysvar::rent;
use anchor_lang::{AccountDeserialize, InstructionData, ToAccountMetas};
use regex::Regex;
use solana_client::client_error::ClientError as SolanaClientError;
@ -428,7 +427,6 @@ impl<'a> RequestBuilder<'a> {
),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(self.program_id, false),
AccountMeta::new_readonly(rent::ID, false),
],
};
accounts.extend_from_slice(&self.accounts);

View File

@ -6,7 +6,7 @@ ANCHOR_CLI=v$(shell awk -F ' = ' '$$1 ~ /version/ { gsub(/[\"]/, "", $$2); print
#
# Solana toolchain.
#
SOLANA_CLI=v1.7.4
SOLANA_CLI=v1.7.11
#
# Build version should match the Anchor cli version.
#

View File

@ -14,6 +14,7 @@ ARG ANCHOR_CLI
ENV HOME="/root"
ENV PATH="${HOME}/.cargo/bin:${PATH}"
ENV PATH="${HOME}/.local/share/solana/install/active_release/bin:${PATH}"
ENV PATH="${HOME}/.nvm/versions/node/v16.9.1/bin:${PATH}"
# Install base utilities.
RUN mkdir -p /workdir && mkdir -p /tmp && \
@ -26,6 +27,15 @@ RUN curl "https://sh.rustup.rs" -sfo rustup.sh && \
sh rustup.sh -y && \
rustup component add rustfmt clippy
# Install node / npm / yarn.
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
ENV NVM_DIR="${HOME}/.nvm"
RUN . $NVM_DIR/nvm.sh && \
nvm install node --latest-npm && \
nvm use node && \
nvm alias default node && \
npm install -g yarn
# Install Solana tools.
RUN sh -c "$(curl -sSfL https://release.solana.com/${SOLANA_CLI}/install)"

View File

@ -15,6 +15,7 @@
},
"license": "MIT",
"devDependencies": {
"@xiaopanda/vuepress-plugin-code-copy": "^1.0.3",
"gh-pages": "^3.1.0",
"vuepress": "^1.5.3",
"vuepress-plugin-dehydrate": "^1.1.5",

View File

@ -64,7 +64,6 @@ module.exports = {
"/tutorials/tutorial-2",
"/tutorials/tutorial-3",
"/tutorials/tutorial-4",
"/tutorials/tutorial-5",
],
},
{
@ -74,6 +73,14 @@ module.exports = {
"/cli/commands",
],
},
{
collapsable: false,
title: "Source Verification",
children: [
"/getting-started/verification",
"/getting-started/publishing",
],
},
],
nav: [
@ -90,5 +97,6 @@ module.exports = {
"dehydrate",
"@vuepress/plugin-back-to-top",
"@vuepress/plugin-medium-zoom",
"@xiaopanda/vuepress-plugin-code-copy",
],
};

View File

@ -21,7 +21,6 @@ SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
idl Commands for interacting with interface definitions
init Initializes a workspace
launch Deploys, initializes an IDL, and migrates all in one command
migrate Runs the deploy migration script
new Creates a new program
test Runs integration tests against a localnetwork
@ -93,7 +92,13 @@ of all workspace programs before running them.
If the configured network is a localnet, then automatically starts the localnetwork and runs
the test.
When running tests we stream program logs to .anchor/program-logs/<address>.<program-name>.log
::: tip Note
Be sure to shutdown any other local validators, otherwise `anchor test` will fail to run.
If you'd prefer to run the program against your local validator use `anchor test --skip-local-validator`.
:::
When running tests we stream program logs to `.anchor/program-logs/<address>.<program-name>.log`
::: tip Note
The Anchor workflow [recommends](https://www.parity.io/paritys-checklist-for-secure-smart-contract-development/)
@ -189,23 +194,6 @@ anchor idl set-authority -n <new-authority> -p <program-id>
Sets a new authority on the IDL account. Both the `new-authority` and `program-id`
must be encoded in base 58.
## Launch
```
anchor launch
```
Builds, deploys and migrates, all in one command. This is particularly
useful when simultaneously developing an app against a Localnet or Devnet. For mainnet, it's
recommended to run each command separately, since transactions can sometimes be
unreliable depending on the Solana RPC node being used.
```
anchor launch --verifiable
```
Runs the build inside a docker image so that the output binary is deterministic (assuming a Cargo.lock file is used).
## New
```
@ -240,4 +228,4 @@ anchor verify <program-id>
```
Verifies the on-chain bytecode matches the locally compiled artifact.

View File

@ -18,7 +18,7 @@ rustup component add rustfmt
See the solana [docs](https://docs.solana.com/cli/install-solana-cli-tools) for installation instructions. On macOS and Linux,
```bash
sh -c "$(curl -sSfL https://release.solana.com/v1.7.4/install)"
sh -c "$(curl -sSfL https://release.solana.com/v1.7.11/install)"
```
## Install Mocha
@ -31,10 +31,20 @@ npm install -g mocha
## Install Anchor
### Install using pre-build binary on x86_64 Linux
Anchor binaries are avalable via an NPM package [`@project-serum/anchor-cli`](https://www.npmjs.com/package/@project-serum/anchor-cli). Only x86_64 Linux is supported currently, you must build from source for other OS'.
```bash
npm i -g @project-serum/anchor-cli
```
### Build from source for other operating systems
For now, we can use Cargo to install the CLI.
```bash
cargo install --git https://github.com/project-serum/anchor --tag v0.11.1 anchor-cli --locked
cargo install --git https://github.com/project-serum/anchor --tag v0.16.1 anchor-cli --locked
```
On Linux systems you may need to install additional dependencies if `cargo install` fails. On Ubuntu,
@ -52,6 +62,20 @@ npm install -g @project-serum/anchor
Make sure your `NODE_PATH` is set properly so that globally installed modules
can be resolved.
Now verify the CLI is installed properly.
```bash
anchor --version
```
## Start a Project
To initialize a new project, simply run:
```bash
anchor init <new-project-name>
```
## Minimum version requirements
| Build tool | Version |

View File

@ -12,5 +12,5 @@ If you're familiar with developing in Ethereum's [Solidity](https://docs.solidit
Here, we'll walk through several tutorials demonstrating how to use Anchor. To skip the tutorials and jump straight to examples, go [here](https://github.com/project-serum/anchor/blob/master/examples). For an introduction to Solana, see the [docs](https://docs.solana.com/developing/programming-model/overview).
::: tip NOTE
Anchor is in active development, so all APIs are subject to change. If you are one of the early developers to try it out and have feedback, please reach out by [filing an issue](https://github.com/project-serum/anchor/issues/new). This documentation is a work in progress and is expected to change dramatically as features continue to be built out. If you have any problems, consult the [source](https://github.com/project-serum/anchor) or feel free to ask questions on the [Serum Discord](https://discord.gg/rg5ZZPmmTm).
Anchor is in active development, so all APIs are subject to change. If you are one of the early developers to try it out and have feedback, please reach out by [filing an issue](https://github.com/project-serum/anchor/issues/new). This documentation is a work in progress and is expected to change dramatically as features continue to be built out. If you have any problems, consult the [source](https://github.com/project-serum/anchor) or feel free to ask questions on the [Discord](https://discord.gg/JgVgQ82erk).
:::

View File

@ -7,10 +7,13 @@ Open a pull request to add your project to the [list](https://github.com/project
* [SolFarm](https://solfarm.io/)
* [Zeta Markets](https://zeta.markets/)
* [Saber](https://saber.so)
* [Mello](https://github.com/mello-markets/Mello)
* [01](https://01protocol.com/)
* [Parrot Finance](https://parrot.fi/)
* [Marinade Finance](https://marinade.finance/)
* [Cryptocurrencies.Ai](https://dex.cryptocurrencies.ai/)
* [Cyclos](https://cyclos.io/)
* [Solend](https://solend.fi)
* [Moët Finance](https://t.co/9eDHRAnGob?amp=1)
* [Drift Trade (formerly Moët)](https://www.drift.trade/)
* [SpringBoard](https://springboard.finance/)
* [Unk](https://unk.finance/)
* [Fabric](https://stake.fsynth.io/)

View File

@ -0,0 +1,88 @@
# Publishing Source
The Anchor Program Registry at [anchor.projectserum.com](https://anchor.projectserum.com)
hosts a catalog of verified programs on Solana both written with and without Anchor. It is recommended
that authors of smart contracts publish their source to promote best
practices for security and transparency.
::: tip note
The Anchor Program Registry is currently in alpha testing. For access to publishing
please ask on [Discord](https://discord.gg/rg5ZZPmmTm).
:::
## Getting Started
The process for publishing is mostly identical to `crates.io`.
* Signup for an account [here](https://anchor.projectserum.com/signup).
* Confirm your email by clicking the link sent to your address.
* Navigate to your Username -> Account Settings on the top navbar.
* Click "New Token" in the **API Access** section.
* Run `anchor login <token>` at the command line.
And you're ready to interact with the registry.
## Configuring a Build
Whether your program is written in Anchor or not, all source being published must
have an `Anchor.toml` to define the build.
An example `Anchor.toml` config looks as follows,
```toml
anchor_version = "0.16.1"
[workspace]
members = ["programs/multisig"]
[provider]
cluster = "mainnet"
wallet = "~/.config/solana/id.json"
[programs.mainnet]
multisig = "A9HAbnCwoD6f2NkZobKFf6buJoN9gUVVvX5PoUnDHS6u"
[programs.localnet]
multisig = "A9HAbnCwoD6f2NkZobKFf6buJoN9gUVVvX5PoUnDHS6u"
```
Here there are four sections.
1. `anchor_version` (optional) - sets the anchor docker image to use. By default, the builder will use the latest version of Anchor.
2. `[workspace]` (optional) - sets the paths--relative to the `Anchor.toml`--
to all programs in the local
workspace, i.e., the path to the `Cargo.toml` manifest associated with each
program that can be compiled by the `anchor` CLI. For programs using the
standard Anchor workflow, this can be ommitted. For programs not written in Anchor
but still want to publish, this should be added.
3. `[provider]` - configures the wallet and cluster settings. Here, `mainnet` is used because the registry only supports `mainnet` binary verification at the moment.
3. `[programs.mainnet]` - configures each program in the workpace, providing
the `address` of the program to verify.
::: tip
When defining program in `[programs.mainnet]`, make sure the name provided
matches the **lib** name for your program, which is defined
by your program's Cargo.toml.
:::
### Examples
#### Anchor Program
An example of a toml file for an Anchor program can be found [here](https://anchor.projectserum.com/build/2).
#### Non Anchor Program
An example of a toml file for a non-anchor program can be found [here](https://anchor.projectserum.com/build/1).
## Publishing
To publish to the Anchor Program Registry, change directories to the `Anchor.toml`
defined root and run
```bash
anchor publish <program-name>
```
where `<program-name>` is as defined in `[programs.mainnet]`, i.e., `multisig`
in the example above.

View File

@ -0,0 +1,52 @@
# Verifiable Builds
Building programs with the Solana CLI may embed machine specfic
code into the resulting binary. As a result, building the same program
on different machines may produce different executables. To get around this
problem, one can build inside a docker image with pinned dependencies to produce
a verifiable build.
Anchor makes this easy by providing CLI commands to build take care of
docker for you. To get started, first make sure you
[install](https://docs.docker.com/get-docker/) docker on your local machine.
## Building
To produce a verifiable build, run
```bash
anchor build --verifiable
```
## Verifying
To verify a build against a program deployed on mainnet, run
```bash
anchor verify -p <lib-name> <program-id>
```
where the `<lib-name>` is defined by your program's Cargo.toml.
If the program has an IDL, it will also check the IDL deployed on chain matches.
## Images
A docker image for each version of Anchor is published on [Docker Hub](https://hub.docker.com/r/projectserum/build). They are tagged in the form `projectserum/build:<version>`. For example, to get the image for Anchor `v0.16.1` one can run
```
docker pull projectserum/build:v0.16.1
```
## Removing an Image
In the event you run a verifiable build from the CLI and exit prematurely,
it's possible the docker image may still be building in the background.
To remove, run
```
docker rm -f anchor-program
```
where `anchor-program` is the name of the image created by default from within
the Anchor CLI.

View File

@ -11,6 +11,12 @@ To get started, clone the repo.
git clone https://github.com/project-serum/anchor
```
Next, checkout the tagged branch of the same version of the anchor cli you have installed.
```bash
git checkout tags/<version>
```
And change directories to the [example](https://github.com/project-serum/anchor/tree/master/examples/tutorial/basic-0).
```bash

View File

@ -31,32 +31,21 @@ Some new syntax elements are introduced here.
First, let's start with the initialize instruction. Notice the `data` argument passed into the program. This argument and any other valid
Rust types can be passed to the instruction to define inputs to the program.
::: tip
If you'd like to pass in your own type as an input to an instruction handler, then it must be
defined in the same `src/lib.rs` file as the `#[program]` module, so that the IDL parser can
pick it up.
:::
Additionally,
notice how we take a mutable reference to `my_account` and assign the `data` to it. This leads us to
the `Initialize` struct, deriving `Accounts`. There are three things to notice about `Initialize`.
the `Initialize` struct, deriving `Accounts`. There are two things to notice about `Initialize`.
1. The `my_account` field is of type `ProgramAccount<'info, MyAccount>`, telling the program it *must*
be **owned** by the currently executing program, and the deserialized data structure is `MyAccount`.
2. The `my_account` field is marked with the `#[account(init)]` attribute. This should be used
in one situation: when a given `ProgramAccount` is newly created and is being used by the program
for the first time (and thus its data field is all zero). If `#[account(init)]` is not used
when account data is zero initialized, the transaction will be rejected.
3. The `Rent` **sysvar** is required for the rent exemption check, which the framework enforces
by default for any account marked with `#[account(init)]`. To be more explicit about the check,
one can specify `#[account(init, rent_exempt = enforce)]`. To skip this check, (and thus
allowing you to omit the `Rent` acccount), you can specify
`#[account(init, rent_exempt = skip)]` on the account being initialized (here, `my_account`).
1. The `my_account` field is of type `Account<'info, MyAccount>` and the deserialized data structure is `MyAccount`.
2. The `my_account` field is marked with the `init` attribute. This will create a new
account owned by the current program, zero initialized. When using `init`, one must also provide
`payer`, which will fund the account creation, `space`, which defines how large the account should be,
and the `system_program`, which is required by the runtime for creating the account.
::: details
All accounts created with Anchor are laid out as follows: `8-byte-discriminator || borsh
serialized data`. The 8-byte-discriminator is created from the first 8 bytes of the
`Sha256` hash of the account's type--using the example above, `sha256("MyAccount")[..8]`.
`Sha256` hash of the account's type--using the example above, `sha256("account:MyAccount")[..8]`.
The `account:` is a fixed prefix.
Importantly, this allows a program to know for certain an account is indeed of a given type.
Without it, a program would be vulnerable to account injection attacks, where a malicious user
@ -80,15 +69,16 @@ for persisting changes.
## Creating and Initializing Accounts
For a moment, assume an account of type `MyAccount` was created on Solana, in which case,
we can invoke the above `initialize` instruction as follows.
We can interact with the program as follows.
<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code-separated
<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code-simplified
The last element passed into the method is common amongst all dynamically generated
methods on the `rpc` namespace, containing several options for a transaction. Here,
we specify the `accounts` field, an object of all the addresses the transaction
needs to touch.
needs to touch, and the `signers` array of all `Signer` objects needed to sign the
transaction. Because `myAccount` is being created, the Solana runtime requries it
to sign the transaction.
::: details
If you've developed on Solana before, you might notice two things 1) the ordering of the accounts doesn't
@ -97,22 +87,6 @@ options are not specified on the account anywhere. In both cases, the framework
of these details for you, by reading the IDL.
:::
However it's common--and sometimes necessary for security purposes--to batch
instructions together. We can extend the example above to both create an account
and initialize it in one atomic transaction.
<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code
Here, notice the **two** fields introduced: `signers` and `instructions`. `signers`
is an array of all `Account` objects to sign the transaction and `instructions` is an
array of all instructions to run **before** the explicitly specified program instruction,
which in this case is `initialize`. Because we are creating `myAccount`, it needs to
sign the transaction, as required by the Solana runtime.
We can simplify this further.
<<< @/../examples/tutorial/basic-1/tests/basic-1.js#code-simplified
As before, we can run the example tests.
```

View File

@ -13,7 +13,7 @@ To address these problems, Anchor provides several types, traits, and macros. It
from the untrusted `&[AccountInfo]` slice given to a Solana program into a validated struct
of deserialized account types.
* [#[account]](https://docs.rs/anchor-lang/latest/anchor_lang/attr.account.html): attribute macro implementing [AccountSerialize](https://docs.rs/anchor-lang/latest/anchor_lang/trait.AccountSerialize.html) and [AccountDeserialize](https://docs.rs/anchor-lang/latest/anchor_lang/trait.AnchorDeserialize.html), automatically prepending a unique 8 byte discriminator to the account array. The discriminator is defined by the first 8 bytes of the `Sha256` hash of the account's Rust identifier--i.e., the struct type name--and ensures no account can be substituted for another.
* [ProgramAccount](https://docs.rs/anchor-lang/latest/anchor_lang/struct.ProgramAccount.html): a wrapper type for a deserialized account implementing `AccountDeserialize`. Using this type within an `Accounts` struct will ensure the account is **owned** by the currently executing program.
* [Account](https://docs.rs/anchor-lang/latest/anchor_lang/struct.Account.html): a wrapper type for a deserialized account implementing `AccountDeserialize`. Using this type within an `Accounts` struct will ensure the account is **owned** by the currently executing program.
With the above, we can define preconditions for our any instruction handler expecting a certain set of
accounts, allowing us to more easily reason about the security of our programs.
@ -47,7 +47,7 @@ Let's focus on the `increment` instruction, specifically the `Increment` struct
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: ProgramAccount<'info, Counter>,
pub counter: Account<'info, Counter>,
#[account(signer)]
pub authority: AccountInfo<'info>,
}

View File

@ -71,9 +71,8 @@ retrieve the return value. In future work, Anchor should do this transparently.
## Conclusion
Now that you can have your programs call other programs, you should be able to access all the work being done by other developers in your own applications!
Now that you can have your programs call other programs, you should be able to access all the work being done by other developers in your own applications!
## Next Steps
Up until now, we've treated programs on Solana as stateless. In the next tutorial we will learn how to add a global state to our program.

View File

@ -1,84 +1,61 @@
# State structs
# Errors
Up until now, we've treated programs on Solana as stateless, using accounts to persist
state between instruction invocations. In this tutorial, we'll give Solana programs the
illusion of state by introducing state structs, which define program account
singletons that can be operated over like any other account.
## Clone the Repo
To get started, clone the repo.
```bash
git clone https://github.com/project-serum/anchor
```
And change directories to the [example](https://github.com/project-serum/anchor/tree/master/examples/tutorial/basic-4).
```bash
cd anchor/examples/tutorial/basic-4
```
If you've ever programmed on a blockchain, you've probably been frustrated by
either non existant or opaque error codes. Anchor attempts to address this by
providing the `#[error]` attribute, which can be used to create typed Errors with
descriptive messages that automatically propagate to the client.
## Defining a Program
<<< @/../examples/tutorial/basic-4/programs/basic-4/src/lib.rs#code
For example,
Unlike the previous examples, all the instructions here not only take in an `Accounts`
struct, but they also operate over a mutable, global account marked by the `#[state]`
attribute. Every instruction defined in the corresponding `impl` block will have access
to this account, making it a great place to store global program state.
```rust
use anchor_lang::prelude::*;
### How it works
#[program]
mod errors {
use super::*;
pub fn hello(_ctx: Context<Hello>) -> Result<()> {
Err(ErrorCode::Hello.into())
}
}
We are able to give a program the illusion of state by adopting conventions in the framework. When invoking the `new` constructor, Anchor will automatically create a
program-owned account inside the program itself, invoking the system program's [create_account_with_seed](https://docs.rs/solana-program/1.5.5/solana_program/system_instruction/fn.create_account_with_seed.html) instruction, using `Pubkey::find_program_address(&[], program_id)` as the **base** and a deterministic string as the **seed** (the string doesn't
matter, as long as the framework is consistent).
#[derive(Accounts)]
pub struct Hello {}
This all has the effect of
giving the `#[state]` account a deterministic address, and so as long as all clients
and programs adopt this convention, programs can have the illusion of state in addition
to the full power of the lower level Solana accounts API. Of course, Anchor will handle this all for you, so you never have to worry about these details.
#[error]
pub enum ErrorCode {
#[msg("This is an error message clients will automatically display")]
Hello,
}
```
## Using the client
Observe the [#[error]](https://docs.rs/anchor-lang/latest/anchor_lang/attr.error.html) attribute on the `ErrorCode` enum. This macro generates two types: an `Error` and a `Result`, both of which can be used when returning from your program.
### Invoke the constructor
To use the `Error`, you can simply use the user defined `ErrorCode` with Rust's [From](https://doc.rust-lang.org/std/convert/trait.From.html) trait. If you're unfamiliar with `From`, no worries. Just know that you need to either call
`.into()` when using your `ErrorCode`. Or use Rust's `?` operator, when returning an error.
Both of these will automatically convert *into* the correct `Error`.
To access the `#[state]` account and associated instructions, you can use the
`anchor.state` namespace on the client. For example, to invoke the constructor,
::: details
What's the deal with this From stuff? Well, because the Solana runtime expects a [ProgramError](https://docs.rs/solana-program/1.5.5/solana_program/program_error/enum.ProgramError.html) in the return value. The framework needs to wrap the user defined error code into a
`ProgramError::Code` variant, before returning. The alternative would be to use the
`ProgramError` directly.
:::
<<< @/../examples/tutorial/basic-4/tests/basic-4.js#ctor
## Using the Client
Note that the constructor can only be invoked once per program. All subsequent calls
to it will fail, since, as explained above, an account at a deterministic address
will be created.
When using the client, we get the error message.
### Fetch the state
```javascript
try {
const tx = await program.rpc.hello();
assert.ok(false);
} catch (err) {
const errMsg = "This is an error message clients will automatically display";
assert.equal(err.toString(), errMsg);
}
```
To fetch the state account,
It's that easy. :)
<<< @/../examples/tutorial/basic-4/tests/basic-4.js#accessor
### Invoke an instruction
To invoke an instruction,
<<< @/../examples/tutorial/basic-4/tests/basic-4.js#instruction
## CPI
Performing CPI from one Anchor program to another's state methods is very similar to performing CPI to normal Anchor instructions, except for two differences:
1. All the generated instructions are located under the `<my_program>::cpi::state` module.
2. You must use a [CpiStateContext](https://docs.rs/anchor-lang/latest/anchor_lang/struct.CpiStateContext.html), instead of a `[CpiContext](https://docs.rs/anchor-lang/latest/anchor_lang/struct.CpiContext.html).
For a full example, see the `test_state_cpi` instruction, [here](https://github.com/project-serum/anchor/blob/master/examples/misc/programs/misc/src/lib.rs#L39).
## Conclusion
Using state structs is intuitive. However, due to the fact that accounts
on Solana have a fixed size, applications often need to use accounts
directly in addition to `#[state]` stucts.
## Next Steps
Next we'll discuss errors.
To run the full example, go [here](https://github.com/project-serum/anchor/tree/master/examples/errors).

View File

@ -1,61 +0,0 @@
# Errors
If you've ever programmed on a blockchain, you've probably been frustrated by
either non existant or opaque error codes. Anchor attempts to address this by
providing the `#[error]` attribute, which can be used to create typed Errors with
descriptive messages that automatically propagate to the client.
## Defining a Program
For example,
```rust
use anchor_lang::prelude::*;
#[program]
mod errors {
use super::*;
pub fn hello(_ctx: Context<Hello>) -> Result<()> {
Err(ErrorCode::Hello.into())
}
}
#[derive(Accounts)]
pub struct Hello {}
#[error]
pub enum ErrorCode {
#[msg("This is an error message clients will automatically display")]
Hello,
}
```
Observe the [#[error]](https://docs.rs/anchor-lang/latest/anchor_lang/attr.error.html) attribute on the `ErrorCode` enum. This macro generates two types: an `Error` and a `Result`, both of which can be used when returning from your program.
To use the `Error`, you can simply use the user defined `ErrorCode` with Rust's [From](https://doc.rust-lang.org/std/convert/trait.From.html) trait. If you're unfamiliar with `From`, no worries. Just know that you need to either call
`.into()` when using your `ErrorCode`. Or use Rust's `?` operator, when returning an error.
Both of these will automatically convert *into* the correct `Error`.
::: details
What's the deal with this From stuff? Well, because the Solana runtime expects a [ProgramError](https://docs.rs/solana-program/1.5.5/solana_program/program_error/enum.ProgramError.html) in the return value. The framework needs to wrap the user defined error code into a
`ProgramError::Code` variant, before returning. The alternative would be to use the
`ProgramError` directly.
:::
## Using the Client
When using the client, we get the error message.
```javascript
try {
const tx = await program.rpc.hello();
assert.ok(false);
} catch (err) {
const errMsg = "This is an error message clients will automatically display";
assert.equal(err.toString(), errMsg);
}
```
It's that easy. :)
To run the full example, go [here](https://github.com/project-serum/anchor/tree/master/examples/errors).

View File

@ -1,115 +0,0 @@
# Associated Accounts
No Solana program should be written without understanding associated accounts.
Every program using an account in a way that maps to a user, which is almost all
programs, should use them.
The TLDR. Associated accounts are accounts whose address are deterministically defined by
a program and some associated data. Usually that data is both a user wallet and some account
instance, for example, a token mint.
Why should you care? UX.
Consider a wallet. Would you rather have a wallet with a single SOL address, which you
can use to receive *all* SPL tokens, or would you rather have a wallet with a different
address for every SPL token? Now generalize this. For every program you use, do you
want a single account, i.e. your SOL wallet, to define your application state? Or do
you want to keep track of all your account addresses, separately, for every program in existance?
Associated accounts allow your users to reason about a single address, their main SOL wallet. This is
a huge improvement on the account model introduced thus far.
Luckily, Anchor provides the ability to easily create associated program accounts for your program.
::: details
If you've explored Solana, you may have come across the [Associated Token Account Program](https://spl.solana.com/associated-token-account) which uniquely and deterministically defines
a token account for a given wallet and a given mint. That is, if you have a SOL address,
then you will have, at most, a single "token account" for every SPL mint in existence
if you only use associated token addresses.
Unfortunately, the SPL token program doesn't do this, strictly. It was built *before* the existance
of associated token accounts (associated token accounts were built as an add-on).
So in reality, there are non associated token accounts floating around Solanaland.
However, for new programs, this isn't necessary, and it's recommended to only use associated
accounts when creating accounts on behalf of users, such as a token account.
:::
## Clone the Repo
To get started, clone the repo.
```bash
git clone https://github.com/project-serum/anchor
```
And change directories to the [example](https://github.com/project-serum/anchor/tree/master/examples/tutorial/basic-5).
```bash
cd anchor/examples/tutorial/basic-5
```
## Defining a Program to Create Associated Accounts
The following program is an *extremely* simplified version of the SPL token program that
does nothing other than create a mint and *associated* token account.
<<< @/../examples/tutorial/basic-5/programs/basic-5/src/lib.rs#code
### Deriving Accounts
Two new keywords are introduced to the `CreateToken` account context:
* `associated = <target>`
* `with = <target>`
Both of these allow you to define input "seeds" that
uniquely define the associated account. By convention, `associated` is used to define
the main address to associate, i.e., the wallet, while `with` is used to define an
auxilliary piece of metadata which has the effect of namespacing the associated account.
This can be used, for example, to create multiple different associated accounts, each of
which is associated *with* a new piece of metadata. In the token program, these pieces
of metadata are mints, i.e., different token types.
Lastly, notice the two accounts at the bottom of account context.
```rust
rent: Sysvar<'info, Rent>,
system_program: AccountInfo<'info>,
```
In the same way that `rent` is required when using `init` in the previous tutorials,
`rent` and additionally the `system-program` must be provided when creating an associated
account. By convention, the names must be as given here.
For more details on how to use `#[account(associated)]`, see [docs.rs](https://docs.rs/anchor-lang/latest/anchor_lang/derive.Accounts.html).
### Defining an Associated Account
The new `#[acount(associated)]` attribute will allow you to create a new associated account similar to `#[account(init)]`, but
to actually define an account as associated, you must use the `#[associated]` attribute *instead* of the `#[account]` attribute.
This new `#[associated]` attribute extends `#[account]` to include two things
* A `Default` implementation, which is required for automatic size detection (performed when `#[account(space = "<size>")]` is omitted from the accounts context).
* The implementation of the [Bump](https://docs.rs/anchor-lang/latest/anchor_lang/trait.Bump.html) trait, which is a bit of an implementation but is required for program derived addresses on Solana.
## Using the Client
The client can be used similarly to all other examples. Additionally, we introduce
two new apis.
* `<program>.account.<account-name>.associatedAddress` returns an associated token address, given seeds.
* `<program>.account.<account-name>.associated` returns the deserialized associated account, given seeds.
We can use them with the example above as follows
<<< @/../examples/tutorial/basic-5/tests/basic-5.js#test
Notice that, in both apis, the "seeds" given match what is expected by the `#[account(associated = <target, with = <target>)]` attribute, where order matters. The `associated` target must come before the `with` target.
## Conclusion
Here, we introduced associated accounts from the perspective of simplifying UX for
a user wallet. However, deterministic addressing can be used beyond this and is a convenient
tool to have in your Solana toolbox. For more, it's recommended to see the Solana [docs](https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses).

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

@ -1 +0,0 @@
Subproject commit a72e59a9b263b7e083af737669f12f5e3ee1997c

View File

@ -1,202 +0,0 @@
const assert = require("assert");
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 { PublicKey, SystemProgram, 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 FEES = "6160355581";
describe("cfo", () => {
anchor.setProvider(anchor.Provider.env());
const program = anchor.workspace.Cfo;
let officer;
let TOKEN_CLIENT;
let officerAccount;
const sweepAuthority = program.provider.wallet.publicKey;
// 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()
);
TOKEN_CLIENT = new Token(
program.provider.connection,
ORDERBOOK_ENV.usdc,
TOKEN_PID,
program.provider.wallet.payer
);
await TOKEN_CLIENT.transfer(
ORDERBOOK_ENV.godUsdc,
ORDERBOOK_ENV.marketA._decoded.quoteVault,
program.provider.wallet.payer,
[],
10000000000000
);
const tokenAccount = await TOKEN_CLIENT.getAccountInfo(
ORDERBOOK_ENV.marketA._decoded.quoteVault
);
assert.ok(tokenAccount.amount.toString() === "10000902263700");
});
it("BOILERPLATE: Executes trades to generate fees", async () => {
await utils.runTradeBot(
ORDERBOOK_ENV.marketA._decoded.ownAddress,
program.provider,
1
);
let marketClient = await Market.load(
program.provider.connection,
ORDERBOOK_ENV.marketA._decoded.ownAddress,
{ commitment: "recent" },
DEX_PID
);
assert.ok(marketClient._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("Creates a CFO!", async () => {
let distribution = {
burn: 80,
stake: 20,
treasury: 0,
};
officer = await program.account.officer.associatedAddress(DEX_PID);
const srmVault = await anchor.utils.publicKey.associated(
program.programId,
officer,
anchor.utils.bytes.utf8.encode("vault"),
);
const stake = await anchor.utils.publicKey.associated(
program.programId,
officer,
anchor.utils.bytes.utf8.encode("stake"),
);
const treasury = await anchor.utils.publicKey.associated(
program.programId,
officer,
Buffer.from(anchor.utils.bytes.utf8.encode("treasury")),
);
await program.rpc.createOfficer(distribution, registrar, msrmRegistrar, {
accounts: {
officer,
srmVault,
stake,
treasury,
mint: ORDERBOOK_ENV.mintA,
authority: program.provider.wallet.publicKey,
dexProgram: DEX_PID,
swapProgram: SWAP_PID,
tokenProgram: TOKEN_PID,
systemProgram: SystemProgram.programId,
rent: SYSVAR_RENT_PUBKEY,
},
});
officerAccount = await program.account.officer.associated(DEX_PID);
assert.ok(
officerAccount.authority.equals(program.provider.wallet.publicKey)
);
assert.ok(
JSON.stringify(officerAccount.distribution) ===
JSON.stringify(distribution)
);
});
it("Creates a token account for the officer associated with the market", async () => {
const token = await anchor.utils.publicKey.associated(
program.programId,
officer,
ORDERBOOK_ENV.usdc
);
await program.rpc.createOfficerToken({
accounts: {
officer,
token,
mint: ORDERBOOK_ENV.usdc,
payer: program.provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PID,
rent: SYSVAR_RENT_PUBKEY,
},
});
const tokenAccount = await TOKEN_CLIENT.getAccountInfo(token);
assert.ok(tokenAccount.state === 1);
assert.ok(tokenAccount.isInitialized);
});
it("Sweeps fees", async () => {
const sweepVault = await anchor.utils.publicKey.associated(
program.programId,
officer,
ORDERBOOK_ENV.usdc
);
const beforeTokenAccount = await serumCmn.getTokenAccount(
program.provider,
sweepVault
);
await program.rpc.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.vaultSigner,
dexProgram: DEX_PID,
tokenProgram: TOKEN_PID,
},
},
});
const afterTokenAccount = await serumCmn.getTokenAccount(
program.provider,
sweepVault
);
assert.ok(
afterTokenAccount.amount.sub(beforeTokenAccount.amount).toString() ===
FEES
);
});
it("TODO", async () => {
// todo
});
});

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,25 +0,0 @@
const anchor = require('@project-serum/anchor');
const assert = require("assert");
describe("events", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
it("Is initialized!", async () => {
const program = anchor.workspace.Events;
let listener = null;
let [event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);
assert.ok(slot > 0);
assert.ok(event.data.toNumber() === 5);
assert.ok(event.label === "hello");
});
});

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,139 +0,0 @@
// TODO: use the `@solana/spl-token` package instead of utils here.
const anchor = require("@project-serum/anchor");
const serumCmn = require("@project-serum/common");
const TokenInstructions = require("@project-serum/serum").TokenInstructions;
// TODO: remove this constant once @project-serum/serum uses the same version
// of @solana/web3.js as anchor (or switch packages).
const TOKEN_PROGRAM_ID = new anchor.web3.PublicKey(
TokenInstructions.TOKEN_PROGRAM_ID.toString()
);
// Our own sleep function.
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
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 = anchor.web3.Keypair.generate();
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: TOKEN_PROGRAM_ID,
}),
TokenInstructions.initializeMint({
mint,
decimals: 6,
mintAuthority: authority,
}),
];
return instructions;
}
async function createTokenAccount(provider, mint, owner) {
const vault = anchor.web3.Keypair.generate();
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: TOKEN_PROGRAM_ID,
}),
TokenInstructions.initializeAccount({
account: newAccountPubkey,
mint,
owner,
}),
];
}
async function mintToAccount(
provider,
mint,
destination,
amount,
mintAuthority
) {
// mint authority is the provider
const tx = new anchor.web3.Transaction();
tx.add(
...(await createMintToAccountInstrs(
mint,
destination,
amount,
mintAuthority
))
);
await provider.send(tx, []);
return;
}
async function createMintToAccountInstrs(
mint,
destination,
amount,
mintAuthority
) {
return [
TokenInstructions.mintTo({
mint,
destination: destination,
amount: amount,
mintAuthority: mintAuthority,
}),
];
}
module.exports = {
TOKEN_PROGRAM_ID,
sleep,
getTokenAccount,
createMint,
createTokenAccount,
mintToAccount,
};

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,7 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[[test.genesis]]
address = "FtMNMKp9DZHKWUyVAsj3Q5QV8ow4P3fUPP7ZrWEQJzKr"
program = "./target/deploy/misc.so"

View File

@ -1,172 +0,0 @@
use crate::account::*;
use crate::misc::MyState;
use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, TokenAccount};
use misc2::misc2::MyState as Misc2State;
#[derive(Accounts)]
#[instruction(nonce: u8)]
pub struct TestTokenSeedsInit<'info> {
#[account(
init,
token = mint,
authority = authority,
seeds = [b"my-token-seed".as_ref(), &[nonce]],
payer = authority,
space = TokenAccount::LEN,
)]
pub my_pda: CpiAccount<'info, TokenAccount>,
pub mint: CpiAccount<'info, Mint>,
pub authority: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
pub token_program: AccountInfo<'info>,
}
#[derive(Accounts)]
#[instruction(nonce: u8)]
pub struct TestInstructionConstraint<'info> {
#[account(seeds = [b"my-seed", my_account.key.as_ref(), &[nonce]])]
pub my_pda: AccountInfo<'info>,
pub my_account: AccountInfo<'info>,
}
#[derive(Accounts)]
#[instruction(domain: String, seed: Vec<u8>, bump: u8)]
pub struct TestPdaInit<'info> {
#[account(
init,
seeds = [b"my-seed", domain.as_bytes(), foo.key.as_ref(), &seed, &[bump]],
payer = my_payer,
)]
pub my_pda: ProgramAccount<'info, DataU16>,
pub my_payer: AccountInfo<'info>,
pub foo: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: AccountInfo<'info>,
}
#[derive(Accounts)]
#[instruction(bump: u8)]
pub struct TestPdaInitZeroCopy<'info> {
#[account(init, seeds = [b"my-seed".as_ref(), &[bump]], payer = my_payer)]
pub my_pda: Loader<'info, DataZeroCopy>,
pub my_payer: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestPdaMutZeroCopy<'info> {
#[account(mut, seeds = [b"my-seed".as_ref(), &[my_pda.load()?.bump]])]
pub my_pda: Loader<'info, DataZeroCopy>,
pub my_payer: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct Ctor {}
#[derive(Accounts)]
pub struct RemainingAccounts {}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init)]
pub data: ProgramAccount<'info, Data>,
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct TestOwner<'info> {
#[account(owner = misc)]
pub data: AccountInfo<'info>,
pub misc: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestExecutable<'info> {
#[account(executable)]
pub program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestStateCpi<'info> {
#[account(signer)]
pub authority: AccountInfo<'info>,
#[account(mut, state = misc2_program)]
pub cpi_state: CpiState<'info, Misc2State>,
#[account(executable)]
pub misc2_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestClose<'info> {
#[account(mut, close = sol_dest)]
pub data: ProgramAccount<'info, Data>,
sol_dest: AccountInfo<'info>,
}
// `my_account` is the associated token account being created.
// `authority` must be a `mut` and `signer` since it will pay for the creation
// of the associated token account. `state` is used as an association, i.e., one
// can *optionally* identify targets to be used as seeds for the program
// derived address by using `with` (and it doesn't have to be a state account).
// For example, the SPL token program uses a `Mint` account. Lastly,
// `rent` and `system_program` are *required* by convention, since the
// accounts are needed when creating the associated program address within
// the program.
#[derive(Accounts)]
pub struct TestInitAssociatedAccount<'info> {
#[account(init, associated = authority, with = state, with = data, with = b"my-seed")]
pub my_account: ProgramAccount<'info, TestData>,
#[account(mut, signer)]
pub authority: AccountInfo<'info>,
pub state: ProgramState<'info, MyState>,
pub data: ProgramAccount<'info, Data>,
pub rent: Sysvar<'info, Rent>,
pub system_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestAssociatedAccount<'info> {
#[account(mut, associated = authority, with = state, with = data, with = b"my-seed")]
pub my_account: ProgramAccount<'info, TestData>,
#[account(mut, signer)]
pub authority: AccountInfo<'info>,
pub state: ProgramState<'info, MyState>,
pub data: ProgramAccount<'info, Data>,
}
#[derive(Accounts)]
pub struct TestU16<'info> {
#[account(init)]
pub my_account: ProgramAccount<'info, DataU16>,
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct TestI16<'info> {
#[account(init)]
pub data: ProgramAccount<'info, DataI16>,
pub rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct TestSimulate {}
#[derive(Accounts)]
pub struct TestSimulateAssociatedAccount<'info> {
#[account(init, associated = authority)]
pub my_account: ProgramAccount<'info, TestData>,
#[account(mut, signer)]
pub authority: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestI8<'info> {
#[account(init)]
pub data: ProgramAccount<'info, DataI8>,
pub rent: Sysvar<'info, Rent>,
}

View File

@ -1,503 +0,0 @@
const anchor = require("@project-serum/anchor");
const PublicKey = anchor.web3.PublicKey;
const assert = require("assert");
const { TOKEN_PROGRAM_ID, Token } = require("@solana/spl-token");
describe("misc", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
const program = anchor.workspace.Misc;
const misc2Program = anchor.workspace.Misc2;
it("Can allocate extra space for a state constructor", async () => {
const tx = await program.state.rpc.new();
const addr = await program.state.address();
const state = await program.state.fetch();
const accountInfo = await program.provider.connection.getAccountInfo(addr);
assert.ok(state.v.equals(Buffer.from([])));
assert.ok(accountInfo.data.length === 99);
});
it("Can use remaining accounts for a state instruction", async () => {
await program.state.rpc.remainingAccounts({
remainingAccounts: [
{ pubkey: misc2Program.programId, isWritable: false, isSigner: false },
],
});
});
const data = anchor.web3.Keypair.generate();
it("Can use u128 and i128", async () => {
const tx = await program.rpc.initialize(
new anchor.BN(1234),
new anchor.BN(22),
{
accounts: {
data: data.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [data],
instructions: [await program.account.data.createInstruction(data)],
}
);
const dataAccount = await program.account.data.fetch(data.publicKey);
assert.ok(dataAccount.udata.eq(new anchor.BN(1234)));
assert.ok(dataAccount.idata.eq(new anchor.BN(22)));
});
it("Can use u16", async () => {
const data = anchor.web3.Keypair.generate();
const tx = await program.rpc.testU16(99, {
accounts: {
myAccount: data.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [data],
instructions: [await program.account.dataU16.createInstruction(data)],
});
const dataAccount = await program.account.dataU16.fetch(data.publicKey);
assert.ok(dataAccount.data === 99);
});
it("Can embed programs into genesis from the Anchor.toml", async () => {
const pid = new anchor.web3.PublicKey(
"FtMNMKp9DZHKWUyVAsj3Q5QV8ow4P3fUPP7ZrWEQJzKr"
);
let accInfo = await anchor.getProvider().connection.getAccountInfo(pid);
assert.ok(accInfo.executable);
});
it("Can use the owner constraint", async () => {
await program.rpc.testOwner({
accounts: {
data: data.publicKey,
misc: program.programId,
},
});
await assert.rejects(
async () => {
await program.rpc.testOwner({
accounts: {
data: program.provider.wallet.publicKey,
misc: program.programId,
},
});
},
(err) => {
return true;
}
);
});
it("Can use the executable attribute", async () => {
await program.rpc.testExecutable({
accounts: {
program: program.programId,
},
});
await assert.rejects(
async () => {
await program.rpc.testExecutable({
accounts: {
program: program.provider.wallet.publicKey,
},
});
},
(err) => {
return true;
}
);
});
it("Can CPI to state instructions", async () => {
const oldData = new anchor.BN(0);
await misc2Program.state.rpc.new({
accounts: {
authority: program.provider.wallet.publicKey,
},
});
let stateAccount = await misc2Program.state.fetch();
assert.ok(stateAccount.data.eq(oldData));
assert.ok(stateAccount.auth.equals(program.provider.wallet.publicKey));
const newData = new anchor.BN(2134);
await program.rpc.testStateCpi(newData, {
accounts: {
authority: program.provider.wallet.publicKey,
cpiState: await misc2Program.state.address(),
misc2Program: misc2Program.programId,
},
});
stateAccount = await misc2Program.state.fetch();
assert.ok(stateAccount.data.eq(newData));
assert.ok(stateAccount.auth.equals(program.provider.wallet.publicKey));
});
it("Can init an associated program account", async () => {
const state = await program.state.address();
// Manual associated address calculation for test only. Clients should use
// the generated methods.
const [
associatedAccount,
nonce,
] = await anchor.web3.PublicKey.findProgramAddress(
[
anchor.utils.bytes.utf8.encode("anchor"),
program.provider.wallet.publicKey.toBuffer(),
state.toBuffer(),
data.publicKey.toBuffer(),
anchor.utils.bytes.utf8.encode("my-seed"),
],
program.programId
);
await assert.rejects(
async () => {
await program.account.testData.fetch(associatedAccount);
},
(err) => {
assert.ok(
err.toString() ===
`Error: Account does not exist ${associatedAccount.toString()}`
);
return true;
}
);
await program.rpc.testInitAssociatedAccount(new anchor.BN(1234), {
accounts: {
myAccount: associatedAccount,
authority: program.provider.wallet.publicKey,
state,
data: data.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
// Try out the generated associated method.
const account = await program.account.testData.associated(
program.provider.wallet.publicKey,
state,
data.publicKey,
anchor.utils.bytes.utf8.encode("my-seed")
);
assert.ok(account.data.toNumber() === 1234);
});
it("Can use an associated program account", async () => {
const state = await program.state.address();
const [
associatedAccount,
nonce,
] = await anchor.web3.PublicKey.findProgramAddress(
[
anchor.utils.bytes.utf8.encode("anchor"),
program.provider.wallet.publicKey.toBuffer(),
state.toBuffer(),
data.publicKey.toBuffer(),
anchor.utils.bytes.utf8.encode("my-seed"),
],
program.programId
);
await program.rpc.testAssociatedAccount(new anchor.BN(5), {
accounts: {
myAccount: associatedAccount,
authority: program.provider.wallet.publicKey,
state,
data: data.publicKey,
},
});
// Try out the generated associated method.
const account = await program.account.testData.associated(
program.provider.wallet.publicKey,
state,
data.publicKey,
anchor.utils.bytes.utf8.encode("my-seed")
);
assert.ok(account.data.toNumber() === 5);
});
it("Can retrieve events when simulating a transaction", async () => {
const resp = await program.simulate.testSimulate(44);
const expectedRaw = [
"Program Z2Ddx1Lcd8CHTV9tkWtNnFQrSz6kxz2H38wrr18zZRZ invoke [1]",
"Program log: NgyCA9omwbMsAAAA",
"Program log: fPhuIELK/k7SBAAA",
"Program log: jvbowsvlmkcJAAAA",
"Program Z2Ddx1Lcd8CHTV9tkWtNnFQrSz6kxz2H38wrr18zZRZ consumed 4819 of 200000 compute units",
"Program Z2Ddx1Lcd8CHTV9tkWtNnFQrSz6kxz2H38wrr18zZRZ success",
];
assert.ok(JSON.stringify(expectedRaw), resp.raw);
assert.ok(resp.events[0].name === "E1");
assert.ok(resp.events[0].data.data === 44);
assert.ok(resp.events[1].name === "E2");
assert.ok(resp.events[1].data.data === 1234);
assert.ok(resp.events[2].name === "E3");
assert.ok(resp.events[2].data.data === 9);
});
it("Can retrieve events when associated account is initialized in simulated transaction", async () => {
const myAccount = await program.account.testData.associatedAddress(
program.provider.wallet.publicKey
);
await assert.rejects(
async () => {
await program.account.testData.fetch(myAccount);
},
(err) => {
assert.ok(
err.toString() ===
`Error: Account does not exist ${myAccount.toString()}`
);
return true;
}
);
const resp = await program.simulate.testSimulateAssociatedAccount(44, {
accounts: {
myAccount,
authority: program.provider.wallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
const expectedRaw = [
"Program Fv6oRfzWETatiMymBvTs1JpRspZz3DbBfjZJEvUTDL1g invoke [1]",
"Program 11111111111111111111111111111111 invoke [2]",
"Program 11111111111111111111111111111111 success",
"Program log: NgyCA9omwbMsAAAA",
"Program log: fPhuIELK/k7SBAAA",
"Program log: jvbowsvlmkcJAAAA",
"Program log: mg+zq/K0sXRV+N/AsG9XLERDZ+J6eQAnnzoQVHlicBQBnGr65KE5Kw==",
"Program Fv6oRfzWETatiMymBvTs1JpRspZz3DbBfjZJEvUTDL1g consumed 20460 of 200000 compute units",
"Program Fv6oRfzWETatiMymBvTs1JpRspZz3DbBfjZJEvUTDL1g success",
];
assert.ok(JSON.stringify(expectedRaw), resp.raw);
assert.ok(resp.events[0].name === "E1");
assert.ok(resp.events[0].data.data === 44);
assert.ok(resp.events[1].name === "E2");
assert.ok(resp.events[1].data.data === 1234);
assert.ok(resp.events[2].name === "E3");
assert.ok(resp.events[2].data.data === 9);
assert.ok(resp.events[3].name === "E4");
assert.ok(resp.events[3].data.data.toBase58() === myAccount.toBase58());
});
it("Can use i8 in the idl", async () => {
const data = anchor.web3.Keypair.generate();
await program.rpc.testI8(-3, {
accounts: {
data: data.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
instructions: [await program.account.dataI8.createInstruction(data)],
signers: [data],
});
const dataAccount = await program.account.dataI8.fetch(data.publicKey);
assert.ok(dataAccount.data === -3);
});
let dataPubkey;
it("Can use i16 in the idl", async () => {
const data = anchor.web3.Keypair.generate();
await program.rpc.testI16(-2048, {
accounts: {
data: data.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
instructions: [await program.account.dataI16.createInstruction(data)],
signers: [data],
});
const dataAccount = await program.account.dataI16.fetch(data.publicKey);
assert.ok(dataAccount.data === -2048);
dataPubkey = data.publicKey;
});
it("Can use base58 strings to fetch an account", async () => {
const dataAccount = await program.account.dataI16.fetch(
dataPubkey.toString()
);
assert.ok(dataAccount.data === -2048);
});
it("Should fail to close an account when sending lamports to itself", async () => {
try {
await program.rpc.testClose({
accounts: {
data: data.publicKey,
solDest: data.publicKey,
},
});
assert.ok(false);
} catch (err) {
const errMsg = "A close constraint was violated";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 151);
}
});
it("Can close an account", async () => {
const openAccount = await program.provider.connection.getAccountInfo(
data.publicKey
);
assert.ok(openAccount !== null);
let beforeBalance = (
await program.provider.connection.getAccountInfo(
program.provider.wallet.publicKey
)
).lamports;
await program.rpc.testClose({
accounts: {
data: data.publicKey,
solDest: program.provider.wallet.publicKey,
},
});
let afterBalance = (
await program.provider.connection.getAccountInfo(
program.provider.wallet.publicKey
)
).lamports;
// Retrieved rent exemption sol.
assert.ok(afterBalance > beforeBalance);
const closedAccount = await program.provider.connection.getAccountInfo(
data.publicKey
);
assert.ok(closedAccount === null);
});
it("Can use instruction data in accounts constraints", async () => {
// b"my-seed"
const seed = Buffer.from([109, 121, 45, 115, 101, 101, 100]);
const [myPda, nonce] = await PublicKey.findProgramAddress(
[seed, anchor.web3.SYSVAR_RENT_PUBKEY.toBuffer()],
program.programId
);
await program.rpc.testInstructionConstraint(nonce, {
accounts: {
myPda,
myAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
},
});
});
it("Can create a PDA account with instruction data", async () => {
const seed = Buffer.from([1, 2, 3, 4]);
const domain = "my-domain";
const foo = anchor.web3.SYSVAR_RENT_PUBKEY;
const [myPda, nonce] = await PublicKey.findProgramAddress(
[
Buffer.from(anchor.utils.bytes.utf8.encode("my-seed")),
Buffer.from(anchor.utils.bytes.utf8.encode(domain)),
foo.toBuffer(),
seed,
],
program.programId
);
await program.rpc.testPdaInit(domain, seed, nonce, {
accounts: {
myPda,
myPayer: program.provider.wallet.publicKey,
foo,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
const myPdaAccount = await program.account.dataU16.fetch(myPda);
assert.ok(myPdaAccount.data === 6);
});
it("Can create a zero copy PDA account", async () => {
const [myPda, nonce] = await PublicKey.findProgramAddress(
[Buffer.from(anchor.utils.bytes.utf8.encode("my-seed"))],
program.programId
);
await program.rpc.testPdaInitZeroCopy(nonce, {
accounts: {
myPda,
myPayer: program.provider.wallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
const myPdaAccount = await program.account.dataZeroCopy.fetch(myPda);
assert.ok(myPdaAccount.data === 9);
assert.ok((myPdaAccount.bump = nonce));
});
it("Can write to a zero copy PDA account", async () => {
const [myPda, bump] = await PublicKey.findProgramAddress(
[Buffer.from(anchor.utils.bytes.utf8.encode("my-seed"))],
program.programId
);
await program.rpc.testPdaMutZeroCopy({
accounts: {
myPda,
myPayer: program.provider.wallet.publicKey,
},
});
const myPdaAccount = await program.account.dataZeroCopy.fetch(myPda);
assert.ok(myPdaAccount.data === 1234);
assert.ok((myPdaAccount.bump = bump));
});
it("Can create a token account from seeds pda", async () => {
const mint = await Token.createMint(
program.provider.connection,
program.provider.wallet.payer,
program.provider.wallet.publicKey,
null,
0,
TOKEN_PROGRAM_ID
);
const [myPda, bump] = await PublicKey.findProgramAddress(
[Buffer.from(anchor.utils.bytes.utf8.encode("my-token-seed"))],
program.programId
);
await program.rpc.testTokenSeedsInit(bump, {
accounts: {
myPda,
mint: mint.publicKey,
authority: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
tokenProgram: TOKEN_PROGRAM_ID,
},
});
const account = await mint.getAccountInfo(myPda);
assert.ok(account.state === 1);
assert.ok(account.amount.toNumber() === 0);
assert.ok(account.isInitialized);
assert.ok(account.owner.equals(program.provider.wallet.publicKey));
assert.ok(account.mint.equals(mint.publicKey));
});
it("Can execute a fallback function", async () => {
await assert.rejects(
async () => {
await anchor.utils.rpc.invoke(program.programId);
},
(err) => {
assert.ok(err.toString().includes("custom program error: 0x4d2"));
return true;
}
);
});
});

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

@ -1 +0,0 @@
Subproject commit 1f6d5867019e242a470deed79cddca0d1f15e0a3

View File

@ -1,345 +0,0 @@
// Note. This example depends on unreleased Serum DEX changes.
use anchor_lang::prelude::*;
use anchor_spl::dex;
use serum_dex::instruction::MarketInstruction;
use serum_dex::state::OpenOrders;
use solana_program::instruction::Instruction;
use solana_program::program;
use solana_program::system_program;
use std::mem::size_of;
/// This demonstrates how to create "permissioned markets" on Serum. A
/// permissioned market is a regular Serum market with an additional
/// open orders authority, which must sign every transaction to create or
/// close an open orders account.
///
/// In practice, what this means is that one can create a program that acts
/// as this authority *and* that marks its own PDAs as the *owner* of all
/// created open orders accounts, making the program the sole arbiter over
/// who can trade on a given market.
///
/// For example, this example forces all trades that execute on this market
/// to set the referral to a hardcoded address, i.e., `fee_owner::ID`.
#[program]
pub mod permissioned_markets {
use super::*;
/// Creates an open orders account controlled by this program on behalf of
/// the user.
///
/// Note that although the owner of the open orders account is the dex
/// program, This instruction must be executed within this program, rather
/// than a relay, because it initializes a PDA.
pub fn init_account(ctx: Context<InitAccount>, bump: u8, bump_init: u8) -> Result<()> {
let cpi_ctx = CpiContext::from(&*ctx.accounts);
let seeds = open_orders_authority! {
program = ctx.program_id,
market = ctx.accounts.market.key,
authority = ctx.accounts.authority.key,
bump = bump
};
let seeds_init = open_orders_init_authority! {
program = ctx.program_id,
market = ctx.accounts.market.key,
bump = bump_init
};
dex::init_open_orders(cpi_ctx.with_signer(&[seeds, seeds_init]))?;
Ok(())
}
/// Fallback function to relay calls to the serum DEX.
///
/// For instructions requiring an open orders authority, checks for
/// a user signature and then swaps the account info for one controlled
/// by the program.
///
/// Note: the "authority" of each open orders account is the account
/// itself, since it's a PDA.
#[access_control(is_serum(accounts))]
pub fn dex_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
data: &[u8],
) -> ProgramResult {
require!(accounts.len() >= 1, NotEnoughAccounts);
let dex_acc_info = &accounts[0];
let dex_accounts = &accounts[1..];
let mut acc_infos = dex_accounts.to_vec();
// Decode instruction.
let ix = MarketInstruction::unpack(data).ok_or_else(|| ErrorCode::CannotUnpack)?;
// Swap the user's account, which is in the open orders authority
// position, for the program's PDA (the real authority).
let (market, user) = match ix {
MarketInstruction::NewOrderV3(_) => {
require!(dex_accounts.len() >= 12, NotEnoughAccounts);
let (market, user) = {
let market = &acc_infos[0];
let user = &acc_infos[7];
if !user.is_signer {
return Err(ErrorCode::UnauthorizedUser.into());
}
(*market.key, *user.key)
};
acc_infos[7] = prepare_pda(&acc_infos[1]);
(market, user)
}
MarketInstruction::CancelOrderV2(_) => {
require!(dex_accounts.len() >= 6, NotEnoughAccounts);
let (market, user) = {
let market = &acc_infos[0];
let user = &acc_infos[4];
if !user.is_signer {
return Err(ErrorCode::UnauthorizedUser.into());
}
(*market.key, *user.key)
};
acc_infos[4] = prepare_pda(&acc_infos[3]);
(market, user)
}
MarketInstruction::CancelOrderByClientIdV2(_) => {
require!(dex_accounts.len() >= 6, NotEnoughAccounts);
let (market, user) = {
let market = &acc_infos[0];
let user = &acc_infos[4];
if !user.is_signer {
return Err(ErrorCode::UnauthorizedUser.into());
}
(*market.key, *user.key)
};
acc_infos[4] = prepare_pda(&acc_infos[3]);
(market, user)
}
MarketInstruction::SettleFunds => {
require!(dex_accounts.len() >= 10, NotEnoughAccounts);
let (market, user) = {
let market = &acc_infos[0];
let user = &acc_infos[2];
let referral = &dex_accounts[9];
if !DISABLE_REFERRAL && referral.key != &referral::ID {
return Err(ErrorCode::InvalidReferral.into());
}
if !user.is_signer {
return Err(ErrorCode::UnauthorizedUser.into());
}
(*market.key, *user.key)
};
acc_infos[2] = prepare_pda(&acc_infos[1]);
(market, user)
}
MarketInstruction::CloseOpenOrders => {
require!(dex_accounts.len() >= 4, NotEnoughAccounts);
let (market, user) = {
let market = &acc_infos[3];
let user = &acc_infos[1];
if !user.is_signer {
return Err(ErrorCode::UnauthorizedUser.into());
}
(*market.key, *user.key)
};
acc_infos[1] = prepare_pda(&acc_infos[0]);
(market, user)
}
_ => return Err(ErrorCode::InvalidInstruction.into()),
};
// CPI to the dex.
let dex_accounts = acc_infos
.iter()
.map(|acc| AccountMeta {
pubkey: *acc.key,
is_signer: acc.is_signer,
is_writable: acc.is_writable,
})
.collect();
acc_infos.push(dex_acc_info.clone());
let ix = Instruction {
data: data.to_vec(),
accounts: dex_accounts,
program_id: dex::ID,
};
let seeds = open_orders_authority! {
program = program_id,
market = market,
authority = user
};
program::invoke_signed(&ix, &acc_infos, &[seeds])
}
}
// Accounts context.
#[derive(Accounts)]
#[instruction(bump: u8, bump_init: u8)]
pub struct InitAccount<'info> {
#[account(seeds = [b"open-orders-init", market.key.as_ref(), &[bump_init]])]
pub open_orders_init_authority: AccountInfo<'info>,
#[account(
init,
seeds = [b"open-orders", market.key.as_ref(), authority.key.as_ref()],
bump = bump,
payer = authority,
owner = dex::ID,
space = size_of::<OpenOrders>() + SERUM_PADDING,
)]
pub open_orders: AccountInfo<'info>,
#[account(signer)]
pub authority: AccountInfo<'info>,
pub market: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
#[account(address = system_program::ID)]
pub system_program: AccountInfo<'info>,
#[account(address = dex::ID)]
pub dex_program: AccountInfo<'info>,
}
// CpiContext transformations.
impl<'info> From<&InitAccount<'info>>
for CpiContext<'_, '_, '_, 'info, dex::InitOpenOrders<'info>>
{
fn from(accs: &InitAccount<'info>) -> Self {
// TODO: add the open orders init authority account here once the
// dex is upgraded.
let accounts = dex::InitOpenOrders {
open_orders: accs.open_orders.clone(),
authority: accs.open_orders.clone(),
market: accs.market.clone(),
rent: accs.rent.to_account_info(),
};
let program = accs.dex_program.clone();
CpiContext::new(program, accounts)
}
}
// Access control modifiers.
fn is_serum<'info>(accounts: &[AccountInfo<'info>]) -> Result<()> {
let dex_acc_info = &accounts[0];
if dex_acc_info.key != &dex::ID {
return Err(ErrorCode::InvalidDexPid.into());
}
Ok(())
}
// Error.
#[error]
pub enum ErrorCode {
#[msg("Program ID does not match the Serum DEX")]
InvalidDexPid,
#[msg("Invalid instruction given")]
InvalidInstruction,
#[msg("Could not unpack the instruction")]
CannotUnpack,
#[msg("Invalid referral address given")]
InvalidReferral,
#[msg("The user didn't sign")]
UnauthorizedUser,
#[msg("Not enough accounts were provided")]
NotEnoughAccounts,
}
// Macros.
/// Returns the seeds used for creating the open orders account PDA.
#[macro_export]
macro_rules! open_orders_authority {
(program = $program:expr, market = $market:expr, authority = $authority:expr, bump = $bump:expr) => {
&[
b"open-orders".as_ref(),
$market.as_ref(),
$authority.as_ref(),
&[$bump],
]
};
(program = $program:expr, market = $market:expr, authority = $authority:expr) => {
&[
b"open-orders".as_ref(),
$market.as_ref(),
$authority.as_ref(),
&[Pubkey::find_program_address(
&[
b"open-orders".as_ref(),
$market.as_ref(),
$authority.as_ref(),
],
$program,
)
.1],
]
};
}
/// Returns the seeds used for the open orders init authority.
/// This is the account that must sign to create a new open orders account on
/// the DEX market.
#[macro_export]
macro_rules! open_orders_init_authority {
(program = $program:expr, market = $market:expr) => {
&[
b"open-orders-init".as_ref(),
$market.as_ref(),
&[Pubkey::find_program_address(
&[b"open-orders-init".as_ref(), $market.as_ref()],
$program,
)
.1],
]
};
(program = $program:expr, market = $market:expr, bump = $bump:expr) => {
&[b"open-orders-init".as_ref(), $market.as_ref(), &[$bump]]
};
}
// Utils.
fn prepare_pda<'info>(acc_info: &AccountInfo<'info>) -> AccountInfo<'info> {
let mut acc_info = acc_info.clone();
acc_info.is_signer = true;
acc_info
}
// Constants.
// Padding added to every serum account.
//
// b"serum".len() + b"padding".len().
const SERUM_PADDING: usize = 12;
// True if we don't care about referral access control (for testing).
const DISABLE_REFERRAL: bool = true;
/// The address that will receive all fees for all markets controlled by this
/// program. Note: this is a dummy address. Do not use in production.
pub mod referral {
solana_program::declare_id!("2k1bb16Hu7ocviT2KC3wcCgETtnC8tEUuvFBH4C5xStG");
}

View File

@ -1,284 +0,0 @@
const assert = require("assert");
const { Token, TOKEN_PROGRAM_ID } = require("@solana/spl-token");
const anchor = require("@project-serum/anchor");
const serum = require("@project-serum/serum");
const { BN } = anchor;
const { Transaction, TransactionInstruction } = anchor.web3;
const { DexInstructions, OpenOrders, Market } = serum;
const { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY } = anchor.web3;
const { initMarket, sleep } = require("./utils");
const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
const REFERRAL = new PublicKey("2k1bb16Hu7ocviT2KC3wcCgETtnC8tEUuvFBH4C5xStG");
describe("permissioned-markets", () => {
// Anchor client setup.
const provider = anchor.Provider.env();
anchor.setProvider(provider);
const program = anchor.workspace.PermissionedMarkets;
// Token clients.
let usdcClient;
// Global DEX accounts and clients shared accross all tests.
let marketClient, tokenAccount, usdcAccount;
let openOrders, openOrdersBump, openOrdersInitAuthority, openOrdersBumpinit;
let usdcPosted;
let marketMakerOpenOrders;
it("BOILERPLATE: Initializes an orderbook", async () => {
const {
marketMakerOpenOrders: mmOo,
marketA,
godA,
godUsdc,
usdc,
} = await initMarket({ provider });
marketClient = marketA;
marketClient._programId = program.programId;
usdcAccount = godUsdc;
tokenAccount = godA;
marketMakerOpenOrders = mmOo;
usdcClient = new Token(
provider.connection,
usdc,
TOKEN_PROGRAM_ID,
provider.wallet.payer
);
});
it("BOILERPLATE: Calculates open orders addresses", async () => {
const [_openOrders, bump] = await PublicKey.findProgramAddress(
[
anchor.utils.bytes.utf8.encode("open-orders"),
marketClient.address.toBuffer(),
program.provider.wallet.publicKey.toBuffer(),
],
program.programId
);
const [
_openOrdersInitAuthority,
bumpInit,
] = await PublicKey.findProgramAddress(
[
anchor.utils.bytes.utf8.encode("open-orders-init"),
marketClient.address.toBuffer(),
],
program.programId
);
// Save global variables re-used across tests.
openOrders = _openOrders;
openOrdersBump = bump;
openOrdersInitAuthority = _openOrdersInitAuthority;
openOrdersBumpInit = bumpInit;
});
it("Creates an open orders account", async () => {
await program.rpc.initAccount(openOrdersBump, openOrdersBumpInit, {
accounts: {
openOrdersInitAuthority,
openOrders,
authority: program.provider.wallet.publicKey,
market: marketClient.address,
rent: SYSVAR_RENT_PUBKEY,
systemProgram: SystemProgram.programId,
dexProgram: DEX_PID,
},
});
const account = await provider.connection.getAccountInfo(openOrders);
assert.ok(account.owner.toString() === DEX_PID.toString());
});
it("Posts a bid on the orderbook", async () => {
const size = 1;
const price = 1;
// The amount of USDC transferred into the dex for the trade.
usdcPosted = new BN(marketClient._decoded.quoteLotSize.toNumber()).mul(
marketClient
.baseSizeNumberToLots(size)
.mul(marketClient.priceNumberToLots(price))
);
// Note: Prepend delegate approve to the tx since the owner of the token
// account must match the owner of the open orders account. We
// can probably hide this in the serum client.
const tx = new Transaction();
tx.add(
Token.createApproveInstruction(
TOKEN_PROGRAM_ID,
usdcAccount,
openOrders,
program.provider.wallet.publicKey,
[],
usdcPosted.toNumber()
)
);
tx.add(
serumProxy(
marketClient.makePlaceOrderInstruction(program.provider.connection, {
owner: program.provider.wallet.publicKey,
payer: usdcAccount,
side: "buy",
price,
size,
orderType: "postOnly",
clientId: new BN(999),
openOrdersAddressKey: openOrders,
selfTradeBehavior: "abortTransaction",
})
)
);
await provider.send(tx);
});
it("Cancels a bid on the orderbook", async () => {
// Given.
const beforeOoAccount = await OpenOrders.load(
provider.connection,
openOrders,
DEX_PID
);
// When.
const tx = new Transaction();
tx.add(
serumProxy(
(
await marketClient.makeCancelOrderByClientIdTransaction(
program.provider.connection,
program.provider.wallet.publicKey,
openOrders,
new BN(999)
)
).instructions[0]
)
);
await provider.send(tx);
// Then.
const afterOoAccount = await OpenOrders.load(
provider.connection,
openOrders,
DEX_PID
);
assert.ok(beforeOoAccount.quoteTokenFree.eq(new BN(0)));
assert.ok(beforeOoAccount.quoteTokenTotal.eq(usdcPosted));
assert.ok(afterOoAccount.quoteTokenFree.eq(usdcPosted));
assert.ok(afterOoAccount.quoteTokenTotal.eq(usdcPosted));
});
// Need to crank the cancel so that we can close later.
it("Cranks the cancel transaction", async () => {
// TODO: can do this in a single transaction if we covert the pubkey bytes
// into a [u64; 4] array and sort. I'm lazy though.
let eq = await marketClient.loadEventQueue(provider.connection);
while (eq.length > 0) {
const tx = new Transaction();
tx.add(
DexInstructions.consumeEvents({
market: marketClient._decoded.ownAddress,
eventQueue: marketClient._decoded.eventQueue,
coinFee: marketClient._decoded.eventQueue,
pcFee: marketClient._decoded.eventQueue,
openOrdersAccounts: [eq[0].openOrders],
limit: 1,
programId: DEX_PID,
})
);
await provider.send(tx);
eq = await marketClient.loadEventQueue(provider.connection);
}
});
it("Settles funds on the orderbook", async () => {
// Given.
const beforeTokenAccount = await usdcClient.getAccountInfo(usdcAccount);
// When.
const tx = new Transaction();
tx.add(
serumProxy(
DexInstructions.settleFunds({
market: marketClient._decoded.ownAddress,
openOrders,
owner: provider.wallet.publicKey,
baseVault: marketClient._decoded.baseVault,
quoteVault: marketClient._decoded.quoteVault,
baseWallet: tokenAccount,
quoteWallet: usdcAccount,
vaultSigner: await PublicKey.createProgramAddress(
[
marketClient.address.toBuffer(),
marketClient._decoded.vaultSignerNonce.toArrayLike(
Buffer,
"le",
8
),
],
DEX_PID
),
programId: program.programId,
referrerQuoteWallet: usdcAccount,
})
)
);
await provider.send(tx);
// Then.
const afterTokenAccount = await usdcClient.getAccountInfo(usdcAccount);
assert.ok(
afterTokenAccount.amount.sub(beforeTokenAccount.amount).toNumber() ===
usdcPosted.toNumber()
);
});
it("Closes an open orders account", async () => {
// Given.
const beforeAccount = await program.provider.connection.getAccountInfo(
program.provider.wallet.publicKey
);
// When.
const tx = new Transaction();
tx.add(
serumProxy(
DexInstructions.closeOpenOrders({
market: marketClient._decoded.ownAddress,
openOrders,
owner: program.provider.wallet.publicKey,
solWallet: program.provider.wallet.publicKey,
programId: program.programId,
})
)
);
await provider.send(tx);
// Then.
const afterAccount = await program.provider.connection.getAccountInfo(
program.provider.wallet.publicKey
);
const closedAccount = await program.provider.connection.getAccountInfo(
openOrders
);
assert.ok(23352768 === afterAccount.lamports - beforeAccount.lamports);
assert.ok(closedAccount === null);
});
});
// Adds the serum dex account to the instruction so that proxies can
// relay (CPI requires the executable account).
//
// TODO: we should add flag in the dex client that says if a proxy is being
// used, and if so, do this automatically.
function serumProxy(ix) {
ix.keys = [
{ pubkey: DEX_PID, isWritable: false, isSigner: false },
...ix.keys,
];
return ix;
}

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

@ -1 +0,0 @@
Subproject commit 19c8e37bf41d044a084b21e58182a50d119d46a2

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,3 +1,9 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[programs.localnet]
basic_0 = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[scripts]
test = "mocha -t 1000000 tests/"

View File

@ -1,5 +1,7 @@
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod basic_0 {
use super::*;

View File

@ -1,3 +1,9 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[programs.localnet]
basic_1 = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[scripts]
test = "mocha -t 1000000 tests/"

View File

@ -1,5 +1,7 @@
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod basic_1 {
use super::*;
@ -19,15 +21,17 @@ mod basic_1 {
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init)]
pub my_account: ProgramAccount<'info, MyAccount>,
pub rent: Sysvar<'info, Rent>,
#[account(init, payer = user, space = 8 + 8)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut)]
pub my_account: ProgramAccount<'info, MyAccount>,
pub my_account: Account<'info, MyAccount>,
}
#[account]

View File

@ -1,5 +1,6 @@
const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("basic-1", () => {
// Use a local provider.
@ -8,102 +9,23 @@ describe("basic-1", () => {
// Configure the client to use the local cluster.
anchor.setProvider(provider);
it("Creates and initializes an account in two different transactions", async () => {
// The program owning the account to create.
const program = anchor.workspace.Basic1;
// The Account to create.
const myAccount = anchor.web3.Keypair.generate();
// Create account transaction.
const tx = new anchor.web3.Transaction();
tx.add(
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: myAccount.publicKey,
space: 8 + 8,
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 8
),
programId: program.programId,
})
);
// Execute the transaction against the cluster.
await provider.send(tx, [myAccount]);
// Execute the RPC.
// #region code-separated
await program.rpc.initialize(new anchor.BN(1234), {
accounts: {
myAccount: myAccount.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
});
// #endregion code-separated
// Fetch the newly created account from the cluster.
const account = await program.account.myAccount.fetch(myAccount.publicKey);
// Check it's state was initialized.
assert.ok(account.data.eq(new anchor.BN(1234)));
});
// Reference to an account to use between multiple tests.
let _myAccount = undefined;
it("Creates and initializes an account in a single atomic transaction", async () => {
// The program to execute.
const program = anchor.workspace.Basic1;
// #region code
// The Account to create.
const myAccount = anchor.web3.Keypair.generate();
// Atomically create the new account and initialize it with the program.
await program.rpc.initialize(new anchor.BN(1234), {
accounts: {
myAccount: myAccount.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [myAccount],
instructions: [
anchor.web3.SystemProgram.createAccount({
fromPubkey: provider.wallet.publicKey,
newAccountPubkey: myAccount.publicKey,
space: 8 + 8, // Add 8 for the account discriminator.
lamports: await provider.connection.getMinimumBalanceForRentExemption(
8 + 8
),
programId: program.programId,
}),
],
});
// Fetch the newly created account from the cluster.
const account = await program.account.myAccount.fetch(myAccount.publicKey);
// Check it's state was initialized.
assert.ok(account.data.eq(new anchor.BN(1234)));
// #endregion code
});
it("Creates and initializes an account in a single atomic transaction (simplified)", async () => {
// #region code-simplified
// The program to execute.
const program = anchor.workspace.Basic1;
// The Account to create.
const myAccount = anchor.web3.Keypair.generate();
// Atomically create the new account and initialize it with the program.
// Create the new account and initialize it with the program.
// #region code-simplified
await program.rpc.initialize(new anchor.BN(1234), {
accounts: {
myAccount: myAccount.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [myAccount],
instructions: [await program.account.myAccount.createInstruction(myAccount)],
});
// #endregion code-simplified

View File

@ -1,3 +1,9 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[programs.localnet]
basic_2 = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[scripts]
test = "mocha -t 1000000 tests/"

View File

@ -1,6 +1,6 @@
use anchor_lang::prelude::*;
// Define the program's instruction handlers.
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod basic_2 {
@ -20,25 +20,22 @@ mod basic_2 {
}
}
// Define the validated accounts for each handler.
#[derive(Accounts)]
pub struct Create<'info> {
#[account(init)]
pub counter: ProgramAccount<'info, Counter>,
pub rent: Sysvar<'info, Rent>,
#[account(init, payer = user, space = 8 + 40)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Increment<'info> {
#[account(mut, has_one = authority)]
pub counter: ProgramAccount<'info, Counter>,
#[account(signer)]
pub authority: AccountInfo<'info>,
pub counter: Account<'info, Counter>,
pub authority: Signer<'info>,
}
// Define the program owned accounts.
#[account]
pub struct Counter {
pub authority: Pubkey,

View File

@ -1,5 +1,6 @@
const assert = require('assert');
const anchor = require('@project-serum/anchor');
const { SystemProgram } = anchor.web3;
describe('basic-2', () => {
const provider = anchor.Provider.local()
@ -17,10 +18,10 @@ describe('basic-2', () => {
await program.rpc.create(provider.wallet.publicKey, {
accounts: {
counter: counter.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [counter],
instructions: [await program.account.counter.createInstruction(counter)],
})
let counterAccount = await program.account.counter.fetch(counter.publicKey)

View File

@ -1,3 +1,10 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[programs.localnet]
puppet = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
puppet_master = "HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L"
[scripts]
test = "mocha -t 1000000 tests/"

View File

@ -1,14 +1,17 @@
// #region core
use anchor_lang::prelude::*;
use puppet::{Puppet, SetData};
use puppet::program::Puppet;
use puppet::{self, Data, SetData};
declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L");
#[program]
mod puppet_master {
use super::*;
pub fn pull_strings(ctx: Context<PullStrings>, data: u64) -> ProgramResult {
let cpi_program = ctx.accounts.puppet_program.clone();
let cpi_program = ctx.accounts.puppet_program.to_account_info();
let cpi_accounts = SetData {
puppet: ctx.accounts.puppet.clone().into(),
puppet: ctx.accounts.puppet.clone(),
};
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
puppet::cpi::set_data(cpi_ctx, data)
@ -18,7 +21,7 @@ mod puppet_master {
#[derive(Accounts)]
pub struct PullStrings<'info> {
#[account(mut)]
pub puppet: CpiAccount<'info, Puppet>,
pub puppet_program: AccountInfo<'info>,
pub puppet: Account<'info, Data>,
pub puppet_program: Program<'info, Puppet>,
}
// #endregion core

View File

@ -1,9 +1,11 @@
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod puppet {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
Ok(())
}
@ -16,18 +18,20 @@ pub mod puppet {
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init)]
pub puppet: ProgramAccount<'info, Puppet>,
pub rent: Sysvar<'info, Rent>,
#[account(init, payer = user, space = 8 + 8)]
pub puppet: Account<'info, Data>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct SetData<'info> {
#[account(mut)]
pub puppet: ProgramAccount<'info, Puppet>,
pub puppet: Account<'info, Data>,
}
#[account]
pub struct Puppet {
pub struct Data {
pub data: u64,
}

View File

@ -1,5 +1,6 @@
const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("basic-3", () => {
const provider = anchor.Provider.local();
@ -16,22 +17,22 @@ describe("basic-3", () => {
const tx = await puppet.rpc.initialize({
accounts: {
puppet: newPuppetAccount.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [newPuppetAccount],
instructions: [await puppet.account.puppet.createInstruction(newPuppetAccount)],
});
// Invoke the puppet master to perform a CPI to the puppet.
await puppetMaster.rpc.pullStrings(new anchor.BN(111), {
accounts: {
puppet: newPuppetAccount.publicKey,
puppetProgram: puppet.programId,
},
accounts: {
puppet: newPuppetAccount.publicKey,
puppetProgram: puppet.programId,
},
});
// Check the state updated.
puppetAccount = await puppet.account.puppet.fetch(newPuppetAccount.publicKey);
puppetAccount = await puppet.account.data.fetch(newPuppetAccount.publicKey);
assert.ok(puppetAccount.data.eq(new anchor.BN(111)));
});
});

View File

@ -1,3 +1,9 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[programs.localnet]
basic_4 = "CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr"
[scripts]
test = "mocha -t 1000000 tests/"

View File

@ -1,6 +1,8 @@
// #region code
use anchor_lang::prelude::*;
declare_id!("CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr");
#[program]
pub mod basic_4 {
use super::*;
@ -31,8 +33,7 @@ pub mod basic_4 {
#[derive(Accounts)]
pub struct Auth<'info> {
#[account(signer)]
authority: AccountInfo<'info>,
authority: Signer<'info>,
}
// #endregion code

View File

@ -1,9 +0,0 @@
# ignore Mac OS noise
.DS_Store
# ignore the build directory for Rust/Anchor
target
# Ignore backup files creates by cargo fmt.
**/*.rs.bk

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,58 +0,0 @@
//! This example displays the use of associated program accounts.
//!
//! This program is an *extremely* simplified version of the SPL token program
//! that does nothing other than create mint and token accounts, where all token
//! accounts are associated accounts.
// #region code
use anchor_lang::prelude::*;
#[program]
pub mod basic_5 {
use super::*;
pub fn create_mint(ctx: Context<CreateMint>) -> ProgramResult {
ctx.accounts.mint.supply = 0;
Ok(())
}
pub fn create_token(ctx: Context<CreateToken>) -> ProgramResult {
let token = &mut ctx.accounts.token;
token.amount = 0;
token.authority = *ctx.accounts.authority.key;
token.mint = *ctx.accounts.mint.to_account_info().key;
Ok(())
}
}
#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(init)]
mint: ProgramAccount<'info, Mint>,
rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct CreateToken<'info> {
#[account(init, associated = authority, with = mint)]
token: ProgramAccount<'info, Token>,
#[account(mut, signer)]
authority: AccountInfo<'info>,
mint: ProgramAccount<'info, Mint>,
rent: Sysvar<'info, Rent>,
system_program: AccountInfo<'info>,
}
#[account]
pub struct Mint {
pub supply: u32,
}
#[associated]
#[derive(Default)]
pub struct Token {
pub amount: u32,
pub authority: Pubkey,
pub mint: Pubkey,
}
// #endregion code

View File

@ -1,57 +0,0 @@
const anchor = require("@project-serum/anchor");
const assert = require("assert");
describe("basic-5", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
const program = anchor.workspace.Basic5;
const mint = anchor.web3.Keypair.generate();
// Setup. Not important for the point of the example.
it("Sets up the test", async () => {
// Create the mint account.
await program.rpc.createMint({
accounts: {
mint: mint.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
instructions: [await program.account.mint.createInstruction(mint)],
signers: [mint],
});
});
it("Creates an associated token account", async () => {
// #region test
// Calculate the associated token address.
const authority = program.provider.wallet.publicKey;
const associatedToken = await program.account.token.associatedAddress(
authority,
mint.publicKey
);
// Execute the transaction to create the associated token account.
await program.rpc.createToken({
accounts: {
token: associatedToken,
authority,
mint: mint.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
// Fetch the new associated account.
const account = await program.account.token.associated(
authority,
mint.publicKey
);
// #endregion test
// Check it was created correctly.
assert.ok(account.amount === 0);
assert.ok(account.authority.equals(authority));
assert.ok(account.mint.equals(mint.publicKey));
});
});

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,3 +0,0 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

View File

@ -1,6 +1,6 @@
[package]
name = "anchor-lang"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/project-serum/anchor"
edition = "2018"
@ -23,16 +23,16 @@ anchor-debug = [
]
[dependencies]
anchor-attribute-access-control = { path = "./attribute/access-control", version = "0.11.1" }
anchor-attribute-account = { path = "./attribute/account", version = "0.11.1" }
anchor-attribute-error = { path = "./attribute/error", version = "0.11.1" }
anchor-attribute-program = { path = "./attribute/program", version = "0.11.1" }
anchor-attribute-state = { path = "./attribute/state", version = "0.11.1" }
anchor-attribute-interface = { path = "./attribute/interface", version = "0.11.1" }
anchor-attribute-event = { path = "./attribute/event", version = "0.11.1" }
anchor-derive-accounts = { path = "./derive/accounts", version = "0.11.1" }
anchor-attribute-access-control = { path = "./attribute/access-control", version = "0.16.1" }
anchor-attribute-account = { path = "./attribute/account", version = "0.16.1" }
anchor-attribute-error = { path = "./attribute/error", version = "0.16.1" }
anchor-attribute-program = { path = "./attribute/program", version = "0.16.1" }
anchor-attribute-state = { path = "./attribute/state", version = "0.16.1" }
anchor-attribute-interface = { path = "./attribute/interface", version = "0.16.1" }
anchor-attribute-event = { path = "./attribute/event", version = "0.16.1" }
anchor-derive-accounts = { path = "./derive/accounts", version = "0.16.1" }
base64 = "0.13.0"
borsh = "0.9"
bytemuck = "1.4.0"
solana-program = "1.7.4"
solana-program = "=1.7.11"
thiserror = "1.0.20"

View File

@ -1,6 +1,6 @@
[package]
name = "anchor-attribute-access-control"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/project-serum/anchor"
license = "Apache-2.0"
@ -18,5 +18,5 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anyhow = "1.0.32"
anchor-syn = { path = "../../syn", version = "0.11.1" }
anchor-syn = { path = "../../syn", version = "0.16.1" }
regex = "1.0"

View File

@ -1,6 +1,6 @@
[package]
name = "anchor-attribute-account"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/project-serum/anchor"
license = "Apache-2.0"
@ -18,4 +18,6 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anyhow = "1.0.32"
anchor-syn = { path = "../../syn", version = "0.11.1", features = ["hash"] }
anchor-syn = { path = "../../syn", version = "0.16.1", features = ["hash"] }
rustversion = "1.0.3"
bs58 = "0.4.0"

View File

@ -0,0 +1,296 @@
//! Copied from solana/sdk/macro so that Anchor programs don't need to specify
//! `solana_program` as an additional crate dependency, but instead can access
//! it via `anchor_lang::declare_id`.
//!
//! Convenience macro to declare a static public key and functions to interact with it
//!
//! Input: a single literal base58 string representation of a program's id
extern crate proc_macro;
use proc_macro2::{Delimiter, Span, TokenTree};
use quote::{quote, ToTokens};
use std::convert::TryFrom;
use syn::{
bracketed,
parse::{Parse, ParseStream, Result},
punctuated::Punctuated,
token::Bracket,
Expr, Ident, LitByte, LitStr, Path, Token,
};
fn parse_id(
input: ParseStream,
pubkey_type: proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let id = if input.peek(syn::LitStr) {
let id_literal: LitStr = input.parse()?;
parse_pubkey(&id_literal, &pubkey_type)?
} else {
let expr: Expr = input.parse()?;
quote! { #expr }
};
if !input.is_empty() {
let stream: proc_macro2::TokenStream = input.parse()?;
return Err(syn::Error::new_spanned(stream, "unexpected token"));
}
Ok(id)
}
fn id_to_tokens(
id: &proc_macro2::TokenStream,
pubkey_type: proc_macro2::TokenStream,
tokens: &mut proc_macro2::TokenStream,
) {
tokens.extend(quote! {
/// The static program ID
pub static ID: #pubkey_type = #id;
/// Confirms that a given pubkey is equivalent to the program ID
pub fn check_id(id: &#pubkey_type) -> bool {
id == &ID
}
/// Returns the program ID
pub fn id() -> #pubkey_type {
ID
}
#[cfg(test)]
#[test]
fn test_id() {
assert!(check_id(&id()));
}
});
}
fn deprecated_id_to_tokens(
id: &proc_macro2::TokenStream,
pubkey_type: proc_macro2::TokenStream,
tokens: &mut proc_macro2::TokenStream,
) {
tokens.extend(quote! {
/// The static program ID
pub static ID: #pubkey_type = #id;
/// Confirms that a given pubkey is equivalent to the program ID
#[deprecated()]
pub fn check_id(id: &#pubkey_type) -> bool {
id == &ID
}
/// Returns the program ID
#[deprecated()]
pub fn id() -> #pubkey_type {
ID
}
#[cfg(test)]
#[test]
fn test_id() {
#[allow(deprecated)]
assert!(check_id(&id()));
}
});
}
pub struct Id(proc_macro2::TokenStream);
impl Parse for Id {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(
input,
quote! { anchor_lang::solana_program::pubkey::Pubkey },
)
.map(Self)
}
}
impl ToTokens for Id {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
id_to_tokens(
&self.0,
quote! { anchor_lang::solana_program::pubkey::Pubkey },
tokens,
)
}
}
struct IdDeprecated(proc_macro2::TokenStream);
impl Parse for IdDeprecated {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(
input,
quote! { anchor_lang::solana_program::pubkey::Pubkey },
)
.map(Self)
}
}
impl ToTokens for IdDeprecated {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
deprecated_id_to_tokens(
&self.0,
quote! { anchor_lang::solana_program::pubkey::Pubkey },
tokens,
)
}
}
struct ProgramSdkId(proc_macro2::TokenStream);
impl Parse for ProgramSdkId {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(
input,
quote! { anchor_lang::solana_program::pubkey::Pubkey },
)
.map(Self)
}
}
impl ToTokens for ProgramSdkId {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
id_to_tokens(
&self.0,
quote! { anchor_lang::solana_program::pubkey::Pubkey },
tokens,
)
}
}
struct ProgramSdkIdDeprecated(proc_macro2::TokenStream);
impl Parse for ProgramSdkIdDeprecated {
fn parse(input: ParseStream) -> Result<Self> {
parse_id(
input,
quote! { anchor_lang::solana_program::pubkey::Pubkey },
)
.map(Self)
}
}
impl ToTokens for ProgramSdkIdDeprecated {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
deprecated_id_to_tokens(
&self.0,
quote! { anchor_lang::solana_program::pubkey::Pubkey },
tokens,
)
}
}
#[allow(dead_code)] // `respan` may be compiled out
struct RespanInput {
to_respan: Path,
respan_using: Span,
}
impl Parse for RespanInput {
fn parse(input: ParseStream) -> Result<Self> {
let to_respan: Path = input.parse()?;
let _comma: Token![,] = input.parse()?;
let respan_tree: TokenTree = input.parse()?;
match respan_tree {
TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
let ident: Ident = syn::parse2(g.stream())?;
Ok(RespanInput {
to_respan,
respan_using: ident.span(),
})
}
val => Err(syn::Error::new_spanned(
val,
"expected None-delimited group",
)),
}
}
}
fn parse_pubkey(
id_literal: &LitStr,
pubkey_type: &proc_macro2::TokenStream,
) -> Result<proc_macro2::TokenStream> {
let id_vec = bs58::decode(id_literal.value())
.into_vec()
.map_err(|_| syn::Error::new_spanned(&id_literal, "failed to decode base58 string"))?;
let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
syn::Error::new_spanned(
&id_literal,
format!("pubkey array is not 32 bytes long: len={}", id_vec.len()),
)
})?;
let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
Ok(quote! {
#pubkey_type::new_from_array(
[#(#bytes,)*]
)
})
}
struct Pubkeys {
method: Ident,
num: usize,
pubkeys: proc_macro2::TokenStream,
}
impl Parse for Pubkeys {
fn parse(input: ParseStream) -> Result<Self> {
let pubkey_type = quote! {
anchor_lang::solana_program::pubkey::Pubkey
};
let method = input.parse()?;
let _comma: Token![,] = input.parse()?;
let (num, pubkeys) = if input.peek(syn::LitStr) {
let id_literal: LitStr = input.parse()?;
(1, parse_pubkey(&id_literal, &pubkey_type)?)
} else if input.peek(Bracket) {
let pubkey_strings;
bracketed!(pubkey_strings in input);
let punctuated: Punctuated<LitStr, Token![,]> =
Punctuated::parse_terminated(&pubkey_strings)?;
let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new();
for string in punctuated.iter() {
pubkeys.push(parse_pubkey(string, &pubkey_type)?);
}
(pubkeys.len(), quote! {#pubkeys})
} else {
let stream: proc_macro2::TokenStream = input.parse()?;
return Err(syn::Error::new_spanned(stream, "unexpected token"));
};
Ok(Pubkeys {
method,
num,
pubkeys,
})
}
}
impl ToTokens for Pubkeys {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let Pubkeys {
method,
num,
pubkeys,
} = self;
let pubkey_type = quote! {
anchor_lang::solana_program::pubkey::Pubkey
};
if *num == 1 {
tokens.extend(quote! {
pub fn #method() -> #pubkey_type {
#pubkeys
}
});
} else {
tokens.extend(quote! {
pub fn #method() -> ::std::vec::Vec<#pubkey_type> {
vec![#pubkeys]
}
});
}
}
}

View File

@ -1,7 +1,9 @@
extern crate proc_macro;
use quote::quote;
use syn::{parse_macro_input, parse_quote};
use syn::parse_macro_input;
mod id;
/// A data structure representing a Solana account, implementing various traits:
///
@ -98,6 +100,21 @@ pub fn account(
format!("{:?}", discriminator).parse().unwrap()
};
let owner_impl = {
if namespace.is_empty() {
quote! {
#[automatically_derived]
impl #impl_gen anchor_lang::Owner for #account_name #type_gen #where_clause {
fn owner() -> Pubkey {
crate::ID
}
}
}
} else {
quote! {}
}
};
proc_macro::TokenStream::from({
if is_zero_copy {
quote! {
@ -142,6 +159,8 @@ pub fn account(
Ok(*account)
}
}
#owner_impl
}
} else {
quote! {
@ -187,78 +206,8 @@ pub fn account(
#discriminator
}
}
}
}
})
}
/// Extends the `#[account]` attribute to allow one to create associated
/// accounts. This includes a `Default` implementation, which means all fields
/// in an `#[associated]` struct must implement `Default` and an
/// `anchor_lang::Bump` trait implementation, which allows the account to be
/// used as a program derived address.
///
/// # Zero Copy Deserialization
///
/// Similar to the `#[account]` attribute one can enable zero copy
/// deserialization by using the `zero_copy` argument:
///
/// ```ignore
/// #[associated(zero_copy)]
/// ```
///
/// For more, see the [`account`](./attr.account.html) attribute.
#[proc_macro_attribute]
pub fn associated(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let mut account_strct = parse_macro_input!(input as syn::ItemStruct);
let account_name = &account_strct.ident;
let (impl_gen, ty_gen, where_clause) = account_strct.generics.split_for_impl();
// Add a `__nonce: u8` field to the struct to hold the bump seed for
// the program dervied address.
match &mut account_strct.fields {
syn::Fields::Named(fields) => {
let mut segments = syn::punctuated::Punctuated::new();
segments.push(syn::PathSegment {
ident: syn::Ident::new("u8", proc_macro2::Span::call_site()),
arguments: syn::PathArguments::None,
});
fields.named.push(syn::Field {
attrs: Vec::new(),
vis: syn::Visibility::Restricted(syn::VisRestricted {
pub_token: syn::token::Pub::default(),
paren_token: syn::token::Paren::default(),
in_token: None,
path: Box::new(parse_quote!(crate)),
}),
ident: Some(syn::Ident::new("__nonce", proc_macro2::Span::call_site())),
colon_token: Some(syn::token::Colon {
spans: [proc_macro2::Span::call_site()],
}),
ty: syn::Type::Path(syn::TypePath {
qself: None,
path: syn::Path {
leading_colon: None,
segments,
},
}),
});
}
_ => panic!("Fields must be named"),
}
let args: proc_macro2::TokenStream = args.into();
proc_macro::TokenStream::from(quote! {
#[anchor_lang::account(#args)]
#account_strct
#[automatically_derived]
impl #impl_gen anchor_lang::Bump for #account_name #ty_gen #where_clause {
fn seed(&self) -> u8 {
self.__nonce
#owner_impl
}
}
})
@ -342,3 +291,11 @@ pub fn zero_copy(
#account_strct
})
}
/// Defines the program's ID. This should be used at the root of all Anchor
/// based programs.
#[proc_macro]
pub fn declare_id(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let id = parse_macro_input!(input as id::Id);
proc_macro::TokenStream::from(quote! {#id})
}

View File

@ -1,6 +1,6 @@
[package]
name = "anchor-attribute-error"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/project-serum/anchor"
license = "Apache-2.0"
@ -17,4 +17,4 @@ anchor-debug = ["anchor-syn/anchor-debug"]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anchor-syn = { path = "../../syn", version = "0.11.1" }
anchor-syn = { path = "../../syn", version = "0.16.1" }

View File

@ -1,6 +1,6 @@
[package]
name = "anchor-attribute-event"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/project-serum/anchor"
license = "Apache-2.0"
@ -18,4 +18,4 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anyhow = "1.0.32"
anchor-syn = { path = "../../syn", version = "0.11.1", features = ["hash"] }
anchor-syn = { path = "../../syn", version = "0.16.1", features = ["hash"] }

View File

@ -1,6 +1,6 @@
[package]
name = "anchor-attribute-interface"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/project-serum/anchor"
license = "Apache-2.0"
@ -18,5 +18,5 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anyhow = "1.0.32"
anchor-syn = { path = "../../syn", version = "0.11.1" }
anchor-syn = { path = "../../syn", version = "0.16.1" }
heck = "0.3.2"

View File

@ -197,7 +197,7 @@ pub fn interface(
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
pub fn #method_name<'a,'b, 'c, 'info, T: anchor_lang::ToAccountMetas + anchor_lang::ToAccountInfos<'info>>(
pub fn #method_name<'a,'b, 'c, 'info, T: anchor_lang::Accounts<'info> + anchor_lang::ToAccountMetas + anchor_lang::ToAccountInfos<'info>>(
ctx: anchor_lang::CpiContext<'a, 'b, 'c, 'info, T>,
#(#args),*
) -> anchor_lang::solana_program::entrypoint::ProgramResult {
@ -211,14 +211,14 @@ pub fn interface(
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotSerialize)?;
let mut data = #sighash_tts.to_vec();
data.append(&mut ix_data);
let accounts = ctx.accounts.to_account_metas(None);
let accounts = ctx.to_account_metas(None);
anchor_lang::solana_program::instruction::Instruction {
program_id: *ctx.program.key,
accounts,
data,
}
};
let mut acc_infos = ctx.accounts.to_account_infos();
let mut acc_infos = ctx.to_account_infos();
acc_infos.push(ctx.program.clone());
anchor_lang::solana_program::program::invoke_signed(
&ix,

View File

@ -1,6 +1,6 @@
[package]
name = "anchor-attribute-program"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/project-serum/anchor"
license = "Apache-2.0"
@ -18,4 +18,4 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anyhow = "1.0.32"
anchor-syn = { path = "../../syn", version = "0.11.1" }
anchor-syn = { path = "../../syn", version = "0.16.1" }

View File

@ -1,6 +1,6 @@
[package]
name = "anchor-attribute-state"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/project-serum/anchor"
license = "Apache-2.0"
@ -18,4 +18,4 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anyhow = "1.0.32"
anchor-syn = { path = "../../syn", version = "0.11.1" }
anchor-syn = { path = "../../syn", version = "0.16.1" }

View File

@ -23,6 +23,10 @@ use syn::parse_macro_input;
/// ```
///
/// For more, see the [`account`](./attr.account.html) attribute.
#[deprecated(
since = "0.14.0",
note = "#[state] will be removed in a future version. Use a PDA with static seeds instead"
)]
#[proc_macro_attribute]
pub fn state(
args: proc_macro::TokenStream,

View File

@ -1,6 +1,6 @@
[package]
name = "anchor-derive-accounts"
version = "0.11.1"
version = "0.16.1"
authors = ["Serum Foundation <foundation@projectserum.com>"]
repository = "https://github.com/project-serum/anchor"
license = "Apache-2.0"
@ -19,4 +19,4 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.60", features = ["full"] }
anyhow = "1.0.32"
anchor-syn = { path = "../../syn", version = "0.11.1" }
anchor-syn = { path = "../../syn", version = "0.16.1" }

View File

@ -39,7 +39,8 @@ use syn::parse_macro_input;
/// |:--|:--|:--|
/// | `#[account(signer)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. |
/// | `#[account(mut)]` | On `AccountInfo`, `ProgramAccount` or `CpiAccount` structs. | Marks the account as mutable and persists the state transition. |
/// | `#[account(init)]` | On `ProgramAccount` structs. | Marks the account as being initialized, skipping the account discriminator check. When using `init`, a `rent` `Sysvar` must be present in the `Accounts` struct. |
/// | `#[account(init)]` | On `ProgramAccount` structs. | Marks the account as being initialized, creating the account via the system program. |
/// | `#[account(zero)]` | On `ProgramAccount` structs. | Asserts the account discriminator is zero. |
/// | `#[account(close = <target>)]` | On `ProgramAccount` and `Loader` structs. | Marks the account as being closed at the end of the instruction's execution, sending the rent exemption lamports to the specified <target>. |
/// | `#[account(has_one = <target>)]` | On `ProgramAccount` or `CpiAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. |
/// | `#[account(seeds = [<seeds>], bump? = <target>, payer? = <target>, space? = <target>, owner? = <target>)]` | On `AccountInfo` structs | Seeds for the program derived address an `AccountInfo` struct represents. If bump is provided, then appends it to the seeds. On initialization, validates the given bump is the bump provided by `Pubkey::find_program_address`.|
@ -49,6 +50,7 @@ use syn::parse_macro_input;
/// | `#[account(executable)]` | On `AccountInfo` structs | Checks the given account is an executable program. |
/// | `#[account(state = <target>)]` | On `CpiState` structs | Checks the given state is the canonical state account for the target program. |
/// | `#[account(owner = <target>)]` | On `CpiState`, `CpiAccount`, and `AccountInfo` | Checks the account owner matches the target. |
/// | `#[account(address = <pubkey>)]` | On `AccountInfo` and `Account` | Checks the account key matches the pubkey. |
// TODO: How do we make the markdown render correctly without putting everything
// on absurdly long lines?
#[proc_macro_derive(Accounts, attributes(account, instruction))]

164
lang/src/account.rs Normal file
View File

@ -0,0 +1,164 @@
use crate::error::ErrorCode;
use crate::*;
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::ops::{Deref, DerefMut};
/// Account container that checks ownership on deserialization.
#[derive(Clone)]
pub struct Account<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> {
account: T,
info: AccountInfo<'info>,
}
impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T> {
fn new(info: AccountInfo<'a>, account: T) -> Account<'a, T> {
Self { info, account }
}
/// Deserializes the given `info` into a `Account`.
#[inline(never)]
pub fn try_from(info: &AccountInfo<'a>) -> Result<Account<'a, T>, ProgramError> {
if info.owner != &T::owner() {
return Err(ErrorCode::AccountNotProgramOwned.into());
}
let mut data: &[u8] = &info.try_borrow_data()?;
Ok(Account::new(info.clone(), T::try_deserialize(&mut data)?))
}
/// Deserializes the given `info` into a `Account` without checking
/// the account discriminator. Be careful when using this and avoid it if
/// possible.
#[inline(never)]
pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<Account<'a, T>, ProgramError> {
if info.owner != &T::owner() {
return Err(ErrorCode::AccountNotProgramOwned.into());
}
let mut data: &[u8] = &info.try_borrow_data()?;
Ok(Account::new(
info.clone(),
T::try_deserialize_unchecked(&mut data)?,
))
}
/// Reloads the account from storage. This is useful, for example, when
/// observing side effects after CPI.
pub fn reload(&mut self) -> ProgramResult {
let mut data: &[u8] = &self.info.try_borrow_data()?;
self.account = T::try_deserialize(&mut data)?;
Ok(())
}
pub fn into_inner(self) -> T {
self.account
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> Accounts<'info>
for Account<'info, T>
where
T: AccountSerialize + AccountDeserialize + Owner + Clone,
{
#[inline(never)]
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
Account::try_from(account)
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsExit<'info>
for Account<'info, T>
{
fn exit(&self, program_id: &Pubkey) -> ProgramResult {
// Only persist if the owner is the current program.
if &T::owner() == program_id {
let info = self.to_account_info();
let mut data = info.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
self.account.try_serialize(&mut cursor)?;
}
Ok(())
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsClose<'info>
for Account<'info, T>
{
fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult {
crate::common::close(self.to_account_info(), sol_destination)
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> ToAccountMetas
for Account<'info, T>
{
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let is_signer = is_signer.unwrap_or(self.info.is_signer);
let meta = match self.info.is_writable {
false => AccountMeta::new_readonly(*self.info.key, is_signer),
true => AccountMeta::new(*self.info.key, is_signer),
};
vec![meta]
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> ToAccountInfos<'info>
for Account<'info, T>
{
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.info.clone()]
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> ToAccountInfo<'info>
for Account<'info, T>
{
fn to_account_info(&self) -> AccountInfo<'info> {
self.info.clone()
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AsRef<AccountInfo<'info>>
for Account<'info, T>
{
fn as_ref(&self) -> &AccountInfo<'info> {
&self.info
}
}
impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Deref for Account<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&(*self).account
}
}
impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> DerefMut for Account<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
#[cfg(feature = "anchor-debug")]
if !self.info.is_writable {
solana_program::msg!("The given Account is not mutable");
panic!();
}
&mut self.account
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> Key for Account<'info, T> {
fn key(&self) -> Pubkey {
*self.info.key
}
}

View File

@ -1,5 +1,5 @@
use crate::error::ErrorCode;
use crate::{Accounts, AccountsExit, AccountsInit, ToAccountInfo, ToAccountInfos, ToAccountMetas};
use crate::{Accounts, AccountsExit, Key, ToAccountInfo, ToAccountInfos, ToAccountMetas};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
@ -21,31 +21,6 @@ impl<'info> Accounts<'info> for AccountInfo<'info> {
}
}
impl<'info> AccountsInit<'info> for AccountInfo<'info> {
fn try_accounts_init(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
// The discriminator should be zero, since we're initializing.
let data: &[u8] = &account.try_borrow_data()?;
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
Ok(account.clone())
}
}
impl<'info> ToAccountMetas for AccountInfo<'info> {
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let is_signer = is_signer.unwrap_or(self.is_signer);
@ -75,3 +50,9 @@ impl<'info> AccountsExit<'info> for AccountInfo<'info> {
Ok(())
}
}
impl<'info> Key for AccountInfo<'info> {
fn key(&self) -> Pubkey {
*self.key
}
}

View File

@ -104,11 +104,13 @@ impl<'info, T: Accounts<'info>> ToAccountMetas for CpiContext<'_, '_, '_, 'info,
/// Context specifying non-argument inputs for cross-program-invocations
/// targeted at program state instructions.
#[deprecated]
pub struct CpiStateContext<'a, 'b, 'c, 'info, T: Accounts<'info>> {
state: AccountInfo<'info>,
cpi_ctx: CpiContext<'a, 'b, 'c, 'info, T>,
}
#[allow(deprecated)]
impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiStateContext<'a, 'b, 'c, 'info, T> {
pub fn new(program: AccountInfo<'info>, state: AccountInfo<'info>, accounts: T) -> Self {
Self {
@ -153,6 +155,7 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiStateContext<'a, 'b, 'c, 'info, T
}
}
#[allow(deprecated)]
impl<'a, 'b, 'c, 'info, T: Accounts<'info>> ToAccountMetas
for CpiStateContext<'a, 'b, 'c, 'info, T>
{
@ -167,6 +170,7 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> ToAccountMetas
}
}
#[allow(deprecated)]
impl<'a, 'b, 'c, 'info, T: Accounts<'info>> ToAccountInfos<'info>
for CpiStateContext<'a, 'b, 'c, 'info, T>
{

View File

@ -1,7 +1,5 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, Accounts, AccountsExit, ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
use crate::*;
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
@ -11,13 +9,15 @@ use std::ops::{Deref, DerefMut};
/// Container for any account *not* owned by the current program.
#[derive(Clone)]
#[deprecated(note = "Please use Account instead")]
pub struct CpiAccount<'a, T: AccountDeserialize + Clone> {
info: AccountInfo<'a>,
account: Box<T>,
}
#[allow(deprecated)]
impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> {
pub fn new(info: AccountInfo<'a>, account: Box<T>) -> CpiAccount<'a, T> {
fn new(info: AccountInfo<'a>, account: Box<T>) -> CpiAccount<'a, T> {
Self { info, account }
}
@ -30,22 +30,20 @@ impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> {
))
}
pub fn try_from_init(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>, ProgramError> {
pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>, ProgramError> {
Self::try_from(info)
}
/// Reloads the account from storage. This is useful, for example, when
/// observing side effects after CPI.
pub fn reload(&self) -> Result<CpiAccount<'a, T>, ProgramError> {
let info = self.to_account_info();
let mut data: &[u8] = &info.try_borrow_data()?;
Ok(CpiAccount::new(
info.clone(),
Box::new(T::try_deserialize(&mut data)?),
))
pub fn reload(&mut self) -> ProgramResult {
let mut data: &[u8] = &self.info.try_borrow_data()?;
self.account = Box::new(T::try_deserialize(&mut data)?);
Ok(())
}
}
#[allow(deprecated)]
impl<'info, T> Accounts<'info> for CpiAccount<'info, T>
where
T: AccountDeserialize + Clone,
@ -67,6 +65,7 @@ where
}
}
#[allow(deprecated)]
impl<'info, T: AccountDeserialize + Clone> ToAccountMetas for CpiAccount<'info, T> {
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let is_signer = is_signer.unwrap_or(self.info.is_signer);
@ -78,18 +77,28 @@ impl<'info, T: AccountDeserialize + Clone> ToAccountMetas for CpiAccount<'info,
}
}
#[allow(deprecated)]
impl<'info, T: AccountDeserialize + Clone> ToAccountInfos<'info> for CpiAccount<'info, T> {
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.info.clone()]
}
}
#[allow(deprecated)]
impl<'info, T: AccountDeserialize + Clone> ToAccountInfo<'info> for CpiAccount<'info, T> {
fn to_account_info(&self) -> AccountInfo<'info> {
self.info.clone()
}
}
#[allow(deprecated)]
impl<'info, T: AccountDeserialize + Clone> AsRef<AccountInfo<'info>> for CpiAccount<'info, T> {
fn as_ref(&self) -> &AccountInfo<'info> {
&self.info
}
}
#[allow(deprecated)]
impl<'a, T: AccountDeserialize + Clone> Deref for CpiAccount<'a, T> {
type Target = T;
@ -98,15 +107,34 @@ impl<'a, T: AccountDeserialize + Clone> Deref for CpiAccount<'a, T> {
}
}
#[allow(deprecated)]
impl<'a, T: AccountDeserialize + Clone> DerefMut for CpiAccount<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.account
}
}
#[allow(deprecated)]
impl<'info, T: AccountDeserialize + Clone> AccountsExit<'info> for CpiAccount<'info, T> {
fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
// no-op
Ok(())
}
}
#[allow(deprecated)]
impl<'info, T: AccountDeserialize + Clone> Key for CpiAccount<'info, T> {
fn key(&self) -> Pubkey {
*self.info.key
}
}
#[allow(deprecated)]
impl<'info, T> From<Account<'info, T>> for CpiAccount<'info, T>
where
T: AccountSerialize + AccountDeserialize + Owner + Clone,
{
fn from(a: Account<'info, T>) -> Self {
Self::new(a.to_account_info(), Box::new(a.into_inner()))
}
}

View File

@ -1,8 +1,10 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, CpiStateContext, ProgramState,
ToAccountInfo, ToAccountInfos, ToAccountMetas,
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Key, ToAccountInfo,
ToAccountInfos, ToAccountMetas,
};
#[allow(deprecated)]
use crate::{CpiStateContext, ProgramState};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
@ -13,6 +15,7 @@ use std::ops::{Deref, DerefMut};
/// Boxed container for the program state singleton, used when the state
/// is for a program not currently executing.
#[derive(Clone)]
#[deprecated]
pub struct CpiState<'info, T: AccountSerialize + AccountDeserialize + Clone> {
inner: Box<Inner<'info, T>>,
}
@ -23,6 +26,7 @@ struct Inner<'info, T: AccountSerialize + AccountDeserialize + Clone> {
account: T,
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> CpiState<'info, T> {
pub fn new(i: AccountInfo<'info>, account: T) -> CpiState<'info, T> {
Self {
@ -58,6 +62,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> CpiState<'info, T>
}
}
#[allow(deprecated)]
impl<'info, T> Accounts<'info> for CpiState<'info, T>
where
T: AccountSerialize + AccountDeserialize + Clone,
@ -81,6 +86,7 @@ where
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
for CpiState<'info, T>
{
@ -94,6 +100,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'info>
for CpiState<'info, T>
{
@ -102,6 +109,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'in
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfo<'info>
for CpiState<'info, T>
{
@ -110,6 +118,16 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfo<'inf
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AsRef<AccountInfo<'info>>
for CpiState<'info, T>
{
fn as_ref(&self) -> &AccountInfo<'info> {
&self.inner.info
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> Deref for CpiState<'info, T> {
type Target = T;
@ -118,12 +136,14 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> Deref for CpiState
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for CpiState<'info, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut DerefMut::deref_mut(&mut self.inner).account
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info>
for CpiState<'info, T>
{
@ -132,3 +152,10 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info
Ok(())
}
}
#[allow(deprecated)]
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> Key for CpiState<'info, T> {
fn key(&self) -> Pubkey {
*self.inner.info.key
}
}

Some files were not shown because too many files have changed in this diff Show More