Pārlūkot izejas kodu

Merge remote-tracking branch 'upstream/master' into igm/types

Ian Macalinao 4 gadi atpakaļ
vecāks
revīzija
18d998982c
100 mainītis faili ar 5364 papildinājumiem un 834 dzēšanām
  1. 2 0
      .gitignore
  2. 12 0
      .gitmodules
  3. 9 2
      .travis.yml
  4. 41 0
      CHANGELOG.md
  5. 133 123
      Cargo.lock
  6. 10 2
      Cargo.toml
  7. 4 0
      Makefile
  8. 2 52
      README.md
  9. 4 6
      cli/Cargo.toml
  10. 84 0
      cli/npm-package/anchor.js
  11. 23 0
      cli/npm-package/package.json
  12. 61 16
      cli/src/config.rs
  13. 105 108
      cli/src/main.rs
  14. 33 3
      cli/src/template.rs
  15. 2 2
      client/Cargo.toml
  16. 18 16
      client/src/lib.rs
  17. 1 1
      docker/Makefile
  18. 2 1
      docs/package.json
  19. 11 3
      docs/src/.vuepress/config.js
  20. 19 0
      docs/src/cli/commands.md
  21. 2 2
      docs/src/getting-started/installation.md
  22. 1 1
      docs/src/getting-started/introduction.md
  23. 16 0
      docs/src/getting-started/projects.md
  24. 1 1
      docs/src/tutorials/tutorial-1.md
  25. 1 1
      docs/src/tutorials/tutorial-6.md
  26. 5 3
      examples/cashiers-check/tests/cashiers-check.js
  27. 41 0
      examples/cfo/Anchor.toml
  28. 9 0
      examples/cfo/Cargo.toml
  29. 1 0
      examples/cfo/deps/serum-dex
  30. 1 0
      examples/cfo/deps/stake
  31. 1 0
      examples/cfo/deps/swap
  32. 13 0
      examples/cfo/migrations/deploy.js
  33. 24 0
      examples/cfo/programs/cfo/Cargo.toml
  34. 2 0
      examples/cfo/programs/cfo/Xargo.toml
  35. 818 0
      examples/cfo/programs/cfo/src/lib.rs
  36. 19 0
      examples/cfo/scripts/common.sh
  37. 34 0
      examples/cfo/scripts/fees.js
  38. 20 0
      examples/cfo/scripts/list-market.js
  39. 58 0
      examples/cfo/scripts/localnet.sh
  40. 1 0
      examples/cfo/scripts/market-maker.json
  41. 16 0
      examples/cfo/scripts/trade-bot.js
  42. 202 0
      examples/cfo/tests/cfo.js
  43. 647 0
      examples/cfo/tests/utils/index.js
  44. 184 0
      examples/cfo/tests/utils/stake.js
  45. 5 5
      examples/errors/programs/errors/src/lib.rs
  46. 4 4
      examples/errors/tests/errors.js
  47. 3 0
      examples/escrow/Anchor.toml
  48. 4 0
      examples/escrow/Cargo.toml
  49. 11 0
      examples/escrow/package.json
  50. 20 0
      examples/escrow/programs/escrow/Cargo.toml
  51. 2 0
      examples/escrow/programs/escrow/Xargo.toml
  52. 228 0
      examples/escrow/programs/escrow/src/lib.rs
  53. 207 0
      examples/escrow/tests/escrow.js
  54. 25 25
      examples/ido-pool/programs/ido-pool/src/lib.rs
  55. 2 0
      examples/lockup/programs/lockup/Cargo.toml
  56. 2 0
      examples/lockup/programs/registry/Cargo.toml
  57. 10 10
      examples/lockup/programs/registry/src/lib.rs
  58. 20 18
      examples/lockup/tests/lockup.js
  59. 1 0
      examples/misc/programs/misc/Cargo.toml
  60. 36 0
      examples/misc/programs/misc/src/account.rs
  61. 172 0
      examples/misc/programs/misc/src/context.rs
  62. 21 0
      examples/misc/programs/misc/src/event.rs
  63. 30 187
      examples/misc/programs/misc/src/lib.rs
  64. 103 5
      examples/misc/tests/misc.js
  65. 2 2
      examples/multisig/programs/multisig/src/lib.rs
  66. 13 0
      examples/permissioned-markets/Anchor.toml
  67. 8 0
      examples/permissioned-markets/Cargo.toml
  68. 1 0
      examples/permissioned-markets/deps/serum-dex
  69. 13 0
      examples/permissioned-markets/migrations/deploy.js
  70. 21 0
      examples/permissioned-markets/programs/permissioned-markets/Cargo.toml
  71. 2 0
      examples/permissioned-markets/programs/permissioned-markets/Xargo.toml
  72. 345 0
      examples/permissioned-markets/programs/permissioned-markets/src/lib.rs
  73. 284 0
      examples/permissioned-markets/tests/permissioned-markets.js
  74. 427 0
      examples/permissioned-markets/tests/utils/index.js
  75. 1 1
      examples/swap/tests/utils/index.js
  76. 6 4
      examples/zero-copy/programs/zero-copy/src/lib.rs
  77. 11 11
      lang/Cargo.toml
  78. 2 2
      lang/attribute/access-control/Cargo.toml
  79. 2 2
      lang/attribute/account/Cargo.toml
  80. 33 22
      lang/attribute/account/src/lib.rs
  81. 2 2
      lang/attribute/error/Cargo.toml
  82. 2 2
      lang/attribute/event/Cargo.toml
  83. 2 2
      lang/attribute/interface/Cargo.toml
  84. 2 2
      lang/attribute/program/Cargo.toml
  85. 2 2
      lang/attribute/state/Cargo.toml
  86. 22 18
      lang/attribute/state/src/lib.rs
  87. 2 2
      lang/derive/accounts/Cargo.toml
  88. 2 4
      lang/derive/accounts/src/lib.rs
  89. 1 1
      lang/src/context.rs
  90. 4 0
      lang/src/cpi_account.rs
  91. 4 2
      lang/src/error.rs
  92. 75 4
      lang/src/lib.rs
  93. 4 0
      lang/src/program_account.rs
  94. 1 1
      lang/src/state.rs
  95. 1 1
      lang/src/sysvar.rs
  96. 77 1
      lang/src/vec.rs
  97. 2 1
      lang/syn/Cargo.toml
  98. 2 1
      lang/syn/src/codegen/accounts/__client_accounts.rs
  99. 378 144
      lang/syn/src/codegen/accounts/constraints.rs
  100. 9 3
      lang/syn/src/codegen/accounts/exit.rs

+ 2 - 0
.gitignore

@@ -13,3 +13,5 @@ examples/**/Cargo.lock
 .DS_Store
 docs/yarn.lock
 ts/docs/
+cli/npm-package/anchor
+cli/npm-package/*.tgz

+ 12 - 0
.gitmodules

@@ -1,3 +1,15 @@
 [submodule "examples/swap/deps/serum-dex"]
 	path = examples/swap/deps/serum-dex
 	url = https://github.com/project-serum/serum-dex
+[submodule "examples/cfo/deps/serum-dex"]
+	path = examples/cfo/deps/serum-dex
+	url = https://github.com/project-serum/serum-dex
+[submodule "examples/cfo/deps/swap"]
+	path = examples/cfo/deps/swap
+	url = https://github.com/project-serum/swap.git
+[submodule "examples/cfo/deps/stake"]
+	path = examples/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
+	url = https://github.com/project-serum/serum-dex

+ 9 - 2
.travis.yml

@@ -6,13 +6,13 @@ cache: cargo
 env:
   global:
     - NODE_VERSION="14.7.0"
-    - SOLANA_CLI_VERSION="1.7.1"
+    - SOLANA_CLI_VERSION="1.7.4"
 git:
   submodules: true
 
 _defaults: &defaults
   before_install:
-  - rustup component add rustfmt
+  - rustup component add rustfmt clippy
   - nvm install $NODE_VERSION
   - sudo apt-get install -y pkg-config build-essential libudev-dev
 
@@ -22,6 +22,7 @@ _examples: &examples
   - npm install -g mocha
   - npm install -g ts-mocha
   - npm install -g typescript
+  - npm install -g ts-node
   - npm install -g buffer
   - cd ts && yarn && yarn build && npm link && cd ../
   - npm install -g @project-serum/serum
@@ -41,7 +42,11 @@ jobs:
       script:
         - cargo build
         - cargo fmt -- --check
+        - cargo clippy --all-targets -- -D warnings
         - cargo test
+        - pushd ts && yarn && popd
+        - pushd ts && yarn test && popd
+        - pushd ts && yarn lint && popd
     - <<: *examples
       name: Runs the examples 1
       script:
@@ -64,9 +69,11 @@ jobs:
         - 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
       script:
+        - pushd examples/escrow && yarn && anchor test && popd
         - pushd examples/pyth && yarn && anchor test && popd
         - pushd examples/tutorial/basic-0 && anchor test && popd
         - pushd examples/tutorial/basic-1 && anchor test && popd

+ 41 - 0
CHANGELOG.md

@@ -11,6 +11,47 @@ incremented for features.
 
 ## [Unreleased]
 
+## [0.11.1] - 2021-07-09
+
+### 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
+
+* lang: Preserve all instruction data for fallback functions ([#483](https://github.com/project-serum/anchor/pull/483)).
+* ts: Event listener not firing when creating associated accounts ([#356](https://github.com/project-serum/anchor/issues/356)).
+
+## [0.11.0] - 2021-07-03
+
+### Features
+
+* lang: Add fallback functions ([#457](https://github.com/project-serum/anchor/pull/457)).
+* lang: Add feature flag for using the old state account discriminator. This is a temporary flag for those with programs built prior to v0.7.0 but want to use the latest Anchor version. Expect this to be removed in a future version ([#446](https://github.com/project-serum/anchor/pull/446)).
+* lang: Add generic support to Accounts ([#496](https://github.com/project-serum/anchor/pull/496)).
+
+### Breaking Changes
+
+* cli: Remove `.spec` suffix on TypeScript tests files ([#441](https://github.com/project-serum/anchor/pull/441)).
+* lang: Remove `belongs_to` constraint ([#459](https://github.com/project-serum/anchor/pull/459)).
+
+## [0.10.0] - 2021-06-27
+
+### Features
+
+* lang: Add `#[account(address = <expr>)]` constraint for asserting the address of an account ([#400](https://github.com/project-serum/anchor/pull/400)).
+* lang: Add `#[account(init, token = <mint-target>, authority = <token-owner-target>...)]` constraint for initializing SPL token accounts as program derived addresses for the program. Can be used when initialized via `seeds` or `associated` ([#400](https://github.com/project-serum/anchor/pull/400)).
+* lang: Add `associated_seeds!` macro for generating signer seeds for CPIs signed by an `#[account(associated = <target>)]` account ([#400](https://github.com/project-serum/anchor/pull/400)).
+* cli: Add `[scripts]` section to the Anchor.toml for specifying workspace scripts that can be run via `anchor run <script>` ([#400](https://github.com/project-serum/anchor/pull/400)).
+* cli: `[clusters.<network>]` table entries can now also use `{ address = <base58-str>, idl = <filepath-str> }` to specify workspace programs ([#400](https://github.com/project-serum/anchor/pull/400)).
+
+### Breaking Changes
+
+* cli: Remove `--yarn` flag in favor of using `npx` ([#432](https://github.com/project-serum/anchor/pull/432)).
+
 ## [0.9.0] - 2021-06-15
 
 ### Features

+ 133 - 123
Cargo.lock

@@ -50,7 +50,7 @@ checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483"
 
 [[package]]
 name = "anchor-attribute-access-control"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-syn",
  "anyhow",
@@ -62,7 +62,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-attribute-account"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-syn",
  "anyhow",
@@ -73,7 +73,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-attribute-error"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-syn",
  "proc-macro2 1.0.24",
@@ -83,7 +83,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-attribute-event"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-syn",
  "anyhow",
@@ -94,7 +94,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-attribute-interface"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-syn",
  "anyhow",
@@ -106,7 +106,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-attribute-program"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-syn",
  "anyhow",
@@ -117,7 +117,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-attribute-state"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-syn",
  "anyhow",
@@ -128,7 +128,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-cli"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-client",
  "anchor-lang",
@@ -141,7 +141,6 @@ dependencies = [
  "rand 0.7.3",
  "serde",
  "serde_json",
- "serde_yaml",
  "serum-common",
  "shellexpand",
  "solana-client",
@@ -149,12 +148,11 @@ dependencies = [
  "solana-sdk",
  "syn 1.0.67",
  "toml",
- "which",
 ]
 
 [[package]]
 name = "anchor-client"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-lang",
  "anyhow",
@@ -168,7 +166,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-derive-accounts"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-syn",
  "anyhow",
@@ -179,7 +177,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-lang"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-attribute-access-control",
  "anchor-attribute-account",
@@ -198,7 +196,7 @@ dependencies = [
 
 [[package]]
 name = "anchor-spl"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anchor-lang",
  "lazy_static",
@@ -209,12 +207,13 @@ dependencies = [
 
 [[package]]
 name = "anchor-syn"
-version = "0.9.0"
+version = "0.11.1"
 dependencies = [
  "anyhow",
  "bs58",
  "heck",
  "proc-macro2 1.0.24",
+ "proc-macro2-diagnostics",
  "quote 1.0.9",
  "serde",
  "serde_json",
@@ -328,17 +327,6 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
 
-[[package]]
-name = "blake2b_simd"
-version = "0.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
-dependencies = [
- "arrayref",
- "arrayvec",
- "constant_time_eq",
-]
-
 [[package]]
 name = "blake3"
 version = "0.3.7"
@@ -393,11 +381,11 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
 
 [[package]]
 name = "borsh"
-version = "0.8.2"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74"
+checksum = "4fcabb02816fdadf90866dc9a7824491ccb63d69f55375a266dc03509ac68d36"
 dependencies = [
- "borsh-derive",
+ "borsh-derive 0.9.0",
  "hashbrown",
 ]
 
@@ -407,8 +395,21 @@ version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd"
 dependencies = [
- "borsh-derive-internal",
- "borsh-schema-derive-internal",
+ "borsh-derive-internal 0.8.2",
+ "borsh-schema-derive-internal 0.8.2",
+ "proc-macro-crate",
+ "proc-macro2 1.0.24",
+ "syn 1.0.67",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bd16f0729b89f0a212b0e2e1d19cc6593df63f771161a11863967780e2d033d"
+dependencies = [
+ "borsh-derive-internal 0.9.0",
+ "borsh-schema-derive-internal 0.9.0",
  "proc-macro-crate",
  "proc-macro2 1.0.24",
  "syn 1.0.67",
@@ -425,6 +426,17 @@ dependencies = [
  "syn 1.0.67",
 ]
 
+[[package]]
+name = "borsh-derive-internal"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e321a130a3ac4b88eb59a6d670bde11eec9721a397b77e0f2079060e2a1b785"
+dependencies = [
+ "proc-macro2 1.0.24",
+ "quote 1.0.9",
+ "syn 1.0.67",
+]
+
 [[package]]
 name = "borsh-schema-derive-internal"
 version = "0.8.2"
@@ -436,6 +448,17 @@ dependencies = [
  "syn 1.0.67",
 ]
 
+[[package]]
+name = "borsh-schema-derive-internal"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15151a485164b319cc7a5160fe4316dc469a27993f71b73d7617dc9032ff0fd7"
+dependencies = [
+ "proc-macro2 1.0.24",
+ "quote 1.0.9",
+ "syn 1.0.67",
+]
+
 [[package]]
 name = "bs58"
 version = "0.3.1"
@@ -926,9 +949,9 @@ dependencies = [
 
 [[package]]
 name = "dirs"
-version = "3.0.1"
+version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff"
+checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
 dependencies = [
  "dirs-sys",
 ]
@@ -945,12 +968,12 @@ dependencies = [
 
 [[package]]
 name = "dirs-sys"
-version = "0.3.5"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
+checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
 dependencies = [
  "libc",
- "redox_users 0.3.5",
+ "redox_users",
  "winapi 0.3.9",
 ]
 
@@ -961,7 +984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
 dependencies = [
  "libc",
- "redox_users 0.4.0",
+ "redox_users",
  "winapi 0.3.9",
 ]
 
@@ -1099,9 +1122,9 @@ checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da"
 
 [[package]]
 name = "field-offset"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf539fba70056b50f40a22e0da30639518a12ee18c35807858a63b158cb6dde7"
+checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92"
 dependencies = [
  "memoffset 0.6.1",
  "rustc_version 0.3.3",
@@ -2282,6 +2305,19 @@ dependencies = [
  "unicode-xid 0.2.1",
 ]
 
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
+dependencies = [
+ "proc-macro2 1.0.24",
+ "quote 1.0.9",
+ "syn 1.0.67",
+ "version_check",
+ "yansi",
+]
+
 [[package]]
 name = "qstring"
 version = "0.7.2"
@@ -2430,17 +2466,6 @@ dependencies = [
  "bitflags",
 ]
 
-[[package]]
-name = "redox_users"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
-dependencies = [
- "getrandom 0.1.16",
- "redox_syscall 0.1.57",
- "rust-argon2",
-]
-
 [[package]]
 name = "redox_users"
 version = "0.4.0"
@@ -2538,18 +2563,6 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
-[[package]]
-name = "rust-argon2"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
-dependencies = [
- "base64 0.13.0",
- "blake2b_simd",
- "constant_time_eq",
- "crossbeam-utils 0.8.3",
-]
-
 [[package]]
 name = "rustc-demangle"
 version = "0.1.18"
@@ -2772,7 +2785,7 @@ version = "0.8.1-serum.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a4fed3f601b23f15dc890f6e52ffdbfe2dcf16418a41e0aa016b5f10cf30c892"
 dependencies = [
- "borsh-derive",
+ "borsh-derive 0.8.2",
  "hashbrown",
  "solana-program",
 ]
@@ -2780,7 +2793,7 @@ dependencies = [
 [[package]]
 name = "serum-common"
 version = "0.1.0"
-source = "git+https://github.com/project-serum/serum-dex#66904088599c1a8d42623f6a6d157cec46c8da62"
+source = "git+https://github.com/project-serum/serum-dex#576e5d2ef2a1669fbd889f13b97b4f552554e334"
 dependencies = [
  "anyhow",
  "arrayref",
@@ -2935,9 +2948,9 @@ dependencies = [
 
 [[package]]
 name = "solana-account-decoder"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28c1af6573936a8ce5bba72bfe092016c684cce288204cae7d7b8c4e7dc57dcd"
+checksum = "49379e5310962e5bd3372106733d94f5341b66dc3d64759310ab00f663a9da4a"
 dependencies = [
  "Inflector",
  "base64 0.12.3",
@@ -2958,9 +2971,9 @@ dependencies = [
 
 [[package]]
 name = "solana-clap-utils"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b25baa693ab8ca9f23d6ecfe49229f94300867258fe18e422f1aceda5ad0d02b"
+checksum = "f3533e04224962dc8956c5ea7087ebcfb9d26911f7c6a9b54d392bd21f0bdfef"
 dependencies = [
  "chrono",
  "clap 2.33.3",
@@ -2975,9 +2988,9 @@ dependencies = [
 
 [[package]]
 name = "solana-cli-config"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "337064b1beaabf9e94ef9b40ca669fbdd59916ac22fbbc9c5bceedf63648480b"
+checksum = "ab2f42943ffbed139e426a2613b1e02de94f84185b968ea14244646c0e83b9a7"
 dependencies = [
  "dirs-next",
  "lazy_static",
@@ -2989,9 +3002,9 @@ dependencies = [
 
 [[package]]
 name = "solana-client"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81d91e50f974a1ee146fae3d3b467e2d07f1ad9b6ffb7c4c96b126edbe2c9476"
+checksum = "c35922e01f742bfcc7156a802aa9314c5510dfec28ea3794eadda1b732d8c71f"
 dependencies = [
  "base64 0.13.0",
  "bincode",
@@ -3023,9 +3036,9 @@ dependencies = [
 
 [[package]]
 name = "solana-config-program"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec681482cc7023f6609e3248ae584134786a9f3179f2961d629d0fddbc394de7"
+checksum = "86cc6edcb0c51cfdd2b0a0f3696002970c2715b422e3829b0e09b5236dc24352"
 dependencies = [
  "bincode",
  "chrono",
@@ -3038,9 +3051,9 @@ dependencies = [
 
 [[package]]
 name = "solana-crate-features"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b7da00208f41eaf547ef66465d1eab02ddff0cb0ec7a1254c49c89469f53de2"
+checksum = "262e476c9578aa6fdea04a0e1084948da2e8a79fa4198230890f8c3296a56338"
 dependencies = [
  "backtrace",
  "bytes 0.4.12",
@@ -3063,9 +3076,9 @@ dependencies = [
 
 [[package]]
 name = "solana-faucet"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "845b5cc13be4036f8138b5216f70078b1e55193f139c8234938205465b7859bc"
+checksum = "25d1d080f7ff9ce6f8f25fa95d855a103166ff4add01ba5568a4dfbbf18b3e07"
 dependencies = [
  "bincode",
  "byteorder",
@@ -3086,9 +3099,9 @@ dependencies = [
 
 [[package]]
 name = "solana-frozen-abi"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8deae875976c64b1cecfc501575ff0185aa3953be29691e83ebab04f94490ac"
+checksum = "9b1e0d064e18bacd418a20b1d3c1a699bd6df1920fda7dbf1a0f84b764aaa0fb"
 dependencies = [
  "bs58",
  "bv",
@@ -3106,9 +3119,9 @@ dependencies = [
 
 [[package]]
 name = "solana-frozen-abi-macro"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b59337556e8d6240dbc75c793af59accbcbbd93e77a45a346c20fc296b3845d"
+checksum = "adb85ca95ec91b6683e9a414cbdf74fc3d104eded6cb20439b2bb6eea22d3514"
 dependencies = [
  "proc-macro2 1.0.24",
  "quote 1.0.9",
@@ -3118,9 +3131,9 @@ dependencies = [
 
 [[package]]
 name = "solana-logger"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb0c0bd132b3efa7b6c561618da1d31ff0688c7f0281545d11199f56ee6ca7cb"
+checksum = "7afaad15211575f769c1f5389fca78a10fe9bc676c2fcc7b91b85be978eadca3"
 dependencies = [
  "env_logger",
  "lazy_static",
@@ -3129,9 +3142,9 @@ dependencies = [
 
 [[package]]
 name = "solana-measure"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4afd1d989687cd3f4862a5c417d1b4d2e2c5d1037e99ae1b506ae0b0ad9b1f8a"
+checksum = "973c24018c0ab2f5d59cf13a91e8877d4f6943adbaaae9fbde53a5ad1fd527b5"
 dependencies = [
  "log",
  "solana-metrics",
@@ -3140,9 +3153,9 @@ dependencies = [
 
 [[package]]
 name = "solana-metrics"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd11d4b9323ff1bf31b5c3202721c29481e7a86658b82a208cd56bdee4d61142"
+checksum = "050216628bf064702a5efe721cfb65011dfb597fa70a7302879f142e5887764e"
 dependencies = [
  "env_logger",
  "gethostname",
@@ -3154,9 +3167,9 @@ dependencies = [
 
 [[package]]
 name = "solana-net-utils"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7134aa434f0c35b998fd05efe6a5ae46f52895e7d9ad6f78a97d16363758de0"
+checksum = "3e91dc21b8cd419fa3ce94d4a9157445b3f7b9c238cc3cf93afaafed38f00ef8"
 dependencies = [
  "bincode",
  "clap 2.33.3",
@@ -3168,6 +3181,7 @@ dependencies = [
  "socket2 0.3.19",
  "solana-clap-utils",
  "solana-logger",
+ "solana-sdk",
  "solana-version",
  "tokio 1.4.0",
  "url",
@@ -3175,14 +3189,14 @@ dependencies = [
 
 [[package]]
 name = "solana-program"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea60f6b6d29488e780ce5a9cd4f7ce0d83aebe58910ae2f10cee36f69e266f30"
+checksum = "7d494621bfad15d6745a2bde5a5fa112c921011c00fa07987637d34bd544f191"
 dependencies = [
  "bincode",
  "blake3",
  "borsh",
- "borsh-derive",
+ "borsh-derive 0.9.0",
  "bs58",
  "bv",
  "curve25519-dalek 2.1.2",
@@ -3209,9 +3223,9 @@ dependencies = [
 
 [[package]]
 name = "solana-rayon-threadlimit"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fd119b97399c133010cf2a146b442378ab37c0f102d287b7f4ea3006a025e60"
+checksum = "fe14a0b8d7c2181cc6692855cfe40c90e87c76728ccc17110ec6d2244817e74f"
 dependencies = [
  "lazy_static",
  "num_cpus",
@@ -3219,9 +3233,9 @@ dependencies = [
 
 [[package]]
 name = "solana-remote-wallet"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0e637e6c0ea98e5f605c784a96b0d0fb178ac0fe2b3cbeb46e68d4e31664d4e"
+checksum = "b109992a9f9e972a09951fe0f9bc42daa17877a937e3195f945cf7bff6a84b68"
 dependencies = [
  "base32",
  "console 0.14.1",
@@ -3240,9 +3254,9 @@ dependencies = [
 
 [[package]]
 name = "solana-runtime"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12c35f34a21438aa2a00496897a5f3bf073e49aa556572a779a8802bd84da0ed"
+checksum = "60faef270e12f4eadaf5bfbc71af60cf8ecf84b7afaf72476c4e375de9c47c8c"
 dependencies = [
  "arrayref",
  "bincode",
@@ -3291,9 +3305,9 @@ dependencies = [
 
 [[package]]
 name = "solana-sdk"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb4c0b2ed521c111bda27da8ccffe48222bac45044d7ffe691221b0db624499a"
+checksum = "045476e010fc091d01ec7ba6854328894db20e37b70124f05bf851cdeb60113b"
 dependencies = [
  "assert_matches",
  "bincode",
@@ -3340,9 +3354,9 @@ dependencies = [
 
 [[package]]
 name = "solana-sdk-macro"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea740a2599496175f2d1bd47f54d799c7f2d8c00152a4eb9aed21047cdb30c23"
+checksum = "3a7afe714e9abb9a93215bf5710ee1e244046f2189574301b3e38afb4a14c8f6"
 dependencies = [
  "bs58",
  "proc-macro2 1.0.24",
@@ -3353,9 +3367,9 @@ dependencies = [
 
 [[package]]
 name = "solana-secp256k1-program"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6654cead7283aec52db495b69bf3f00228f399b361e5d0a9c80e265f1892b599"
+checksum = "59f67361482a020437060ef48c5d738f69179958304d079638c9cd23ed239c83"
 dependencies = [
  "bincode",
  "digest 0.9.0",
@@ -3368,9 +3382,9 @@ dependencies = [
 
 [[package]]
 name = "solana-stake-program"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47fc219f90c6cf0eea580fc656a43030fcdbd3719aa7e47d755feadfdf47263a"
+checksum = "954770d1ddf8618b96e2150a2da349a969df47046d4f7e11aca94b3b34267cf2"
 dependencies = [
  "bincode",
  "log",
@@ -3390,9 +3404,9 @@ dependencies = [
 
 [[package]]
 name = "solana-transaction-status"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35d60c5316a845f10dfa0c5595028163112523e2aaf8395266478068f5c47435"
+checksum = "7d8fc7eb73bd4b76a8148c022914ab8bec49395be91dd493d296e818d21f8b37"
 dependencies = [
  "Inflector",
  "base64 0.12.3",
@@ -3414,9 +3428,9 @@ dependencies = [
 
 [[package]]
 name = "solana-version"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c25fd4ecc760955c2ebf23e9ce6e18e257b4b2701ad5151b80c24bea2236b80"
+checksum = "8a36ce09f7a1c47585378fa5a03a5d95dffb23b3b613ad3904385ec97fee91cf"
 dependencies = [
  "log",
  "rustc_version 0.2.3",
@@ -3430,9 +3444,9 @@ dependencies = [
 
 [[package]]
 name = "solana-vote-program"
-version = "1.7.2"
+version = "1.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "212ca48d1afad59db7f068465ee8b108615803bb7147ff21dbd7540517558d34"
+checksum = "053c78004e2a5fdf26ca9561a955a1f447440570ae65decb30650340e22e6ce1"
 dependencies = [
  "bincode",
  "log",
@@ -4248,16 +4262,6 @@ dependencies = [
  "webpki",
 ]
 
-[[package]]
-name = "which"
-version = "4.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe"
-dependencies = [
- "either",
- "libc",
-]
-
 [[package]]
 name = "winapi"
 version = "0.2.8"
@@ -4347,6 +4351,12 @@ dependencies = [
  "linked-hash-map",
 ]
 
+[[package]]
+name = "yansi"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
+
 [[package]]
 name = "zeroize"
 version = "1.2.0"

+ 10 - 2
Cargo.toml

@@ -1,3 +1,9 @@
+[profile.release]
+lto = true
+
+[profile.release.package.anchor-cli]
+codegen-units = 1
+
 [workspace]
 members = [
     "cli",
@@ -9,5 +15,7 @@ members = [
     "spl",
 ]
 exclude = [
-    "examples/swap/deps/serum-dex"
-]
+    "examples/swap/deps/serum-dex",
+    "examples/cfo/deps/serum-dex",
+    "examples/permissioned-markets/deps/serum-dex",
+]

+ 4 - 0
Makefile

@@ -0,0 +1,4 @@
+.PHONY: build-cli
+build-cli:
+	cargo build -p anchor-cli --release
+	cp target/release/anchor cli/npm-package/anchor

+ 2 - 52
README.md

@@ -35,54 +35,9 @@ To jump straight to examples, go [here](https://github.com/project-serum/anchor/
 
 ## Examples
 
-Build stateful programs on Solana by defining a state struct with associated
-methods. Here's a classic counter example, where only the designated `authority`
+Here's a counter program, where only the designated `authority`
 can increment the count.
 
-```rust
-#[program]
-mod counter {
-
-    #[state]
-    pub struct Counter {
-      authority: Pubkey,
-      count: u64,
-    }
-
-    pub fn new(ctx: Context<Auth>) -> Result<Self> {
-        Ok(Self {
-            auth: *ctx.accounts.authority.key
-        })
-    }
-
-    pub fn increment(&mut self, ctx: Context<Auth>) -> Result<()> {
-        if &self.authority != ctx.accounts.authority.key {
-            return Err(ErrorCode::Unauthorized.into());
-        }
-
-        self.count += 1;
-
-        Ok(())
-    }
-}
-
-#[derive(Accounts)]
-pub struct Auth<'info> {
-    #[account(signer)]
-    authority: AccountInfo<'info>,
-}
-
-#[error]
-pub enum ErrorCode {
-    #[msg("You are not authorized to perform this action.")]
-    Unauthorized,
-}
-```
-
-Additionally, one can utilize the full power of Solana's parallel execution model by
-keeping the program stateless and working with accounts directly. The above example
-can be rewritten as follows.
-
 ```rust
 use anchor::prelude::*;
 
@@ -102,7 +57,7 @@ mod counter {
     pub fn increment(ctx: Context<Increment>) -> Result<()> {
         let counter = &mut ctx.accounts.counter;
 
-        counter += 1;
+        counter.count += 1;
 
         Ok(())
     }
@@ -136,11 +91,6 @@ pub enum ErrorCode {
 }
 ```
 
-Due to the fact that account sizes on Solana are fixed, some combination of
-the above is often required. For example, one can store store global state
-associated with the entire program in the `#[state]` struct and local
-state assocated with each user in individual `#[account]` structs.
-
 For more, see the [examples](https://github.com/project-serum/anchor/tree/master/examples)
 directory.
 

+ 4 - 6
cli/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-cli"
-version = "0.9.0"
+version = "0.11.1"
 authors = ["armaniferrante <armaniferrante@gmail.com>"]
 edition = "2018"
 
@@ -21,15 +21,13 @@ anchor-client = { path = "../client" }
 anchor-syn = { path = "../lang/syn", features = ["idl"] }
 serde_json = "1.0"
 shellexpand = "2.1.0"
-serde_yaml = "0.8"
 toml = "0.5.8"
 serde = { version = "1.0.122", features = ["derive"] }
-solana-sdk = "1.7.2"
-solana-program = "1.7.2"
-solana-client = "1.7.2"
+solana-sdk = "1.7.4"
+solana-program = "1.7.4"
+solana-client = "1.7.4"
 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"
-which = "4.1.0"

+ 84 - 0
cli/npm-package/anchor.js

@@ -0,0 +1,84 @@
+#!/usr/bin/env node
+const { spawn, spawnSync } = require("child_process");
+const path = require("path");
+const { arch, platform } = require("os");
+const { version } = require("./package.json");
+
+const PACKAGE_VERSION = `anchor-cli ${version}`;
+const PACKAGE_ANCHOR_PATH = path.join(__dirname, "anchor");
+
+function getBinaryVersion(location) {
+  const result = spawnSync(location, ["--version"]);
+  const error =
+    (result.error && result.error.toString()) ||
+    (result.stderr.length > 0 && result.stderr.toString().trim()) ||
+    null;
+  return [error, result.stdout && result.stdout.toString().trim()];
+}
+
+function runAnchor(location) {
+  const args = process.argv.slice(2);
+  const anchor = spawn(location, args, { stdio: "inherit" });
+  anchor.on("exit", (code, signal) => {
+    process.on("exit", () => {
+      if (signal) {
+        process.kill(process.pid, signal);
+      } else {
+        process.exit(code);
+      }
+    });
+  });
+
+  process.on("SIGINT", function () {
+    anchor.kill("SIGINT");
+    anchor.kill("SIGTERM");
+  });
+}
+
+function tryPackageAnchor() {
+  if (arch() !== "x64" || platform() !== "linux") {
+    console.error(`Only x86_64 / Linux distributed in NPM package right now.`);
+    return false;
+  }
+
+  const [error, binaryVersion] = getBinaryVersion(PACKAGE_ANCHOR_PATH);
+  if (error !== null) {
+    console.error(`Failed to get version of local binary: ${error}`);
+    return false;
+  }
+  if (binaryVersion !== PACKAGE_VERSION) {
+    console.error(
+      `Package binary version is not correct. Expected "${PACKAGE_VERSION}", found "${binaryVersion}".`
+    );
+    return false;
+  }
+
+  runAnchor(PACKAGE_ANCHOR_PATH);
+  return true;
+}
+
+function trySystemAnchor() {
+  console.error("Trying globally installed anchor.");
+
+  const added = path.dirname(process.argv[1]);
+  const directories = process.env.PATH.split(":").filter(
+    (dir) => dir !== added
+  );
+  process.env.PATH = directories.join(":");
+
+  const [error, binaryVersion] = getBinaryVersion("anchor");
+  if (error !== null) {
+    console.error(`Failed to get version of global binary: ${error}`);
+    return;
+  }
+  if (binaryVersion !== PACKAGE_VERSION) {
+    console.error(
+      `Globally installed anchor version is not correct. Expected "${PACKAGE_VERSION}", found "${binaryVersion}".`
+    );
+    return;
+  }
+
+  runAnchor("anchor");
+}
+
+tryPackageAnchor() || trySystemAnchor();

+ 23 - 0
cli/npm-package/package.json

@@ -0,0 +1,23 @@
+{
+  "name": "@project-serum/anchor-cli",
+  "version": "0.11.1",
+  "description": "Anchor CLI tool",
+  "homepage": "https://github.com/project-serum/anchor#readme",
+  "bugs": {
+    "url": "https://github.com/project-serum/anchor/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/project-serum/anchor.git"
+  },
+  "license": "(MIT OR Apache-2.0)",
+  "bin": {
+    "anchor": "./anchor.js"
+  },
+  "scripts": {
+    "prepack": "[ \"$(uname -op)\" != \"x86_64 GNU/Linux\" ] && (echo Can be packed only on x86_64 GNU/Linux && exit 1) || ([ \"$(./anchor --version)\" != \"anchor-cli $(jq -r .version package.json)\" ] && (echo Check anchor binary version && exit 2) || exit 0)"
+  },
+  "publishConfig": {
+    "access": "public"
+  }
+}

+ 61 - 16
cli/src/config.rs

@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
 use solana_sdk::pubkey::Pubkey;
 use solana_sdk::signature::Keypair;
 use std::collections::BTreeMap;
+use std::convert::TryFrom;
 use std::fs::{self, File};
 use std::io::prelude::*;
 use std::path::Path;
@@ -16,6 +17,7 @@ use std::str::FromStr;
 pub struct Config {
     pub provider: ProviderConfig,
     pub clusters: ClustersConfig,
+    pub scripts: ScriptsConfig,
     pub test: Option<Test>,
 }
 
@@ -25,6 +27,8 @@ pub struct ProviderConfig {
     pub wallet: WalletPath,
 }
 
+pub type ScriptsConfig = BTreeMap<String, String>;
+
 pub type ClustersConfig = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;
 
 impl Config {
@@ -100,7 +104,8 @@ impl Config {
 struct _Config {
     provider: Provider,
     test: Option<Test>,
-    clusters: Option<BTreeMap<String, BTreeMap<String, String>>>,
+    scripts: Option<ScriptsConfig>,
+    clusters: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
 }
 
 #[derive(Debug, Serialize, Deserialize)]
@@ -113,7 +118,7 @@ impl ToString for Config {
     fn to_string(&self) -> String {
         let clusters = {
             let c = ser_clusters(&self.clusters);
-            if c.len() == 0 {
+            if c.is_empty() {
                 None
             } else {
                 Some(c)
@@ -125,6 +130,10 @@ impl ToString for Config {
                 wallet: self.provider.wallet.to_string(),
             },
             test: self.test.clone(),
+            scripts: match self.scripts.is_empty() {
+                true => None,
+                false => Some(self.scripts.clone()),
+            },
             clusters,
         };
 
@@ -143,32 +152,36 @@ impl FromStr for Config {
                 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()), |c| deser_clusters(c))?,
+            clusters: cfg.clusters.map_or(Ok(BTreeMap::new()), deser_clusters)?,
         })
     }
 }
 
 fn ser_clusters(
     clusters: &BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>,
-) -> BTreeMap<String, BTreeMap<String, String>> {
+) -> BTreeMap<String, BTreeMap<String, serde_json::Value>> {
     clusters
         .iter()
         .map(|(cluster, programs)| {
             let cluster = cluster.to_string();
             let programs = programs
                 .iter()
-                .map(|(name, deployment)| (name.clone(), deployment.program_id.to_string()))
-                .collect::<BTreeMap<String, String>>();
+                .map(|(name, deployment)| {
+                    (
+                        name.clone(),
+                        serde_json::to_value(&_ProgramDeployment::from(deployment)).unwrap(),
+                    )
+                })
+                .collect::<BTreeMap<String, serde_json::Value>>();
             (cluster, programs)
         })
-        .collect::<BTreeMap<String, BTreeMap<String, String>>>()
+        .collect::<BTreeMap<String, BTreeMap<String, serde_json::Value>>>()
 }
 
 fn deser_clusters(
-    clusters: BTreeMap<String, BTreeMap<String, String>>,
+    clusters: BTreeMap<String, BTreeMap<String, serde_json::Value>>,
 ) -> Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>> {
     clusters
         .iter()
@@ -179,10 +192,17 @@ fn deser_clusters(
                 .map(|(name, program_id)| {
                     Ok((
                         name.clone(),
-                        ProgramDeployment {
-                            name: name.clone(),
-                            program_id: program_id.parse()?,
-                        },
+                        ProgramDeployment::try_from(match &program_id {
+                            serde_json::Value::String(address) => _ProgramDeployment {
+                                address: address.parse()?,
+                                idl: None,
+                            },
+                            serde_json::Value::Object(_) => {
+                                serde_json::from_value(program_id.clone())
+                                    .map_err(|_| anyhow!("Unable to read toml"))?
+                            }
+                            _ => return Err(anyhow!("Invalid toml type")),
+                        })?,
                     ))
                 })
                 .collect::<Result<BTreeMap<String, ProgramDeployment>>>()?;
@@ -269,8 +289,33 @@ impl Program {
 
 #[derive(Debug, Default)]
 pub struct ProgramDeployment {
-    pub name: String,
-    pub program_id: Pubkey,
+    pub address: Pubkey,
+    pub idl: Option<String>,
+}
+
+impl TryFrom<_ProgramDeployment> for ProgramDeployment {
+    type Error = anyhow::Error;
+    fn try_from(pd: _ProgramDeployment) -> Result<Self, Self::Error> {
+        Ok(ProgramDeployment {
+            address: pd.address.parse()?,
+            idl: pd.idl,
+        })
+    }
+}
+
+#[derive(Debug, Default, Serialize, Deserialize)]
+pub struct _ProgramDeployment {
+    pub address: String,
+    pub idl: Option<String>,
+}
+
+impl From<&ProgramDeployment> for _ProgramDeployment {
+    fn from(pd: &ProgramDeployment) -> Self {
+        Self {
+            address: pd.address.to_string(),
+            idl: pd.idl.clone(),
+        }
+    }
 }
 
 pub struct ProgramWorkspace {

+ 105 - 108
cli/src/main.rs

@@ -96,9 +96,6 @@ pub enum Command {
         /// use this to save time when running test and the program code is not altered.
         #[clap(long)]
         skip_build: bool,
-        /// Use this flag if you want to use yarn as your package manager.
-        #[clap(long)]
-        yarn: bool,
         file: Option<String>,
     },
     /// Creates a new program.
@@ -147,6 +144,11 @@ pub enum Command {
     /// Starts a node shell with an Anchor client setup according to the local
     /// config.
     Shell,
+    /// Runs the script defined by the current workspace's Anchor.toml.
+    Run {
+        /// The name of the script to run.
+        script: String,
+    },
 }
 
 #[derive(Debug, Clap)]
@@ -253,20 +255,19 @@ fn main() -> Result<()> {
             skip_deploy,
             skip_local_validator,
             skip_build,
-            yarn,
             file,
         } => test(
             &opts.cfg_override,
             skip_deploy,
             skip_local_validator,
             skip_build,
-            yarn,
             file,
         ),
         #[cfg(feature = "dev")]
         Command::Airdrop => airdrop(cfg_override),
         Command::Cluster { subcmd } => cluster(subcmd),
         Command::Shell => shell(&opts.cfg_override),
+        Command::Run { script } => run(&opts.cfg_override, script),
     }
 }
 
@@ -310,16 +311,16 @@ fn init(cfg_override: &ConfigOverride, name: String, typescript: bool) -> Result
         ts_config.write_all(template::ts_config().as_bytes())?;
 
         let mut deploy = File::create("migrations/deploy.ts")?;
-        deploy.write_all(&template::ts_deploy_script().as_bytes())?;
+        deploy.write_all(template::ts_deploy_script().as_bytes())?;
 
-        let mut mocha = File::create(&format!("tests/{}.spec.ts", name))?;
+        let mut mocha = File::create(&format!("tests/{}.ts", name))?;
         mocha.write_all(template::ts_mocha(&name).as_bytes())?;
     } else {
         let mut mocha = File::create(&format!("tests/{}.js", name))?;
         mocha.write_all(template::mocha(&name).as_bytes())?;
 
         let mut deploy = File::create("migrations/deploy.js")?;
-        deploy.write_all(&template::deploy_script().as_bytes())?;
+        deploy.write_all(template::deploy_script().as_bytes())?;
     }
 
     println!("{} initialized", name);
@@ -349,11 +350,11 @@ fn new_program(name: &str) -> Result<()> {
     fs::create_dir(&format!("programs/{}", name))?;
     fs::create_dir(&format!("programs/{}/src/", name))?;
     let mut cargo_toml = File::create(&format!("programs/{}/Cargo.toml", name))?;
-    cargo_toml.write_all(template::cargo_toml(&name).as_bytes())?;
+    cargo_toml.write_all(template::cargo_toml(name).as_bytes())?;
     let mut xargo_toml = File::create(&format!("programs/{}/Xargo.toml", name))?;
     xargo_toml.write_all(template::xargo_toml().as_bytes())?;
     let mut lib_rs = File::create(&format!("programs/{}/src/lib.rs", name))?;
-    lib_rs.write_all(template::lib_rs(&name).as_bytes())?;
+    lib_rs.write_all(template::lib_rs(name).as_bytes())?;
     Ok(())
 }
 
@@ -457,7 +458,7 @@ fn build_cwd_verifiable(workspace_dir: &Path) -> Result<()> {
         .args(&[
             "run",
             "--name",
-            &container_name,
+            container_name,
             "-v",
             &volume_mount,
             &image_name,
@@ -503,7 +504,7 @@ fn build_cwd_verifiable(workspace_dir: &Path) -> Result<()> {
 
     // Remove the docker image.
     let exit = std::process::Command::new("docker")
-        .args(&["rm", &container_name])
+        .args(&["rm", container_name])
         .stdout(Stdio::inherit())
         .stderr(Stdio::inherit())
         .output()
@@ -539,7 +540,7 @@ fn _build_cwd(idl_out: Option<PathBuf>) -> Result<()> {
 
 fn verify(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
     let (cfg, _path, cargo) = Config::discover(cfg_override)?.expect("Not in workspace.");
-    let cargo = cargo.ok_or(anyhow!("Must be inside program subdirectory."))?;
+    let cargo = cargo.ok_or_else(|| anyhow!("Must be inside program subdirectory."))?;
     let program_dir = cargo.parent().unwrap();
 
     // Build the program we want to verify.
@@ -691,7 +692,7 @@ fn idl_init(cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: Str
         let bytes = std::fs::read(idl_filepath)?;
         let idl: Idl = serde_json::from_reader(&*bytes)?;
 
-        let idl_address = create_idl_account(&cfg, &keypair, &program_id, &idl)?;
+        let idl_address = create_idl_account(cfg, &keypair, &program_id, &idl)?;
 
         println!("Idl account created: {:?}", idl_address);
         Ok(())
@@ -709,8 +710,8 @@ fn idl_write_buffer(
         let bytes = std::fs::read(idl_filepath)?;
         let idl: Idl = serde_json::from_reader(&*bytes)?;
 
-        let idl_buffer = create_idl_buffer(&cfg, &keypair, &program_id, &idl)?;
-        idl_write(&cfg, &program_id, &idl, idl_buffer)?;
+        let idl_buffer = create_idl_buffer(cfg, &keypair, &program_id, &idl)?;
+        idl_write(cfg, &program_id, &idl, idl_buffer)?;
 
         println!("Idl buffer created: {:?}", idl_buffer);
 
@@ -972,7 +973,6 @@ fn test(
     skip_deploy: bool,
     skip_local_validator: bool,
     skip_build: bool,
-    use_yarn: bool,
     file: Option<String>,
 ) -> Result<()> {
     with_workspace(cfg_override, |cfg, _path, _cargo| {
@@ -988,10 +988,8 @@ fn test(
         //
         // In either case, skip the deploy if the user specifies.
         let is_localnet = cfg.provider.cluster == Cluster::Localnet;
-        if !is_localnet || (is_localnet && skip_local_validator) {
-            if !skip_deploy {
-                deploy(cfg_override, None)?;
-            }
+        if (!is_localnet || skip_local_validator) && !skip_deploy {
+            deploy(cfg_override, None)?;
         }
         // Start local test validator, if needed.
         let mut validator_handle = None;
@@ -1004,66 +1002,37 @@ fn test(
         }
 
         // Setup log reader.
-        let log_streams = stream_logs(&cfg.provider.cluster.url());
-
-        // Check to see if yarn is installed, panic if not.
-        if use_yarn {
-            which::which("yarn").unwrap();
-        }
+        let log_streams = stream_logs(cfg.provider.cluster.url());
 
         // Run the tests.
         let test_result: Result<_> = {
             let ts_config_exist = Path::new("tsconfig.json").exists();
-            let mut args = vec!["-t", "1000000"];
-            if let Some(ref file) = file {
-                args.push(file);
-            } else if ts_config_exist {
-                args.push("tests/**/*.spec.ts");
+            let cmd = if ts_config_exist { "ts-mocha" } else { "mocha" };
+            let mut args = if ts_config_exist {
+                vec![cmd, "-p", "./tsconfig.json"]
             } else {
-                args.push("tests/");
-            }
-            let exit = match (ts_config_exist, use_yarn) {
-                (true, true) => std::process::Command::new("yarn")
-                    .arg("ts-mocha")
-                    .arg("-p")
-                    .arg("./tsconfig.json")
-                    .args(args)
-                    .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
-                    .stdout(Stdio::inherit())
-                    .stderr(Stdio::inherit())
-                    .output()
-                    .map_err(anyhow::Error::from)
-                    .with_context(|| "ts-mocha"),
-                (false, true) => std::process::Command::new("yarn")
-                    .arg("mocha")
-                    .args(args)
-                    .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
-                    .stdout(Stdio::inherit())
-                    .stderr(Stdio::inherit())
-                    .output()
-                    .map_err(anyhow::Error::from)
-                    .with_context(|| "mocha"),
-                (true, false) => std::process::Command::new("ts-mocha")
-                    .arg("-p")
-                    .arg("./tsconfig.json")
-                    .args(args)
-                    .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
-                    .stdout(Stdio::inherit())
-                    .stderr(Stdio::inherit())
-                    .output()
-                    .map_err(anyhow::Error::from)
-                    .with_context(|| "ts-mocha"),
-                (false, false) => std::process::Command::new("mocha")
-                    .args(args)
-                    .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
-                    .stdout(Stdio::inherit())
-                    .stderr(Stdio::inherit())
-                    .output()
-                    .map_err(anyhow::Error::from)
-                    .with_context(|| "mocha"),
+                vec![cmd]
             };
-
-            exit
+            args.extend_from_slice(&[
+                "-t",
+                "1000000",
+                if let Some(ref file) = file {
+                    file
+                } else if ts_config_exist {
+                    "tests/**/*.ts"
+                } else {
+                    "tests/"
+                },
+            ]);
+
+            std::process::Command::new("npx")
+                .args(args)
+                .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
+                .stdout(Stdio::inherit())
+                .stderr(Stdio::inherit())
+                .output()
+                .map_err(anyhow::Error::from)
+                .context(cmd)
         };
 
         // Check all errors and shut down.
@@ -1327,7 +1296,7 @@ fn launch(
 ) -> Result<()> {
     // Build and deploy.
     build(cfg_override, None, verifiable, program_name.clone())?;
-    let programs = _deploy(cfg_override, program_name.clone())?;
+    let programs = _deploy(cfg_override, program_name)?;
 
     with_workspace(cfg_override, |cfg, _path, _cargo| {
         let keypair = cfg.provider.wallet.to_string();
@@ -1335,7 +1304,7 @@ fn launch(
         // Add metadata to all IDLs.
         for (address, program) in programs {
             // Store the IDL on chain.
-            let idl_address = create_idl_account(&cfg, &keypair, &address, &program.idl)?;
+            let idl_address = create_idl_account(cfg, &keypair, &address, &program.idl)?;
             println!("IDL account created: {}", idl_address.to_string());
         }
 
@@ -1499,42 +1468,36 @@ fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
 
         let url = cfg.provider.cluster.url().to_string();
         let cur_dir = std::env::current_dir()?;
-        let module_path = cur_dir.join("migrations/deploy.js");
 
-        let ts_config_exist = Path::new("tsconfig.json").exists();
-        let ts_deploy_file_exists = Path::new("migrations/deploy.ts").exists();
-
-        if ts_config_exist && ts_deploy_file_exists {
-            let ts_module_path = cur_dir.join("migrations/deploy.ts");
-            let exit = std::process::Command::new("tsc")
-                .arg(&ts_module_path)
-                .stdout(Stdio::inherit())
-                .stderr(Stdio::inherit())
-                .output()?;
-            if !exit.status.success() {
-                std::process::exit(exit.status.code().unwrap());
-            }
-        };
-
-        let deploy_script_host_str =
-            template::deploy_script_host(&url, &module_path.display().to_string());
+        let use_ts =
+            Path::new("tsconfig.json").exists() && Path::new("migrations/deploy.ts").exists();
 
         if !Path::new(".anchor").exists() {
             fs::create_dir(".anchor")?;
         }
         std::env::set_current_dir(".anchor")?;
 
-        std::fs::write("deploy.js", deploy_script_host_str)?;
-        let exit = std::process::Command::new("node")
-            .arg("deploy.js")
-            .stdout(Stdio::inherit())
-            .stderr(Stdio::inherit())
-            .output()?;
-
-        if ts_config_exist && ts_deploy_file_exists {
-            std::fs::remove_file(&module_path)
-                .map_err(|_| anyhow!("Unable to remove file {}", module_path.display()))?;
-        }
+        let exit = if use_ts {
+            let module_path = cur_dir.join("migrations/deploy.ts");
+            let deploy_script_host_str =
+                template::deploy_ts_script_host(&url, &module_path.display().to_string());
+            std::fs::write("deploy.ts", deploy_script_host_str)?;
+            std::process::Command::new("ts-node")
+                .arg("deploy.ts")
+                .stdout(Stdio::inherit())
+                .stderr(Stdio::inherit())
+                .output()?
+        } else {
+            let module_path = cur_dir.join("migrations/deploy.js");
+            let deploy_script_host_str =
+                template::deploy_js_script_host(&url, &module_path.display().to_string());
+            std::fs::write("deploy.js", deploy_script_host_str)?;
+            std::process::Command::new("node")
+                .arg("deploy.js")
+                .stdout(Stdio::inherit())
+                .stderr(Stdio::inherit())
+                .output()?
+        };
 
         if !exit.status.success() {
             println!("Deploy failed.");
@@ -1613,17 +1576,31 @@ fn cluster(_cmd: ClusterCommand) -> Result<()> {
 fn shell(cfg_override: &ConfigOverride) -> Result<()> {
     with_workspace(cfg_override, |cfg, _path, _cargo| {
         let programs = {
-            let idls: HashMap<String, Idl> = read_all_programs()?
+            let mut idls: HashMap<String, Idl> = read_all_programs()?
                 .iter()
                 .map(|program| (program.idl.name.clone(), program.idl.clone()))
                 .collect();
+            // Insert all manually specified idls into the idl map.
+            if let Some(programs) = cfg.clusters.get(&cfg.provider.cluster) {
+                let _ = programs
+                    .iter()
+                    .map(|(name, pd)| {
+                        if let Some(idl_fp) = &pd.idl {
+                            let file_str =
+                                std::fs::read_to_string(idl_fp).expect("Unable to read IDL file");
+                            let idl = serde_json::from_str(&file_str).expect("Idl not readable");
+                            idls.insert(name.clone(), idl);
+                        }
+                    })
+                    .collect::<Vec<_>>();
+            }
             match cfg.clusters.get(&cfg.provider.cluster) {
                 None => Vec::new(),
                 Some(programs) => programs
                     .iter()
                     .map(|(name, program_deployment)| ProgramWorkspace {
                         name: name.to_string(),
-                        program_id: program_deployment.program_id,
+                        program_id: program_deployment.address,
                         idl: match idls.get(name) {
                             None => {
                                 println!("Unable to find IDL for {}", name);
@@ -1655,6 +1632,26 @@ fn shell(cfg_override: &ConfigOverride) -> Result<()> {
     })
 }
 
+fn run(cfg_override: &ConfigOverride, script: String) -> Result<()> {
+    with_workspace(cfg_override, |cfg, _path, _cargo| {
+        let script = cfg
+            .scripts
+            .get(&script)
+            .ok_or_else(|| anyhow!("Unable to find script"))?;
+        let exit = std::process::Command::new("bash")
+            .arg("-c")
+            .arg(&script)
+            .stdout(Stdio::inherit())
+            .stderr(Stdio::inherit())
+            .output()
+            .unwrap();
+        if !exit.status.success() {
+            std::process::exit(exit.status.code().unwrap_or(1));
+        }
+        Ok(())
+    })
+}
+
 // with_workspace ensures the current working directory is always the top level
 // workspace directory, i.e., where the `Anchor.toml` file is located, before
 // and after the closure invocation.

+ 33 - 3
cli/src/template.rs

@@ -38,7 +38,7 @@ anchor-lang = "{2}"
     )
 }
 
-pub fn deploy_script_host(cluster_url: &str, script_path: &str) -> String {
+pub fn deploy_js_script_host(cluster_url: &str, script_path: &str) -> String {
     format!(
         r#"
 const anchor = require('@project-serum/anchor');
@@ -66,6 +66,34 @@ main();
     )
 }
 
+pub fn deploy_ts_script_host(cluster_url: &str, script_path: &str) -> String {
+    format!(
+        r#"
+import * as anchor from '@project-serum/anchor';
+
+// Deploy script defined by the user.
+const userScript = require("{0}");
+
+async function main() {{
+    const url = "{1}";
+    const preflightCommitment = 'recent';
+    const connection = new anchor.web3.Connection(url, preflightCommitment);
+    const wallet = anchor.Wallet.local();
+
+    const provider = new anchor.Provider(connection, wallet, {{
+        preflightCommitment,
+        commitment: 'recent',
+    }});
+
+    // Run the user's deploy script.
+    userScript(provider);
+}}
+main();
+"#,
+        script_path, cluster_url,
+    )
+}
+
 pub fn deploy_script() -> &'static str {
     r#"
 // Migrations are an early feature. Currently, they're nothing more than this
@@ -102,7 +130,8 @@ module.exports = async function (provider) {
 
 pub fn xargo_toml() -> &'static str {
     r#"[target.bpfel-unknown-unknown.dependencies.std]
-features = []"#
+features = []
+"#
 }
 
 pub fn lib_rs(name: &str) -> String {
@@ -118,7 +147,8 @@ pub mod {} {{
 }}
 
 #[derive(Accounts)]
-pub struct Initialize {{}}"#,
+pub struct Initialize {{}}
+"#,
         name.to_snake_case(),
     )
 }

+ 2 - 2
client/Cargo.toml

@@ -1,13 +1,13 @@
 [package]
 name = "anchor-client"
-version = "0.9.0"
+version = "0.11.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.9.0" }
+anchor-lang = { path = "../lang", version = "0.11.1" }
 anyhow = "1.0.32"
 regex = "1.4.5"
 serde = { version = "1.0.122", features = ["derive"] }

+ 18 - 16
client/src/lib.rs

@@ -91,7 +91,7 @@ impl Program {
     pub fn request(&self) -> RequestBuilder {
         RequestBuilder::from(
             self.program_id,
-            &self.cfg.cluster.url(),
+            self.cfg.cluster.url(),
             Keypair::from_bytes(&self.cfg.payer.to_bytes()).unwrap(),
             self.cfg.options,
             RequestNamespace::Global,
@@ -102,7 +102,7 @@ impl Program {
     pub fn state_request(&self) -> RequestBuilder {
         RequestBuilder::from(
             self.program_id,
-            &self.cfg.cluster.url(),
+            self.cfg.cluster.url(),
             Keypair::from_bytes(&self.cfg.payer.to_bytes()).unwrap(),
             self.cfg.options,
             RequestNamespace::State { new: false },
@@ -140,7 +140,7 @@ impl Program {
 
     pub fn on<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
         &self,
-        f: impl Fn(&EventContext, T) -> () + Send + 'static,
+        f: impl Fn(&EventContext, T) + Send + 'static,
     ) -> Result<EventHandle, ClientError> {
         let addresses = vec![self.program_id.to_string()];
         let filter = RpcTransactionLogsFilter::Mentions(addresses);
@@ -149,7 +149,7 @@ impl Program {
             commitment: self.cfg.options,
         };
         let self_program_str = self.program_id.to_string();
-        let (client, receiver) = PubsubClient::logs_subscribe(&ws_url, filter.clone(), cfg)?;
+        let (client, receiver) = PubsubClient::logs_subscribe(&ws_url, filter, cfg)?;
         std::thread::spawn(move || {
             loop {
                 match receiver.recv() {
@@ -159,23 +159,24 @@ impl Program {
                             slot: logs.context.slot,
                         };
                         let mut logs = &logs.value.logs[..];
-                        if logs.len() > 0 {
+                        if !logs.is_empty() {
                             if let Ok(mut execution) = Execution::new(&mut logs) {
                                 for l in logs {
                                     // Parse the log.
                                     let (event, new_program, did_pop) = {
                                         if self_program_str == execution.program() {
-                                            handle_program_log(&self_program_str, &l)
-                                                .unwrap_or_else(|e| {
+                                            handle_program_log(&self_program_str, l).unwrap_or_else(
+                                                |e| {
                                                     println!(
                                                         "Unable to parse log: {}",
                                                         e.to_string()
                                                     );
                                                     std::process::exit(1);
-                                                })
+                                                },
+                                            )
                                         } else {
                                             let (program, did_pop) =
-                                                handle_system_log(&self_program_str, &l);
+                                                handle_system_log(&self_program_str, l);
                                             (None, program, did_pop)
                                         }
                                     };
@@ -232,7 +233,7 @@ fn handle_program_log<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
     }
     // System log.
     else {
-        let (program, did_pop) = handle_system_log(&self_program_str, &l);
+        let (program, did_pop) = handle_system_log(self_program_str, l);
         Ok((None, program, did_pop))
     }
 }
@@ -264,10 +265,10 @@ impl Execution {
         let re = Regex::new(r"^Program (.*) invoke.*$").unwrap();
         let c = re
             .captures(l)
-            .ok_or(ClientError::LogParseError(l.to_string()))?;
+            .ok_or_else(|| ClientError::LogParseError(l.to_string()))?;
         let program = c
             .get(1)
-            .ok_or(ClientError::LogParseError(l.to_string()))?
+            .ok_or_else(|| ClientError::LogParseError(l.to_string()))?
             .as_str()
             .to_string();
         Ok(Self {
@@ -276,7 +277,7 @@ impl Execution {
     }
 
     pub fn program(&self) -> String {
-        assert!(self.stack.len() > 0);
+        assert!(!self.stack.is_empty());
         self.stack[self.stack.len() - 1].clone()
     }
 
@@ -285,7 +286,7 @@ impl Execution {
     }
 
     pub fn pop(&mut self) {
-        assert!(self.stack.len() > 0);
+        assert!(!self.stack.is_empty());
         self.stack.pop().unwrap();
     }
 }
@@ -394,6 +395,7 @@ impl<'a> RequestBuilder<'a> {
     }
 
     /// Invokes the `#[state]`'s `new` constructor.
+    #[allow(clippy::wrong_self_convention)]
     pub fn new(mut self, args: impl InstructionData) -> Self {
         assert!(self.namespace == RequestNamespace::State { new: false });
         self.namespace = RequestNamespace::State { new: true };
@@ -483,7 +485,7 @@ mod tests {
         let log = "Program 7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw success";
         let (program, did_pop) = handle_system_log("asdf", log);
         assert_eq!(program, None);
-        assert_eq!(did_pop, true);
+        assert!(did_pop);
     }
 
     #[test]
@@ -491,6 +493,6 @@ mod tests {
         let log = "Program 7swsTUiQ6KUK4uFYquQKg4epFRsBnvbrTf2fZQCa2sTJ qwer";
         let (program, did_pop) = handle_system_log("asdf", log);
         assert_eq!(program, None);
-        assert_eq!(did_pop, false);
+        assert!(!did_pop);
     }
 }

+ 1 - 1
docker/Makefile

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

+ 2 - 1
docs/package.json

@@ -17,6 +17,7 @@
   "devDependencies": {
     "gh-pages": "^3.1.0",
     "vuepress": "^1.5.3",
-    "vuepress-plugin-dehydrate": "^1.1.5"
+    "vuepress-plugin-dehydrate": "^1.1.5",
+    "vuepress-theme-default-prefers-color-scheme": "^2.0.0"
   }
 }

+ 11 - 3
docs/src/.vuepress/config.js

@@ -5,7 +5,7 @@ module.exports = {
   /**
    * Ref:https://v1.vuepress.vuejs.org/config/#title
    */
-  title: "Anchor",
+  title: "Anchor",
   /**
    * Ref:https://v1.vuepress.vuejs.org/config/#description
    */
@@ -25,6 +25,8 @@ module.exports = {
     ],
   ],
 
+  theme: "default-prefers-color-scheme",
+
   /**
    * Theme configuration, here is the default theme configuration for VuePress.
    *
@@ -48,7 +50,14 @@ module.exports = {
       },
       {
         collapsable: false,
-        title: "Programs",
+        title: "Teams",
+        children: [
+          "/getting-started/projects",
+        ],
+      },
+      {
+        collapsable: false,
+        title: "Tutorials",
         children: [
           "/tutorials/tutorial-0",
           "/tutorials/tutorial-1",
@@ -56,7 +65,6 @@ module.exports = {
           "/tutorials/tutorial-3",
           "/tutorials/tutorial-4",
           "/tutorials/tutorial-5",
-          "/tutorials/tutorial-6",
         ],
       },
       {

+ 19 - 0
docs/src/cli/commands.md

@@ -54,6 +54,12 @@ anchor build
 
 Builds programs in the workspace targeting Solana's BPF runtime and emitting IDLs in the `target/idl` directory.
 
+```
+anchor build --verifiable
+```
+
+Runs the build inside a docker image so that the output binary is deterministic (assuming a Cargo.lock file is used). This command must be run from within a single crate subdirectory within the workspace. For example, `programs/<my-program>/`.
+
 ## Deploy
 
 ```
@@ -87,6 +93,8 @@ 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
 The Anchor workflow [recommends](https://www.parity.io/paritys-checklist-for-secure-smart-contract-development/)
 to test your program using integration tests in a language other
@@ -192,6 +200,12 @@ useful when simultaneously developing an app against a Localnet or Devnet. For m
 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
 
 ```
@@ -221,4 +235,9 @@ Cluster Endpoints:
 
 ## Verify
 
+```
+anchor verify <program-id>
+```
+
 Verifies the on-chain bytecode matches the locally compiled artifact.
+    

+ 2 - 2
docs/src/getting-started/installation.md

@@ -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.1/install)"
+sh -c "$(curl -sSfL https://release.solana.com/v1.7.4/install)"
 ```
 
 ## Install Mocha
@@ -34,7 +34,7 @@ npm install -g mocha
 For now, we can use Cargo to install the CLI.
 
 ```bash
-cargo install --git https://github.com/project-serum/anchor --tag v0.9.0 anchor-cli --locked
+cargo install --git https://github.com/project-serum/anchor --tag v0.11.1 anchor-cli --locked
 ```
 
 On Linux systems you may need to install additional dependencies if `cargo install` fails. On Ubuntu,

+ 1 - 1
docs/src/getting-started/introduction.md

@@ -9,7 +9,7 @@ Anchor is a framework for Solana's [Sealevel](https://medium.com/solana-labs/sea
 
 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.
 
-Here, we'll walkthrough 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).
+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).

+ 16 - 0
docs/src/getting-started/projects.md

@@ -0,0 +1,16 @@
+# Projects
+
+Open a pull request to add your project to the [list](https://github.com/project-serum/anchor/blob/master/docs/src/getting-started/projects.md).
+
+* [Serum](https://github.com/project-serum)
+* [Synthetify](https://github.com/Synthetify)
+* [SolFarm](https://solfarm.io/)
+* [Zeta Markets](https://zeta.markets/)
+* [Saber](https://saber.so)
+* [Mello](https://github.com/mello-markets/Mello)
+* [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)

+ 1 - 1
docs/src/tutorials/tutorial-1.md

@@ -122,5 +122,5 @@ anchor test
 ## Next Steps
 
 We've covered all the basics of developing applications using Anchor. However, we've
-left out one import aspect to ensure the security of our programs--validating input
+left out one important aspect to ensure the security of our programs--validating input
 and access control. We'll cover that next.

+ 1 - 1
docs/src/tutorials/tutorial-6.md

@@ -30,7 +30,7 @@ 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 recommend to only use associated
+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.
 :::
 

+ 5 - 3
examples/cashiers-check/tests/cashiers-check.js

@@ -1,7 +1,9 @@
 const anchor = require("@project-serum/anchor");
 const serumCmn = require("@project-serum/common");
-const TokenInstructions = require("@project-serum/serum").TokenInstructions;
 const assert = require("assert");
+const { TOKEN_PROGRAM_ID } = require("@solana/spl-token");
+
+
 
 describe("cashiers-check", () => {
   // Configure the client to use the local cluster.
@@ -48,7 +50,7 @@ describe("cashiers-check", () => {
         from: god,
         to: receiver,
         owner: program.provider.wallet.publicKey,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
         rent: anchor.web3.SYSVAR_RENT_PUBKEY,
       },
       signers: [check, vault],
@@ -87,7 +89,7 @@ describe("cashiers-check", () => {
         checkSigner: checkSigner,
         to: receiver,
         owner: program.provider.wallet.publicKey,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
       },
     });
 

+ 41 - 0
examples/cfo/Anchor.toml

@@ -0,0 +1,41 @@
+[provider]
+cluster = "localnet"
+wallet = "~/.config/solana/id.json"
+
+[clusters.localnet]
+registry = { address = "GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv", idl = "./deps/stake/target/idl/registry.json" }
+lockup = { address = "6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks", idl = "./deps/stake/target/idl/lockup.json" }
+
+[scripts]
+#
+# Testing.
+#
+test = "anchor run build && anchor test --skip-build"
+#
+# Build the program and all CPI dependencies.
+#
+build = "anchor run build-deps && anchor build"
+build-deps = "anchor run build-dex && anchor run build-swap && anchor run build-stake"
+build-dex = "pushd deps/serum-dex/dex/ && cargo build-bpf && popd"
+build-swap = "cd deps/swap && pwd && anchor build && cd ../../"
+build-stake = "pushd deps/stake && anchor build && popd"
+#
+# Runs a localnet with all the programs deployed.
+#
+localnet = "./scripts/localnet.sh"
+
+[[test.genesis]]
+address = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
+program = "./deps/serum-dex/dex/target/deploy/serum_dex.so"
+
+[[test.genesis]]
+address = "22Y43yTVxuUkoRKdm9thyRhQ3SdgQS7c7kB6UNCiaczD"
+program = "./deps/swap/target/deploy/swap.so"
+
+[[test.genesis]]
+address = "GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv"
+program = "./deps/stake/target/deploy/registry.so"
+
+[[test.genesis]]
+address = "6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks"
+program = "./deps/stake/target/deploy/lockup.so"

+ 9 - 0
examples/cfo/Cargo.toml

@@ -0,0 +1,9 @@
+[workspace]
+members = [
+    "programs/*"
+]
+exclude = [
+    "deps/serum-dex",
+    "deps/stake",
+    "deps/swap"
+]

+ 1 - 0
examples/cfo/deps/serum-dex

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

+ 1 - 0
examples/cfo/deps/stake

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

+ 1 - 0
examples/cfo/deps/swap

@@ -0,0 +1 @@
+Subproject commit 0382f2e27db5f95d09aec5e6df7bb01bfc8f0e7f

+ 13 - 0
examples/cfo/migrations/deploy.js

@@ -0,0 +1,13 @@
+
+// Migrations are an early feature. Currently, they're nothing more than this
+// single deploy script that's invoked from the CLI, injecting a provider
+// configured from the workspace's Anchor.toml.
+
+const anchor = require("@project-serum/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+}

+ 24 - 0
examples/cfo/programs/cfo/Cargo.toml

@@ -0,0 +1,24 @@
+[package]
+name = "cfo"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "cfo"
+
+[features]
+no-entrypoint = []
+no-idl = []
+cpi = ["no-entrypoint"]
+default = ["test"]
+test = []
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }
+anchor-spl = { path = "../../../../spl" }
+spl-token = { version ="3.1.1", features = ["no-entrypoint"] }
+swap = { path = "../../deps/swap/programs/swap", features = ["cpi"] }
+registry = { path = "../../deps/stake/programs/registry", features = ["cpi"] }
+lockup = { path = "../../deps/stake/programs/lockup", features = ["cpi"] }

+ 2 - 0
examples/cfo/programs/cfo/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 818 - 0
examples/cfo/programs/cfo/src/lib.rs

@@ -0,0 +1,818 @@
+// WIP. This program has been checkpointed and is not production ready.
+
+use anchor_lang::associated_seeds;
+use anchor_lang::prelude::*;
+use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
+use anchor_lang::solana_program::{system_instruction, system_program};
+use anchor_spl::token::{self, Mint, TokenAccount};
+use anchor_spl::{dex, mint};
+use registry::{Registrar, RewardVendorKind};
+use std::convert::TryInto;
+
+/// CFO is the program representing the Serum chief financial officer. It is
+/// the program responsible for collecting and distributing fees from the Serum
+/// DEX.
+#[program]
+pub mod cfo {
+    use super::*;
+
+    /// Creates a financial officer account associated with a DEX program ID.
+    #[access_control(is_distribution_valid(&d))]
+    pub fn create_officer(
+        ctx: Context<CreateOfficer>,
+        d: Distribution,
+        registrar: Pubkey,
+        msrm_registrar: Pubkey,
+    ) -> Result<()> {
+        let officer = &mut ctx.accounts.officer;
+        officer.authority = *ctx.accounts.authority.key;
+        officer.swap_program = *ctx.accounts.swap_program.key;
+        officer.dex_program = *ctx.accounts.dex_program.key;
+        officer.distribution = d;
+        officer.registrar = registrar;
+        officer.msrm_registrar = msrm_registrar;
+        officer.stake = *ctx.accounts.stake.to_account_info().key;
+        officer.treasury = *ctx.accounts.treasury.to_account_info().key;
+        officer.srm_vault = *ctx.accounts.srm_vault.to_account_info().key;
+        emit!(OfficerDidCreate {
+            pubkey: *officer.to_account_info().key,
+        });
+        Ok(())
+    }
+
+    /// Creates a deterministic token account owned by the CFO.
+    /// This should be used when a new mint is used for collecting fees.
+    /// Can only be called once per token CFO and token mint.
+    pub fn create_officer_token(_ctx: Context<CreateOfficerToken>) -> Result<()> {
+        Ok(())
+    }
+
+    /// Updates the cfo's fee distribution.
+    #[access_control(is_distribution_valid(&d))]
+    pub fn set_distribution(ctx: Context<SetDistribution>, d: Distribution) -> Result<()> {
+        ctx.accounts.officer.distribution = d.clone();
+        emit!(DistributionDidChange { distribution: d });
+        Ok(())
+    }
+
+    /// Transfers fees from the dex to the CFO.
+    pub fn sweep_fees<'info>(ctx: Context<'_, '_, '_, 'info, SweepFees<'info>>) -> Result<()> {
+        let seeds = associated_seeds! {
+            account = ctx.accounts.officer,
+            associated = ctx.accounts.dex.dex_program
+        };
+        let cpi_ctx: CpiContext<'_, '_, '_, 'info, dex::SweepFees<'info>> = (&*ctx.accounts).into();
+        dex::sweep_fees(cpi_ctx.with_signer(&[seeds]))?;
+        Ok(())
+    }
+
+    /// Convert the CFO's entire non-SRM token balance into USDC.
+    /// Assumes USDC is the quote currency.
+    #[access_control(is_not_trading(&ctx.accounts.instructions))]
+    pub fn swap_to_usdc<'info>(
+        ctx: Context<'_, '_, '_, 'info, SwapToUsdc<'info>>,
+        min_exchange_rate: ExchangeRate,
+    ) -> Result<()> {
+        let seeds = associated_seeds! {
+            account = ctx.accounts.officer,
+            associated = ctx.accounts.dex_program
+        };
+        let cpi_ctx: CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> = (&*ctx.accounts).into();
+        swap::cpi::swap(
+            cpi_ctx.with_signer(&[seeds]),
+            swap::Side::Bid,
+            token::accessor::amount(&ctx.accounts.from_vault)?,
+            min_exchange_rate.into(),
+        )?;
+        Ok(())
+    }
+
+    /// Convert the CFO's entire token balance into SRM.
+    /// Assumes SRM is the base currency.
+    #[access_control(is_not_trading(&ctx.accounts.instructions))]
+    pub fn swap_to_srm<'info>(
+        ctx: Context<'_, '_, '_, 'info, SwapToSrm<'info>>,
+        min_exchange_rate: ExchangeRate,
+    ) -> Result<()> {
+        let seeds = associated_seeds! {
+            account = ctx.accounts.officer,
+            associated = ctx.accounts.dex_program
+        };
+        let cpi_ctx: CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> = (&*ctx.accounts).into();
+        swap::cpi::swap(
+            cpi_ctx.with_signer(&[seeds]),
+            swap::Side::Bid,
+            token::accessor::amount(&ctx.accounts.from_vault)?,
+            min_exchange_rate.into(),
+        )?;
+        Ok(())
+    }
+
+    /// Distributes srm tokens to the various categories. Before calling this,
+    /// one must convert the fees into SRM via the swap APIs.
+    #[access_control(is_distribution_ready(&ctx.accounts))]
+    pub fn distribute<'info>(ctx: Context<'_, '_, '_, 'info, Distribute<'info>>) -> Result<()> {
+        let total_fees = ctx.accounts.srm_vault.amount;
+        let seeds = associated_seeds! {
+            account = ctx.accounts.officer,
+            associated = ctx.accounts.dex_program
+        };
+
+        // Burn.
+        let burn_amount: u64 = u128::from(total_fees)
+            .checked_mul(ctx.accounts.officer.distribution.burn.into())
+            .unwrap()
+            .checked_div(100)
+            .unwrap()
+            .try_into()
+            .map_err(|_| ErrorCode::U128CannotConvert)?;
+        token::burn(ctx.accounts.into_burn().with_signer(&[seeds]), burn_amount)?;
+
+        // Stake.
+        let stake_amount: u64 = u128::from(total_fees)
+            .checked_mul(ctx.accounts.officer.distribution.stake.into())
+            .unwrap()
+            .checked_div(100)
+            .unwrap()
+            .try_into()
+            .map_err(|_| ErrorCode::U128CannotConvert)?;
+        token::transfer(
+            ctx.accounts.into_stake_transfer().with_signer(&[seeds]),
+            stake_amount,
+        )?;
+
+        // Treasury.
+        let treasury_amount: u64 = u128::from(total_fees)
+            .checked_mul(ctx.accounts.officer.distribution.treasury.into())
+            .unwrap()
+            .checked_div(100)
+            .unwrap()
+            .try_into()
+            .map_err(|_| ErrorCode::U128CannotConvert)?;
+        token::transfer(
+            ctx.accounts.into_treasury_transfer().with_signer(&[seeds]),
+            treasury_amount,
+        )?;
+
+        Ok(())
+    }
+
+    #[access_control(is_stake_reward_ready(&ctx.accounts))]
+    pub fn drop_stake_reward<'info>(
+        ctx: Context<'_, '_, '_, 'info, DropStakeReward<'info>>,
+    ) -> Result<()> {
+        // Common reward parameters.
+        let expiry_ts = 1853942400; // 9/30/2028.
+        let expiry_receiver = *ctx.accounts.officer.to_account_info().key;
+        let locked_kind = {
+            let start_ts = 1633017600; // 9/30/2021.
+            let end_ts = 1822320000; // 9/30/2027.
+            let period_count = 2191;
+            RewardVendorKind::Locked {
+                start_ts,
+                end_ts,
+                period_count,
+            }
+        };
+        let seeds = associated_seeds! {
+            account = ctx.accounts.officer,
+            associated = ctx.accounts.dex_program
+        };
+
+        // Total amount staked denominated in SRM (i.e. MSRM is converted to
+        // SRM)
+        let total_pool_value = u128::from(ctx.accounts.srm.pool_mint.supply)
+            .checked_mul(500)
+            .unwrap()
+            .checked_add(
+                u128::from(ctx.accounts.msrm.pool_mint.supply)
+                    .checked_mul(1_000_000)
+                    .unwrap(),
+            )
+            .unwrap();
+
+        // Total reward split between both the SRM and MSRM stake pools.
+        let total_reward_amount = u128::from(ctx.accounts.stake.amount);
+
+        // Proportion of the reward going to the srm pool.
+        //
+        // total_reward_amount * (srm_pool_value / total_pool_value)
+        //
+        let srm_amount: u64 = u128::from(ctx.accounts.srm.pool_mint.supply)
+            .checked_mul(500)
+            .unwrap()
+            .checked_mul(total_reward_amount)
+            .unwrap()
+            .checked_div(total_pool_value)
+            .unwrap()
+            .try_into()
+            .map_err(|_| ErrorCode::U128CannotConvert)?;
+
+        // Proportion of the reward going to the msrm pool.
+        //
+        // total_reward_amount * (msrm_pool_value / total_pool_value)
+        //
+        let msrm_amount = u128::from(ctx.accounts.msrm.pool_mint.supply)
+            .checked_mul(total_reward_amount)
+            .unwrap()
+            .checked_div(total_pool_value)
+            .unwrap()
+            .try_into()
+            .map_err(|_| ErrorCode::U128CannotConvert)?;
+
+        // SRM drop.
+        {
+            // Drop locked reward.
+            let (_, nonce) = Pubkey::find_program_address(
+                &[
+                    ctx.accounts.srm.registrar.to_account_info().key.as_ref(),
+                    ctx.accounts.srm.vendor.to_account_info().key.as_ref(),
+                ],
+                ctx.accounts.token_program.key,
+            );
+            registry::cpi::drop_reward(
+                ctx.accounts.into_srm_reward().with_signer(&[seeds]),
+                locked_kind.clone(),
+                srm_amount.try_into().unwrap(),
+                expiry_ts,
+                expiry_receiver,
+                nonce,
+            )?;
+
+            // Drop unlocked reward.
+            registry::cpi::drop_reward(
+                ctx.accounts.into_srm_reward().with_signer(&[seeds]),
+                RewardVendorKind::Unlocked,
+                srm_amount,
+                expiry_ts,
+                expiry_receiver,
+                nonce,
+            )?;
+        }
+
+        // MSRM drop.
+        {
+            // Drop locked reward.
+            let (_, nonce) = Pubkey::find_program_address(
+                &[
+                    ctx.accounts.msrm.registrar.to_account_info().key.as_ref(),
+                    ctx.accounts.msrm.vendor.to_account_info().key.as_ref(),
+                ],
+                ctx.accounts.token_program.key,
+            );
+            registry::cpi::drop_reward(
+                ctx.accounts.into_msrm_reward().with_signer(&[seeds]),
+                locked_kind,
+                msrm_amount,
+                expiry_ts,
+                expiry_receiver,
+                nonce,
+            )?;
+
+            // Drop unlocked reward.
+            registry::cpi::drop_reward(
+                ctx.accounts.into_msrm_reward().with_signer(&[seeds]),
+                RewardVendorKind::Unlocked,
+                msrm_amount,
+                expiry_ts,
+                expiry_receiver,
+                nonce,
+            )?;
+        }
+
+        Ok(())
+    }
+}
+
+// Context accounts.
+
+#[derive(Accounts)]
+pub struct CreateOfficer<'info> {
+    #[account(init, associated = dex_program, payer = authority)]
+    officer: ProgramAccount<'info, Officer>,
+    #[account(
+        init,
+        token = mint,
+        associated = officer, with = b"vault",
+        space = TokenAccount::LEN,
+        payer = authority,
+    )]
+    srm_vault: CpiAccount<'info, TokenAccount>,
+    #[account(
+        init,
+        token = mint,
+        associated = officer, with = b"stake",
+        space = TokenAccount::LEN,
+        payer = authority,
+    )]
+    stake: CpiAccount<'info, TokenAccount>,
+    #[account(
+        init,
+        token = mint,
+        associated = officer, with = b"treasury",
+        space = TokenAccount::LEN,
+        payer = authority,
+    )]
+    treasury: CpiAccount<'info, TokenAccount>,
+    #[account(signer)]
+    authority: AccountInfo<'info>,
+    #[cfg_attr(
+        not(feature = "test"),
+        account(address = mint::SRM),
+    )]
+    mint: AccountInfo<'info>,
+    #[account(executable)]
+    dex_program: AccountInfo<'info>,
+    #[account(executable)]
+    swap_program: AccountInfo<'info>,
+    #[account(address = system_program::ID)]
+    system_program: AccountInfo<'info>,
+    #[account(address = spl_token::ID)]
+    token_program: AccountInfo<'info>,
+    rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct CreateOfficerToken<'info> {
+    officer: ProgramAccount<'info, Officer>,
+    #[account(
+        init,
+        token = mint,
+        associated = officer, with = mint,
+        space = TokenAccount::LEN,
+        payer = payer,
+    )]
+    token: CpiAccount<'info, TokenAccount>,
+    #[account(owner = token_program)]
+    mint: AccountInfo<'info>,
+    #[account(mut, signer)]
+    payer: AccountInfo<'info>,
+    #[account(address = system_program::ID)]
+    system_program: AccountInfo<'info>,
+    #[account(address = spl_token::ID)]
+    token_program: AccountInfo<'info>,
+    rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct SetDistribution<'info> {
+    #[account(has_one = authority)]
+    officer: ProgramAccount<'info, Officer>,
+    #[account(signer)]
+    authority: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct SweepFees<'info> {
+    #[account(associated = dex.dex_program)]
+    officer: ProgramAccount<'info, Officer>,
+    #[account(
+        mut,
+        owner = dex.token_program,
+        associated = officer, with = mint,
+    )]
+    sweep_vault: AccountInfo<'info>,
+    mint: AccountInfo<'info>,
+    dex: Dex<'info>,
+}
+
+#[derive(Accounts)]
+pub struct Dex<'info> {
+    #[account(mut)]
+    market: AccountInfo<'info>,
+    #[account(mut)]
+    pc_vault: AccountInfo<'info>,
+    sweep_authority: AccountInfo<'info>,
+    vault_signer: AccountInfo<'info>,
+    dex_program: AccountInfo<'info>,
+    #[account(address = spl_token::ID)]
+    token_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct SwapToUsdc<'info> {
+    #[account(associated = dex_program)]
+    officer: ProgramAccount<'info, Officer>,
+    market: DexMarketAccounts<'info>,
+    #[account(
+        owner = token_program,
+        constraint = &officer.treasury != from_vault.key,
+        constraint = &officer.stake != from_vault.key,
+    )]
+    from_vault: AccountInfo<'info>,
+    #[account(owner = token_program)]
+    quote_vault: AccountInfo<'info>,
+    #[account(associated = officer, with = mint::USDC)]
+    usdc_vault: AccountInfo<'info>,
+    #[account(address = swap::ID)]
+    swap_program: AccountInfo<'info>,
+    #[account(address = dex::ID)]
+    dex_program: AccountInfo<'info>,
+    #[account(address = token::ID)]
+    token_program: AccountInfo<'info>,
+    rent: Sysvar<'info, Rent>,
+    #[account(address = tx_instructions::ID)]
+    instructions: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct SwapToSrm<'info> {
+    #[account(associated = dex_program)]
+    officer: ProgramAccount<'info, Officer>,
+    market: DexMarketAccounts<'info>,
+    #[account(
+        owner = token_program,
+        constraint = &officer.treasury != from_vault.key,
+        constraint = &officer.stake != from_vault.key,
+    )]
+    from_vault: AccountInfo<'info>,
+    #[account(owner = token_program)]
+    quote_vault: AccountInfo<'info>,
+    #[account(
+        associated = officer,
+        with = mint::SRM,
+        constraint = &officer.treasury != from_vault.key,
+        constraint = &officer.stake != from_vault.key,
+    )]
+    srm_vault: AccountInfo<'info>,
+    #[account(address = swap::ID)]
+    swap_program: AccountInfo<'info>,
+    #[account(address = dex::ID)]
+    dex_program: AccountInfo<'info>,
+    #[account(address = token::ID)]
+    token_program: AccountInfo<'info>,
+    rent: Sysvar<'info, Rent>,
+    #[account(address = tx_instructions::ID)]
+    instructions: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct DexMarketAccounts<'info> {
+    #[account(mut)]
+    market: AccountInfo<'info>,
+    #[account(mut)]
+    open_orders: AccountInfo<'info>,
+    #[account(mut)]
+    request_queue: AccountInfo<'info>,
+    #[account(mut)]
+    event_queue: AccountInfo<'info>,
+    #[account(mut)]
+    bids: AccountInfo<'info>,
+    #[account(mut)]
+    asks: AccountInfo<'info>,
+    // The `spl_token::Account` that funds will be taken from, i.e., transferred
+    // from the user into the market's vault.
+    //
+    // For bids, this is the base currency. For asks, the quote.
+    #[account(mut)]
+    order_payer_token_account: AccountInfo<'info>,
+    // Also known as the "base" currency. For a given A/B market,
+    // this is the vault for the A mint.
+    #[account(mut)]
+    coin_vault: AccountInfo<'info>,
+    // Also known as the "quote" currency. For a given A/B market,
+    // this is the vault for the B mint.
+    #[account(mut)]
+    pc_vault: AccountInfo<'info>,
+    // PDA owner of the DEX's token accounts for base + quote currencies.
+    vault_signer: AccountInfo<'info>,
+    // User wallets.
+    #[account(mut)]
+    coin_wallet: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct Distribute<'info> {
+    #[account(has_one = treasury, has_one = stake)]
+    officer: ProgramAccount<'info, Officer>,
+    treasury: AccountInfo<'info>,
+    stake: AccountInfo<'info>,
+    #[account(
+        owner = token_program,
+        constraint = srm_vault.mint == mint::SRM,
+    )]
+    srm_vault: CpiAccount<'info, TokenAccount>,
+    #[account(address = mint::SRM)]
+    mint: AccountInfo<'info>,
+    #[account(address = spl_token::ID)]
+    token_program: AccountInfo<'info>,
+    #[account(address = dex::ID)]
+    dex_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct DropStakeReward<'info> {
+    #[account(
+        has_one = stake,
+        constraint = srm.registrar.key == &officer.registrar,
+        constraint = msrm.registrar.key == &officer.msrm_registrar,
+    )]
+    officer: ProgramAccount<'info, Officer>,
+    #[account(associated = officer, with = b"stake", with = mint)]
+    stake: CpiAccount<'info, TokenAccount>,
+    #[cfg_attr(
+        not(feature = "test"),
+        account(address = mint::SRM),
+    )]
+    mint: AccountInfo<'info>,
+    srm: DropStakeRewardPool<'info>,
+    msrm: DropStakeRewardPool<'info>,
+    #[account(owner = registry_program)]
+    msrm_registrar: CpiAccount<'info, Registrar>,
+    #[account(address = token::ID)]
+    token_program: AccountInfo<'info>,
+    #[account(address = registry::ID)]
+    registry_program: AccountInfo<'info>,
+    #[account(address = lockup::ID)]
+    lockup_program: AccountInfo<'info>,
+    #[account(address = dex::ID)]
+    dex_program: AccountInfo<'info>,
+    clock: Sysvar<'info, Clock>,
+    rent: Sysvar<'info, Rent>,
+}
+
+// Don't bother doing validation on the individual accounts. Allow the stake
+// program to handle it.
+#[derive(Accounts)]
+pub struct DropStakeRewardPool<'info> {
+    registrar: AccountInfo<'info>,
+    reward_event_q: AccountInfo<'info>,
+    pool_mint: CpiAccount<'info, Mint>,
+    vendor: AccountInfo<'info>,
+    vendor_vault: AccountInfo<'info>,
+}
+
+// Accounts.
+
+#[associated]
+#[derive(Default)]
+pub struct Officer {
+    // Priviledged account.
+    pub authority: Pubkey,
+    // Vault holding the officer's SRM tokens prior to distribution.
+    pub srm_vault: Pubkey,
+    // Escrow SRM vault holding tokens which are dropped onto stakers.
+    pub stake: Pubkey,
+    // SRM token account to send treasury earned tokens to.
+    pub treasury: Pubkey,
+    // Defines the fee distribution, i.e., what percent each fee category gets.
+    pub distribution: Distribution,
+    // Swap frontend for the dex.
+    pub swap_program: Pubkey,
+    // Dex program the officer is associated with.
+    pub dex_program: Pubkey,
+    // SRM stake pool address
+    pub registrar: Pubkey,
+    // MSRM stake pool address.
+    pub msrm_registrar: Pubkey,
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Default, Clone)]
+pub struct Distribution {
+    burn: u8,
+    stake: u8,
+    treasury: u8,
+}
+
+// CpiContext transformations.
+
+impl<'info> From<&SweepFees<'info>> for CpiContext<'_, '_, '_, 'info, dex::SweepFees<'info>> {
+    fn from(sweep: &SweepFees<'info>) -> Self {
+        let program = sweep.dex.dex_program.to_account_info();
+        let accounts = dex::SweepFees {
+            market: sweep.dex.market.to_account_info(),
+            pc_vault: sweep.dex.pc_vault.to_account_info(),
+            sweep_authority: sweep.dex.sweep_authority.to_account_info(),
+            sweep_receiver: sweep.sweep_vault.to_account_info(),
+            vault_signer: sweep.dex.vault_signer.to_account_info(),
+            token_program: sweep.dex.token_program.to_account_info(),
+        };
+        CpiContext::new(program, accounts)
+    }
+}
+
+impl<'info> From<&SwapToSrm<'info>> for CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> {
+    fn from(accs: &SwapToSrm<'info>) -> Self {
+        let program = accs.swap_program.to_account_info();
+        let accounts = swap::Swap {
+            market: swap::MarketAccounts {
+                market: accs.market.market.clone(),
+                open_orders: accs.market.open_orders.clone(),
+                request_queue: accs.market.request_queue.clone(),
+                event_queue: accs.market.event_queue.clone(),
+                bids: accs.market.bids.clone(),
+                asks: accs.market.asks.clone(),
+                order_payer_token_account: accs.market.order_payer_token_account.clone(),
+                coin_vault: accs.market.coin_vault.clone(),
+                pc_vault: accs.market.pc_vault.clone(),
+                vault_signer: accs.market.vault_signer.clone(),
+                coin_wallet: accs.srm_vault.clone(),
+            },
+            authority: accs.officer.to_account_info(),
+            pc_wallet: accs.from_vault.to_account_info(),
+            dex_program: accs.dex_program.to_account_info(),
+            token_program: accs.token_program.to_account_info(),
+            rent: accs.rent.to_account_info(),
+        };
+        CpiContext::new(program, accounts)
+    }
+}
+
+impl<'info> From<&SwapToUsdc<'info>> for CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> {
+    fn from(accs: &SwapToUsdc<'info>) -> Self {
+        let program = accs.swap_program.to_account_info();
+        let accounts = swap::Swap {
+            market: swap::MarketAccounts {
+                market: accs.market.market.clone(),
+                open_orders: accs.market.open_orders.clone(),
+                request_queue: accs.market.request_queue.clone(),
+                event_queue: accs.market.event_queue.clone(),
+                bids: accs.market.bids.clone(),
+                asks: accs.market.asks.clone(),
+                order_payer_token_account: accs.market.order_payer_token_account.clone(),
+                coin_vault: accs.market.coin_vault.clone(),
+                pc_vault: accs.market.pc_vault.clone(),
+                vault_signer: accs.market.vault_signer.clone(),
+                coin_wallet: accs.from_vault.to_account_info(),
+            },
+            authority: accs.officer.to_account_info(),
+            pc_wallet: accs.usdc_vault.clone(),
+            dex_program: accs.dex_program.to_account_info(),
+            token_program: accs.token_program.to_account_info(),
+            rent: accs.rent.to_account_info(),
+        };
+        CpiContext::new(program, accounts)
+    }
+}
+
+impl<'info> From<&Distribute<'info>> for CpiContext<'_, '_, '_, 'info, token::Burn<'info>> {
+    fn from(accs: &Distribute<'info>) -> Self {
+        let program = accs.token_program.to_account_info();
+        let accounts = token::Burn {
+            mint: accs.mint.to_account_info(),
+            to: accs.srm_vault.to_account_info(),
+            authority: accs.officer.to_account_info(),
+        };
+        CpiContext::new(program, accounts)
+    }
+}
+
+impl<'info> DropStakeReward<'info> {
+    fn into_srm_reward(&self) -> CpiContext<'_, '_, '_, 'info, registry::DropReward<'info>> {
+        let program = self.registry_program.clone();
+        let accounts = registry::DropReward {
+            registrar: ProgramAccount::try_from(&self.srm.registrar).unwrap(),
+            reward_event_q: ProgramAccount::try_from(&self.srm.reward_event_q).unwrap(),
+            pool_mint: self.srm.pool_mint.clone(),
+            vendor: ProgramAccount::try_from(&self.srm.vendor).unwrap(),
+            vendor_vault: CpiAccount::try_from(&self.srm.vendor_vault).unwrap(),
+            depositor: self.stake.to_account_info(),
+            depositor_authority: self.officer.to_account_info(),
+            token_program: self.token_program.clone(),
+            clock: self.clock.clone(),
+            rent: self.rent.clone(),
+        };
+        CpiContext::new(program, accounts)
+    }
+
+    fn into_msrm_reward(&self) -> CpiContext<'_, '_, '_, 'info, registry::DropReward<'info>> {
+        let program = self.registry_program.clone();
+        let accounts = registry::DropReward {
+            registrar: ProgramAccount::try_from(&self.msrm.registrar).unwrap(),
+            reward_event_q: ProgramAccount::try_from(&self.msrm.reward_event_q).unwrap(),
+            pool_mint: self.msrm.pool_mint.clone(),
+            vendor: ProgramAccount::try_from(&self.msrm.vendor).unwrap(),
+            vendor_vault: CpiAccount::try_from(&self.msrm.vendor_vault).unwrap(),
+            depositor: self.stake.to_account_info(),
+            depositor_authority: self.officer.to_account_info(),
+            token_program: self.token_program.clone(),
+            clock: self.clock.clone(),
+            rent: self.rent.clone(),
+        };
+        CpiContext::new(program, accounts)
+    }
+}
+
+impl<'info> Distribute<'info> {
+    fn into_burn(&self) -> CpiContext<'_, '_, '_, 'info, token::Burn<'info>> {
+        let program = self.token_program.clone();
+        let accounts = token::Burn {
+            mint: self.mint.clone(),
+            to: self.srm_vault.to_account_info(),
+            authority: self.officer.to_account_info(),
+        };
+        CpiContext::new(program, accounts)
+    }
+
+    fn into_stake_transfer(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
+        let program = self.token_program.clone();
+        let accounts = token::Transfer {
+            from: self.srm_vault.to_account_info(),
+            to: self.stake.to_account_info(),
+            authority: self.officer.to_account_info(),
+        };
+        CpiContext::new(program, accounts)
+    }
+
+    fn into_treasury_transfer(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
+        let program = self.token_program.clone();
+        let accounts = token::Transfer {
+            from: self.srm_vault.to_account_info(),
+            to: self.treasury.to_account_info(),
+            authority: self.officer.to_account_info(),
+        };
+        CpiContext::new(program, accounts)
+    }
+}
+
+// Events.
+
+#[event]
+pub struct DistributionDidChange {
+    distribution: Distribution,
+}
+
+#[event]
+pub struct OfficerDidCreate {
+    pubkey: Pubkey,
+}
+
+// Error.
+
+#[error]
+pub enum ErrorCode {
+    #[msg("Distribution does not add to 100")]
+    InvalidDistribution,
+    #[msg("u128 cannot be converted into u64")]
+    U128CannotConvert,
+    #[msg("Only one instruction is allowed for this transaction")]
+    TooManyInstructions,
+    #[msg("Not enough SRM has been accumulated to distribute")]
+    InsufficientDistributionAmount,
+    #[msg("Must drop more SRM onto the stake pool")]
+    InsufficientStakeReward,
+}
+
+// Access control.
+
+fn is_distribution_valid(d: &Distribution) -> Result<()> {
+    if d.burn + d.stake + d.treasury != 100 {
+        return Err(ErrorCode::InvalidDistribution.into());
+    }
+    Ok(())
+}
+
+fn is_distribution_ready(accounts: &Distribute) -> Result<()> {
+    if accounts.srm_vault.amount < 1_000_000 {
+        return Err(ErrorCode::InsufficientDistributionAmount.into());
+    }
+    Ok(())
+}
+
+// `ixs` must be the Instructions sysvar.
+fn is_not_trading(ixs: &AccountInfo) -> Result<()> {
+    let data = ixs.try_borrow_data()?;
+    match tx_instructions::load_instruction_at(1, &data) {
+        Ok(_) => Err(ErrorCode::TooManyInstructions.into()),
+        Err(_) => Ok(()),
+    }
+}
+
+fn is_stake_reward_ready(accounts: &DropStakeReward) -> Result<()> {
+    // Min drop is 15,0000 SRM.
+    let min_reward: u64 = 15_000_000_000;
+    if accounts.stake.amount < min_reward {
+        return Err(ErrorCode::InsufficientStakeReward.into());
+    }
+    Ok(())
+}
+
+// Redefintions.
+//
+// The following types are redefined so that they can be parsed into the IDL,
+// since Anchor doesn't yet support idl parsing across multiple crates.
+
+#[derive(AnchorSerialize, AnchorDeserialize)]
+pub struct ExchangeRate {
+    rate: u64,
+    from_decimals: u8,
+    quote_decimals: u8,
+    strict: bool,
+}
+
+impl From<ExchangeRate> for swap::ExchangeRate {
+    fn from(e: ExchangeRate) -> Self {
+        let ExchangeRate {
+            rate,
+            from_decimals,
+            quote_decimals,
+            strict,
+        } = e;
+        Self {
+            rate,
+            from_decimals,
+            quote_decimals,
+            strict,
+        }
+    }
+}

+ 19 - 0
examples/cfo/scripts/common.sh

@@ -0,0 +1,19 @@
+cleanup() {
+    pkill -P $$ || true
+    wait || true
+}
+
+trap_add() {
+    trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error"
+    for trap_add_name in "$@"; do
+        trap -- "$(
+            extract_trap_cmd() { printf '%s\n' "${3:-}"; }
+            eval "extract_trap_cmd $(trap -p "${trap_add_name}")"
+            printf '%s\n' "${trap_add_cmd}"
+        )" "${trap_add_name}" \
+            || fatal "unable to add to trap ${trap_add_name}"
+    done
+}
+
+declare -f -t trap_add
+trap_add 'cleanup' EXIT

+ 34 - 0
examples/cfo/scripts/fees.js

@@ -0,0 +1,34 @@
+#!/usr/bin/env node
+
+const process = require("process");
+const fs = require("fs");
+const anchor = require("@project-serum/anchor");
+const { Market, OpenOrders } = require("@project-serum/serum");
+const Account = anchor.web3.Account;
+const Program = anchor.Program;
+const provider = anchor.Provider.local();
+const secret = JSON.parse(fs.readFileSync("./scripts/market-maker.json"));
+const MARKET_MAKER = new Account(secret);
+const PublicKey = anchor.web3.PublicKey;
+
+const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
+
+async function main() {
+  const market = new PublicKey(process.argv[2]);
+  while (true) {
+    let marketClient = await Market.load(
+      provider.connection,
+      market,
+      { commitment: "recent" },
+      DEX_PID
+    );
+    console.log("Fees: ", marketClient._decoded.quoteFeesAccrued.toString());
+    await sleep(3000);
+  }
+}
+
+main();
+
+function sleep(ms) {
+  return new Promise((resolve) => setTimeout(resolve, ms));
+}

+ 20 - 0
examples/cfo/scripts/list-market.js

@@ -0,0 +1,20 @@
+#!/usr/bin/env node
+
+// Script to list a market, logging the address to stdout.
+
+const utils = require("../tests/utils");
+const fs = require("fs");
+const anchor = require("@project-serum/anchor");
+const provider = anchor.Provider.local();
+
+async function main() {
+  ORDERBOOK_ENV = await utils.initMarket({
+    provider,
+  });
+  const out = {
+    market: ORDERBOOK_ENV.marketA._decoded.ownAddress.toString(),
+  };
+  console.log(JSON.stringify(out));
+}
+
+main();

+ 58 - 0
examples/cfo/scripts/localnet.sh

@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+source scripts/common.sh
+
+DEX_PID="9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
+PAYER_FILEPATH="$HOME/.config/solana/id.json"
+CRANK="/home/armaniferrante/Documents/code/src/github.com/project-serum/serum-dex/target/debug/crank"
+VALIDATOR_OUT="./validator-stdout.txt"
+CRANK_LOGS="crank-logs.txt"
+CRANK_STDOUT="crank-stdout.txt"
+TRADE_BOT_STDOUT="trade-bot-stdout.txt"
+FEES_STDOUT="fees.txt"
+
+main () {
+		echo "Cleaning old output files..."
+		rm -rf test-ledger
+		rm -f $TRADE_BOT_STDOUT
+		rm -f $FEES_STDOUT
+		rm -f $VALIDATOR_OUT
+		rm -f $CRANK_LOGS && touch $CRANK_LOGS
+
+		echo "Starting local network..."
+		solana-test-validator \
+				--bpf-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin ./deps/serum-dex/dex/target/deploy/serum_dex.so \
+				--bpf-program 22Y43yTVxuUkoRKdm9thyRhQ3SdgQS7c7kB6UNCiaczD ./deps/swap/target/deploy/swap.so \
+				--bpf-program GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv ./deps/stake/target/deploy/registry.so \
+				--bpf-program 6ebQNeTPZ1j7k3TtkCCtEPRvG7GQsucQrZ7sSEDQi9Ks ./deps/stake/target/deploy/lockup.so \
+				--bpf-program 5CHQcwNhkFiFXXM8HakHi8cB7AKP3M3GPdEBDeRJBWQq ./target/deploy/cfo.so > $VALIDATOR_OUT &
+		sleep 2
+
+		echo "Listing market..."
+		market=$(./scripts/list-market.js | jq -r .market)
+		sleep 2
+		echo "Market listed $market"
+
+		echo "Running crank..."
+		$CRANK localnet consume-events \
+					-c $market \
+					-d $DEX_PID -e 5 \
+					--log-directory $CRANK_LOGS \
+					--market $market \
+					--num-workers 1 \
+					--payer $PAYER_FILEPATH \
+					--pc-wallet $market > $CRANK_STDOUT &
+		echo "Running trade bot..."
+		./scripts/trade-bot.js $market > $TRADE_BOT_STDOUT &
+
+		echo "Running fees listener..."
+		./scripts/fees.js $market > $FEES_STDOUT &
+
+		echo "Localnet running..."
+		echo "Ctl-c to exit."
+		wait
+}
+
+main

+ 1 - 0
examples/cfo/scripts/market-maker.json

@@ -0,0 +1 @@
+[13,174,53,150,78,228,12,98,170,254,212,211,125,193,2,241,97,137,49,209,189,199,27,215,220,65,57,203,215,93,105,203,217,32,5,194,157,118,162,47,102,126,235,65,99,80,56,231,217,114,25,225,239,140,169,92,150,146,211,218,183,139,9,104]

+ 16 - 0
examples/cfo/scripts/trade-bot.js

@@ -0,0 +1,16 @@
+#!/usr/bin/env node
+
+// Script to infinitely post orders that are immediately filled.
+
+const process = require("process");
+const anchor = require("@project-serum/anchor");
+const PublicKey = anchor.web3.PublicKey;
+const { runTradeBot } = require("../tests/utils");
+
+async function main() {
+  const market = new PublicKey(process.argv[2]);
+  const provider = anchor.Provider.local();
+  runTradeBot(market, provider);
+}
+
+main();

+ 202 - 0
examples/cfo/tests/cfo.js

@@ -0,0 +1,202 @@
+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
+  });
+});

+ 647 - 0
examples/cfo/tests/utils/index.js

@@ -0,0 +1,647 @@
+// Boilerplate utils to bootstrap an orderbook for testing on a localnet.
+// not super relevant to the point of the example, though may be useful to
+// include into your own workspace for testing.
+//
+// TODO: Modernize all these apis. This is all quite clunky.
+
+const Token = require("@solana/spl-token").Token;
+const TOKEN_PROGRAM_ID = require("@solana/spl-token").TOKEN_PROGRAM_ID;
+const TokenInstructions = require("@project-serum/serum").TokenInstructions;
+const { Market, OpenOrders } = require("@project-serum/serum");
+const DexInstructions = require("@project-serum/serum").DexInstructions;
+const web3 = require("@project-serum/anchor").web3;
+const Connection = web3.Connection;
+const anchor = require("@project-serum/anchor");
+const BN = anchor.BN;
+const serumCmn = require("@project-serum/common");
+const Account = web3.Account;
+const Transaction = web3.Transaction;
+const PublicKey = web3.PublicKey;
+const SystemProgram = web3.SystemProgram;
+const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
+const secret = JSON.parse(
+  require("fs").readFileSync("./scripts/market-maker.json")
+);
+const MARKET_MAKER = new Account(secret);
+
+async function initMarket({ provider }) {
+  // Setup mints with initial tokens owned by the provider.
+  const decimals = 6;
+  const [MINT_A, GOD_A] = await serumCmn.createMintAndVault(
+    provider,
+    new BN("1000000000000000000"),
+    undefined,
+    decimals
+  );
+  const [USDC, GOD_USDC] = await serumCmn.createMintAndVault(
+    provider,
+    new BN("1000000000000000000"),
+    undefined,
+    decimals
+  );
+
+  // Create a funded account to act as market maker.
+  const amount = new BN("10000000000000").muln(10 ** decimals);
+  const marketMaker = await fundAccount({
+    provider,
+    mints: [
+      { god: GOD_A, mint: MINT_A, amount, decimals },
+      { god: GOD_USDC, mint: USDC, amount, decimals },
+    ],
+  });
+
+  // Setup A/USDC with resting orders.
+  const asks = [
+    [6.041, 7.8],
+    [6.051, 72.3],
+    [6.055, 5.4],
+    [6.067, 15.7],
+    [6.077, 390.0],
+    [6.09, 24.0],
+    [6.11, 36.3],
+    [6.133, 300.0],
+    [6.167, 687.8],
+  ];
+  const bids = [
+    [6.004, 8.5],
+    [5.995, 12.9],
+    [5.987, 6.2],
+    [5.978, 15.3],
+    [5.965, 82.8],
+    [5.961, 25.4],
+  ];
+
+  [MARKET_A_USDC, vaultSigner] = await setupMarket({
+    baseMint: MINT_A,
+    quoteMint: USDC,
+    marketMaker: {
+      account: marketMaker.account,
+      baseToken: marketMaker.tokens[MINT_A.toString()],
+      quoteToken: marketMaker.tokens[USDC.toString()],
+    },
+    bids,
+    asks,
+    provider,
+  });
+
+  return {
+    marketA: MARKET_A_USDC,
+    vaultSigner,
+    marketMaker,
+    mintA: MINT_A,
+    usdc: USDC,
+    godA: GOD_A,
+    godUsdc: GOD_USDC,
+  };
+}
+
+// Creates everything needed for an orderbook to be running
+//
+// * Mints for both the base and quote currencies.
+// * Lists the market.
+// * Provides resting orders on the market.
+//
+// Returns a client that can be used to interact with the market
+// (and some other data, e.g., the mints and market maker account).
+async function initOrderbook({ provider, bids, asks }) {
+  if (!bids || !asks) {
+    asks = [
+      [6.041, 7.8],
+      [6.051, 72.3],
+      [6.055, 5.4],
+      [6.067, 15.7],
+      [6.077, 390.0],
+      [6.09, 24.0],
+      [6.11, 36.3],
+      [6.133, 300.0],
+      [6.167, 687.8],
+    ];
+    bids = [
+      [6.004, 8.5],
+      [5.995, 12.9],
+      [5.987, 6.2],
+      [5.978, 15.3],
+      [5.965, 82.8],
+      [5.961, 25.4],
+    ];
+  }
+  // Create base and quote currency mints.
+  const decimals = 6;
+  const [MINT_A, GOD_A] = await serumCmn.createMintAndVault(
+    provider,
+    new BN(1000000000000000),
+    undefined,
+    decimals
+  );
+  const [USDC, GOD_USDC] = await serumCmn.createMintAndVault(
+    provider,
+    new BN(1000000000000000),
+    undefined,
+    decimals
+  );
+
+  // Create a funded account to act as market maker.
+  const amount = 100000 * 10 ** decimals;
+  const marketMaker = await fundAccount({
+    provider,
+    mints: [
+      { god: GOD_A, mint: MINT_A, amount, decimals },
+      { god: GOD_USDC, mint: USDC, amount, decimals },
+    ],
+  });
+
+  [marketClient, vaultSigner] = await setupMarket({
+    baseMint: MINT_A,
+    quoteMint: USDC,
+    marketMaker: {
+      account: marketMaker.account,
+      baseToken: marketMaker.tokens[MINT_A.toString()],
+      quoteToken: marketMaker.tokens[USDC.toString()],
+    },
+    bids,
+    asks,
+    provider,
+  });
+
+  return {
+    marketClient,
+    baseMint: MINT_A,
+    quoteMint: USDC,
+    marketMaker,
+    vaultSigner,
+  };
+}
+
+async function fundAccount({ provider, mints }) {
+  const marketMaker = {
+    tokens: {},
+    account: MARKET_MAKER,
+  };
+
+  // Transfer lamports to market maker.
+  await provider.send(
+    (() => {
+      const tx = new Transaction();
+      tx.add(
+        SystemProgram.transfer({
+          fromPubkey: provider.wallet.publicKey,
+          toPubkey: MARKET_MAKER.publicKey,
+          lamports: 100000000000,
+        })
+      );
+      return tx;
+    })()
+  );
+
+  // Transfer SPL tokens to the market maker.
+  for (let k = 0; k < mints.length; k += 1) {
+    const { mint, god, amount, decimals } = mints[k];
+    let MINT_A = mint;
+    let GOD_A = god;
+    // Setup token accounts owned by the market maker.
+    const mintAClient = new Token(
+      provider.connection,
+      MINT_A,
+      TOKEN_PROGRAM_ID,
+      provider.wallet.payer // node only
+    );
+    const marketMakerTokenA = await mintAClient.createAccount(
+      MARKET_MAKER.publicKey
+    );
+
+    await provider.send(
+      (() => {
+        const tx = new Transaction();
+        tx.add(
+          Token.createTransferCheckedInstruction(
+            TOKEN_PROGRAM_ID,
+            GOD_A,
+            MINT_A,
+            marketMakerTokenA,
+            provider.wallet.publicKey,
+            [],
+            amount,
+            decimals
+          )
+        );
+        return tx;
+      })()
+    );
+
+    marketMaker.tokens[mint.toString()] = marketMakerTokenA;
+  }
+
+  return marketMaker;
+}
+
+async function setupMarket({
+  provider,
+  marketMaker,
+  baseMint,
+  quoteMint,
+  bids,
+  asks,
+}) {
+  const [marketAPublicKey, vaultOwner] = await listMarket({
+    connection: provider.connection,
+    wallet: provider.wallet,
+    baseMint: baseMint,
+    quoteMint: quoteMint,
+    baseLotSize: 100000,
+    quoteLotSize: 100,
+    dexProgramId: DEX_PID,
+    feeRateBps: 0,
+  });
+  const MARKET_A_USDC = await Market.load(
+    provider.connection,
+    marketAPublicKey,
+    { commitment: "recent" },
+    DEX_PID
+  );
+  for (let k = 0; k < asks.length; k += 1) {
+    let ask = asks[k];
+    const { transaction, signers } =
+      await MARKET_A_USDC.makePlaceOrderTransaction(provider.connection, {
+        owner: marketMaker.account,
+        payer: marketMaker.baseToken,
+        side: "sell",
+        price: ask[0],
+        size: ask[1],
+        orderType: "postOnly",
+        clientId: undefined,
+        openOrdersAddressKey: undefined,
+        openOrdersAccount: undefined,
+        feeDiscountPubkey: null,
+        selfTradeBehavior: "abortTransaction",
+      });
+    await provider.send(transaction, signers.concat(marketMaker.account));
+  }
+
+  for (let k = 0; k < bids.length; k += 1) {
+    let bid = bids[k];
+    const { transaction, signers } =
+      await MARKET_A_USDC.makePlaceOrderTransaction(provider.connection, {
+        owner: marketMaker.account,
+        payer: marketMaker.quoteToken,
+        side: "buy",
+        price: bid[0],
+        size: bid[1],
+        orderType: "postOnly",
+        clientId: undefined,
+        openOrdersAddressKey: undefined,
+        openOrdersAccount: undefined,
+        feeDiscountPubkey: null,
+        selfTradeBehavior: "abortTransaction",
+      });
+    await provider.send(transaction, signers.concat(marketMaker.account));
+  }
+
+  return [MARKET_A_USDC, vaultOwner];
+}
+
+async function listMarket({
+  connection,
+  wallet,
+  baseMint,
+  quoteMint,
+  baseLotSize,
+  quoteLotSize,
+  dexProgramId,
+  feeRateBps,
+}) {
+  const market = new Account();
+  const requestQueue = new Account();
+  const eventQueue = new Account();
+  const bids = new Account();
+  const asks = new Account();
+  const baseVault = new Account();
+  const quoteVault = new Account();
+  const quoteDustThreshold = new BN(100);
+
+  const [vaultOwner, vaultSignerNonce] = await getVaultOwnerAndNonce(
+    market.publicKey,
+    dexProgramId
+  );
+
+  const tx1 = new Transaction();
+  tx1.add(
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: baseVault.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(165),
+      space: 165,
+      programId: TOKEN_PROGRAM_ID,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: quoteVault.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(165),
+      space: 165,
+      programId: TOKEN_PROGRAM_ID,
+    }),
+    TokenInstructions.initializeAccount({
+      account: baseVault.publicKey,
+      mint: baseMint,
+      owner: vaultOwner,
+    }),
+    TokenInstructions.initializeAccount({
+      account: quoteVault.publicKey,
+      mint: quoteMint,
+      owner: vaultOwner,
+    })
+  );
+
+  const tx2 = new Transaction();
+  tx2.add(
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: market.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(
+        Market.getLayout(dexProgramId).span
+      ),
+      space: Market.getLayout(dexProgramId).span,
+      programId: dexProgramId,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: requestQueue.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(5120 + 12),
+      space: 5120 + 12,
+      programId: dexProgramId,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: eventQueue.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(262144 + 12),
+      space: 262144 + 12,
+      programId: dexProgramId,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: bids.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
+      space: 65536 + 12,
+      programId: dexProgramId,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: asks.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
+      space: 65536 + 12,
+      programId: dexProgramId,
+    }),
+    DexInstructions.initializeMarket({
+      market: market.publicKey,
+      requestQueue: requestQueue.publicKey,
+      eventQueue: eventQueue.publicKey,
+      bids: bids.publicKey,
+      asks: asks.publicKey,
+      baseVault: baseVault.publicKey,
+      quoteVault: quoteVault.publicKey,
+      baseMint,
+      quoteMint,
+      baseLotSize: new BN(baseLotSize),
+      quoteLotSize: new BN(quoteLotSize),
+      feeRateBps,
+      vaultSignerNonce,
+      quoteDustThreshold,
+      programId: dexProgramId,
+    })
+  );
+
+  const signedTransactions = await signTransactions({
+    transactionsAndSigners: [
+      { transaction: tx1, signers: [baseVault, quoteVault] },
+      {
+        transaction: tx2,
+        signers: [market, requestQueue, eventQueue, bids, asks],
+      },
+    ],
+    wallet,
+    connection,
+  });
+  for (let signedTransaction of signedTransactions) {
+    await sendAndConfirmRawTransaction(
+      connection,
+      signedTransaction.serialize()
+    );
+  }
+  const acc = await connection.getAccountInfo(market.publicKey);
+
+  return [market.publicKey, vaultOwner];
+}
+
+async function signTransactions({
+  transactionsAndSigners,
+  wallet,
+  connection,
+}) {
+  const blockhash = (await connection.getRecentBlockhash("max")).blockhash;
+  transactionsAndSigners.forEach(({ transaction, signers = [] }) => {
+    transaction.recentBlockhash = blockhash;
+    transaction.setSigners(
+      wallet.publicKey,
+      ...signers.map((s) => s.publicKey)
+    );
+    if (signers?.length > 0) {
+      transaction.partialSign(...signers);
+    }
+  });
+  return await wallet.signAllTransactions(
+    transactionsAndSigners.map(({ transaction }) => transaction)
+  );
+}
+
+async function sendAndConfirmRawTransaction(
+  connection,
+  raw,
+  commitment = "recent"
+) {
+  let tx = await connection.sendRawTransaction(raw, {
+    skipPreflight: true,
+  });
+  return await connection.confirmTransaction(tx, commitment);
+}
+
+async function getVaultOwnerAndNonce(marketPublicKey, dexProgramId = DEX_PID) {
+  const nonce = new BN(0);
+  while (nonce.toNumber() < 255) {
+    try {
+      const vaultOwner = await PublicKey.createProgramAddress(
+        [marketPublicKey.toBuffer(), nonce.toArrayLike(Buffer, "le", 8)],
+        dexProgramId
+      );
+      return [vaultOwner, nonce];
+    } catch (e) {
+      nonce.iaddn(1);
+    }
+  }
+  throw new Error("Unable to find nonce");
+}
+
+async function runTradeBot(market, provider, iterations = undefined) {
+  let marketClient = await Market.load(
+    provider.connection,
+    market,
+    { commitment: "recent" },
+    DEX_PID
+  );
+  const baseTokenUser1 = (
+    await marketClient.getTokenAccountsByOwnerForMint(
+      provider.connection,
+      MARKET_MAKER.publicKey,
+      marketClient.baseMintAddress
+    )
+  )[0].pubkey;
+  const quoteTokenUser1 = (
+    await marketClient.getTokenAccountsByOwnerForMint(
+      provider.connection,
+      MARKET_MAKER.publicKey,
+      marketClient.quoteMintAddress
+    )
+  )[0].pubkey;
+
+  const baseTokenUser2 = (
+    await marketClient.getTokenAccountsByOwnerForMint(
+      provider.connection,
+      provider.wallet.publicKey,
+      marketClient.baseMintAddress
+    )
+  )[0].pubkey;
+  const quoteTokenUser2 = (
+    await marketClient.getTokenAccountsByOwnerForMint(
+      provider.connection,
+      provider.wallet.publicKey,
+      marketClient.quoteMintAddress
+    )
+  )[0].pubkey;
+
+  const makerOpenOrdersUser1 = (
+    await OpenOrders.findForMarketAndOwner(
+      provider.connection,
+      market,
+      MARKET_MAKER.publicKey,
+      DEX_PID
+    )
+  )[0];
+  makerOpenOrdersUser2 = (
+    await OpenOrders.findForMarketAndOwner(
+      provider.connection,
+      market,
+      provider.wallet.publicKey,
+      DEX_PID
+    )
+  )[0];
+
+  const price = 6.041;
+  const size = 700000.8;
+
+  let maker = MARKET_MAKER;
+  let taker = provider.wallet.payer;
+  let baseToken = baseTokenUser1;
+  let quoteToken = quoteTokenUser2;
+  let makerOpenOrders = makerOpenOrdersUser1;
+
+  let k = 1;
+
+  while (true) {
+    if (iterations && k > iterations) {
+      break;
+    }
+    const clientId = new anchor.BN(k);
+    if (k % 5 === 0) {
+      if (maker.publicKey.equals(MARKET_MAKER.publicKey)) {
+        maker = provider.wallet.payer;
+        makerOpenOrders = makerOpenOrdersUser2;
+        taker = MARKET_MAKER;
+        baseToken = baseTokenUser2;
+        quoteToken = quoteTokenUser1;
+      } else {
+        maker = MARKET_MAKER;
+        makerOpenOrders = makerOpenOrdersUser1;
+        taker = provider.wallet.payer;
+        baseToken = baseTokenUser1;
+        quoteToken = quoteTokenUser2;
+      }
+    }
+
+    // Post ask.
+    const { transaction: tx_ask, signers: sigs_ask } =
+      await marketClient.makePlaceOrderTransaction(provider.connection, {
+        owner: maker,
+        payer: baseToken,
+        side: "sell",
+        price,
+        size,
+        orderType: "postOnly",
+        clientId,
+        openOrdersAddressKey: undefined,
+        openOrdersAccount: undefined,
+        feeDiscountPubkey: null,
+        selfTradeBehavior: "abortTransaction",
+      });
+    let txSig = await provider.send(tx_ask, sigs_ask.concat(maker));
+    console.log("Ask", txSig);
+
+    // Take.
+    const { transaction: tx_bid, signers: sigs_bid } =
+      await marketClient.makePlaceOrderTransaction(provider.connection, {
+        owner: taker,
+        payer: quoteToken,
+        side: "buy",
+        price,
+        size,
+        orderType: "ioc",
+        clientId: undefined,
+        openOrdersAddressKey: undefined,
+        openOrdersAccount: undefined,
+        feeDiscountPubkey: null,
+        selfTradeBehavior: "abortTransaction",
+      });
+    txSig = await provider.send(tx_bid, sigs_bid.concat(taker));
+    console.log("Bid", txSig);
+
+    await sleep(1000);
+
+    // Cancel anything remaining.
+    try {
+      txSig = await marketClient.cancelOrderByClientId(
+        provider.connection,
+        maker,
+        makerOpenOrders.address,
+        clientId
+      );
+      console.log("Cancelled the rest", txSig);
+      await sleep(1000);
+    } catch (e) {
+      console.log("Unable to cancel order", e);
+    }
+    k += 1;
+
+    // If the open orders account wasn't previously initialized, it is now.
+    if (makerOpenOrdersUser2 === undefined) {
+      makerOpenOrdersUser2 = (
+        await OpenOrders.findForMarketAndOwner(
+          provider.connection,
+          market,
+          provider.wallet.publicKey,
+          DEX_PID
+        )
+      )[0];
+    }
+  }
+}
+
+function sleep(ms) {
+  return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+module.exports = {
+  fundAccount,
+  initMarket,
+  initOrderbook,
+  setupMarket,
+  DEX_PID,
+  getVaultOwnerAndNonce,
+  runTradeBot,
+};

+ 184 - 0
examples/cfo/tests/utils/stake.js

@@ -0,0 +1,184 @@
+const anchor = require("@project-serum/anchor");
+const serumCmn = require("@project-serum/common");
+const TokenInstructions = require("@project-serum/serum").TokenInstructions;
+const utils = require("../../deps/stake/tests/utils");
+
+const lockup = anchor.workspace.Lockup;
+const registry = anchor.workspace.Registry;
+const provider = anchor.Provider.env();
+
+let lockupAddress = null;
+let mint = null;
+let god = null;
+
+let registrarAccount = null;
+let registrarSigner = null;
+let nonce = null;
+let poolMint = null;
+
+const registrar = new anchor.web3.Account();
+const rewardQ = new anchor.web3.Account();
+const withdrawalTimelock = new anchor.BN(4);
+const stakeRate = new anchor.BN(2);
+const rewardQLen = 170;
+let member = null;
+
+let memberAccount = null;
+let memberSigner = null;
+let balances = null;
+let balancesLocked = null;
+
+const WHITELIST_SIZE = 10;
+
+async function setupStakePool(mint, god) {
+  // Registry genesis.
+  const [_registrarSigner, _nonce] =
+    await anchor.web3.PublicKey.findProgramAddress(
+      [registrar.publicKey.toBuffer()],
+      registry.programId
+    );
+  registrarSigner = _registrarSigner;
+  nonce = _nonce;
+  poolMint = await serumCmn.createMint(provider, registrarSigner);
+
+  try {
+    // Init registry.
+    await registry.state.rpc.new({
+      accounts: { lockupProgram: lockup.programId },
+    });
+
+    // Init lockup.
+    await lockup.state.rpc.new({
+      accounts: {
+        authority: provider.wallet.publicKey,
+      },
+    });
+  } catch (err) {
+    // Skip errors for convenience when developing locally,
+    // since the state constructors can only be called once.
+  }
+
+  // Initialize stake pool.
+  await registry.rpc.initialize(
+    mint,
+    provider.wallet.publicKey,
+    nonce,
+    withdrawalTimelock,
+    stakeRate,
+    rewardQLen,
+    {
+      accounts: {
+        registrar: registrar.publicKey,
+        poolMint,
+        rewardEventQ: rewardQ.publicKey,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+      },
+      signers: [registrar, rewardQ],
+      instructions: [
+        await registry.account.registrar.createInstruction(registrar),
+        await registry.account.rewardQueue.createInstruction(rewardQ, 8250),
+      ],
+    }
+  );
+  registrarAccount = await registry.account.registrar.fetch(
+    registrar.publicKey
+  );
+  console.log("Registrar", registrar.publicKey.toString());
+  console.log("Wallet", registry.provider.wallet.publicKey.toString());
+  // Create account for staker.
+  const seed = anchor.utils.sha256
+    .hash(`${registrar.publicKey.toString()}:Member`)
+    .slice(0, 32);
+  member = await anchor.web3.PublicKey.createWithSeed(
+    registry.provider.wallet.publicKey,
+    seed,
+    registry.programId
+  );
+  const [_memberSigner, nonce2] =
+    await anchor.web3.PublicKey.findProgramAddress(
+      [registrar.publicKey.toBuffer(), member.toBuffer()],
+      registry.programId
+    );
+  memberSigner = _memberSigner;
+  const [mainTx, _balances] = await utils.createBalanceSandbox(
+    provider,
+    registrarAccount,
+    memberSigner
+  );
+  const [lockedTx, _balancesLocked] = await utils.createBalanceSandbox(
+    provider,
+    registrarAccount,
+    memberSigner
+  );
+  balances = _balances;
+  balancesLocked = _balancesLocked;
+  const tx = registry.transaction.createMember(nonce2, {
+    accounts: {
+      registrar: registrar.publicKey,
+      member: member,
+      beneficiary: provider.wallet.publicKey,
+      memberSigner,
+      balances,
+      balancesLocked,
+      tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+      rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+    },
+    instructions: [
+      anchor.web3.SystemProgram.createAccountWithSeed({
+        fromPubkey: registry.provider.wallet.publicKey,
+        newAccountPubkey: member,
+        basePubkey: registry.provider.wallet.publicKey,
+        seed,
+        lamports:
+          await registry.provider.connection.getMinimumBalanceForRentExemption(
+            registry.account.member.size
+          ),
+        space: registry.account.member.size,
+        programId: registry.programId,
+      }),
+    ],
+  });
+  const signers = [provider.wallet.payer];
+  const allTxs = [mainTx, lockedTx, { tx, signers }];
+  await provider.sendAll(allTxs);
+  memberAccount = await registry.account.member.fetch(member);
+
+  // Deposit into stake program.
+  const depositAmount = new anchor.BN(120);
+  await registry.rpc.deposit(depositAmount, {
+    accounts: {
+      depositor: god,
+      depositorAuthority: provider.wallet.publicKey,
+      tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+      vault: memberAccount.balances.vault,
+      beneficiary: provider.wallet.publicKey,
+      member: member,
+    },
+  });
+
+  // Stake.
+  const stakeAmount = new anchor.BN(10);
+  await registry.rpc.stake(stakeAmount, false, {
+    accounts: {
+      // Stake instance.
+      registrar: registrar.publicKey,
+      rewardEventQ: rewardQ.publicKey,
+      poolMint,
+      // Member.
+      member: member,
+      beneficiary: provider.wallet.publicKey,
+      balances,
+      balancesLocked,
+      // Program signers.
+      memberSigner,
+      registrarSigner,
+      // Misc.
+      clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
+      tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+    },
+  });
+}
+
+module.exports = {
+  setupStakePool,
+};

+ 5 - 5
examples/errors/programs/errors/src/lib.rs

@@ -23,7 +23,7 @@ mod errors {
         Ok(())
     }
 
-    pub fn belongs_to_error(_ctx: Context<BelongsToError>) -> Result<()> {
+    pub fn has_one_error(_ctx: Context<HasOneError>) -> Result<()> {
         Ok(())
     }
 
@@ -42,9 +42,9 @@ pub struct MutError<'info> {
 }
 
 #[derive(Accounts)]
-pub struct BelongsToError<'info> {
-    #[account(init, belongs_to = owner)]
-    my_account: ProgramAccount<'info, BelongsToAccount>,
+pub struct HasOneError<'info> {
+    #[account(init, has_one = owner)]
+    my_account: ProgramAccount<'info, HasOneAccount>,
     owner: AccountInfo<'info>,
     rent: Sysvar<'info, Rent>,
 }
@@ -56,7 +56,7 @@ pub struct SignerError<'info> {
 }
 
 #[account]
-pub struct BelongsToAccount {
+pub struct HasOneAccount {
     owner: Pubkey,
 }
 

+ 4 - 4
examples/errors/tests/errors.js

@@ -61,23 +61,23 @@ describe("errors", () => {
     }
   });
 
-  it("Emits a belongs to error", async () => {
+  it("Emits a has one error", async () => {
     try {
       const account = new Account();
-      const tx = await program.rpc.belongsToError({
+      const tx = await program.rpc.hasOneError({
         accounts: {
           myAccount: account.publicKey,
           owner: anchor.web3.SYSVAR_RENT_PUBKEY,
           rent: anchor.web3.SYSVAR_RENT_PUBKEY,
         },
         instructions: [
-          await program.account.belongsToAccount.createInstruction(account),
+          await program.account.hasOneAccount.createInstruction(account),
         ],
         signers: [account],
       });
       assert.ok(false);
     } catch (err) {
-      const errMsg = "A belongs_to constraint was violated";
+      const errMsg = "A has_one constraint was violated";
       assert.equal(err.toString(), errMsg);
       assert.equal(err.msg, errMsg);
       assert.equal(err.code, 141);

+ 3 - 0
examples/escrow/Anchor.toml

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

+ 4 - 0
examples/escrow/Cargo.toml

@@ -0,0 +1,4 @@
+[workspace]
+members = [
+    "programs/*"
+]

+ 11 - 0
examples/escrow/package.json

@@ -0,0 +1,11 @@
+{
+  "dependencies": {
+    "@project-serum/anchor": "^0.9.0",
+    "@project-serum/serum": "0.13.38",
+    "@solana/web3.js": "^1.18.0",
+    "@solana/spl-token": "^0.1.6"
+  },
+  "devDependencies": {
+    "ts-mocha": "^8.0.0"
+  }
+}

+ 20 - 0
examples/escrow/programs/escrow/Cargo.toml

@@ -0,0 +1,20 @@
+[package]
+name = "escrow"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "escrow"
+
+[features]
+no-entrypoint = []
+no-idl = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }
+anchor-spl = { path = "../../../../spl" }
+spl-token = { version = "3.1.1", features = ["no-entrypoint"] }

+ 2 - 0
examples/escrow/programs/escrow/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 228 - 0
examples/escrow/programs/escrow/src/lib.rs

@@ -0,0 +1,228 @@
+//! An example of an escrow program, inspired by PaulX tutorial seen here
+//! https://paulx.dev/blog/2021/01/14/programming-on-solana-an-introduction/
+//! This example has some changes to implementation, but more or less should be the same overall
+//! Also gives examples on how to use some newer anchor features and CPI
+//!
+//! User (Initializer) constructs an escrow deal:
+//! - SPL token (X) they will offer and amount
+//! - SPL token (Y) count they want in return and amount
+//! - Program will take ownership of initializer's token X account
+//!
+//! Once this escrow is initialised, either:
+//! 1. User (Taker) can call the exchange function to exchange their Y for X
+//! - This will close the escrow account and no longer be usable
+//! OR
+//! 2. If no one has exchanged, the initializer can close the escrow account
+//! - Initializer will get back ownership of their token X account
+
+use anchor_lang::prelude::*;
+use anchor_spl::token::{self, SetAuthority, TokenAccount, Transfer};
+use spl_token::instruction::AuthorityType;
+
+#[program]
+pub mod escrow {
+    use super::*;
+
+    pub fn initialize_escrow(
+        ctx: Context<InitializeEscrow>,
+        initializer_amount: u64,
+        taker_amount: u64,
+    ) -> ProgramResult {
+        ctx.accounts.escrow_account.initializer_key = *ctx.accounts.initializer.key;
+        ctx.accounts
+            .escrow_account
+            .initializer_deposit_token_account = *ctx
+            .accounts
+            .initializer_deposit_token_account
+            .to_account_info()
+            .key;
+        ctx.accounts
+            .escrow_account
+            .initializer_receive_token_account = *ctx
+            .accounts
+            .initializer_receive_token_account
+            .to_account_info()
+            .key;
+        ctx.accounts.escrow_account.initializer_amount = initializer_amount;
+        ctx.accounts.escrow_account.taker_amount = taker_amount;
+
+        let (pda, _bump_seed) = Pubkey::find_program_address(&[b"escrow"], ctx.program_id);
+        token::set_authority(ctx.accounts.into(), AuthorityType::AccountOwner, Some(pda))?;
+        Ok(())
+    }
+
+    pub fn cancel_escrow(ctx: Context<CancelEscrow>) -> ProgramResult {
+        let (_pda, bump_seed) = Pubkey::find_program_address(&[b"escrow"], ctx.program_id);
+        let seeds = &[&b"escrow"[..], &[bump_seed]];
+
+        token::set_authority(
+            ctx.accounts
+                .into_set_authority_context()
+                .with_signer(&[&seeds[..]]),
+            AuthorityType::AccountOwner,
+            Some(ctx.accounts.escrow_account.initializer_key),
+        )?;
+
+        Ok(())
+    }
+
+    pub fn exchange(ctx: Context<Exchange>) -> ProgramResult {
+        // Transferring from initializer to taker
+        let (_pda, bump_seed) = Pubkey::find_program_address(&[b"escrow"], ctx.program_id);
+        let seeds = &[&b"escrow"[..], &[bump_seed]];
+
+        token::transfer(
+            ctx.accounts
+                .into_transfer_to_taker_context()
+                .with_signer(&[&seeds[..]]),
+            ctx.accounts.escrow_account.initializer_amount,
+        )?;
+
+        token::transfer(
+            ctx.accounts.into_transfer_to_initializer_context(),
+            ctx.accounts.escrow_account.taker_amount,
+        )?;
+
+        token::set_authority(
+            ctx.accounts
+                .into_set_authority_context()
+                .with_signer(&[&seeds[..]]),
+            AuthorityType::AccountOwner,
+            Some(ctx.accounts.escrow_account.initializer_key),
+        )?;
+
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+#[instruction(initializer_amount: u64)]
+pub struct InitializeEscrow<'info> {
+    #[account(signer)]
+    pub initializer: AccountInfo<'info>,
+    #[account(
+        mut,
+        constraint = initializer_deposit_token_account.amount >= initializer_amount
+    )]
+    pub initializer_deposit_token_account: CpiAccount<'info, TokenAccount>,
+    pub initializer_receive_token_account: CpiAccount<'info, TokenAccount>,
+    #[account(init)]
+    pub escrow_account: ProgramAccount<'info, EscrowAccount>,
+    pub token_program: AccountInfo<'info>,
+    pub rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct Exchange<'info> {
+    #[account(signer)]
+    pub taker: AccountInfo<'info>,
+    #[account(mut)]
+    pub taker_deposit_token_account: CpiAccount<'info, TokenAccount>,
+    #[account(mut)]
+    pub taker_receive_token_account: CpiAccount<'info, TokenAccount>,
+    #[account(mut)]
+    pub pda_deposit_token_account: CpiAccount<'info, TokenAccount>,
+    #[account(mut)]
+    pub initializer_receive_token_account: CpiAccount<'info, TokenAccount>,
+    #[account(mut)]
+    pub initializer_main_account: AccountInfo<'info>,
+    #[account(
+        mut,
+        constraint = escrow_account.taker_amount <= taker_deposit_token_account.amount,
+        constraint = escrow_account.initializer_deposit_token_account == *pda_deposit_token_account.to_account_info().key,
+        constraint = escrow_account.initializer_receive_token_account == *initializer_receive_token_account.to_account_info().key,
+        constraint = escrow_account.initializer_key == *initializer_main_account.key,
+        close = initializer_main_account
+    )]
+    pub escrow_account: ProgramAccount<'info, EscrowAccount>,
+    pub pda_account: AccountInfo<'info>,
+    pub token_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct CancelEscrow<'info> {
+    pub initializer: AccountInfo<'info>,
+    #[account(mut)]
+    pub pda_deposit_token_account: CpiAccount<'info, TokenAccount>,
+    pub pda_account: AccountInfo<'info>,
+    #[account(
+        mut,
+        constraint = escrow_account.initializer_key == *initializer.key,
+        constraint = escrow_account.initializer_deposit_token_account == *pda_deposit_token_account.to_account_info().key,
+        close = initializer
+    )]
+    pub escrow_account: ProgramAccount<'info, EscrowAccount>,
+    pub token_program: AccountInfo<'info>,
+}
+
+#[account]
+pub struct EscrowAccount {
+    pub initializer_key: Pubkey,
+    pub initializer_deposit_token_account: Pubkey,
+    pub initializer_receive_token_account: Pubkey,
+    pub initializer_amount: u64,
+    pub taker_amount: u64,
+}
+
+impl<'info> From<&mut InitializeEscrow<'info>>
+    for CpiContext<'_, '_, '_, 'info, SetAuthority<'info>>
+{
+    fn from(accounts: &mut InitializeEscrow<'info>) -> Self {
+        let cpi_accounts = SetAuthority {
+            account_or_mint: accounts
+                .initializer_deposit_token_account
+                .to_account_info()
+                .clone(),
+            current_authority: accounts.initializer.clone(),
+        };
+        let cpi_program = accounts.token_program.clone();
+        CpiContext::new(cpi_program, cpi_accounts)
+    }
+}
+
+impl<'info> CancelEscrow<'info> {
+    fn into_set_authority_context(&self) -> CpiContext<'_, '_, '_, 'info, SetAuthority<'info>> {
+        let cpi_accounts = SetAuthority {
+            account_or_mint: self.pda_deposit_token_account.to_account_info().clone(),
+            current_authority: self.pda_account.clone(),
+        };
+        CpiContext::new(self.token_program.clone(), cpi_accounts)
+    }
+}
+
+impl<'info> Exchange<'info> {
+    fn into_set_authority_context(&self) -> CpiContext<'_, '_, '_, 'info, SetAuthority<'info>> {
+        let cpi_accounts = SetAuthority {
+            account_or_mint: self.pda_deposit_token_account.to_account_info().clone(),
+            current_authority: self.pda_account.clone(),
+        };
+        CpiContext::new(self.token_program.clone(), cpi_accounts)
+    }
+}
+
+impl<'info> Exchange<'info> {
+    fn into_transfer_to_taker_context(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> {
+        let cpi_accounts = Transfer {
+            from: self.pda_deposit_token_account.to_account_info().clone(),
+            to: self.taker_receive_token_account.to_account_info().clone(),
+            authority: self.pda_account.clone(),
+        };
+        CpiContext::new(self.token_program.clone(), cpi_accounts)
+    }
+}
+
+impl<'info> Exchange<'info> {
+    fn into_transfer_to_initializer_context(
+        &self,
+    ) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> {
+        let cpi_accounts = Transfer {
+            from: self.taker_deposit_token_account.to_account_info().clone(),
+            to: self
+                .initializer_receive_token_account
+                .to_account_info()
+                .clone(),
+            authority: self.taker.clone(),
+        };
+        CpiContext::new(self.token_program.clone(), cpi_accounts)
+    }
+}

+ 207 - 0
examples/escrow/tests/escrow.js

@@ -0,0 +1,207 @@
+const anchor = require("@project-serum/anchor");
+const { TOKEN_PROGRAM_ID, Token } = require("@solana/spl-token");
+const assert = require("assert");
+
+describe("escrow", () => {
+  const provider = anchor.Provider.env();
+  anchor.setProvider(provider);
+
+  const program = anchor.workspace.Escrow;
+
+  let mintA = null;
+  let mintB = null;
+  let initializerTokenAccountA = null;
+  let initializerTokenAccountB = null;
+  let takerTokenAccountA = null;
+  let takerTokenAccountB = null;
+  let pda = null;
+
+  const takerAmount = 1000;
+  const initializerAmount = 500;
+
+  const escrowAccount = anchor.web3.Keypair.generate();
+  const payer = anchor.web3.Keypair.generate();
+  const mintAuthority = anchor.web3.Keypair.generate();
+
+  it("Initialise escrow state", async () => {
+    // Airdropping tokens to a payer.
+    await provider.connection.confirmTransaction(
+      await provider.connection.requestAirdrop(payer.publicKey, 10000000000),
+      "confirmed"
+    );
+
+    mintA = await Token.createMint(
+      provider.connection,
+      payer,
+      mintAuthority.publicKey,
+      null,
+      0,
+      TOKEN_PROGRAM_ID
+    );
+
+    mintB = await Token.createMint(
+      provider.connection,
+      payer,
+      mintAuthority.publicKey,
+      null,
+      0,
+      TOKEN_PROGRAM_ID
+    );
+
+    initializerTokenAccountA = await mintA.createAccount(provider.wallet.publicKey);
+    takerTokenAccountA = await mintA.createAccount(provider.wallet.publicKey);
+
+    initializerTokenAccountB = await mintB.createAccount(provider.wallet.publicKey);
+    takerTokenAccountB = await mintB.createAccount(provider.wallet.publicKey);
+
+    await mintA.mintTo(
+      initializerTokenAccountA,
+      mintAuthority.publicKey,
+      [mintAuthority],
+      initializerAmount
+    );
+
+    await mintB.mintTo(
+      takerTokenAccountB,
+      mintAuthority.publicKey,
+      [mintAuthority],
+      takerAmount
+    );
+
+    let _initializerTokenAccountA = await mintA.getAccountInfo(initializerTokenAccountA);
+    let _takerTokenAccountB = await mintB.getAccountInfo(takerTokenAccountB);
+
+    assert.ok(_initializerTokenAccountA.amount.toNumber() == initializerAmount);
+    assert.ok(_takerTokenAccountB.amount.toNumber() == takerAmount);
+  });
+
+  it("Initialize escrow", async () => {
+    await program.rpc.initializeEscrow(
+      new anchor.BN(initializerAmount),
+      new anchor.BN(takerAmount),
+      {
+        accounts: {
+          initializer: provider.wallet.publicKey,
+          initializerDepositTokenAccount: initializerTokenAccountA,
+          initializerReceiveTokenAccount: initializerTokenAccountB,
+          escrowAccount: escrowAccount.publicKey,
+          tokenProgram: TOKEN_PROGRAM_ID,
+          rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+        },
+        instructions: [
+          await program.account.escrowAccount.createInstruction(escrowAccount),
+        ],
+        signers: [escrowAccount],
+      }
+    );
+
+    // Get the PDA that is assigned authority to token account.
+    const [_pda, _nonce] = await anchor.web3.PublicKey.findProgramAddress(
+      [Buffer.from(anchor.utils.bytes.utf8.encode("escrow"))],
+      program.programId
+    );
+
+    pda = _pda;
+
+    let _initializerTokenAccountA = await mintA.getAccountInfo(initializerTokenAccountA);
+
+    let _escrowAccount = await program.account.escrowAccount.fetch(
+      escrowAccount.publicKey
+    );
+
+    // Check that the new owner is the PDA.
+    assert.ok(_initializerTokenAccountA.owner.equals(pda));
+
+    // Check that the values in the escrow account match what we expect.
+    assert.ok(_escrowAccount.initializerKey.equals(provider.wallet.publicKey));
+    assert.ok(_escrowAccount.initializerAmount.toNumber() == initializerAmount);
+    assert.ok(_escrowAccount.takerAmount.toNumber() == takerAmount);
+    assert.ok(
+      _escrowAccount.initializerDepositTokenAccount.equals(initializerTokenAccountA)
+    );
+    assert.ok(
+      _escrowAccount.initializerReceiveTokenAccount.equals(initializerTokenAccountB)
+    );
+  });
+
+  it("Exchange escrow", async () => {
+    await program.rpc.exchange({
+      accounts: {
+        taker: provider.wallet.publicKey,
+        takerDepositTokenAccount: takerTokenAccountB,
+        takerReceiveTokenAccount: takerTokenAccountA,
+        pdaDepositTokenAccount: initializerTokenAccountA,
+        initializerReceiveTokenAccount: initializerTokenAccountB,
+        initializerMainAccount: provider.wallet.publicKey,
+        escrowAccount: escrowAccount.publicKey,
+        pdaAccount: pda,
+        tokenProgram: TOKEN_PROGRAM_ID,
+      },
+    });
+
+    let _takerTokenAccountA = await mintA.getAccountInfo(takerTokenAccountA);
+    let _takerTokenAccountB = await mintB.getAccountInfo(takerTokenAccountB);
+    let _initializerTokenAccountA = await mintA.getAccountInfo(initializerTokenAccountA);
+    let _initializerTokenAccountB = await mintB.getAccountInfo(initializerTokenAccountB);
+
+    // Check that the initializer gets back ownership of their token account.
+    assert.ok(_takerTokenAccountA.owner.equals(provider.wallet.publicKey));
+
+    assert.ok(_takerTokenAccountA.amount.toNumber() == initializerAmount);
+    assert.ok(_initializerTokenAccountA.amount.toNumber() == 0);
+    assert.ok(_initializerTokenAccountB.amount.toNumber() == takerAmount);
+    assert.ok(_takerTokenAccountB.amount.toNumber() == 0);
+  });
+
+  let newEscrow = anchor.web3.Keypair.generate();
+
+  it("Initialize escrow and cancel escrow", async () => {
+    // Put back tokens into initializer token A account.
+    await mintA.mintTo(
+      initializerTokenAccountA,
+      mintAuthority.publicKey,
+      [mintAuthority],
+      initializerAmount
+    );
+
+    await program.rpc.initializeEscrow(
+      new anchor.BN(initializerAmount),
+      new anchor.BN(takerAmount),
+      {
+        accounts: {
+          initializer: provider.wallet.publicKey,
+          initializerDepositTokenAccount: initializerTokenAccountA,
+          initializerReceiveTokenAccount: initializerTokenAccountB,
+          escrowAccount: newEscrow.publicKey,
+          tokenProgram: TOKEN_PROGRAM_ID,
+          rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+        },
+        instructions: [await program.account.escrowAccount.createInstruction(newEscrow)],
+        signers: [newEscrow],
+      }
+    );
+
+    let _initializerTokenAccountA = await mintA.getAccountInfo(initializerTokenAccountA);
+
+    // Check that the new owner is the PDA.
+    assert.ok(_initializerTokenAccountA.owner.equals(pda));
+
+    // Cancel the escrow.
+    await program.rpc.cancelEscrow({
+      accounts: {
+        initializer: provider.wallet.publicKey,
+        pdaDepositTokenAccount: initializerTokenAccountA,
+        pdaAccount: pda,
+        escrowAccount: newEscrow.publicKey,
+        tokenProgram: TOKEN_PROGRAM_ID,
+      },
+    });
+
+    // Check the final owner should be the provider public key.
+    _initializerTokenAccountA = await mintA.getAccountInfo(initializerTokenAccountA);
+    assert.ok(_initializerTokenAccountA.owner.equals(provider.wallet.publicKey));
+
+    // Check all the funds are still there.
+    assert.ok(_initializerTokenAccountA.amount.toNumber() == initializerAmount);
+  });
+});

+ 25 - 25
examples/ido-pool/programs/ido-pool/src/lib.rs

@@ -195,21 +195,21 @@ pub struct InitializePool<'info> {
     pub pool_account: ProgramAccount<'info, PoolAccount>,
     pub pool_signer: AccountInfo<'info>,
     #[account(
-        "redeemable_mint.mint_authority == COption::Some(*pool_signer.key)",
-        "redeemable_mint.supply == 0"
+        constraint = redeemable_mint.mint_authority == COption::Some(*pool_signer.key),
+        constraint = redeemable_mint.supply == 0
     )]
     pub redeemable_mint: CpiAccount<'info, Mint>,
-    #[account("usdc_mint.decimals == redeemable_mint.decimals")]
+    #[account(constraint = usdc_mint.decimals == redeemable_mint.decimals)]
     pub usdc_mint: CpiAccount<'info, Mint>,
-    #[account(mut, "pool_watermelon.owner == *pool_signer.key")]
+    #[account(mut, constraint = pool_watermelon.owner == *pool_signer.key)]
     pub pool_watermelon: CpiAccount<'info, TokenAccount>,
-    #[account("pool_usdc.owner == *pool_signer.key")]
+    #[account(constraint = pool_usdc.owner == *pool_signer.key)]
     pub pool_usdc: CpiAccount<'info, TokenAccount>,
     #[account(signer)]
     pub distribution_authority: AccountInfo<'info>,
-    #[account(mut, "creator_watermelon.owner == *distribution_authority.key")]
+    #[account(mut, constraint = creator_watermelon.owner == *distribution_authority.key)]
     pub creator_watermelon: CpiAccount<'info, TokenAccount>,
-    #[account("token_program.key == &token::ID")]
+    #[account(constraint = token_program.key == &token::ID)]
     pub token_program: AccountInfo<'info>,
     pub rent: Sysvar<'info, Rent>,
     pub clock: Sysvar<'info, Clock>,
@@ -237,18 +237,18 @@ pub struct ExchangeUsdcForRedeemable<'info> {
     pool_signer: AccountInfo<'info>,
     #[account(
         mut,
-        "redeemable_mint.mint_authority == COption::Some(*pool_signer.key)"
+        constraint = redeemable_mint.mint_authority == COption::Some(*pool_signer.key)
     )]
     pub redeemable_mint: CpiAccount<'info, Mint>,
-    #[account(mut, "pool_usdc.owner == *pool_signer.key")]
+    #[account(mut, constraint = pool_usdc.owner == *pool_signer.key)]
     pub pool_usdc: CpiAccount<'info, TokenAccount>,
     #[account(signer)]
     pub user_authority: AccountInfo<'info>,
-    #[account(mut, "user_usdc.owner == *user_authority.key")]
+    #[account(mut, constraint = user_usdc.owner == *user_authority.key)]
     pub user_usdc: CpiAccount<'info, TokenAccount>,
-    #[account(mut, "user_redeemable.owner == *user_authority.key")]
+    #[account(mut, constraint = user_redeemable.owner == *user_authority.key)]
     pub user_redeemable: CpiAccount<'info, TokenAccount>,
-    #[account("token_program.key == &token::ID")]
+    #[account(constraint = token_program.key == &token::ID)]
     pub token_program: AccountInfo<'info>,
     pub clock: Sysvar<'info, Clock>,
 }
@@ -261,18 +261,18 @@ pub struct ExchangeRedeemableForUsdc<'info> {
     pool_signer: AccountInfo<'info>,
     #[account(
         mut,
-        "redeemable_mint.mint_authority == COption::Some(*pool_signer.key)"
+        constraint = redeemable_mint.mint_authority == COption::Some(*pool_signer.key)
     )]
     pub redeemable_mint: CpiAccount<'info, Mint>,
-    #[account(mut, "pool_usdc.owner == *pool_signer.key")]
+    #[account(mut, constraint = pool_usdc.owner == *pool_signer.key)]
     pub pool_usdc: CpiAccount<'info, TokenAccount>,
     #[account(signer)]
     pub user_authority: AccountInfo<'info>,
-    #[account(mut, "user_usdc.owner == *user_authority.key")]
+    #[account(mut, constraint = user_usdc.owner == *user_authority.key)]
     pub user_usdc: CpiAccount<'info, TokenAccount>,
-    #[account(mut, "user_redeemable.owner == *user_authority.key")]
+    #[account(mut, constraint = user_redeemable.owner == *user_authority.key)]
     pub user_redeemable: CpiAccount<'info, TokenAccount>,
-    #[account("token_program.key == &token::ID")]
+    #[account(constraint = token_program.key == &token::ID)]
     pub token_program: AccountInfo<'info>,
     pub clock: Sysvar<'info, Clock>,
 }
@@ -285,18 +285,18 @@ pub struct ExchangeRedeemableForWatermelon<'info> {
     pool_signer: AccountInfo<'info>,
     #[account(
         mut,
-        "redeemable_mint.mint_authority == COption::Some(*pool_signer.key)"
+        constraint = redeemable_mint.mint_authority == COption::Some(*pool_signer.key)
     )]
     pub redeemable_mint: CpiAccount<'info, Mint>,
-    #[account(mut, "pool_watermelon.owner == *pool_signer.key")]
+    #[account(mut, constraint = pool_watermelon.owner == *pool_signer.key)]
     pub pool_watermelon: CpiAccount<'info, TokenAccount>,
     #[account(signer)]
     pub user_authority: AccountInfo<'info>,
-    #[account(mut, "user_watermelon.owner == *user_authority.key")]
+    #[account(mut, constraint = user_watermelon.owner == *user_authority.key)]
     pub user_watermelon: CpiAccount<'info, TokenAccount>,
-    #[account(mut, "user_redeemable.owner == *user_authority.key")]
+    #[account(mut, constraint = user_redeemable.owner == *user_authority.key)]
     pub user_redeemable: CpiAccount<'info, TokenAccount>,
-    #[account("token_program.key == &token::ID")]
+    #[account(constraint = token_program.key == &token::ID)]
     pub token_program: AccountInfo<'info>,
     pub clock: Sysvar<'info, Clock>,
 }
@@ -307,13 +307,13 @@ pub struct WithdrawPoolUsdc<'info> {
     pub pool_account: ProgramAccount<'info, PoolAccount>,
     #[account(seeds = [pool_account.watermelon_mint.as_ref(), &[pool_account.nonce]])]
     pub pool_signer: AccountInfo<'info>,
-    #[account(mut, "pool_usdc.owner == *pool_signer.key")]
+    #[account(mut, constraint = pool_usdc.owner == *pool_signer.key)]
     pub pool_usdc: CpiAccount<'info, TokenAccount>,
     #[account(signer)]
     pub distribution_authority: AccountInfo<'info>,
-    #[account(mut, "creator_usdc.owner == *distribution_authority.key")]
+    #[account(mut, constraint = creator_usdc.owner == *distribution_authority.key)]
     pub creator_usdc: CpiAccount<'info, TokenAccount>,
-    #[account("token_program.key == &token::ID")]
+    #[account(constraint = token_program.key == &token::ID)]
     pub token_program: AccountInfo<'info>,
     pub clock: Sysvar<'info, Clock>,
 }

+ 2 - 0
examples/lockup/programs/lockup/Cargo.toml

@@ -11,6 +11,8 @@ name = "lockup"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+anchor-deprecated-state = []
+default = ["anchor-deprecated-state"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 2 - 0
examples/lockup/programs/registry/Cargo.toml

@@ -11,6 +11,8 @@ name = "registry"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+anchor-deprecated-state = []
+default = ["anchor-deprecated-state"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 10 - 10
examples/lockup/programs/registry/src/lib.rs

@@ -739,7 +739,7 @@ pub struct DepositLocked<'info> {
     // Program specific.
     registry: ProgramState<'info, Registry>,
     registrar: ProgramAccount<'info, Registrar>,
-    #[account(belongs_to = registrar, has_one = beneficiary)]
+    #[account(has_one = registrar, has_one = beneficiary)]
     member: ProgramAccount<'info, Member>,
     #[account(signer)]
     beneficiary: AccountInfo<'info>,
@@ -755,7 +755,7 @@ pub struct Stake<'info> {
     pool_mint: CpiAccount<'info, Mint>,
 
     // Member.
-    #[account(mut, has_one = beneficiary, belongs_to = registrar)]
+    #[account(mut, has_one = beneficiary, has_one = registrar)]
     member: ProgramAccount<'info, Member>,
     #[account(signer)]
     beneficiary: AccountInfo<'info>,
@@ -794,7 +794,7 @@ pub struct StartUnstake<'info> {
     // Member.
     #[account(init)]
     pending_withdrawal: ProgramAccount<'info, PendingWithdrawal>,
-    #[account(has_one = beneficiary, belongs_to = registrar)]
+    #[account(has_one = beneficiary, has_one = registrar)]
     member: ProgramAccount<'info, Member>,
     #[account(signer)]
     beneficiary: AccountInfo<'info>,
@@ -824,11 +824,11 @@ pub struct StartUnstake<'info> {
 pub struct EndUnstake<'info> {
     registrar: ProgramAccount<'info, Registrar>,
 
-    #[account(belongs_to = registrar, has_one = beneficiary)]
+    #[account(has_one = registrar, has_one = beneficiary)]
     member: ProgramAccount<'info, Member>,
     #[account(signer)]
     beneficiary: AccountInfo<'info>,
-    #[account(mut, belongs_to = registrar, belongs_to = member, "!pending_withdrawal.burned")]
+    #[account(mut, has_one = registrar, has_one = member, "!pending_withdrawal.burned")]
     pending_withdrawal: ProgramAccount<'info, PendingWithdrawal>,
 
     // If we had ordered maps implementing Accounts we could do a constraint like
@@ -859,7 +859,7 @@ pub struct Withdraw<'info> {
     // Stake instance.
     registrar: ProgramAccount<'info, Registrar>,
     // Member.
-    #[account(belongs_to = registrar, has_one = beneficiary)]
+    #[account(has_one = registrar, has_one = beneficiary)]
     member: ProgramAccount<'info, Member>,
     #[account(signer)]
     beneficiary: AccountInfo<'info>,
@@ -912,7 +912,7 @@ pub struct WithdrawLocked<'info> {
     // Program specific.
     registry: ProgramState<'info, Registry>,
     registrar: ProgramAccount<'info, Registrar>,
-    #[account(belongs_to = registrar, has_one = beneficiary)]
+    #[account(has_one = registrar, has_one = beneficiary)]
     member: ProgramAccount<'info, Member>,
     #[account(signer)]
     beneficiary: AccountInfo<'info>,
@@ -984,7 +984,7 @@ pub struct ClaimRewardCommon<'info> {
     // Stake instance.
     registrar: ProgramAccount<'info, Registrar>,
     // Member.
-    #[account(mut, belongs_to = registrar, has_one = beneficiary)]
+    #[account(mut, has_one = registrar, has_one = beneficiary)]
     member: ProgramAccount<'info, Member>,
     #[account(signer)]
     beneficiary: AccountInfo<'info>,
@@ -993,7 +993,7 @@ pub struct ClaimRewardCommon<'info> {
     #[account("BalanceSandbox::from(&balances_locked) == member.balances_locked")]
     balances_locked: BalanceSandboxAccounts<'info>,
     // Vendor.
-    #[account(belongs_to = registrar, has_one = vault)]
+    #[account(has_one = registrar, has_one = vault)]
     vendor: ProgramAccount<'info, RewardVendor>,
     #[account(mut)]
     vault: AccountInfo<'info>,
@@ -1016,7 +1016,7 @@ pub struct ExpireReward<'info> {
     // Staking instance globals.
     registrar: ProgramAccount<'info, Registrar>,
     // Vendor.
-    #[account(mut, belongs_to = registrar, has_one = vault, has_one = expiry_receiver)]
+    #[account(mut, has_one = registrar, has_one = vault, has_one = expiry_receiver)]
     vendor: ProgramAccount<'info, RewardVendor>,
     #[account(mut)]
     vault: CpiAccount<'info, TokenAccount>,

+ 20 - 18
examples/lockup/tests/lockup.js

@@ -1,9 +1,11 @@
 const assert = require("assert");
 const anchor = require("@project-serum/anchor");
 const serumCmn = require("@project-serum/common");
-const TokenInstructions = require("@project-serum/serum").TokenInstructions;
+const { TOKEN_PROGRAM_ID } = require("@solana/spl-token");
 const utils = require("./utils");
 
+anchor.utils.features.set('anchor-deprecated-state');
+
 describe("Lockup and Registry", () => {
   // Read the provider from the configured environmnet.
   const provider = anchor.Provider.env();
@@ -130,7 +132,7 @@ describe("Lockup and Registry", () => {
       },
     });
     let lockupAccount = await lockup.state.fetch();
-    assert.deepEqual(lockupAccount.whitelist, entries.slice(1));
+    assert.deepStrictEqual(lockupAccount.whitelist, entries.slice(1));
   });
 
   const vesting = anchor.web3.Keypair.generate();
@@ -168,7 +170,7 @@ describe("Lockup and Registry", () => {
           vault: vault.publicKey,
           depositor: god,
           depositorAuthority: provider.wallet.publicKey,
-          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+          tokenProgram: TOKEN_PROGRAM_ID,
           rent: anchor.web3.SYSVAR_RENT_PUBKEY,
           clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
         },
@@ -210,7 +212,7 @@ describe("Lockup and Registry", () => {
             token: god,
             vault: vestingAccount.vault,
             vestingSigner: vestingSigner,
-            tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+            tokenProgram: TOKEN_PROGRAM_ID,
             clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
           },
         });
@@ -241,7 +243,7 @@ describe("Lockup and Registry", () => {
         token,
         vault: vestingAccount.vault,
         vestingSigner,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
         clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
       },
     });
@@ -373,7 +375,7 @@ describe("Lockup and Registry", () => {
         memberSigner,
         balances,
         balancesLocked,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
         rent: anchor.web3.SYSVAR_RENT_PUBKEY,
       },
       instructions: [await registry.account.member.createInstruction(member)],
@@ -408,7 +410,7 @@ describe("Lockup and Registry", () => {
       accounts: {
         depositor: god,
         depositorAuthority: provider.wallet.publicKey,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
         vault: memberAccount.balances.vault,
         beneficiary: provider.wallet.publicKey,
         member: member.publicKey,
@@ -440,7 +442,7 @@ describe("Lockup and Registry", () => {
         registrarSigner,
         // Misc.
         clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
       },
     });
 
@@ -499,7 +501,7 @@ describe("Lockup and Registry", () => {
           depositor: god,
           depositorAuthority: provider.wallet.publicKey,
 
-          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+          tokenProgram: TOKEN_PROGRAM_ID,
           clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
           rent: anchor.web3.SYSVAR_RENT_PUBKEY,
         },
@@ -562,7 +564,7 @@ describe("Lockup and Registry", () => {
           vault: unlockedVendorVault.publicKey,
           vendorSigner: unlockedVendorSigner,
 
-          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+          tokenProgram: TOKEN_PROGRAM_ID,
           clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
         },
       },
@@ -618,7 +620,7 @@ describe("Lockup and Registry", () => {
           depositor: god,
           depositorAuthority: provider.wallet.publicKey,
 
-          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+          tokenProgram: TOKEN_PROGRAM_ID,
           clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
           rent: anchor.web3.SYSVAR_RENT_PUBKEY,
         },
@@ -684,7 +686,7 @@ describe("Lockup and Registry", () => {
         vault: vendoredVestingVault.publicKey,
         depositor: lockedVendorVault.publicKey,
         depositorAuthority: lockedVendorSigner,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
         rent: anchor.web3.SYSVAR_RENT_PUBKEY,
         clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
       })
@@ -710,7 +712,7 @@ describe("Lockup and Registry", () => {
           vault: lockedVendorVault.publicKey,
           vendorSigner: lockedVendorSigner,
 
-          tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+          tokenProgram: TOKEN_PROGRAM_ID,
           clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
         },
       },
@@ -765,7 +767,7 @@ describe("Lockup and Registry", () => {
             token,
             vault: vendoredVestingVault.publicKey,
             vestingSigner: vendoredVestingSigner,
-            tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+            tokenProgram: TOKEN_PROGRAM_ID,
             clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
           },
           // TODO: trait methods generated on the client. Until then, we need to manually
@@ -807,7 +809,7 @@ describe("Lockup and Registry", () => {
 
         memberSigner,
 
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
         clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
         rent: anchor.web3.SYSVAR_RENT_PUBKEY,
       },
@@ -852,7 +854,7 @@ describe("Lockup and Registry", () => {
         memberSigner,
 
         clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
       },
     });
   };
@@ -905,7 +907,7 @@ describe("Lockup and Registry", () => {
         vault: memberAccount.balances.vault,
         memberSigner,
         depositor: token,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
       },
     });
 
@@ -928,7 +930,7 @@ describe("Lockup and Registry", () => {
         token,
         vault: vendoredVestingVault.publicKey,
         vestingSigner: vendoredVestingSigner,
-        tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
+        tokenProgram: TOKEN_PROGRAM_ID,
         clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
       },
       // TODO: trait methods generated on the client. Until then, we need to manually

+ 1 - 0
examples/misc/programs/misc/Cargo.toml

@@ -16,4 +16,5 @@ default = []
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }
+anchor-spl = { path = "../../../../spl" }
 misc2 = { path = "../misc2", features = ["cpi"] }

+ 36 - 0
examples/misc/programs/misc/src/account.rs

@@ -0,0 +1,36 @@
+use anchor_lang::prelude::*;
+
+#[associated]
+#[derive(Default)]
+pub struct TestData {
+    pub data: u64,
+}
+
+#[account]
+pub struct Data {
+    pub udata: u128,
+    pub idata: i128,
+}
+
+#[account]
+#[derive(Default)]
+pub struct DataU16 {
+    pub data: u16,
+}
+
+#[account]
+pub struct DataI8 {
+    pub data: i8,
+}
+
+#[account]
+pub struct DataI16 {
+    pub data: i16,
+}
+
+#[account(zero_copy)]
+#[derive(Default)]
+pub struct DataZeroCopy {
+    pub data: u16,
+    pub bump: u8,
+}

+ 172 - 0
examples/misc/programs/misc/src/context.rs

@@ -0,0 +1,172 @@
+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>,
+}

+ 21 - 0
examples/misc/programs/misc/src/event.rs

@@ -0,0 +1,21 @@
+use anchor_lang::prelude::*;
+
+#[event]
+pub struct E1 {
+    pub data: u32,
+}
+
+#[event]
+pub struct E2 {
+    pub data: u32,
+}
+
+#[event]
+pub struct E3 {
+    pub data: u32,
+}
+
+#[event]
+pub struct E4 {
+    pub data: Pubkey,
+}

+ 30 - 187
examples/misc/programs/misc/src/lib.rs

@@ -2,9 +2,14 @@
 //! It's not too instructive/coherent by itself, so please see other examples.
 
 use anchor_lang::prelude::*;
-use misc2::misc2::MyState as Misc2State;
+use context::*;
+use event::*;
 use misc2::Auth;
 
+mod account;
+mod context;
+mod event;
+
 #[program]
 pub mod misc {
     use super::*;
@@ -80,6 +85,20 @@ pub mod misc {
         Ok(())
     }
 
+    pub fn test_simulate_associated_account(
+        ctx: Context<TestSimulateAssociatedAccount>,
+        data: u32,
+    ) -> ProgramResult {
+        let associated_account = *ctx.accounts.my_account.to_account_info().key;
+        emit!(E1 { data });
+        emit!(E2 { data: 1234 });
+        emit!(E3 { data: 9 });
+        emit!(E4 {
+            data: associated_account
+        });
+        Ok(())
+    }
+
     pub fn test_i8(ctx: Context<TestI8>, data: i8) -> ProgramResult {
         ctx.accounts.data.data = data;
         Ok(())
@@ -123,192 +142,16 @@ pub mod misc {
         acc.data = 1234;
         Ok(())
     }
-}
-
-#[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,
-    )]
-    my_pda: ProgramAccount<'info, DataU16>,
-    my_payer: AccountInfo<'info>,
-    foo: AccountInfo<'info>,
-    rent: Sysvar<'info, Rent>,
-    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)]
-    my_pda: Loader<'info, DataZeroCopy>,
-    my_payer: AccountInfo<'info>,
-    rent: Sysvar<'info, Rent>,
-    system_program: AccountInfo<'info>,
-}
 
-#[derive(Accounts)]
-pub struct TestPdaMutZeroCopy<'info> {
-    #[account(mut, seeds = [b"my-seed".as_ref(), &[my_pda.load()?.bump]])]
-    my_pda: Loader<'info, DataZeroCopy>,
-    my_payer: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct Ctor {}
-
-#[derive(Accounts)]
-pub struct RemainingAccounts {}
-
-#[derive(Accounts)]
-pub struct Initialize<'info> {
-    #[account(init)]
-    data: ProgramAccount<'info, Data>,
-    rent: Sysvar<'info, Rent>,
-}
-
-#[derive(Accounts)]
-pub struct TestOwner<'info> {
-    #[account(owner = misc)]
-    data: AccountInfo<'info>,
-    misc: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestExecutable<'info> {
-    #[account(executable)]
-    program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestStateCpi<'info> {
-    #[account(signer)]
-    authority: AccountInfo<'info>,
-    #[account(mut, state = misc2_program)]
-    cpi_state: CpiState<'info, Misc2State>,
-    #[account(executable)]
-    misc2_program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestClose<'info> {
-    #[account(mut, close = sol_dest)]
-    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)]
-    my_account: ProgramAccount<'info, TestData>,
-    #[account(mut, signer)]
-    authority: AccountInfo<'info>,
-    state: ProgramState<'info, MyState>,
-    data: ProgramAccount<'info, Data>,
-    rent: Sysvar<'info, Rent>,
-    system_program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestAssociatedAccount<'info> {
-    #[account(mut, associated = authority, with = state, with = data)]
-    my_account: ProgramAccount<'info, TestData>,
-    #[account(mut, signer)]
-    authority: AccountInfo<'info>,
-    state: ProgramState<'info, MyState>,
-    data: ProgramAccount<'info, Data>,
-}
-
-#[derive(Accounts)]
-pub struct TestU16<'info> {
-    #[account(init)]
-    my_account: ProgramAccount<'info, DataU16>,
-    rent: Sysvar<'info, Rent>,
-}
-
-#[derive(Accounts)]
-pub struct TestI16<'info> {
-    #[account(init)]
-    data: ProgramAccount<'info, DataI16>,
-    rent: Sysvar<'info, Rent>,
-}
-
-#[derive(Accounts)]
-pub struct TestSimulate {}
-
-#[derive(Accounts)]
-pub struct TestI8<'info> {
-    #[account(init)]
-    data: ProgramAccount<'info, DataI8>,
-    rent: Sysvar<'info, Rent>,
-}
-
-#[associated]
-#[derive(Default)]
-pub struct TestData {
-    data: u64,
-}
-
-#[account]
-pub struct Data {
-    udata: u128,
-    idata: i128,
-}
-
-#[account]
-#[derive(Default)]
-pub struct DataU16 {
-    data: u16,
-}
-
-#[account]
-pub struct DataI8 {
-    data: i8,
-}
-
-#[account]
-pub struct DataI16 {
-    data: i16,
-}
-
-#[account(zero_copy)]
-#[derive(Default)]
-pub struct DataZeroCopy {
-    data: u16,
-    bump: u8,
-}
-
-#[event]
-pub struct E1 {
-    data: u32,
-}
-
-#[event]
-pub struct E2 {
-    data: u32,
-}
+    pub fn test_token_seeds_init(_ctx: Context<TestTokenSeedsInit>, _nonce: u8) -> ProgramResult {
+        Ok(())
+    }
 
-#[event]
-pub struct E3 {
-    data: u32,
+    pub fn default<'info>(
+        _program_id: &Pubkey,
+        _accounts: &[AccountInfo<'info>],
+        _data: &[u8],
+    ) -> ProgramResult {
+        Err(ProgramError::Custom(1234))
+    }
 }

+ 103 - 5
examples/misc/tests/misc.js

@@ -1,7 +1,7 @@
 const anchor = require("@project-serum/anchor");
 const PublicKey = anchor.web3.PublicKey;
-const serumCmn = require("@project-serum/common");
 const assert = require("assert");
+const { TOKEN_PROGRAM_ID, Token } = require("@solana/spl-token");
 
 describe("misc", () => {
   // Configure the client to use the local cluster.
@@ -145,10 +145,11 @@ describe("misc", () => {
       nonce,
     ] = await anchor.web3.PublicKey.findProgramAddress(
       [
-        Buffer.from([97, 110, 99, 104, 111, 114]), // b"anchor".
+        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
     );
@@ -178,7 +179,8 @@ describe("misc", () => {
     const account = await program.account.testData.associated(
       program.provider.wallet.publicKey,
       state,
-      data.publicKey
+      data.publicKey,
+      anchor.utils.bytes.utf8.encode("my-seed")
     );
     assert.ok(account.data.toNumber() === 1234);
   });
@@ -190,10 +192,11 @@ describe("misc", () => {
       nonce,
     ] = await anchor.web3.PublicKey.findProgramAddress(
       [
-        Buffer.from([97, 110, 99, 104, 111, 114]), // b"anchor".
+        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
     );
@@ -209,7 +212,8 @@ describe("misc", () => {
     const account = await program.account.testData.associated(
       program.provider.wallet.publicKey,
       state,
-      data.publicKey
+      data.publicKey,
+      anchor.utils.bytes.utf8.encode("my-seed")
     );
     assert.ok(account.data.toNumber() === 5);
   });
@@ -224,6 +228,7 @@ describe("misc", () => {
       "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);
@@ -233,6 +238,55 @@ describe("misc", () => {
     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, {
@@ -402,4 +456,48 @@ describe("misc", () => {
     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;
+      }
+    );
+  });
 });

+ 2 - 2
examples/multisig/programs/multisig/src/lib.rs

@@ -183,7 +183,7 @@ pub struct CreateTransaction<'info> {
 #[derive(Accounts)]
 pub struct Approve<'info> {
     multisig: ProgramAccount<'info, Multisig>,
-    #[account(mut, belongs_to = multisig)]
+    #[account(mut, has_one = multisig)]
     transaction: ProgramAccount<'info, Transaction>,
     // One of the multisig owners. Checked in the handler.
     #[account(signer)]
@@ -209,7 +209,7 @@ pub struct ExecuteTransaction<'info> {
         &[multisig.nonce],
     ])]
     multisig_signer: AccountInfo<'info>,
-    #[account(mut, belongs_to = multisig)]
+    #[account(mut, has_one = multisig)]
     transaction: ProgramAccount<'info, Transaction>,
 }
 

+ 13 - 0
examples/permissioned-markets/Anchor.toml

@@ -0,0 +1,13 @@
+[provider]
+cluster = "localnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "anchor run build && anchor test"
+build = "anchor run build-deps && anchor build"
+build-deps = "anchor run build-dex"
+build-dex = "pushd deps/serum-dex/dex/ && cargo build-bpf && popd"
+
+[[test.genesis]]
+address = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
+program = "./deps/serum-dex/dex/target/deploy/serum_dex.so"

+ 8 - 0
examples/permissioned-markets/Cargo.toml

@@ -0,0 +1,8 @@
+[workspace]
+members = [
+    "programs/*"
+]
+
+exclude = [
+    "deps/serum-dex",
+]

+ 1 - 0
examples/permissioned-markets/deps/serum-dex

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

+ 13 - 0
examples/permissioned-markets/migrations/deploy.js

@@ -0,0 +1,13 @@
+
+// Migrations are an early feature. Currently, they're nothing more than this
+// single deploy script that's invoked from the CLI, injecting a provider
+// configured from the workspace's Anchor.toml.
+
+const anchor = require("@project-serum/anchor");
+
+module.exports = async function (provider) {
+  // Configure client to use the provider.
+  anchor.setProvider(provider);
+
+  // Add your deploy script here.
+}

+ 21 - 0
examples/permissioned-markets/programs/permissioned-markets/Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "permissioned-markets"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "permissioned_markets"
+
+[features]
+no-entrypoint = []
+no-idl = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }
+anchor-spl = { path = "../../../../spl" }
+serum_dex = { path = "../../deps/serum-dex/dex", features = ["no-entrypoint"] }
+solana-program = "1.7.4"

+ 2 - 0
examples/permissioned-markets/programs/permissioned-markets/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 345 - 0
examples/permissioned-markets/programs/permissioned-markets/src/lib.rs

@@ -0,0 +1,345 @@
+// 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");
+}

+ 284 - 0
examples/permissioned-markets/tests/permissioned-markets.js

@@ -0,0 +1,284 @@
+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;
+}

+ 427 - 0
examples/permissioned-markets/tests/utils/index.js

@@ -0,0 +1,427 @@
+// Boilerplate utils to bootstrap an orderbook for testing on a localnet.
+// not super relevant to the point of the example, though may be useful to
+// include into your own workspace for testing.
+//
+// TODO: Modernize all these apis. This is all quite clunky.
+
+const Token = require("@solana/spl-token").Token;
+const TOKEN_PROGRAM_ID = require("@solana/spl-token").TOKEN_PROGRAM_ID;
+const TokenInstructions = require("@project-serum/serum").TokenInstructions;
+const { Market, OpenOrders } = require("@project-serum/serum");
+const DexInstructions = require("@project-serum/serum").DexInstructions;
+const web3 = require("@project-serum/anchor").web3;
+const Connection = web3.Connection;
+const anchor = require("@project-serum/anchor");
+const BN = anchor.BN;
+const serumCmn = require("@project-serum/common");
+const Account = web3.Account;
+const Transaction = web3.Transaction;
+const PublicKey = web3.PublicKey;
+const SystemProgram = web3.SystemProgram;
+const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
+const MARKET_MAKER = new Account();
+
+async function initMarket({ provider }) {
+  // Setup mints with initial tokens owned by the provider.
+  const decimals = 6;
+  const [MINT_A, GOD_A] = await serumCmn.createMintAndVault(
+    provider,
+    new BN("1000000000000000000"),
+    undefined,
+    decimals
+  );
+  const [USDC, GOD_USDC] = await serumCmn.createMintAndVault(
+    provider,
+    new BN("1000000000000000000"),
+    undefined,
+    decimals
+  );
+
+  // Create a funded account to act as market maker.
+  const amount = new BN("10000000000000").muln(10 ** decimals);
+  const marketMaker = await fundAccount({
+    provider,
+    mints: [
+      { god: GOD_A, mint: MINT_A, amount, decimals },
+      { god: GOD_USDC, mint: USDC, amount, decimals },
+    ],
+  });
+
+  // Setup A/USDC with resting orders.
+  const asks = [
+    [6.041, 7.8],
+    [6.051, 72.3],
+    [6.055, 5.4],
+    [6.067, 15.7],
+    [6.077, 390.0],
+    [6.09, 24.0],
+    [6.11, 36.3],
+    [6.133, 300.0],
+    [6.167, 687.8],
+  ];
+  const bids = [
+    [6.004, 8.5],
+    [5.995, 12.9],
+    [5.987, 6.2],
+    [5.978, 15.3],
+    [5.965, 82.8],
+    [5.961, 25.4],
+  ];
+
+  [MARKET_A_USDC, vaultSigner] = await setupMarket({
+    baseMint: MINT_A,
+    quoteMint: USDC,
+    marketMaker: {
+      account: marketMaker.account,
+      baseToken: marketMaker.tokens[MINT_A.toString()],
+      quoteToken: marketMaker.tokens[USDC.toString()],
+    },
+    bids,
+    asks,
+    provider,
+  });
+
+  const marketMakerOpenOrders = (
+    await OpenOrders.findForMarketAndOwner(
+      provider.connection,
+      MARKET_A_USDC.address,
+      marketMaker.account.publicKey,
+      DEX_PID
+    )
+  )[0].address;
+
+  return {
+    marketA: MARKET_A_USDC,
+    vaultSigner,
+    marketMaker,
+    marketMakerOpenOrders,
+    mintA: MINT_A,
+    usdc: USDC,
+    godA: GOD_A,
+    godUsdc: GOD_USDC,
+  };
+}
+
+async function fundAccount({ provider, mints }) {
+  const marketMaker = {
+    tokens: {},
+    account: MARKET_MAKER,
+  };
+
+  // Transfer lamports to market maker.
+  await provider.send(
+    (() => {
+      const tx = new Transaction();
+      tx.add(
+        SystemProgram.transfer({
+          fromPubkey: provider.wallet.publicKey,
+          toPubkey: MARKET_MAKER.publicKey,
+          lamports: 100000000000,
+        })
+      );
+      return tx;
+    })()
+  );
+
+  // Transfer SPL tokens to the market maker.
+  for (let k = 0; k < mints.length; k += 1) {
+    const { mint, god, amount, decimals } = mints[k];
+    let MINT_A = mint;
+    let GOD_A = god;
+    // Setup token accounts owned by the market maker.
+    const mintAClient = new Token(
+      provider.connection,
+      MINT_A,
+      TOKEN_PROGRAM_ID,
+      provider.wallet.payer // node only
+    );
+    const marketMakerTokenA = await mintAClient.createAccount(
+      MARKET_MAKER.publicKey
+    );
+
+    await provider.send(
+      (() => {
+        const tx = new Transaction();
+        tx.add(
+          Token.createTransferCheckedInstruction(
+            TOKEN_PROGRAM_ID,
+            GOD_A,
+            MINT_A,
+            marketMakerTokenA,
+            provider.wallet.publicKey,
+            [],
+            amount,
+            decimals
+          )
+        );
+        return tx;
+      })()
+    );
+
+    marketMaker.tokens[mint.toString()] = marketMakerTokenA;
+  }
+
+  return marketMaker;
+}
+
+async function setupMarket({
+  provider,
+  marketMaker,
+  baseMint,
+  quoteMint,
+  bids,
+  asks,
+}) {
+  const [marketAPublicKey, vaultOwner] = await listMarket({
+    connection: provider.connection,
+    wallet: provider.wallet,
+    baseMint: baseMint,
+    quoteMint: quoteMint,
+    baseLotSize: 100000,
+    quoteLotSize: 100,
+    dexProgramId: DEX_PID,
+    feeRateBps: 0,
+  });
+  const MARKET_A_USDC = await Market.load(
+    provider.connection,
+    marketAPublicKey,
+    { commitment: "recent" },
+    DEX_PID
+  );
+  for (let k = 0; k < asks.length; k += 1) {
+    let ask = asks[k];
+    const {
+      transaction,
+      signers,
+    } = await MARKET_A_USDC.makePlaceOrderTransaction(provider.connection, {
+      owner: marketMaker.account,
+      payer: marketMaker.baseToken,
+      side: "sell",
+      price: ask[0],
+      size: ask[1],
+      orderType: "postOnly",
+      clientId: undefined,
+      openOrdersAddressKey: undefined,
+      openOrdersAccount: undefined,
+      feeDiscountPubkey: null,
+      selfTradeBehavior: "abortTransaction",
+    });
+    await provider.send(transaction, signers.concat(marketMaker.account));
+  }
+
+  for (let k = 0; k < bids.length; k += 1) {
+    let bid = bids[k];
+    const {
+      transaction,
+      signers,
+    } = await MARKET_A_USDC.makePlaceOrderTransaction(provider.connection, {
+      owner: marketMaker.account,
+      payer: marketMaker.quoteToken,
+      side: "buy",
+      price: bid[0],
+      size: bid[1],
+      orderType: "postOnly",
+      clientId: undefined,
+      openOrdersAddressKey: undefined,
+      openOrdersAccount: undefined,
+      feeDiscountPubkey: null,
+      selfTradeBehavior: "abortTransaction",
+    });
+    await provider.send(transaction, signers.concat(marketMaker.account));
+  }
+
+  return [MARKET_A_USDC, vaultOwner];
+}
+
+async function listMarket({
+  connection,
+  wallet,
+  baseMint,
+  quoteMint,
+  baseLotSize,
+  quoteLotSize,
+  dexProgramId,
+  feeRateBps,
+}) {
+  const market = new Account();
+  const requestQueue = new Account();
+  const eventQueue = new Account();
+  const bids = new Account();
+  const asks = new Account();
+  const baseVault = new Account();
+  const quoteVault = new Account();
+  const quoteDustThreshold = new BN(100);
+
+  const [vaultOwner, vaultSignerNonce] = await getVaultOwnerAndNonce(
+    market.publicKey,
+    dexProgramId
+  );
+
+  const tx1 = new Transaction();
+  tx1.add(
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: baseVault.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(165),
+      space: 165,
+      programId: TOKEN_PROGRAM_ID,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: quoteVault.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(165),
+      space: 165,
+      programId: TOKEN_PROGRAM_ID,
+    }),
+    TokenInstructions.initializeAccount({
+      account: baseVault.publicKey,
+      mint: baseMint,
+      owner: vaultOwner,
+    }),
+    TokenInstructions.initializeAccount({
+      account: quoteVault.publicKey,
+      mint: quoteMint,
+      owner: vaultOwner,
+    })
+  );
+
+  const tx2 = new Transaction();
+  tx2.add(
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: market.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(
+        Market.getLayout(dexProgramId).span
+      ),
+      space: Market.getLayout(dexProgramId).span,
+      programId: dexProgramId,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: requestQueue.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(5120 + 12),
+      space: 5120 + 12,
+      programId: dexProgramId,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: eventQueue.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(262144 + 12),
+      space: 262144 + 12,
+      programId: dexProgramId,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: bids.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
+      space: 65536 + 12,
+      programId: dexProgramId,
+    }),
+    SystemProgram.createAccount({
+      fromPubkey: wallet.publicKey,
+      newAccountPubkey: asks.publicKey,
+      lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
+      space: 65536 + 12,
+      programId: dexProgramId,
+    }),
+    DexInstructions.initializeMarket({
+      market: market.publicKey,
+      requestQueue: requestQueue.publicKey,
+      eventQueue: eventQueue.publicKey,
+      bids: bids.publicKey,
+      asks: asks.publicKey,
+      baseVault: baseVault.publicKey,
+      quoteVault: quoteVault.publicKey,
+      baseMint,
+      quoteMint,
+      baseLotSize: new BN(baseLotSize),
+      quoteLotSize: new BN(quoteLotSize),
+      feeRateBps,
+      vaultSignerNonce,
+      quoteDustThreshold,
+      programId: dexProgramId,
+    })
+  );
+
+  const signedTransactions = await signTransactions({
+    transactionsAndSigners: [
+      { transaction: tx1, signers: [baseVault, quoteVault] },
+      {
+        transaction: tx2,
+        signers: [market, requestQueue, eventQueue, bids, asks],
+      },
+    ],
+    wallet,
+    connection,
+  });
+  for (let signedTransaction of signedTransactions) {
+    await sendAndConfirmRawTransaction(
+      connection,
+      signedTransaction.serialize()
+    );
+  }
+  const acc = await connection.getAccountInfo(market.publicKey);
+
+  return [market.publicKey, vaultOwner];
+}
+
+async function signTransactions({
+  transactionsAndSigners,
+  wallet,
+  connection,
+}) {
+  const blockhash = (await connection.getRecentBlockhash("max")).blockhash;
+  transactionsAndSigners.forEach(({ transaction, signers = [] }) => {
+    transaction.recentBlockhash = blockhash;
+    transaction.setSigners(
+      wallet.publicKey,
+      ...signers.map((s) => s.publicKey)
+    );
+    if (signers?.length > 0) {
+      transaction.partialSign(...signers);
+    }
+  });
+  return await wallet.signAllTransactions(
+    transactionsAndSigners.map(({ transaction }) => transaction)
+  );
+}
+
+async function sendAndConfirmRawTransaction(
+  connection,
+  raw,
+  commitment = "recent"
+) {
+  let tx = await connection.sendRawTransaction(raw, {
+    skipPreflight: true,
+  });
+  return await connection.confirmTransaction(tx, commitment);
+}
+
+async function getVaultOwnerAndNonce(marketPublicKey, dexProgramId = DEX_PID) {
+  const nonce = new BN(0);
+  while (nonce.toNumber() < 255) {
+    try {
+      const vaultOwner = await PublicKey.createProgramAddress(
+        [marketPublicKey.toBuffer(), nonce.toArrayLike(Buffer, "le", 8)],
+        dexProgramId
+      );
+      return [vaultOwner, nonce];
+    } catch (e) {
+      nonce.iaddn(1);
+    }
+  }
+  throw new Error("Unable to find nonce");
+}
+
+function sleep(ms) {
+  return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+module.exports = {
+  fundAccount,
+  initMarket,
+  setupMarket,
+  DEX_PID,
+  getVaultOwnerAndNonce,
+  sleep,
+};

+ 1 - 1
examples/swap/tests/utils/index.js

@@ -464,7 +464,7 @@ async function signTransactions({
       wallet.publicKey,
       ...signers.map((s) => s.publicKey)
     );
-    if (signers?.length > 0) {
+    if (signers.length > 0) {
       transaction.partialSign(...signers);
     }
   });

+ 6 - 4
examples/zero-copy/programs/zero-copy/src/lib.rs

@@ -1,7 +1,9 @@
-//! This example demonstrates the use of zero copy deserialization for accounts.
-//! The main noticeable benefit one achieves using zero copy is the ability
-//! to create accounts larger than the size of the stack or heap, as is
-//! demonstrated by the event queue in this example.
+//! This example demonstrates the use of zero-copy deserialization for accounts.
+//! Zero-copy is a deserialization technique that creates data structures by borrowing
+//! (not copying!) from the array holding the input, avoiding the expensive memory
+//! allocation and processing of traditional deserialization.
+//! With zero-copy, we can create accounts larger than the size of the stack or heap,
+//! as is demonstrated by the event queue in this example.
 
 use anchor_lang::prelude::*;
 

+ 11 - 11
lang/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-lang"
-version = "0.9.0"
+version = "0.11.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.9.0" }
-anchor-attribute-account = { path = "./attribute/account", version = "0.9.0" }
-anchor-attribute-error = { path = "./attribute/error", version = "0.9.0" }
-anchor-attribute-program = { path = "./attribute/program", version = "0.9.0" }
-anchor-attribute-state = { path = "./attribute/state", version = "0.9.0" }
-anchor-attribute-interface = { path = "./attribute/interface", version = "0.9.0" }
-anchor-attribute-event = { path = "./attribute/event", version = "0.9.0" }
-anchor-derive-accounts = { path = "./derive/accounts", version = "0.9.0" }
+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" }
 base64 = "0.13.0"
-borsh = "0.8.2"
+borsh = "0.9"
 bytemuck = "1.4.0"
-solana-program = "1.7.2"
+solana-program = "1.7.4"
 thiserror = "1.0.20"

+ 2 - 2
lang/attribute/access-control/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-attribute-access-control"
-version = "0.9.0"
+version = "0.11.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.9.0" }
+anchor-syn = { path = "../../syn", version = "0.11.1" }
 regex = "1.0"

+ 2 - 2
lang/attribute/account/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-attribute-account"
-version = "0.9.0"
+version = "0.11.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.9.0", features = ["hash"] }
+anchor-syn = { path = "../../syn", version = "0.11.1", features = ["hash"] }

+ 33 - 22
lang/attribute/account/src/lib.rs

@@ -1,7 +1,7 @@
 extern crate proc_macro;
 
 use quote::quote;
-use syn::parse_macro_input;
+use syn::{parse_macro_input, parse_quote};
 
 /// A data structure representing a Solana account, implementing various traits:
 ///
@@ -59,10 +59,10 @@ pub fn account(
 ) -> proc_macro::TokenStream {
     let mut namespace = "".to_string();
     let mut is_zero_copy = false;
-    if args.to_string().split(",").collect::<Vec<_>>().len() > 2 {
+    if args.to_string().split(',').count() > 2 {
         panic!("Only two args are allowed to the account attribute.")
     }
-    for arg in args.to_string().split(",") {
+    for arg in args.to_string().split(',') {
         let ns = arg
             .to_string()
             .replace("\"", "")
@@ -78,6 +78,7 @@ pub fn account(
 
     let account_strct = parse_macro_input!(input as syn::ItemStruct);
     let account_name = &account_strct.ident;
+    let (impl_gen, type_gen, where_clause) = account_strct.generics.split_for_impl();
 
     let discriminator: proc_macro2::TokenStream = {
         // Namespace the discriminator to prevent collisions.
@@ -103,12 +104,16 @@ pub fn account(
                 #[zero_copy]
                 #account_strct
 
-                unsafe impl anchor_lang::__private::bytemuck::Pod for #account_name {}
-                unsafe impl anchor_lang::__private::bytemuck::Zeroable for #account_name {}
+                #[automatically_derived]
+                unsafe impl #impl_gen anchor_lang::__private::bytemuck::Pod for #account_name #type_gen #where_clause {}
+                #[automatically_derived]
+                unsafe impl #impl_gen anchor_lang::__private::bytemuck::Zeroable for #account_name #type_gen #where_clause {}
 
-                impl anchor_lang::ZeroCopy for #account_name {}
+                #[automatically_derived]
+                impl #impl_gen anchor_lang::ZeroCopy for #account_name #type_gen #where_clause {}
 
-                impl anchor_lang::Discriminator for #account_name {
+                #[automatically_derived]
+                impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
                     fn discriminator() -> [u8; 8] {
                         #discriminator
                     }
@@ -116,7 +121,8 @@ pub fn account(
 
                 // This trait is useful for clients deserializing accounts.
                 // It's expected on-chain programs deserialize via zero-copy.
-                impl anchor_lang::AccountDeserialize for #account_name {
+                #[automatically_derived]
+                impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
                     fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
                         if buf.len() < #discriminator.len() {
                             return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
@@ -142,7 +148,8 @@ pub fn account(
                 #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
                 #account_strct
 
-                impl anchor_lang::AccountSerialize for #account_name {
+                #[automatically_derived]
+                impl #impl_gen anchor_lang::AccountSerialize for #account_name #type_gen #where_clause {
                     fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), ProgramError> {
                         writer.write_all(&#discriminator).map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotSerialize)?;
                         AnchorSerialize::serialize(
@@ -154,7 +161,8 @@ pub fn account(
                     }
                 }
 
-                impl anchor_lang::AccountDeserialize for #account_name {
+                #[automatically_derived]
+                impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
                     fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
                         if buf.len() < #discriminator.len() {
                             return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
@@ -173,7 +181,8 @@ pub fn account(
                     }
                 }
 
-                impl anchor_lang::Discriminator for #account_name {
+                #[automatically_derived]
+                impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
                     fn discriminator() -> [u8; 8] {
                         #discriminator
                     }
@@ -206,6 +215,7 @@ pub fn associated(
 ) -> 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.
@@ -218,7 +228,12 @@ pub fn associated(
             });
             fields.named.push(syn::Field {
                 attrs: Vec::new(),
-                vis: syn::Visibility::Inherited,
+                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()],
@@ -240,7 +255,8 @@ pub fn associated(
         #[anchor_lang::account(#args)]
         #account_strct
 
-        impl anchor_lang::Bump for #account_name {
+        #[automatically_derived]
+        impl #impl_gen anchor_lang::Bump for #account_name #ty_gen #where_clause {
             fn seed(&self) -> u8 {
                 self.__nonce
             }
@@ -252,6 +268,7 @@ pub fn associated(
 pub fn derive_zero_copy_accessor(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let account_strct = parse_macro_input!(item as syn::ItemStruct);
     let account_name = &account_strct.ident;
+    let (impl_gen, ty_gen, where_clause) = account_strct.generics.split_for_impl();
 
     let fields = match &account_strct.fields {
         syn::Fields::Named(n) => n,
@@ -264,14 +281,7 @@ pub fn derive_zero_copy_accessor(item: proc_macro::TokenStream) -> proc_macro::T
             field
                 .attrs
                 .iter()
-                .filter(|attr| {
-                    let name = anchor_syn::parser::tts_to_string(&attr.path);
-                    if name != "accessor" {
-                        return false;
-                    }
-                    return true;
-                })
-                .next()
+                .find(|attr| anchor_syn::parser::tts_to_string(&attr.path) == "accessor")
                 .map(|attr| {
                     let mut tts = attr.tokens.clone().into_iter();
                     let g_stream = match tts.next().expect("Must have a token group") {
@@ -302,7 +312,8 @@ pub fn derive_zero_copy_accessor(item: proc_macro::TokenStream) -> proc_macro::T
         })
         .collect();
     proc_macro::TokenStream::from(quote! {
-        impl #account_name {
+        #[automatically_derived]
+        impl #impl_gen #account_name #ty_gen #where_clause {
             #(#methods)*
         }
     })

+ 2 - 2
lang/attribute/error/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-attribute-error"
-version = "0.9.0"
+version = "0.11.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.9.0" }
+anchor-syn = { path = "../../syn", version = "0.11.1" }

+ 2 - 2
lang/attribute/event/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-attribute-event"
-version = "0.9.0"
+version = "0.11.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.9.0", features = ["hash"] }
+anchor-syn = { path = "../../syn", version = "0.11.1", features = ["hash"] }

+ 2 - 2
lang/attribute/interface/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-attribute-interface"
-version = "0.9.0"
+version = "0.11.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.9.0" }
+anchor-syn = { path = "../../syn", version = "0.11.1" }
 heck = "0.3.2"

+ 2 - 2
lang/attribute/program/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-attribute-program"
-version = "0.9.0"
+version = "0.11.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.9.0" }
+anchor-syn = { path = "../../syn", version = "0.11.1" }

+ 2 - 2
lang/attribute/state/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-attribute-state"
-version = "0.9.0"
+version = "0.11.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.9.0" }
+anchor-syn = { path = "../../syn", version = "0.11.1" }

+ 22 - 18
lang/attribute/state/src/lib.rs

@@ -46,24 +46,22 @@ pub fn state(
                     }
                 }
             }
-        } else {
-            if is_zero_copy {
-                quote! {
-                    impl anchor_lang::__private::AccountSize for #struct_ident {
-                        fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
-                            let len = anchor_lang::__private::bytemuck::bytes_of(self).len() as u64;
-                            Ok(8 + len)
-                        }
+        } else if is_zero_copy {
+            quote! {
+                impl anchor_lang::__private::AccountSize for #struct_ident {
+                    fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
+                        let len = anchor_lang::__private::bytemuck::bytes_of(self).len() as u64;
+                        Ok(8 + len)
                     }
                 }
-            } else {
-                let size = proc_macro2::TokenStream::from(args.clone());
-                // Size override given to the macro. Use it.
-                quote! {
-                    impl anchor_lang::__private::AccountSize for #struct_ident {
-                        fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
-                            Ok(#size)
-                        }
+            }
+        } else {
+            let size = proc_macro2::TokenStream::from(args);
+            // Size override given to the macro. Use it.
+            quote! {
+                impl anchor_lang::__private::AccountSize for #struct_ident {
+                    fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
+                        Ok(#size)
                     }
                 }
             }
@@ -71,8 +69,14 @@ pub fn state(
     };
 
     let attribute = match is_zero_copy {
-        false => quote! {#[account("state")]},
-        true => quote! {#[account("state", zero_copy)]},
+        false => quote! {
+            #[cfg_attr(feature = "anchor-deprecated-state", account)]
+            #[cfg_attr(not(feature = "anchor-deprecated-state"), account("state"))]
+        },
+        true => quote! {
+            #[cfg_attr(feature = "anchor-deprecated-state", account(zero_copy))]
+            #[cfg_attr(not(feature = "anchor-deprecated-state"), account("state", zero_copy))]
+        },
     };
 
     proc_macro::TokenStream::from(quote! {

+ 2 - 2
lang/derive/accounts/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-derive-accounts"
-version = "0.9.0"
+version = "0.11.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.9.0" }
+anchor-syn = { path = "../../syn", version = "0.11.1" }

+ 2 - 4
lang/derive/accounts/src/lib.rs

@@ -41,16 +41,14 @@ use syn::parse_macro_input;
 /// | `#[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(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(belongs_to = <target>)]` | On `ProgramAccount` or `CpiAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. |
-/// | `#[account(has_one = <target>)]` | On `ProgramAccount` or `CpiAccount` structs | Semantically different, but otherwise the same as `belongs_to`. |
-/// | `#[account(seeds = [<seeds>])]` | On `AccountInfo` structs | Seeds for the program derived address an `AccountInfo` struct represents. |
+/// | `#[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`.|
 /// | `#[account(constraint = <expression>)]` | On any type deriving `Accounts` | Executes the given code as a constraint. The expression should evaluate to a boolean. |
 /// | `#[account("<literal>")]` | Deprecated | Executes the given code literal as a constraint. The literal should evaluate to a boolean. |
 /// | `#[account(rent_exempt = <skip>)]` | On `AccountInfo` or `ProgramAccount` structs | Optional attribute to skip the rent exemption check. By default, all accounts marked with `#[account(init)]` will be rent exempt, and so this should rarely (if ever) be used. Similarly, omitting `= skip` will mark the account rent exempt. |
 /// | `#[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(associated = <target>, with? = <target>, payer? = <target>, space? = "<literal>")]` | On `ProgramAccount` | Whe `init` is provided, creates an associated program account at a program derived address. `associated` is the SOL address to create the account for. `with` is an optional association, for example, a `Mint` account in the SPL token program. `payer` is an optional account to pay for the account creation, defaulting to the `associated` target if none is given. `space` is an optional literal specifying how large the account is, defaulting to the account's serialized `Default::default` size (+ 8 for the account discriminator) if none is given. When creating an associated account, a `rent` `Sysvar` and `system_program` `AccountInfo` must be present in the `Accounts` struct. When `init` is not provided, then ensures the given associated account has the expected address, defined by the program and the given seeds. |
 // TODO: How do we make the markdown render correctly without putting everything
 //       on absurdly long lines?
 #[proc_macro_derive(Accounts, attributes(account, instruction))]

+ 1 - 1
lang/src/context.rs

@@ -21,8 +21,8 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> Context<'a, 'b, 'c, 'info, T> {
         remaining_accounts: &'c [AccountInfo<'info>],
     ) -> Self {
         Self {
-            accounts,
             program_id,
+            accounts,
             remaining_accounts,
         }
     }

+ 4 - 0
lang/src/cpi_account.rs

@@ -30,6 +30,10 @@ impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> {
         ))
     }
 
+    pub fn try_from_init(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> {

+ 4 - 2
lang/src/error.rs

@@ -22,8 +22,8 @@ pub enum ErrorCode {
     // Constraints.
     #[msg("A mut constraint was violated")]
     ConstraintMut = 140,
-    #[msg("A belongs to constraint was violated")]
-    ConstraintBelongsTo,
+    #[msg("A has one constraint was violated")]
+    ConstraintHasOne,
     #[msg("A signer constraint as violated")]
     ConstraintSigner,
     #[msg("A raw constraint was violated")]
@@ -44,6 +44,8 @@ pub enum ErrorCode {
     ConstraintAssociatedInit,
     #[msg("A close constraint was violated")]
     ConstraintClose,
+    #[msg("An address constraint was violated")]
+    ConstraintAddress,
 
     // Accounts.
     #[msg("The account discriminator was already set on this account")]

+ 75 - 4
lang/src/lib.rs

@@ -210,14 +210,33 @@ pub trait Bump {
     fn seed(&self) -> u8;
 }
 
+pub trait Key {
+    fn key(&self) -> Pubkey;
+}
+
+impl<'info, T> Key for T
+where
+    T: ToAccountInfo<'info>,
+{
+    fn key(&self) -> Pubkey {
+        *self.to_account_info().key
+    }
+}
+
+impl Key for Pubkey {
+    fn key(&self) -> Pubkey {
+        *self
+    }
+}
+
 /// The prelude contains all commonly used components of the crate.
 /// All programs should include it via `anchor_lang::prelude::*;`.
 pub mod prelude {
     pub use super::{
-        access_control, account, associated, emit, error, event, interface, program, state,
-        zero_copy, AccountDeserialize, AccountSerialize, Accounts, AccountsExit, AccountsInit,
-        AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext, CpiState,
-        CpiStateContext, Loader, ProgramAccount, ProgramState, Sysvar, ToAccountInfo,
+        access_control, account, associated, emit, error, event, interface, program, require,
+        state, zero_copy, AccountDeserialize, AccountSerialize, Accounts, AccountsExit,
+        AccountsInit, AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext,
+        CpiState, CpiStateContext, Loader, ProgramAccount, ProgramState, Sysvar, ToAccountInfo,
         ToAccountInfos, ToAccountMetas,
     };
 
@@ -287,3 +306,55 @@ pub mod __private {
     pub use crate::state::PROGRAM_STATE_SEED;
     pub const CLOSED_ACCOUNT_DISCRIMINATOR: [u8; 8] = [255, 255, 255, 255, 255, 255, 255, 255];
 }
+
+/// Returns the program-derived-address seeds used for creating the associated
+/// account.
+#[macro_export]
+macro_rules! associated_seeds {
+    (account = $pda:expr, associated = $associated:expr) => {
+        &[
+            b"anchor".as_ref(),
+            $associated.to_account_info().key.as_ref(),
+            &[anchor_lang::Bump::seed(&*$pda)],
+        ]
+    };
+    (account = $pda:expr, associated = $associated:expr, $(with = $with:expr),+) => {
+        &[
+            b"anchor".as_ref(),
+            $associated.to_account_info().key.as_ref(),
+            $($with.to_account_info().key.as_ref()),+,
+            &[anchor_lang::Bump::seed(&*$pda)][..],
+        ]
+    };
+}
+
+/// Ensures a condition is true, otherwise returns the given error.
+/// Use this with a custom error type.
+///
+/// # Example
+///
+/// After defining an `ErrorCode`
+///
+/// ```ignore
+/// #[error]
+/// pub struct ErrorCode {
+///     InvalidArgument,
+/// }
+/// ```
+///
+/// One can write a `require` assertion as
+///
+/// ```ignore
+/// require!(condition, InvalidArgument);
+/// ```
+///
+/// which would exit the program with the `InvalidArgument` error code if
+/// `condition` is false.
+#[macro_export]
+macro_rules! require {
+    ($invariant:expr, $error:tt $(,)?) => {
+        if !($invariant) {
+            return Err(crate::ErrorCode::$error.into());
+        }
+    };
+}

+ 4 - 0
lang/src/program_account.rs

@@ -61,6 +61,10 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T>
             T::try_deserialize_unchecked(&mut data)?,
         ))
     }
+
+    pub fn into_inner(self) -> T {
+        self.inner.account
+    }
 }
 
 impl<'info, T> Accounts<'info> for ProgramAccount<'info, T>

+ 1 - 1
lang/src/state.rs

@@ -10,7 +10,7 @@ use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::ops::{Deref, DerefMut};
 
-pub const PROGRAM_STATE_SEED: &'static str = "unversioned";
+pub const PROGRAM_STATE_SEED: &str = "unversioned";
 
 /// Boxed container for the program state singleton.
 #[derive(Clone)]

+ 1 - 1
lang/src/sysvar.rs

@@ -19,7 +19,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Sysvar<'info, T> {
     ) -> Result<Sysvar<'info, T>, ProgramError> {
         Ok(Sysvar {
             info: acc_info.clone(),
-            account: T::from_account_info(&acc_info)?,
+            account: T::from_account_info(acc_info)?,
         })
     }
 }

+ 77 - 1
lang/src/vec.rs

@@ -1,6 +1,8 @@
-use crate::{ToAccountInfos, ToAccountMetas};
+use crate::{Accounts, ToAccountInfos, ToAccountMetas};
 use solana_program::account_info::AccountInfo;
 use solana_program::instruction::AccountMeta;
+use solana_program::program_error::ProgramError;
+use solana_program::pubkey::Pubkey;
 
 impl<'info, T: ToAccountInfos<'info>> ToAccountInfos<'info> for Vec<T> {
     fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
@@ -17,3 +19,77 @@ impl<T: ToAccountMetas> ToAccountMetas for Vec<T> {
             .collect()
     }
 }
+
+impl<'info, T: Accounts<'info>> Accounts<'info> for Vec<T> {
+    fn try_accounts(
+        program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
+        ix_data: &[u8],
+    ) -> Result<Self, ProgramError> {
+        let mut vec: Vec<T> = Vec::new();
+        T::try_accounts(program_id, accounts, ix_data).map(|item| vec.push(item))?;
+        Ok(vec)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::ToAccountInfo;
+    use solana_program::clock::Epoch;
+    use solana_program::pubkey::Pubkey;
+
+    use super::*;
+
+    #[derive(Accounts)]
+    pub struct Test<'info> {
+        #[account(signer)]
+        test: AccountInfo<'info>,
+    }
+
+    #[test]
+    fn test_accounts_trait_for_vec() {
+        let program_id = Pubkey::default();
+
+        let key = Pubkey::default();
+        let mut lamports1 = 0;
+        let mut data1 = vec![0; 10];
+        let owner = Pubkey::default();
+        let account1 = AccountInfo::new(
+            &key,
+            true,
+            true,
+            &mut lamports1,
+            &mut data1,
+            &owner,
+            false,
+            Epoch::default(),
+        );
+
+        let mut lamports2 = 0;
+        let mut data2 = vec![0; 10];
+        let account2 = AccountInfo::new(
+            &key,
+            true,
+            true,
+            &mut lamports2,
+            &mut data2,
+            &owner,
+            false,
+            Epoch::default(),
+        );
+
+        let mut accounts = &[account1, account2][..];
+        let parsed_accounts = Vec::<Test>::try_accounts(&program_id, &mut accounts, &[]).unwrap();
+
+        assert_eq!(accounts.len(), parsed_accounts.len());
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_accounts_trait_for_vec_empty() {
+        let program_id = Pubkey::default();
+
+        let mut accounts = &[][..];
+        Vec::<Test>::try_accounts(&program_id, &mut accounts, &[]).unwrap();
+    }
+}

+ 2 - 1
lang/syn/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "anchor-syn"
-version = "0.9.0"
+version = "0.11.1"
 authors = ["Serum Foundation <foundation@projectserum.com>"]
 repository = "https://github.com/project-serum/anchor"
 license = "Apache-2.0"
@@ -15,6 +15,7 @@ anchor-debug = []
 
 [dependencies]
 proc-macro2 = "1.0"
+proc-macro2-diagnostics = "0.9"
 quote = "1.0"
 syn = { version = "1.0.60", features = ["full", "extra-traits", "parsing"] }
 anyhow = "1.0.32"

+ 2 - 1
lang/syn/src/codegen/accounts/__client_accounts.rs

@@ -105,7 +105,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
         ///
         /// To access the struct in this module, one should use the sibling
         /// `accounts` module (also generated), which re-exports this.
-        mod #account_mod_name {
+        pub(crate) mod #account_mod_name {
             use super::*;
             use anchor_lang::prelude::borsh;
             #(#re_exports)*
@@ -115,6 +115,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
                 #(#account_struct_fields),*
             }
 
+            #[automatically_derived]
             impl anchor_lang::ToAccountMetas for #name {
                 fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
                     let mut account_metas = vec![];

+ 378 - 144
lang/syn/src/codegen/accounts/constraints.rs

@@ -1,11 +1,12 @@
 use crate::{
-    CompositeField, Constraint, ConstraintAssociatedGroup, ConstraintBelongsTo, ConstraintClose,
-    ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
-    ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeedsGroup, ConstraintSigner,
-    ConstraintState, Field, Ty,
+    CompositeField, Constraint, ConstraintAddress, ConstraintAssociatedGroup, ConstraintClose,
+    ConstraintExecutable, ConstraintGroup, ConstraintHasOne, ConstraintInit, ConstraintLiteral,
+    ConstraintMut, ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeedsGroup,
+    ConstraintSigner, ConstraintState, Field, PdaKind, Ty,
 };
+use proc_macro2_diagnostics::SpanDiagnosticExt;
 use quote::quote;
-use syn::LitInt;
+use syn::Expr;
 
 pub fn generate(f: &Field) -> proc_macro2::TokenStream {
     let checks: Vec<proc_macro2::TokenStream> = linearize(&f.constraints)
@@ -42,7 +43,7 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
         init,
         mutable,
         signer,
-        belongs_to,
+        has_one,
         literal,
         raw,
         owner,
@@ -52,6 +53,7 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
         state,
         associated,
         close,
+        address,
     } = c_group.clone();
 
     let mut constraints = Vec::new();
@@ -71,19 +73,9 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
     if let Some(c) = signer {
         constraints.push(Constraint::Signer(c));
     }
-    constraints.append(
-        &mut belongs_to
-            .into_iter()
-            .map(|c| Constraint::BelongsTo(c))
-            .collect(),
-    );
-    constraints.append(
-        &mut literal
-            .into_iter()
-            .map(|c| Constraint::Literal(c))
-            .collect(),
-    );
-    constraints.append(&mut raw.into_iter().map(|c| Constraint::Raw(c)).collect());
+    constraints.append(&mut has_one.into_iter().map(Constraint::HasOne).collect());
+    constraints.append(&mut literal.into_iter().map(Constraint::Literal).collect());
+    constraints.append(&mut raw.into_iter().map(Constraint::Raw).collect());
     if let Some(c) = owner {
         constraints.push(Constraint::Owner(c));
     }
@@ -99,6 +91,9 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
     if let Some(c) = close {
         constraints.push(Constraint::Close(c));
     }
+    if let Some(c) = address {
+        constraints.push(Constraint::Address(c));
+    }
     constraints
 }
 
@@ -106,7 +101,7 @@ fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
     match c {
         Constraint::Init(c) => generate_constraint_init(f, c),
         Constraint::Mut(c) => generate_constraint_mut(f, c),
-        Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, c),
+        Constraint::HasOne(c) => generate_constraint_has_one(f, c),
         Constraint::Signer(c) => generate_constraint_signer(f, c),
         Constraint::Literal(c) => generate_constraint_literal(c),
         Constraint::Raw(c) => generate_constraint_raw(c),
@@ -117,6 +112,7 @@ fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
         Constraint::State(c) => generate_constraint_state(f, c),
         Constraint::AssociatedGroup(c) => generate_constraint_associated(f, c),
         Constraint::Close(c) => generate_constraint_close(f, c),
+        Constraint::Address(c) => generate_constraint_address(f, c),
     }
 }
 
@@ -128,6 +124,16 @@ fn generate_constraint_composite(_f: &CompositeField, c: &Constraint) -> proc_ma
     }
 }
 
+fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream {
+    let field = &f.ident;
+    let addr = &c.address;
+    quote! {
+        if #field.to_account_info().key != &#addr {
+            return Err(anchor_lang::__private::ErrorCode::ConstraintAddress.into());
+        }
+    }
+}
+
 pub fn generate_constraint_init(_f: &Field, _c: &ConstraintInit) -> proc_macro2::TokenStream {
     quote! {}
 }
@@ -151,10 +157,7 @@ pub fn generate_constraint_mut(f: &Field, _c: &ConstraintMut) -> proc_macro2::To
     }
 }
 
-pub fn generate_constraint_belongs_to(
-    f: &Field,
-    c: &ConstraintBelongsTo,
-) -> proc_macro2::TokenStream {
+pub fn generate_constraint_has_one(f: &Field, c: &ConstraintHasOne) -> proc_macro2::TokenStream {
     let target = c.join_target.clone();
     let ident = &f.ident;
     let field = match &f.ty {
@@ -163,7 +166,7 @@ pub fn generate_constraint_belongs_to(
     };
     quote! {
         if &#field.#target != #target.to_account_info().key {
-            return Err(anchor_lang::__private::ErrorCode::ConstraintBelongsTo.into());
+            return Err(anchor_lang::__private::ErrorCode::ConstraintHasOne.into());
         }
     }
 }
@@ -192,8 +195,13 @@ pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macr
 pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenStream {
     let lit: proc_macro2::TokenStream = {
         let lit = &c.lit;
-        let lit_ts: proc_macro2::TokenStream = quote! {#lit};
-        lit_ts.to_string().replace("\"", "").parse().unwrap()
+        let constraint = lit.value().replace("\"", "");
+        let message = format!(
+            "Deprecated. Should be used with constraint: #[account(constraint = {})]",
+            constraint,
+        );
+        lit.span().warning(message).emit_as_item_tokens();
+        constraint.parse().unwrap()
     };
     quote! {
         if !(#lit) {
@@ -226,11 +234,8 @@ pub fn generate_constraint_rent_exempt(
     c: &ConstraintRentExempt,
 ) -> proc_macro2::TokenStream {
     let ident = &f.ident;
-    let info = match f.ty {
-        Ty::AccountInfo => quote! { #ident },
-        Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
-        Ty::Loader(_) => quote! { #ident.to_account_info() },
-        _ => panic!("Invalid syntax: rent exemption cannot be specified."),
+    let info = quote! {
+        #ident.to_account_info()
     };
     match c {
         ConstraintRentExempt::Skip => quote! {},
@@ -257,15 +262,27 @@ fn generate_constraint_seeds_init(f: &Field, c: &ConstraintSeedsGroup) -> proc_m
             let payer = #p.to_account_info();
         }
     };
+    let seeds_constraint = generate_constraint_seeds_address(f, c);
     let seeds_with_nonce = {
         let s = &c.seeds;
-        let seeds_constraint = generate_constraint_seeds_address(f, c);
-        quote! {
-            #seeds_constraint
-            let seeds = [#s];
+        match c.bump.as_ref() {
+            None => quote! {
+                [#s]
+            },
+            Some(b) => quote! {
+                [#s, &[#b]]
+            },
         }
     };
-    generate_pda(f, seeds_with_nonce, payer, &c.space, false)
+    generate_pda(
+        f,
+        seeds_constraint,
+        seeds_with_nonce,
+        payer,
+        &c.space,
+        false,
+        &c.kind,
+    )
 }
 
 fn generate_constraint_seeds_address(
@@ -273,14 +290,47 @@ fn generate_constraint_seeds_address(
     c: &ConstraintSeedsGroup,
 ) -> proc_macro2::TokenStream {
     let name = &f.ident;
-    let seeds = &c.seeds;
-    quote! {
-        let __program_signer = Pubkey::create_program_address(
-            &[#seeds],
-            program_id,
-        ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
-        if #name.to_account_info().key != &__program_signer {
-            return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
+
+    // If the bump is provided on *initialization*, then force it to be the
+    // canonical nonce.
+    if c.is_init && c.bump.is_some() {
+        let s = &c.seeds;
+        let b = c.bump.as_ref().unwrap();
+        quote! {
+            let (__program_signer, __bump) = anchor_lang::solana_program::pubkey::Pubkey::find_program_address(
+                &[#s],
+                program_id,
+            );
+            if #name.to_account_info().key != &__program_signer {
+                return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
+            }
+            if __bump != #b {
+                return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
+            }
+        }
+    } else {
+        let seeds = match c.bump.as_ref() {
+            None => {
+                let s = &c.seeds;
+                quote! {
+                    [#s]
+                }
+            }
+            Some(b) => {
+                let s = &c.seeds;
+                quote! {
+                    [#s, &[#b]]
+                }
+            }
+        };
+        quote! {
+            let __program_signer = Pubkey::create_program_address(
+                &#seeds,
+                program_id,
+            ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
+            if #name.to_account_info().key != &__program_signer {
+                return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
+            }
         }
     }
 }
@@ -309,45 +359,100 @@ pub fn generate_constraint_associated_init(
             let payer = #p.to_account_info();
         },
     };
-    let associated_seeds_constraint = generate_constraint_associated_seeds(f, c);
-    let seeds_with_nonce = match c.associated_seeds.len() {
-        0 => quote! {
-            #associated_seeds_constraint
-            let seeds = [
-                &b"anchor"[..],
-                #associated_target.to_account_info().key.as_ref(),
-                &[nonce],
-            ];
-        },
-        _ => {
+    let seeds_constraint = generate_constraint_associated_seeds(f, c);
+    let seeds_with_nonce = {
+        if c.associated_seeds.is_empty() {
+            quote! {
+                [
+                    &b"anchor"[..],
+                    #associated_target.to_account_info().key.as_ref(),
+                    &[nonce],
+                ]
+            }
+        } else {
             let seeds = to_seeds_tts(&c.associated_seeds);
             quote! {
-                #associated_seeds_constraint
-                let seeds = [
+                [
                     &b"anchor"[..],
                     #associated_target.to_account_info().key.as_ref(),
                     #seeds
                     &[nonce],
-                ];
+                ]
             }
         }
     };
-    generate_pda(f, seeds_with_nonce, payer, &c.space, true)
+
+    generate_pda(
+        f,
+        seeds_constraint,
+        seeds_with_nonce,
+        payer,
+        &c.space,
+        true,
+        &c.kind,
+    )
+}
+
+fn parse_ty(f: &Field) -> (proc_macro2::TokenStream, proc_macro2::TokenStream, bool) {
+    match &f.ty {
+        Ty::ProgramAccount(ty) => {
+            let ident = &ty.account_type_path;
+            (
+                quote! {
+                    #ident
+                },
+                quote! {
+                    anchor_lang::ProgramAccount
+                },
+                false,
+            )
+        }
+        Ty::Loader(ty) => {
+            let ident = &ty.account_type_path;
+            (
+                quote! {
+                    #ident
+                },
+                quote! {
+                    anchor_lang::Loader
+                },
+                true,
+            )
+        }
+        Ty::CpiAccount(ty) => {
+            let ident = &ty.account_type_path;
+            (
+                quote! {
+                    #ident
+                },
+                quote! {
+                    anchor_lang::CpiAccount
+                },
+                false,
+            )
+        }
+        Ty::AccountInfo => (
+            quote! {
+                AccountInfo
+            },
+            quote! {},
+            false,
+        ),
+        _ => panic!("Invalid type for initializing a program derived address"),
+    }
 }
 
 pub fn generate_pda(
     f: &Field,
+    seeds_constraint: proc_macro2::TokenStream,
     seeds_with_nonce: proc_macro2::TokenStream,
     payer: proc_macro2::TokenStream,
-    space: &Option<LitInt>,
+    space: &Option<Expr>,
     assign_nonce: bool,
+    kind: &PdaKind,
 ) -> proc_macro2::TokenStream {
     let field = &f.ident;
-    let (account_ty, is_zero_copy) = match &f.ty {
-        Ty::ProgramAccount(ty) => (&ty.account_ident, false),
-        Ty::Loader(ty) => (&ty.account_ident, true),
-        _ => panic!("Invalid type for initializing a program derived address"),
-    };
+    let (account_ty, account_wrapper_ty, is_zero_copy) = parse_ty(f);
 
     let space = match space {
         // If no explicit space param was given, serialize the type to bytes
@@ -370,64 +475,161 @@ pub fn generate_pda(
         },
     };
 
-    let account_wrapper_ty = match is_zero_copy {
-        false => quote! {
-            anchor_lang::ProgramAccount
-        },
-        true => quote! {
-            anchor_lang::Loader
-        },
-    };
     let nonce_assignment = match assign_nonce {
         false => quote! {},
-        true => match is_zero_copy {
-            false => quote! {
-                pa.__nonce = nonce;
-            },
-            // Zero copy is not deserialized, so the data must be lazy loaded.
-            true => quote! {
-                pa.load_init()?.__nonce = nonce;
+        true => match &f.ty {
+            Ty::CpiAccount(_) => quote! {},
+            _ => match is_zero_copy {
+                false => quote! {
+                    pa.__nonce = nonce;
+                },
+                // Zero copy is not deserialized, so the data must be lazy loaded.
+                true => quote! {
+                    pa.load_init()?.__nonce = nonce;
+                },
             },
         },
     };
 
-    quote! {
-        let #field: #account_wrapper_ty<#account_ty> = {
-            #space
-            #payer
-
-            let lamports = rent.minimum_balance(space);
-            let ix = anchor_lang::solana_program::system_instruction::create_account(
-                payer.to_account_info().key,
-                #field.to_account_info().key,
-                lamports,
-                space as u64,
-                program_id,
-            );
+    let (combined_account_ty, try_from) = match f.ty {
+        Ty::AccountInfo => (
+            quote! {
+                AccountInfo
+            },
+            quote! {
+                #field.to_account_info()
+            },
+        ),
+        _ => (
+            quote! {
+                #account_wrapper_ty<#account_ty>
+            },
+            quote! {
+                #account_wrapper_ty::try_from_init(
+                    &#field.to_account_info(),
+                )?
+            },
+        ),
+    };
 
-            #seeds_with_nonce
-            let signer = &[&seeds[..]];
-            anchor_lang::solana_program::program::invoke_signed(
-                &ix,
-                &[
-
-                    #field.to_account_info(),
-                    payer.to_account_info(),
-                    system_program.to_account_info(),
-                ],
-                signer,
-            ).map_err(|e| {
-                anchor_lang::solana_program::msg!("Unable to create associated account");
-                e
-            })?;
-            // For now, we assume all accounts created with the `associated`
-            // attribute have a `nonce` field in their account.
-            let mut pa: #account_wrapper_ty<#account_ty> = #account_wrapper_ty::try_from_init(
-                &#field.to_account_info(),
-            )?;
-            #nonce_assignment
-            pa
-        };
+    match kind {
+        PdaKind::Token { owner, mint } => quote! {
+            let #field: #combined_account_ty = {
+                #space
+                #payer
+                #seeds_constraint
+
+                // Fund the account for rent exemption.
+                let required_lamports = rent
+                    .minimum_balance(anchor_spl::token::TokenAccount::LEN)
+                    .max(1)
+                    .saturating_sub(#field.to_account_info().lamports());
+                if required_lamports > 0 {
+                    anchor_lang::solana_program::program::invoke(
+                        &anchor_lang::solana_program::system_instruction::transfer(
+                            payer.to_account_info().key,
+                            #field.to_account_info().key,
+                            required_lamports,
+                        ),
+                        &[
+                            payer.to_account_info(),
+                            #field.to_account_info(),
+                            system_program.to_account_info().clone(),
+                        ],
+                    )?;
+                }
+
+                // Allocate space.
+                anchor_lang::solana_program::program::invoke_signed(
+                    &anchor_lang::solana_program::system_instruction::allocate(
+                        #field.to_account_info().key,
+                        anchor_spl::token::TokenAccount::LEN as u64,
+                    ),
+                    &[
+                        #field.to_account_info(),
+                        system_program.clone(),
+                    ],
+                    &[&#seeds_with_nonce[..]],
+                )?;
+
+                // Assign to the spl token program.
+                let __ix = anchor_lang::solana_program::system_instruction::assign(
+                    #field.to_account_info().key,
+                    token_program.to_account_info().key,
+                );
+                anchor_lang::solana_program::program::invoke_signed(
+                    &__ix,
+                    &[
+                        #field.to_account_info(),
+                        system_program.to_account_info(),
+                    ],
+                    &[&#seeds_with_nonce[..]],
+                )?;
+
+                // Initialize the token account.
+                let cpi_program = token_program.to_account_info();
+                let accounts = anchor_spl::token::InitializeAccount {
+                    account: #field.to_account_info(),
+                    mint: #mint.to_account_info(),
+                    authority: #owner.to_account_info(),
+                    rent: rent.to_account_info(),
+                };
+                let cpi_ctx = CpiContext::new(cpi_program, accounts);
+                anchor_spl::token::initialize_account(cpi_ctx)?;
+                anchor_lang::CpiAccount::try_from_init(
+                    &#field.to_account_info(),
+                )?
+            };
+        },
+        PdaKind::Program { owner } => {
+            // Owner of the account being created. If not specified,
+            // default to the currently executing program.
+            let owner = match owner {
+                None => quote! {
+                    program_id
+                },
+                Some(o) => quote! {
+                    &#o
+                },
+            };
+            quote! {
+                let #field = {
+                    #space
+                    #payer
+                    #seeds_constraint
+
+                    let lamports = rent.minimum_balance(space);
+                    let ix = anchor_lang::solana_program::system_instruction::create_account(
+                        payer.to_account_info().key,
+                        #field.to_account_info().key,
+                        lamports,
+                        space as u64,
+                        #owner,
+                    );
+
+                    anchor_lang::solana_program::program::invoke_signed(
+                        &ix,
+                        &[
+
+                            #field.to_account_info(),
+                            payer.to_account_info(),
+                            system_program.to_account_info(),
+                        ],
+                        &[&#seeds_with_nonce[..]]
+                    ).map_err(|e| {
+                        anchor_lang::solana_program::msg!("Unable to create associated account");
+                        e
+                    })?;
+
+                    // For now, we assume all accounts created with the `associated`
+                    // attribute have a `nonce` field in their account.
+                    let mut pa: #combined_account_ty = #try_from;
+
+                    #nonce_assignment
+                    pa
+                };
+            }
+        }
     }
 }
 
@@ -437,29 +639,50 @@ pub fn generate_constraint_associated_seeds(
 ) -> proc_macro2::TokenStream {
     let field = &f.ident;
     let associated_target = c.associated_target.clone();
-    let seeds_no_nonce = match c.associated_seeds.len() {
-        0 => quote! {
-            [
-                &b"anchor"[..],
-                #associated_target.to_account_info().key.as_ref(),
-            ]
-        },
-        _ => {
-            let seeds = to_seeds_tts(&c.associated_seeds);
-            quote! {
-                [
-                    &b"anchor"[..],
-                    #associated_target.to_account_info().key.as_ref(),
-                    #seeds
-                ]
+    let seeds_no_nonce = if c.associated_seeds.is_empty() {
+        quote! {
+            &b"anchor"[..],
+            #associated_target.to_account_info().key.as_ref(),
+        }
+    } else {
+        let seeds = to_seeds_tts(&c.associated_seeds);
+        quote! {
+            &b"anchor"[..],
+            #associated_target.to_account_info().key.as_ref(),
+            #seeds
+        }
+    };
+
+    let is_find_nonce = match &f.ty {
+        Ty::CpiAccount(_) => true,
+        Ty::AccountInfo => true,
+        _ => c.is_init,
+    };
+    let associated_field = if is_find_nonce {
+        quote! {
+            let (__associated_field, nonce) = Pubkey::find_program_address(
+                &[#seeds_no_nonce],
+                program_id,
+            );
+        }
+    } else {
+        let nonce = match &f.ty {
+            Ty::ProgramAccount(_) => quote! { #field.__nonce },
+            Ty::Loader(_) => {
+                // Zero copy is not deserialized, so the data must be lazy loaded.
+                quote! { #field.load()?.__nonce }
             }
+            _ => panic!("Invalid type for initializing a program derived address"),
+        };
+        quote! {
+            let __associated_field = Pubkey::create_program_address(
+                &[#seeds_no_nonce &[#nonce]],
+                program_id,
+            )?;
         }
     };
     quote! {
-        let (__associated_field, nonce) = Pubkey::find_program_address(
-            &#seeds_no_nonce,
-            program_id,
-        );
+        #associated_field
         if &__associated_field != #field.to_account_info().key {
             return Err(anchor_lang::__private::ErrorCode::ConstraintAssociatedInit.into());
         }
@@ -482,7 +705,7 @@ pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2:
     let program_target = c.program_target.clone();
     let ident = &f.ident;
     let account_ty = match &f.ty {
-        Ty::CpiState(ty) => &ty.account_ident,
+        Ty::CpiState(ty) => &ty.account_type_path,
         _ => panic!("Invalid state constraint"),
     };
     quote! {
@@ -498,16 +721,27 @@ pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2:
 }
 
 // Returns the inner part of the seeds slice as a token stream.
-fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
-    assert!(seeds.len() > 0);
+fn to_seeds_tts(seeds: &[syn::Expr]) -> proc_macro2::TokenStream {
+    assert!(!seeds.is_empty());
     let seed_0 = &seeds[0];
-    let mut tts = quote! {
-        #seed_0.to_account_info().key.as_ref(),
+    let mut tts = match seed_0 {
+        syn::Expr::Path(_) => quote! {
+            anchor_lang::Key::key(&#seed_0).as_ref(),
+        },
+        _ => quote! {
+            #seed_0,
+        },
     };
     for seed in &seeds[1..] {
-        tts = quote! {
-            #tts
-            #seed.to_account_info().key.as_ref(),
+        tts = match seed {
+            syn::Expr::Path(_) => quote! {
+                #tts
+                anchor_lang::Key::key(&#seed).as_ref(),
+            },
+            _ => quote! {
+                #tts
+                #seed,
+            },
         };
     }
     tts

+ 9 - 3
lang/syn/src/codegen/accounts/exit.rs

@@ -1,11 +1,16 @@
-use crate::codegen::accounts::generics;
+use crate::codegen::accounts::{generics, ParsedGenerics};
 use crate::{AccountField, AccountsStruct};
 use quote::quote;
 
 // Generates the `Exit` trait implementation.
 pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
     let name = &accs.ident;
-    let (combined_generics, trait_generics, strct_generics) = generics(accs);
+    let ParsedGenerics {
+        combined_generics,
+        trait_generics,
+        struct_generics,
+        where_clause,
+    } = generics(accs);
 
     let on_save: Vec<proc_macro2::TokenStream> = accs
         .fields
@@ -39,7 +44,8 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
         })
         .collect();
     quote! {
-        impl#combined_generics anchor_lang::AccountsExit#trait_generics for #name#strct_generics {
+        #[automatically_derived]
+        impl<#combined_generics> anchor_lang::AccountsExit<#trait_generics> for #name<#struct_generics> #where_clause{
             fn exit(&self, program_id: &anchor_lang::solana_program::pubkey::Pubkey) -> anchor_lang::solana_program::entrypoint::ProgramResult {
                 #(#on_save)*
                 Ok(())

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels