Merge branch 'master' into types
This commit is contained in:
commit
26b7337481
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
|
|
53
.travis.yml
53
.travis.yml
|
@ -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
|
||||
|
|
105
CHANGELOG.md
105
CHANGELOG.md
|
@ -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
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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",
|
||||
]
|
||||
|
|
47
Makefile
47
Makefile
|
@ -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
|
||||
|
|
72
README.md
72
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
use anchor_cli::Opts;
|
||||
use anyhow::Result;
|
||||
use clap::Clap;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
anchor_cli::entry(Opts::parse())
|
||||
}
|
|
@ -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
|
@ -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",
|
||||
}},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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"
|
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
#
|
||||
|
|
|
@ -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)"
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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).
|
||||
:::
|
||||
|
|
|
@ -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/)
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
```
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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).
|
|
@ -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).
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1 +0,0 @@
|
|||
Subproject commit a72e59a9b263b7e083af737669f12f5e3ee1997c
|
|
@ -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
|
||||
});
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -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");
|
||||
});
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -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,
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1,7 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[[test.genesis]]
|
||||
address = "FtMNMKp9DZHKWUyVAsj3Q5QV8ow4P3fUPP7ZrWEQJzKr"
|
||||
program = "./target/deploy/misc.so"
|
|
@ -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>,
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 1f6d5867019e242a470deed79cddca0d1f15e0a3
|
|
@ -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");
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 19c8e37bf41d044a084b21e58182a50d119d46a2
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1,3 +1,9 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.localnet]
|
||||
basic_0 = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||
|
||||
[scripts]
|
||||
test = "mocha -t 1000000 tests/"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
#[program]
|
||||
mod basic_0 {
|
||||
use super::*;
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.localnet]
|
||||
basic_1 = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||
|
||||
[scripts]
|
||||
test = "mocha -t 1000000 tests/"
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.localnet]
|
||||
basic_2 = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
||||
|
||||
[scripts]
|
||||
test = "mocha -t 1000000 tests/"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.localnet]
|
||||
basic_4 = "CwrqeMj2U8tFr1Rhkgwc84tpAsqbt9pTt2a4taoTADPr"
|
||||
|
||||
[scripts]
|
||||
test = "mocha -t 1000000 tests/"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -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
|
|
@ -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));
|
||||
});
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -1,3 +0,0 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
|
@ -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]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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})
|
||||
}
|
||||
|
|
|
@ -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" }
|
|
@ -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"] }
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
{
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue