├── .envrc
├── .eslintignore
├── .eslintrc.cjs
├── .github
├── dependabot.yml
└── workflows
│ ├── programs-e2e.yml
│ ├── programs-unit.yml
│ └── release.yml
├── .gitignore
├── .husky
└── pre-commit
├── .mocharc.cjs
├── .prettierignore
├── .vscode
├── extensions.json
└── settings.json
├── .yarn
├── releases
│ └── yarn-4.0.0-rc.22.cjs
└── sdks
│ ├── eslint
│ ├── bin
│ │ └── eslint.js
│ ├── lib
│ │ └── api.js
│ └── package.json
│ ├── integrations.yml
│ ├── prettier
│ ├── index.js
│ └── package.json
│ └── typescript
│ ├── bin
│ ├── tsc
│ └── tsserver
│ ├── lib
│ ├── tsc.js
│ ├── tsserver.js
│ ├── tsserverlibrary.js
│ └── typescript.js
│ └── package.json
├── .yarnrc.yml
├── Anchor.toml
├── CHANGELOG.md
├── Captain.toml
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── SECURITY.md
├── audit
└── quantstamp.pdf
├── ci.nix
├── flake.lock
├── flake.nix
├── images
└── banner.png
├── package.json
├── programs
├── quarry-merge-mine
│ ├── Cargo.toml
│ ├── README.md
│ ├── Xargo.toml
│ └── src
│ │ ├── account_conversions.rs
│ │ ├── account_validators.rs
│ │ ├── events.rs
│ │ ├── lib.rs
│ │ ├── macros.rs
│ │ ├── mm_cpi.rs
│ │ ├── processor
│ │ ├── claim.rs
│ │ ├── deposit.rs
│ │ ├── init.rs
│ │ ├── mod.rs
│ │ ├── rescue_tokens.rs
│ │ └── withdraw.rs
│ │ └── state.rs
├── quarry-mine
│ ├── Cargo.toml
│ ├── README.md
│ ├── Xargo.toml
│ ├── proptest-regressions
│ │ ├── payroll.txt
│ │ └── rewarder.txt
│ └── src
│ │ ├── account_validators.rs
│ │ ├── addresses.rs
│ │ ├── instructions
│ │ ├── claim_rewards.rs
│ │ ├── claim_rewards_v2.rs
│ │ ├── create_miner.rs
│ │ ├── create_quarry.rs
│ │ ├── create_quarry_v2.rs
│ │ ├── mod.rs
│ │ ├── new_rewarder.rs
│ │ ├── new_rewarder_v2.rs
│ │ └── rescue_tokens.rs
│ │ ├── lib.rs
│ │ ├── macros.rs
│ │ ├── payroll.rs
│ │ ├── quarry.rs
│ │ ├── rewarder.rs
│ │ └── state.rs
├── quarry-mint-wrapper
│ ├── Cargo.toml
│ ├── README.md
│ ├── Xargo.toml
│ └── src
│ │ ├── account_validators.rs
│ │ ├── instructions
│ │ ├── mod.rs
│ │ ├── new_minter.rs
│ │ └── new_wrapper.rs
│ │ ├── lib.rs
│ │ ├── macros.rs
│ │ └── state.rs
├── quarry-operator
│ ├── Cargo.toml
│ ├── README.md
│ ├── Xargo.toml
│ └── src
│ │ ├── account_validators.rs
│ │ ├── instructions
│ │ ├── create_operator.rs
│ │ ├── delegate_create_quarry.rs
│ │ ├── delegate_create_quarry_v2.rs
│ │ └── mod.rs
│ │ ├── lib.rs
│ │ ├── macros.rs
│ │ └── state.rs
├── quarry-redeemer
│ ├── Cargo.toml
│ ├── README.md
│ ├── Xargo.toml
│ └── src
│ │ ├── account_validators.rs
│ │ ├── lib.rs
│ │ ├── macros.rs
│ │ ├── redeem_cpi.rs
│ │ └── state.rs
└── quarry-registry
│ ├── Cargo.toml
│ ├── README.md
│ ├── Xargo.toml
│ └── src
│ ├── account_validators.rs
│ └── lib.rs
├── scripts
├── diff-v2.sh
├── generate-idl-types.sh
└── parse-idls.sh
├── src
├── constants.ts
├── index.ts
├── programs
│ ├── index.ts
│ ├── mine.ts
│ ├── mintWrapper.ts
│ ├── operator.ts
│ ├── quarryMergeMine.ts
│ ├── redeemer.ts
│ └── registry.ts
├── sdk.ts
└── wrappers
│ ├── index.ts
│ ├── mergeMine
│ ├── index.ts
│ ├── mergeMiner.ts
│ ├── mergePool.ts
│ ├── pda.ts
│ └── quarryMergeMine.ts
│ ├── mine
│ ├── index.ts
│ ├── mine.ts
│ ├── miner.ts
│ ├── payroll.ts
│ ├── pda.ts
│ ├── quarry.ts
│ ├── rewarder.ts
│ └── types.ts
│ ├── mintWrapper
│ ├── index.ts
│ ├── mintWrapper.ts
│ ├── pda.ts
│ └── types.ts
│ ├── operator
│ ├── index.ts
│ └── pda.ts
│ ├── redeemer
│ ├── index.ts
│ └── pda.ts
│ └── registry
│ ├── index.ts
│ ├── pda.ts
│ └── registry.ts
├── tests
├── famine.spec.ts
├── mine.spec.ts
├── mineRewards.spec.ts
├── mintWrapper.spec.ts
├── operator.spec.ts
├── quarryMergeMine.spec.ts
├── quarryRedeemer.spec.ts
├── quarryUtils.ts
├── registry.spec.ts
├── test-key.json
├── utils.ts
└── workspace.ts
├── tsconfig.cjs.json
├── tsconfig.esm.json
├── tsconfig.json
└── yarn.lock
/.envrc:
--------------------------------------------------------------------------------
1 | watch_file flake.nix
2 | watch_file flake.lock
3 | mkdir -p .direnv
4 | dotenv
5 | eval "$(nix print-dev-env --profile "$(direnv_layout_dir)/flake-profile")"
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | artifacts/
2 | target/
3 | .pnp.cjs
4 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
4 | // @ts-ignore
5 | require("@rushstack/eslint-patch/modern-module-resolution");
6 |
7 | /** @type import('eslint').Linter.Config */
8 | module.exports = {
9 | root: true,
10 | parserOptions: {
11 | project: "tsconfig.json",
12 | },
13 | extends: ["@saberhq"],
14 | env: {
15 | node: true,
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | - package-ecosystem: "npm"
8 | directory: "/"
9 | schedule:
10 | interval: "daily"
11 | - package-ecosystem: "cargo"
12 | directory: "/"
13 | schedule:
14 | interval: "daily"
15 |
--------------------------------------------------------------------------------
/.github/workflows/programs-e2e.yml:
--------------------------------------------------------------------------------
1 | name: E2E
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 | SOLANA_VERSION: "1.10.39"
12 | RUST_TOOLCHAIN: "1.64.0"
13 |
14 | jobs:
15 | sdk:
16 | runs-on: ubuntu-latest
17 | name: Build the SDK
18 | steps:
19 | - uses: actions/checkout@v3
20 |
21 | - uses: cachix/install-nix-action@v17
22 | - name: Setup Cachix
23 | uses: cachix/cachix-action@v10
24 | with:
25 | name: quarry
26 | extraPullNames: saber
27 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
28 | - name: Parse IDLs
29 | run: nix shell .#ci --command ./scripts/parse-idls.sh
30 |
31 | - name: Get yarn cache directory path
32 | id: yarn-cache-dir-path
33 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
34 | - name: Yarn Cache
35 | uses: actions/cache@v3
36 | with:
37 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
38 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
39 | restore-keys: |
40 | ${{ runner.os }}-modules-
41 |
42 | - name: Install Yarn dependencies
43 | run: nix shell .#ci --command yarn install
44 | - run: nix shell .#ci --command ./scripts/generate-idl-types.sh
45 | - run: nix shell .#ci --command yarn build
46 | - run: nix shell .#ci --command yarn typecheck
47 | - run: nix shell .#ci --command yarn lint
48 | - run: nix shell .#ci --command yarn dlx @yarnpkg/doctor
49 |
50 | integration-tests:
51 | runs-on: ubuntu-latest
52 | steps:
53 | - uses: actions/checkout@v3
54 |
55 | # Install Cachix
56 | - uses: cachix/install-nix-action@v17
57 | - name: Setup Cachix
58 | uses: cachix/cachix-action@v10
59 | with:
60 | name: quarry
61 | extraPullNames: saber
62 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
63 |
64 | # Install Rust and Anchor
65 | - name: Install Rust nightly
66 | uses: actions-rs/toolchain@v1
67 | with:
68 | override: true
69 | profile: minimal
70 | toolchain: ${{ env.RUST_TOOLCHAIN }}
71 | - uses: Swatinem/rust-cache@v1
72 | - name: Install Linux dependencies
73 | run: |
74 | sudo apt-get update
75 | sudo apt-get install -y pkg-config build-essential libudev-dev
76 |
77 | # Install Solana
78 | - name: Cache Solana binaries
79 | id: solana-cache
80 | uses: actions/cache@v3
81 | with:
82 | path: |
83 | ~/.cache/solana
84 | ~/.local/share/solana/install
85 | key: ${{ runner.os }}-${{ env.SOLANA_VERSION }}
86 | - name: Install Solana
87 | if: steps.solana-cache.outputs.cache-hit != 'true'
88 | run: |
89 | nix shell .#ci --command solana-install init ${{ env.SOLANA_VERSION }}
90 | - name: Setup Solana Path
91 | run: |
92 | echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
93 | export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH"
94 | solana --version
95 |
96 | # Run build
97 | - name: Build program
98 | run: nix shell .#ci --command anchor build
99 |
100 | - name: Get yarn cache directory path
101 | id: yarn-cache-dir-path
102 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
103 | - name: Yarn Cache
104 | uses: actions/cache@v3
105 | with:
106 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
107 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
108 | restore-keys: |
109 | ${{ runner.os }}-modules-
110 |
111 | - run: nix shell .#ci --command yarn install
112 | - name: Generate IDL types
113 | run: nix shell .#ci --command yarn idl:generate:nolint
114 | - run: nix shell .#ci --command yarn build
115 | - name: Run e2e tests
116 | run: nix shell .#ci --command yarn test:e2e
117 |
--------------------------------------------------------------------------------
/.github/workflows/programs-unit.yml:
--------------------------------------------------------------------------------
1 | name: Unit
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | paths:
7 | - .github/workflows/programs-unit.yml
8 | - programs/**
9 | - Cargo.toml
10 | - Cargo.lock
11 | pull_request:
12 | branches: [master]
13 | paths:
14 | - .github/workflows/programs-unit.yml
15 | - programs/**
16 | - Cargo.toml
17 | - Cargo.lock
18 |
19 | env:
20 | CARGO_TERM_COLOR: always
21 | RUST_TOOLCHAIN: "1.58.1"
22 |
23 | jobs:
24 | lint:
25 | runs-on: ubuntu-latest
26 | steps:
27 | - uses: actions/checkout@v3
28 | - name: Install Rust
29 | uses: actions-rs/toolchain@v1
30 | with:
31 | override: true
32 | profile: minimal
33 | toolchain: ${{ env.RUST_TOOLCHAIN }}
34 | components: rustfmt, clippy
35 | - uses: Swatinem/rust-cache@v1
36 | - name: Run fmt
37 | run: cargo fmt -- --check
38 | - name: Run clippy
39 | run: cargo clippy --all-targets -- --deny=warnings
40 | - name: Check if publish works
41 | run: cargo publish --no-verify --dry-run
42 |
43 | unit-tests:
44 | runs-on: ubuntu-latest
45 | steps:
46 | - uses: actions/checkout@v3
47 | - name: Install Rust
48 | uses: actions-rs/toolchain@v1
49 | with:
50 | override: true
51 | profile: minimal
52 | toolchain: ${{ env.RUST_TOOLCHAIN }}
53 | components: rustfmt, clippy
54 | - uses: Swatinem/rust-cache@v1
55 | - name: Run unit tests
56 | run: cargo test
57 |
58 | doc:
59 | runs-on: ubuntu-latest
60 | steps:
61 | - uses: actions/checkout@v3
62 | - name: Install Rust
63 | uses: actions-rs/toolchain@v1
64 | with:
65 | override: true
66 | profile: minimal
67 | toolchain: ${{ env.RUST_TOOLCHAIN }}
68 | components: rustfmt, clippy
69 | - uses: Swatinem/rust-cache@v1
70 | - name: Generate docs
71 | run: cargo doc
72 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | workflow_dispatch: {}
5 | push:
6 | tags:
7 | - "v*.*.*"
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 | RUST_TOOLCHAIN: nightly-2021-12-10
12 | NPM_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
13 |
14 | jobs:
15 | release-sdk:
16 | runs-on: ubuntu-latest
17 | name: Release SDK on NPM
18 | steps:
19 | - uses: actions/checkout@v3
20 |
21 | - uses: cachix/install-nix-action@v17
22 | - name: Setup Cachix
23 | uses: cachix/cachix-action@v10
24 | with:
25 | name: quarry
26 | extraPullNames: saber
27 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
28 |
29 | - name: Get yarn cache directory path
30 | id: yarn-cache-dir-path
31 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
32 | - name: Yarn Cache
33 | uses: actions/cache@v3
34 | with:
35 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
36 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
37 | restore-keys: |
38 | ${{ runner.os }}-modules-
39 |
40 | - name: Install Yarn dependencies
41 | run: yarn install
42 | - name: Parse IDLs
43 | run: nix shell .#ci --command yarn idl:generate
44 | - run: yarn build
45 | - run: |
46 | echo 'npmAuthToken: "${NPM_AUTH_TOKEN}"' >> .yarnrc.yml
47 | - name: Publish
48 | run: yarn npm publish
49 |
50 | release-crate:
51 | runs-on: ubuntu-latest
52 | name: Release crate on crates.io
53 | steps:
54 | - uses: actions/checkout@v3
55 | - uses: cachix/install-nix-action@v17
56 | - name: Setup Cachix
57 | uses: cachix/cachix-action@v10
58 | with:
59 | name: quarry
60 | extraPullNames: saber
61 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
62 |
63 | - name: Install Rust nightly
64 | uses: actions-rs/toolchain@v1
65 | with:
66 | override: true
67 | profile: minimal
68 | toolchain: ${{ env.RUST_TOOLCHAIN }}
69 | - uses: Swatinem/rust-cache@v1
70 | - name: Publish crates
71 | run: nix shell .#ci --command cargo workspaces publish --from-git --yes --skip-published --token ${{ secrets.CARGO_PUBLISH_TOKEN }}
72 |
73 | release-binaries:
74 | runs-on: ubuntu-latest
75 | name: Release verifiable binaries
76 | steps:
77 | - uses: actions/checkout@v3
78 | - uses: cachix/install-nix-action@v17
79 | - name: Setup Cachix
80 | uses: cachix/cachix-action@v10
81 | with:
82 | name: quarry
83 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
84 |
85 | - name: Build programs
86 | run: nix shell .#ci --command anchor build --verifiable
87 | - name: Release
88 | uses: softprops/action-gh-release@v1
89 | with:
90 | files: |
91 | target/deploy/*
92 | target/idl/*
93 | target/verifiable/*
94 |
95 | site:
96 | runs-on: ubuntu-latest
97 | steps:
98 | - name: Checkout
99 | uses: actions/checkout@v3
100 |
101 | - uses: cachix/install-nix-action@v17
102 | - name: Setup Cachix
103 | uses: cachix/cachix-action@v10
104 | with:
105 | name: quarry
106 | extraPullNames: saber
107 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
108 |
109 | - name: Get yarn cache directory path
110 | id: yarn-cache-dir-path
111 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
112 | - name: Yarn Cache
113 | uses: actions/cache@v3
114 | with:
115 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
116 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
117 | restore-keys: |
118 | ${{ runner.os }}-modules-
119 |
120 | - name: Install Yarn dependencies
121 | run: yarn install
122 | - name: Parse IDLs
123 | run: nix shell .#ci --command yarn idl:generate
124 | - run: yarn docs:generate
125 | - run: cp -R images/ site/
126 |
127 | - name: Deploy 🚀
128 | uses: JamesIves/github-pages-deploy-action@v4.4.0
129 | with:
130 | branch: gh-pages
131 | folder: site
132 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore Mac OS noise
2 | .DS_Store
3 |
4 | # Ignore the build directory for Rust/Anchor
5 | target
6 |
7 | # Ignore backup files creates by cargo fmt.
8 | **/*.rs.bk
9 |
10 | # Ignore logs
11 | .anchor
12 | yarn-error.log
13 | lerna-debug.log
14 |
15 | # Ignore node modules
16 | node_modules
17 | .eslintcache
18 |
19 | # Ignore submodule dependencies
20 | deps
21 |
22 | # VM
23 | .vagrant/
24 | test-ledger/
25 |
26 | # Generated IDL types
27 | artifacts/
28 | dist/
29 | src/idls/
30 |
31 | site/
32 |
33 | .pnp.*
34 | .yarn/*
35 | !.yarn/patches
36 | !.yarn/plugins
37 | !.yarn/releases
38 | !.yarn/sdks
39 | !.yarn/versions
40 |
41 | .coderrect/
42 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn lint-staged
5 |
--------------------------------------------------------------------------------
/.mocharc.cjs:
--------------------------------------------------------------------------------
1 | require("./.pnp.cjs").setup();
2 |
3 | module.exports = {
4 | timeout: 30_000,
5 | require: [require.resolve("ts-node/register")],
6 | };
7 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .yarn
2 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "arcanis.vscode-zipfs",
4 | "dbaeumer.vscode-eslint",
5 | "esbenp.prettier-vscode"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "search.exclude": {
3 | "**/.yarn": true,
4 | "**/.pnp.*": true,
5 | "/dist/**/*": true
6 | },
7 | "eslint.nodePath": ".yarn/sdks",
8 | "typescript.tsdk": ".yarn/sdks/typescript/lib",
9 | "typescript.enablePromptUseWorkspaceTsdk": true,
10 | "prettier.prettierPath": ".yarn/sdks/prettier/index.js"
11 | }
12 |
--------------------------------------------------------------------------------
/.yarn/sdks/eslint/bin/eslint.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const {existsSync} = require(`fs`);
4 | const {createRequire} = require(`module`);
5 | const {resolve} = require(`path`);
6 |
7 | const relPnpApiPath = "../../../../.pnp.cjs";
8 |
9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath);
10 | const absRequire = createRequire(absPnpApiPath);
11 |
12 | if (existsSync(absPnpApiPath)) {
13 | if (!process.versions.pnp) {
14 | // Setup the environment to be able to require eslint/bin/eslint.js
15 | require(absPnpApiPath).setup();
16 | }
17 | }
18 |
19 | // Defer to the real eslint/bin/eslint.js your application uses
20 | module.exports = absRequire(`eslint/bin/eslint.js`);
21 |
--------------------------------------------------------------------------------
/.yarn/sdks/eslint/lib/api.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const {existsSync} = require(`fs`);
4 | const {createRequire} = require(`module`);
5 | const {resolve} = require(`path`);
6 |
7 | const relPnpApiPath = "../../../../.pnp.cjs";
8 |
9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath);
10 | const absRequire = createRequire(absPnpApiPath);
11 |
12 | if (existsSync(absPnpApiPath)) {
13 | if (!process.versions.pnp) {
14 | // Setup the environment to be able to require eslint
15 | require(absPnpApiPath).setup();
16 | }
17 | }
18 |
19 | // Defer to the real eslint your application uses
20 | module.exports = absRequire(`eslint`);
21 |
--------------------------------------------------------------------------------
/.yarn/sdks/eslint/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint",
3 | "version": "8.24.0-sdk",
4 | "main": "./lib/api.js",
5 | "type": "commonjs"
6 | }
7 |
--------------------------------------------------------------------------------
/.yarn/sdks/integrations.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by @yarnpkg/sdks.
2 | # Manual changes might be lost!
3 |
4 | integrations:
5 | - vscode
6 |
--------------------------------------------------------------------------------
/.yarn/sdks/prettier/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const {existsSync} = require(`fs`);
4 | const {createRequire} = require(`module`);
5 | const {resolve} = require(`path`);
6 |
7 | const relPnpApiPath = "../../../.pnp.cjs";
8 |
9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath);
10 | const absRequire = createRequire(absPnpApiPath);
11 |
12 | if (existsSync(absPnpApiPath)) {
13 | if (!process.versions.pnp) {
14 | // Setup the environment to be able to require prettier/index.js
15 | require(absPnpApiPath).setup();
16 | }
17 | }
18 |
19 | // Defer to the real prettier/index.js your application uses
20 | module.exports = absRequire(`prettier/index.js`);
21 |
--------------------------------------------------------------------------------
/.yarn/sdks/prettier/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prettier",
3 | "version": "2.7.1-sdk",
4 | "main": "./index.js",
5 | "type": "commonjs"
6 | }
7 |
--------------------------------------------------------------------------------
/.yarn/sdks/typescript/bin/tsc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const {existsSync} = require(`fs`);
4 | const {createRequire} = require(`module`);
5 | const {resolve} = require(`path`);
6 |
7 | const relPnpApiPath = "../../../../.pnp.cjs";
8 |
9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath);
10 | const absRequire = createRequire(absPnpApiPath);
11 |
12 | if (existsSync(absPnpApiPath)) {
13 | if (!process.versions.pnp) {
14 | // Setup the environment to be able to require typescript/bin/tsc
15 | require(absPnpApiPath).setup();
16 | }
17 | }
18 |
19 | // Defer to the real typescript/bin/tsc your application uses
20 | module.exports = absRequire(`typescript/bin/tsc`);
21 |
--------------------------------------------------------------------------------
/.yarn/sdks/typescript/bin/tsserver:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const {existsSync} = require(`fs`);
4 | const {createRequire} = require(`module`);
5 | const {resolve} = require(`path`);
6 |
7 | const relPnpApiPath = "../../../../.pnp.cjs";
8 |
9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath);
10 | const absRequire = createRequire(absPnpApiPath);
11 |
12 | if (existsSync(absPnpApiPath)) {
13 | if (!process.versions.pnp) {
14 | // Setup the environment to be able to require typescript/bin/tsserver
15 | require(absPnpApiPath).setup();
16 | }
17 | }
18 |
19 | // Defer to the real typescript/bin/tsserver your application uses
20 | module.exports = absRequire(`typescript/bin/tsserver`);
21 |
--------------------------------------------------------------------------------
/.yarn/sdks/typescript/lib/tsc.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const {existsSync} = require(`fs`);
4 | const {createRequire} = require(`module`);
5 | const {resolve} = require(`path`);
6 |
7 | const relPnpApiPath = "../../../../.pnp.cjs";
8 |
9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath);
10 | const absRequire = createRequire(absPnpApiPath);
11 |
12 | if (existsSync(absPnpApiPath)) {
13 | if (!process.versions.pnp) {
14 | // Setup the environment to be able to require typescript/lib/tsc.js
15 | require(absPnpApiPath).setup();
16 | }
17 | }
18 |
19 | // Defer to the real typescript/lib/tsc.js your application uses
20 | module.exports = absRequire(`typescript/lib/tsc.js`);
21 |
--------------------------------------------------------------------------------
/.yarn/sdks/typescript/lib/typescript.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const {existsSync} = require(`fs`);
4 | const {createRequire} = require(`module`);
5 | const {resolve} = require(`path`);
6 |
7 | const relPnpApiPath = "../../../../.pnp.cjs";
8 |
9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath);
10 | const absRequire = createRequire(absPnpApiPath);
11 |
12 | if (existsSync(absPnpApiPath)) {
13 | if (!process.versions.pnp) {
14 | // Setup the environment to be able to require typescript/lib/typescript.js
15 | require(absPnpApiPath).setup();
16 | }
17 | }
18 |
19 | // Defer to the real typescript/lib/typescript.js your application uses
20 | module.exports = absRequire(`typescript/lib/typescript.js`);
21 |
--------------------------------------------------------------------------------
/.yarn/sdks/typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typescript",
3 | "version": "4.8.4-sdk",
4 | "main": "./lib/typescript.js",
5 | "type": "commonjs"
6 | }
7 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: pnp
2 |
3 | yarnPath: .yarn/releases/yarn-4.0.0-rc.22.cjs
4 |
--------------------------------------------------------------------------------
/Anchor.toml:
--------------------------------------------------------------------------------
1 | anchor_version = "0.24.2"
2 | solana_version = "1.10.39"
3 |
4 | [features]
5 | seeds = true
6 |
7 | [scripts]
8 | test = "yarn mocha -b"
9 |
10 | [provider]
11 | cluster = "localnet"
12 | wallet = "./tests/test-key.json"
13 |
14 | [programs.mainnet]
15 | quarry_merge_mine = "QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto"
16 | quarry_mine = "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB"
17 | quarry_mint_wrapper = "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV"
18 | quarry_operator = "QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz"
19 | quarry_redeemer = "QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9"
20 | quarry_registry = "QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc"
21 |
22 | [programs.devnet]
23 | quarry_merge_mine = "QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto"
24 | quarry_mine = "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB"
25 | quarry_mint_wrapper = "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV"
26 | quarry_operator = "QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz"
27 | quarry_redeemer = "QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9"
28 | quarry_registry = "QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc"
29 |
30 | [programs.testnet]
31 | quarry_merge_mine = "QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto"
32 | quarry_mine = "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB"
33 | quarry_mint_wrapper = "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV"
34 | quarry_operator = "QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz"
35 | quarry_redeemer = "QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9"
36 | quarry_registry = "QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc"
37 |
38 | [programs.localnet]
39 | quarry_merge_mine = "QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto"
40 | quarry_mine = "QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB"
41 | quarry_mint_wrapper = "QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV"
42 | quarry_operator = "QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz"
43 | quarry_redeemer = "QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9"
44 | quarry_registry = "QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc"
45 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Quarry Protocol Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## Unreleased Changes
9 |
10 | ## [v5.0.2]
11 |
12 | - Optimizations and bug fixes
13 |
14 | ## [v5.0.1]
15 |
16 | - Optimizations and bug fixes
17 |
18 | ## [v5.0.0]
19 |
20 | ### Features
21 |
22 | - New variants of many instructions reduce the number of accounts and the need to supply the bump.
23 | - Add `quarry_mine::claim_rewards_v2` instruction, which reduces the required accounts for claiming rewards by 2 (64 bytes).
24 | - Add `quarry_mine::create_quarry_v2` instruction, which reduces the required accounts for creating a new quarry by 1 (32 bytes).
25 | - Add `quarry_mine::create_miner_v2` instruction, which removes the need to supply the bump seed.
26 | - Add `quarry_mine::create_rewarder_v2` instruction, which removes the need to supply the bump seed and clock (32 bytes).
27 | - Add `quarry_operator::delegate_create_quarry_v2` instruction, which calls `create_quarry_v2`.
28 | - And more
29 |
30 | ### Breaking
31 |
32 | - Rename `stake` to `claim` in `quarry_mine::claim_rewards`.
33 | - Rename `Miner.quarry_key` to `Miner.quarry` in `quarry_mine`.
34 | - Rename `Quarry.rewarder_key` to `Quarry.rewarder` in `quarry_mine`.
35 |
36 | ## [v4.2.1]
37 |
38 | ### Features
39 |
40 | - Update to Anchor v0.24.
41 | - Add support for Neodyme's [security.txt](https://github.com/neodyme-labs/solana-security-txt) standard.
42 |
43 | ## [v4.2.0]
44 |
45 | ### Features
46 |
47 | - Publicly release Soteria audit code changes.
48 |
49 | ## [v4.1.0]
50 |
51 | ### Features
52 |
53 | - Allow rescuing stuck tokens from Quarry mines ([#454](https://github.com/QuarryProtocol/quarry/pull/454)).
54 |
55 | ## [v4.0.0]
56 |
57 | ### Breaking
58 |
59 | - Upgrade to Anchor v0.23.0 ([#447](https://github.com/QuarryProtocol/quarry/pull/447)).
60 |
61 | ## [v3.0.0]
62 |
63 | ### Breaking
64 |
65 | - Upgrade to Anchor v0.22.0 ([#409](https://github.com/QuarryProtocol/quarry/pull/409)).
66 |
67 | ## [v2.0.1]
68 |
69 | Fixed Cargo.toml dependency references.
70 |
71 | ## [v2.0.0]
72 |
73 | ### Fixes
74 |
75 | - Upgrade to Vipers v1.6 ([#397](https://github.com/QuarryProtocol/quarry/pull/397)).
76 |
77 | ### Breaking
78 |
79 | - Upgrade to Anchor v0.21.0 ([#397](https://github.com/QuarryProtocol/quarry/pull/397)).
80 |
--------------------------------------------------------------------------------
/Captain.toml:
--------------------------------------------------------------------------------
1 | [paths]
2 | artifacts = "../program-artifacts"
3 | program_keypairs = "~/deployments/quarry/programs"
4 |
5 | [networks.devnet]
6 | deployer = "~/deployments/quarry/deployers/devnet.json"
7 | upgrade_authority = "Hqedjjj4JGpAvhLXRsfLHatBgikGarfwHFDFjLsmth2y"
8 | url = "https://api.devnet.solana.com"
9 |
10 | [networks.testnet]
11 | deployer = "~/deployments/quarry/deployers/testnet.json"
12 | upgrade_authority = "Hqedjjj4JGpAvhLXRsfLHatBgikGarfwHFDFjLsmth2y"
13 | url = "https://api.testnet.solana.com"
14 |
15 | [networks.mainnet]
16 | deployer = "~/deployments/quarry/deployers/mainnet.json"
17 | upgrade_authority = "Hqedjjj4JGpAvhLXRsfLHatBgikGarfwHFDFjLsmth2y"
18 | url = "https://api.mainnet-beta.solana.com"
19 |
20 | [networks.localnet]
21 | deployer = "~/deployments/quarry/deployers/localnet.json"
22 | upgrade_authority = "Hqedjjj4JGpAvhLXRsfLHatBgikGarfwHFDFjLsmth2y"
23 | url = "http://localhost:8899"
24 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["programs/*"]
3 |
4 | [profile.release]
5 | lto = "fat"
6 | codegen-units = 1
7 |
8 | [profile.release.build-override]
9 | opt-level = 3
10 | incremental = false
11 | codegen-units = 1
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ⛏ Quarry
2 |
3 | [](https://github.com/QuarryProtocol/quarry/blob/master/LICENSE)
4 | [](https://github.com/QuarryProtocol/quarry/actions/workflows/programs-e2e.yml?query=branch%3Amaster)
5 | [](https://github.com/QuarryProtocol/quarry/graphs/contributors)
6 |
7 |
8 |
9 |
10 |
11 |
12 | An open protocol for launching liquidity mining programs on Solana.
13 |
14 |
15 | ## Background
16 |
17 | Quarry was built with the intention of helping more Solana projects launch on-chain liquidity mining programs. It is currently standard for projects to manually send tokens to addresses-- while this is better than no distribution, it would be much better for the growth of the ecosystem if liquidity mining programs were composable and enforceable on-chain.
18 |
19 | ## Audit
20 |
21 | Quarry Protocol has been audited by [Quantstamp](https://quantstamp.com/). View the audit report
22 | [here](https://github.com/QuarryProtocol/quarry/blob/master/audit/quantstamp.pdf).
23 |
24 | ## Packages
25 |
26 | | Package | Description | Version | Docs |
27 | | :--------------------------- | :---------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------- |
28 | | `quarry-merge-mine` | Mines multiple quarries at the same time | [](https://crates.io/crates/quarry-merge-mine) | [](https://docs.rs/quarry-merge-mine) |
29 | | `quarry-mine` | Distributes liquidity mining rewards to token stakers | [](https://crates.io/crates/quarry-mine) | [](https://docs.rs/quarry-mine) |
30 | | `quarry-mint-wrapper` | Mints tokens to authorized accounts | [](https://crates.io/crates/quarry-mint-wrapper) | [](https://docs.rs/quarry-mint-wrapper) |
31 | | `quarry-operator` | Delegates Quarry Rewarder authority roles. | [](https://crates.io/crates/quarry-operator) | [](https://docs.rs/quarry-operator) |
32 | | `quarry-redeemer` | Redeems one token for another | [](https://crates.io/crates/quarry-redeemer) | [](https://docs.rs/quarry-redeemer) |
33 | | `quarry-registry` | Registry to index all quarries of a rewarder. | [](https://crates.io/crates/quarry-registry) | [](https://docs.rs/quarry-registry) |
34 | | `@quarryprotocol/quarry-sdk` | TypeScript SDK for Quarry | [](https://www.npmjs.com/package/@quarryprotocol/quarry-sdk) | [](https://docs.quarry.so/ts/) |
35 |
36 | ## Addresses
37 |
38 | Program addresses are the same on devnet, testnet, and mainnet-beta.
39 |
40 | - MergeMine: [`QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto`](https://explorer.solana.com/address/QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto)
41 | - Mine: [`QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB`](https://explorer.solana.com/address/QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB)
42 | - MintWrapper: [`QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV`](https://explorer.solana.com/address/QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV)
43 | - Operator: [`QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz`](https://explorer.solana.com/address/QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz)
44 | - Redeemer: [`QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9`](https://explorer.solana.com/address/QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9)
45 | - Registry: [`QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc`](https://explorer.solana.com/address/QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc)
46 |
47 | ## Documentation
48 |
49 | Documentation is a work in progress. For now, one should read [the end-to-end tests of the SDK](/tests/mintWrapper.spec.ts).
50 |
51 | We soon plan on releasing a React library to make it easy to integrate Quarry with your frontend.
52 |
53 | ## License
54 |
55 | Quarry Protocol is licensed under the GNU Affero General Public License v3.0.
56 |
57 | In short, this means that any changes to this code must be made open source and available under the AGPL-v3.0 license, even if only used privately. If you have a need to use this program and cannot respect the terms of the license, please message us our legal team directly at [legal@quarry.so](mailto:legal@quarry.so).
58 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting security problems to Quarry Protocol
4 |
5 | **DO NOT CREATE AN ISSUE** to report a security problem. Instead, please send an
6 | email to team@quarry.so and provide your GitHub username so we can add you
7 | to a new draft security advisory for further discussion.
8 |
9 | Expect a response as fast as possible, typically within 72 hours.
10 |
--------------------------------------------------------------------------------
/audit/quantstamp.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuarryProtocol/quarry/df6a75aa7fe53236cf41fa8ba7f87657bd9ec7ae/audit/quantstamp.pdf
--------------------------------------------------------------------------------
/ci.nix:
--------------------------------------------------------------------------------
1 | { pkgs, saber-pkgs }:
2 |
3 | with saber-pkgs;
4 |
5 | pkgs.buildEnv {
6 | name = "ci";
7 | paths = with pkgs;
8 | (pkgs.lib.optionals pkgs.stdenv.isLinux [ udev ]) ++ [
9 | anchor-0_24_2
10 | cargo-workspaces
11 | solana-1_10-basic
12 |
13 | nodejs
14 | yarn
15 | python3
16 |
17 | pkg-config
18 | openssl
19 | jq
20 | gnused
21 |
22 | libiconv
23 | ] ++ (pkgs.lib.optionals pkgs.stdenv.isDarwin [
24 | pkgs.darwin.apple_sdk.frameworks.AppKit
25 | pkgs.darwin.apple_sdk.frameworks.IOKit
26 | pkgs.darwin.apple_sdk.frameworks.Foundation
27 | ]);
28 | }
29 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "locked": {
5 | "lastModified": 1659877975,
6 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
7 | "owner": "numtide",
8 | "repo": "flake-utils",
9 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "numtide",
14 | "repo": "flake-utils",
15 | "type": "github"
16 | }
17 | },
18 | "flake-utils_2": {
19 | "locked": {
20 | "lastModified": 1659877975,
21 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
22 | "owner": "numtide",
23 | "repo": "flake-utils",
24 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
25 | "type": "github"
26 | },
27 | "original": {
28 | "owner": "numtide",
29 | "repo": "flake-utils",
30 | "type": "github"
31 | }
32 | },
33 | "flake-utils_3": {
34 | "locked": {
35 | "lastModified": 1656928814,
36 | "narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
37 | "owner": "numtide",
38 | "repo": "flake-utils",
39 | "rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
40 | "type": "github"
41 | },
42 | "original": {
43 | "owner": "numtide",
44 | "repo": "flake-utils",
45 | "type": "github"
46 | }
47 | },
48 | "goki-cli": {
49 | "flake": false,
50 | "locked": {
51 | "lastModified": 1656706106,
52 | "narHash": "sha256-gxavNplCWTQF3pr8njtgN6FT1PGoHy6qxfJkRbMmUtE=",
53 | "owner": "GokiProtocol",
54 | "repo": "goki-cli",
55 | "rev": "d044040f583c2bb666375b67a25795540f4eefdd",
56 | "type": "github"
57 | },
58 | "original": {
59 | "owner": "GokiProtocol",
60 | "ref": "master",
61 | "repo": "goki-cli",
62 | "type": "github"
63 | }
64 | },
65 | "nixpkgs": {
66 | "locked": {
67 | "lastModified": 1664356419,
68 | "narHash": "sha256-PD0hM9YWp2lepAJk7edh8g1VtzJip5rals1fpoQUlY0=",
69 | "owner": "NixOS",
70 | "repo": "nixpkgs",
71 | "rev": "46e8398474ac3b1b7bb198bf9097fc213bbf59b1",
72 | "type": "github"
73 | },
74 | "original": {
75 | "owner": "NixOS",
76 | "ref": "nixpkgs-unstable",
77 | "repo": "nixpkgs",
78 | "type": "github"
79 | }
80 | },
81 | "nixpkgs_2": {
82 | "locked": {
83 | "lastModified": 1663264531,
84 | "narHash": "sha256-2ncO5chPXlTxaebDlhx7MhL0gOEIWxzSyfsl0r0hxQk=",
85 | "owner": "NixOS",
86 | "repo": "nixpkgs",
87 | "rev": "454887a35de6317a30be284e8adc2d2f6d8a07c4",
88 | "type": "github"
89 | },
90 | "original": {
91 | "owner": "NixOS",
92 | "ref": "nixpkgs-unstable",
93 | "repo": "nixpkgs",
94 | "type": "github"
95 | }
96 | },
97 | "root": {
98 | "inputs": {
99 | "flake-utils": "flake-utils",
100 | "nixpkgs": "nixpkgs",
101 | "saber-overlay": "saber-overlay"
102 | }
103 | },
104 | "rust-overlay": {
105 | "inputs": {
106 | "flake-utils": "flake-utils_3",
107 | "nixpkgs": [
108 | "saber-overlay",
109 | "nixpkgs"
110 | ]
111 | },
112 | "locked": {
113 | "lastModified": 1663297375,
114 | "narHash": "sha256-7pjd2x9fSXXynIzp9XiXjbYys7sR6MKCot/jfGL7dgE=",
115 | "owner": "oxalica",
116 | "repo": "rust-overlay",
117 | "rev": "0678b6187a153eb0baa9688335b002fe14ba6712",
118 | "type": "github"
119 | },
120 | "original": {
121 | "owner": "oxalica",
122 | "repo": "rust-overlay",
123 | "type": "github"
124 | }
125 | },
126 | "saber-overlay": {
127 | "inputs": {
128 | "flake-utils": "flake-utils_2",
129 | "goki-cli": "goki-cli",
130 | "nixpkgs": "nixpkgs_2",
131 | "rust-overlay": "rust-overlay"
132 | },
133 | "locked": {
134 | "lastModified": 1664388865,
135 | "narHash": "sha256-hOP0HHCdP4kqQiRdstUsbpAAQFNjQuxWJq9U3ZQBUcc=",
136 | "owner": "saber-hq",
137 | "repo": "saber-overlay",
138 | "rev": "638cb4ff75bd990ef371e6e2e66872afe08c804e",
139 | "type": "github"
140 | },
141 | "original": {
142 | "owner": "saber-hq",
143 | "repo": "saber-overlay",
144 | "type": "github"
145 | }
146 | }
147 | },
148 | "root": "root",
149 | "version": 7
150 | }
151 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "Quarry Protocol development environment.";
3 |
4 | inputs = {
5 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
6 | saber-overlay.url = "github:saber-hq/saber-overlay";
7 | flake-utils.url = "github:numtide/flake-utils";
8 | };
9 |
10 | outputs = { self, nixpkgs, saber-overlay, flake-utils }:
11 | flake-utils.lib.eachSystem [
12 | "aarch64-darwin"
13 | "x86_64-linux"
14 | "x86_64-darwin"
15 | ]
16 | (system:
17 | let
18 | pkgs = import nixpkgs { inherit system; };
19 | saber-pkgs = saber-overlay.packages.${system};
20 | ci = import ./ci.nix { inherit pkgs saber-pkgs; };
21 | in
22 | {
23 | packages.ci = ci;
24 | devShell = pkgs.stdenvNoCC.mkDerivation {
25 | name = "devshell";
26 | buildInputs = with pkgs; [ ci rustup cargo-deps gh cargo-expand ];
27 | };
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/images/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuarryProtocol/quarry/df6a75aa7fe53236cf41fa8ba7f87657bd9ec7ae/images/banner.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@quarryprotocol/quarry-sdk",
3 | "version": "5.3.0",
4 | "description": "Quarry Protocol SDK",
5 | "keywords": [
6 | "solana",
7 | "anchor",
8 | "quarry"
9 | ],
10 | "exports": {
11 | ".": {
12 | "import": "./dist/esm/index.js",
13 | "require": "./dist/cjs/index.js"
14 | }
15 | },
16 | "main": "dist/cjs/index.js",
17 | "module": "dist/esm/index.js",
18 | "repository": "git@github.com:QuarryProtocol/quarry.git",
19 | "author": "Quarry Protocol ",
20 | "bugs": {
21 | "url": "https://github.com/QuarryProtocol/quarry/issues",
22 | "email": "team@quarry.so"
23 | },
24 | "homepage": "https://quarry.so",
25 | "license": "AGPL-3.0",
26 | "scripts": {
27 | "build": "rm -fr dist/ && tsc -P tsconfig.cjs.json && tsc -P tsconfig.esm.json",
28 | "clean": "rm -fr dist/",
29 | "idl:generate": "./scripts/parse-idls.sh && ./scripts/generate-idl-types.sh",
30 | "idl:generate:nolint": "./scripts/parse-idls.sh && RUN_ESLINT=none ./scripts/generate-idl-types.sh",
31 | "typecheck": "tsc",
32 | "lint": "eslint . --cache",
33 | "lint:ci": "eslint . --max-warnings=0",
34 | "test:e2e": "anchor test --skip-build 'tests/**/*.ts'",
35 | "docs:generate": "typedoc --excludePrivate --includeVersion --out site/ts/ src/index.ts",
36 | "prepare": "husky install"
37 | },
38 | "devDependencies": {
39 | "@project-serum/anchor": "^0.25.0",
40 | "@rushstack/eslint-patch": "^1.2.0",
41 | "@saberhq/anchor-contrib": "^1.14.8",
42 | "@saberhq/chai-solana": "^1.14.8",
43 | "@saberhq/eslint-config": "^2.1.0",
44 | "@saberhq/solana-contrib": "^1.14.8",
45 | "@saberhq/token-utils": "^1.14.8",
46 | "@saberhq/tsconfig": "^2.1.0",
47 | "@solana/web3.js": "^1.63.1",
48 | "@types/bn.js": "^5.1.1",
49 | "@types/chai": "^4.3.3",
50 | "@types/eslint": "^8.4.6",
51 | "@types/mocha": "^9.1.1",
52 | "@types/node": "^18.7.23",
53 | "@types/prettier": "^2.7.1",
54 | "bn.js": "^5.2.1",
55 | "chai": "=4.3.4",
56 | "eslint": "^8.24.0",
57 | "eslint-import-resolver-node": "^0.3.6",
58 | "eslint-plugin-import": "^2.26.0",
59 | "husky": "^8.0.1",
60 | "jsbi": "^4.3.0",
61 | "lint-staged": "^13.0.3",
62 | "mocha": "^10.0.0",
63 | "prettier": "^2.7.1",
64 | "ts-node": "^10.9.1",
65 | "typedoc": "^0.23.15",
66 | "typescript": "^4.8.4"
67 | },
68 | "peerDependencies": {
69 | "@project-serum/anchor": ">=0.24",
70 | "@saberhq/anchor-contrib": "^1.13",
71 | "@saberhq/solana-contrib": "^1.13",
72 | "@saberhq/token-utils": "^1.13",
73 | "@solana/web3.js": "^1.43",
74 | "bn.js": "^5.2.1",
75 | "jsbi": "^3 || ^4"
76 | },
77 | "resolutions": {
78 | "bn.js": "^5.2.1"
79 | },
80 | "publishConfig": {
81 | "access": "public"
82 | },
83 | "files": [
84 | "dist/",
85 | "src/"
86 | ],
87 | "lint-staged": {
88 | "*.{ts,tsx}": "eslint --cache --fix",
89 | "*.{js,json,jsx,html,css,md}": "prettier --write"
90 | },
91 | "dependencies": {
92 | "superstruct": "^0.16.5",
93 | "tiny-invariant": "^1.3.1",
94 | "tslib": "^2.4.0"
95 | },
96 | "packageManager": "yarn@4.0.0-rc.22"
97 | }
98 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quarry-merge-mine"
3 | version = "5.3.0"
4 | description = "Mines multiple quarries at the same time"
5 | homepage = "https://quarry.so"
6 | repository = "https://github.com/QuarryProtocol/quarry"
7 | authors = ["Larry Jarry "]
8 | license = "AGPL-3.0"
9 | keywords = ["solana", "quarry"]
10 | edition = "2021"
11 |
12 | [lib]
13 | crate-type = ["cdylib", "lib"]
14 | name = "quarry_merge_mine"
15 |
16 | [features]
17 | no-entrypoint = []
18 | no-idl = []
19 | cpi = ["no-entrypoint"]
20 | default = []
21 |
22 | [dependencies]
23 | anchor-lang = ">=0.22, <=0.25"
24 | anchor-spl = ">=0.22, <=0.25"
25 | quarry-mine = { path = "../quarry-mine", features = ["cpi"], version = "5.3.0" }
26 | quarry-mint-wrapper = { path = "../quarry-mint-wrapper", features = [
27 | "cpi"
28 | ], version = "5.3.0" }
29 | num-traits = "0.2"
30 | solana-security-txt = "1.0.1"
31 | vipers = "^2.0"
32 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/README.md:
--------------------------------------------------------------------------------
1 | # quarry-merge-mine
2 |
3 | Allows mining multiple quarries simultaneously.
4 |
5 | ## Overview
6 |
7 | The Quarry merge mine program works by defining a `MergePool`, which is a pool of tokens associated with a staked mint, and a `MergeMiner`, which is a user's association with a `MergePool`.
8 |
9 | A merge miner can stake two types of mints:
10 |
11 | - Primary, the underlying staked token.
12 | - Replica, which can only be minted for a pool if there are enough primary tokens.
13 |
14 | There can be an unlimited number of Replica tokens minted, but a merge miner may only mint + stake Replica tokens if it has a corresponding amount of Primary tokens.
15 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/Xargo.toml:
--------------------------------------------------------------------------------
1 | [target.bpfel-unknown-unknown.dependencies.std]
2 | features = []
3 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/account_conversions.rs:
--------------------------------------------------------------------------------
1 | //! Account conversions
2 |
3 | use crate::{
4 | ClaimRewards, InitMiner, QuarryStake, QuarryStakePrimary, QuarryStakeReplica, WithdrawTokens,
5 | };
6 | use anchor_lang::prelude::*;
7 | use anchor_spl::token::{self, TokenAccount, Transfer};
8 |
9 | impl<'info> InitMiner<'info> {
10 | /// Conversion.
11 | pub fn to_create_miner_accounts(&self) -> quarry_mine::cpi::accounts::CreateMiner<'info> {
12 | quarry_mine::cpi::accounts::CreateMiner {
13 | authority: self.mm.to_account_info(),
14 | miner: self.miner.to_account_info(),
15 | quarry: self.quarry.to_account_info(),
16 | rewarder: self.rewarder.to_account_info(),
17 | system_program: self.system_program.to_account_info(),
18 | payer: self.payer.to_account_info(),
19 | token_mint: self.token_mint.to_account_info(),
20 | miner_vault: self.miner_vault.to_account_info(),
21 | token_program: self.token_program.to_account_info(),
22 | }
23 | }
24 | }
25 |
26 | impl<'info> ClaimRewards<'info> {
27 | /// Conversion.
28 | pub fn to_claim_rewards_accounts(&self) -> quarry_mine::cpi::accounts::ClaimRewardsV2<'info> {
29 | quarry_mine::cpi::accounts::ClaimRewardsV2 {
30 | mint_wrapper: self.mint_wrapper.to_account_info(),
31 | claim_fee_token_account: self.claim_fee_token_account.to_account_info(),
32 | mint_wrapper_program: self.mint_wrapper_program.to_account_info(),
33 | minter: self.minter.to_account_info(),
34 | rewards_token_mint: self.rewards_token_mint.to_account_info(),
35 | rewards_token_account: self.rewards_token_account.to_account_info(),
36 | claim: self.stake.gen_user_claim(),
37 | }
38 | }
39 |
40 | /// Conversion.
41 | pub fn to_user_stake_accounts(&self) -> quarry_mine::cpi::accounts::UserStake<'info> {
42 | self.stake.gen_user_stake(&self.stake_token_account)
43 | }
44 | }
45 |
46 | impl<'info> QuarryStake<'info> {
47 | /// Generates the [quarry_mine::UserStake] accounts.
48 | fn gen_user_claim(&self) -> quarry_mine::cpi::accounts::UserClaimV2<'info> {
49 | quarry_mine::cpi::accounts::UserClaimV2 {
50 | authority: self.mm.to_account_info(),
51 | miner: self.miner.to_account_info(),
52 | quarry: self.quarry.to_account_info(),
53 | token_program: self.token_program.to_account_info(),
54 | rewarder: self.rewarder.to_account_info(),
55 | }
56 | }
57 |
58 | /// Generates the [quarry_mine::UserStake] accounts.
59 | fn gen_user_stake(
60 | &self,
61 | token_account: &Account<'info, TokenAccount>,
62 | ) -> quarry_mine::cpi::accounts::UserStake<'info> {
63 | quarry_mine::cpi::accounts::UserStake {
64 | authority: self.mm.to_account_info(),
65 | miner: self.miner.to_account_info(),
66 | quarry: self.quarry.to_account_info(),
67 | miner_vault: self.miner_vault.to_account_info(),
68 | token_account: token_account.to_account_info(),
69 | token_program: self.token_program.to_account_info(),
70 | rewarder: self.rewarder.to_account_info(),
71 | }
72 | }
73 | }
74 |
75 | impl<'info> QuarryStakeReplica<'info> {
76 | /// Conversion.
77 | pub fn to_user_stake_accounts(&self) -> quarry_mine::cpi::accounts::UserStake<'info> {
78 | self.stake.gen_user_stake(&self.replica_mint_token_account)
79 | }
80 |
81 | /// Generates the accounts for minting replica tokens into a pool.
82 | pub fn to_mint_accounts(&self) -> token::MintTo<'info> {
83 | token::MintTo {
84 | mint: self.replica_mint.to_account_info(),
85 | to: self.replica_mint_token_account.to_account_info(),
86 | authority: self.stake.pool.to_account_info(),
87 | }
88 | }
89 |
90 | /// Generates the burn accounts for burning the replica tokens.
91 | pub fn to_burn_accounts(&self) -> token::Burn<'info> {
92 | token::Burn {
93 | mint: self.replica_mint.to_account_info(),
94 | from: self.replica_mint_token_account.to_account_info(),
95 | authority: self.stake.mm.to_account_info(),
96 | }
97 | }
98 | }
99 |
100 | impl<'info> QuarryStakePrimary<'info> {
101 | /// Conversion.
102 | pub fn to_user_stake_accounts(&self) -> quarry_mine::cpi::accounts::UserStake<'info> {
103 | self.stake.gen_user_stake(&self.mm_primary_token_account)
104 | }
105 | }
106 |
107 | impl<'info> WithdrawTokens<'info> {
108 | /// Conversion.
109 | pub fn to_transfer_accounts(&self) -> Transfer<'info> {
110 | Transfer {
111 | from: self.mm_token_account.to_account_info(),
112 | to: self.token_destination.to_account_info(),
113 | authority: self.mm.to_account_info(),
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/events.rs:
--------------------------------------------------------------------------------
1 | //! Events emitted.
2 |
3 | use crate::*;
4 |
5 | /// Emitted when a new [MergePool] is created.
6 | #[event]
7 | pub struct NewMergePoolEvent {
8 | /// The [MergePool].
9 | pub pool: Pubkey,
10 | /// The [Mint] staked into the [MergePool].
11 | pub primary_mint: Pubkey,
12 | }
13 |
14 | /// Emitted when a new [MergeMiner] is created.
15 | #[event]
16 | pub struct InitMergeMinerEvent {
17 | /// The [MergePool].
18 | pub pool: Pubkey,
19 | /// The [MergeMiner].
20 | pub mm: Pubkey,
21 | /// The [Mint] of the primary token.
22 | pub primary_mint: Pubkey,
23 | /// Owner of the [MergeMiner].
24 | pub owner: Pubkey,
25 | }
26 |
27 | /// Emitted when a new [quarry_mine::Miner] is created.
28 | #[event]
29 | pub struct InitMinerEvent {
30 | /// The [MergePool].
31 | pub pool: Pubkey,
32 | /// The [MergeMiner].
33 | pub mm: Pubkey,
34 | /// The [quarry_mine::Miner].
35 | pub miner: Pubkey,
36 | }
37 |
38 | /// Emitted when tokens are staked into the primary [quarry_mine::Miner].
39 | #[event]
40 | pub struct StakePrimaryEvent {
41 | /// The [MergePool].
42 | pub pool: Pubkey,
43 | /// The [MergeMiner].
44 | pub mm: Pubkey,
45 | /// The [quarry_mine::Miner].
46 | pub miner: Pubkey,
47 | /// The owner of the [MergeMiner].
48 | pub owner: Pubkey,
49 | /// Amount staked.
50 | pub amount: u64,
51 | }
52 |
53 | /// Emitted when tokens are staked into the replica [quarry_mine::Miner].
54 | #[event]
55 | pub struct StakeReplicaEvent {
56 | /// The [MergePool].
57 | pub pool: Pubkey,
58 | /// The [MergeMiner].
59 | pub mm: Pubkey,
60 | /// The [quarry_mine::Miner].
61 | pub miner: Pubkey,
62 | /// The owner of the [MergeMiner].
63 | pub owner: Pubkey,
64 | /// Amount staked.
65 | pub amount: u64,
66 | }
67 |
68 | /// Emitted when tokens are unstaked from the primary [quarry_mine::Miner].
69 | #[event]
70 | pub struct UnstakePrimaryEvent {
71 | /// The [MergePool].
72 | pub pool: Pubkey,
73 | /// The [MergeMiner].
74 | pub mm: Pubkey,
75 | /// The [quarry_mine::Miner].
76 | pub miner: Pubkey,
77 | /// The owner of the [MergeMiner].
78 | pub owner: Pubkey,
79 | /// Amount unstaked.
80 | pub amount: u64,
81 | }
82 |
83 | /// Emitted when tokens are unstaked from the replica [quarry_mine::Miner].
84 | #[event]
85 | pub struct UnstakeReplicaEvent {
86 | /// The [MergePool].
87 | pub pool: Pubkey,
88 | /// The [MergeMiner].
89 | pub mm: Pubkey,
90 | /// The [quarry_mine::Miner].
91 | pub miner: Pubkey,
92 | /// The owner of the [MergeMiner].
93 | pub owner: Pubkey,
94 | /// Amount unstaked.
95 | pub amount: u64,
96 | }
97 |
98 | /// Emitted when tokens are withdrawn from a [MergePool].
99 | #[event]
100 | pub struct WithdrawTokensEvent {
101 | /// The [MergePool].
102 | pub pool: Pubkey,
103 | /// The [MergeMiner].
104 | pub mm: Pubkey,
105 | /// The owner of the [MergeMiner].
106 | pub owner: Pubkey,
107 | /// The mint withdrawn.
108 | pub mint: Pubkey,
109 | /// Amount withdrawn.
110 | pub amount: u64,
111 | }
112 |
113 | /// Emitted when tokens are claimed.
114 | #[event]
115 | pub struct ClaimEvent {
116 | /// The [MergePool].
117 | pub pool: Pubkey,
118 | /// The [MergeMiner].
119 | pub mm: Pubkey,
120 | /// The [Mint] claimed.
121 | pub mint: Pubkey,
122 | /// Amount received.
123 | pub amount: u64,
124 | /// Balance before claim.
125 | pub initial_balance: u64,
126 | /// Balance after claim.
127 | pub end_balance: u64,
128 | }
129 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/macros.rs:
--------------------------------------------------------------------------------
1 | //! Macros for [crate::quarry_merge_mine].
2 |
3 | macro_rules! gen_pool_signer_seeds {
4 | ($pool:expr) => {
5 | &[
6 | b"MergePool" as &[u8],
7 | &$pool.primary_mint.to_bytes(),
8 | &[$pool.bump],
9 | ]
10 | };
11 | }
12 |
13 | macro_rules! gen_merge_miner_signer_seeds {
14 | ($miner:expr) => {
15 | &[
16 | b"MergeMiner" as &[u8],
17 | &$miner.pool.to_bytes(),
18 | &$miner.owner.to_bytes(),
19 | &[$miner.bump],
20 | ]
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/processor/claim.rs:
--------------------------------------------------------------------------------
1 | //! Claim-related instructions.
2 |
3 | use crate::{events::*, ClaimRewards};
4 | use anchor_lang::prelude::*;
5 | use vipers::prelude::*;
6 |
7 | /// Claims [quarry_mine] rewards on behalf of the [MergeMiner].
8 | pub fn claim_rewards(ctx: Context) -> Result<()> {
9 | let initial_balance = ctx.accounts.rewards_token_account.amount;
10 |
11 | let mm = &ctx.accounts.stake.mm;
12 | mm.claim_rewards(ctx.accounts)?;
13 |
14 | ctx.accounts.rewards_token_account.reload()?;
15 | let end_balance = ctx.accounts.rewards_token_account.amount;
16 | let amount = unwrap_int!(end_balance.checked_sub(initial_balance));
17 |
18 | emit!(ClaimEvent {
19 | pool: mm.pool,
20 | mm: mm.key(),
21 | mint: ctx.accounts.rewards_token_account.mint,
22 | amount,
23 | initial_balance,
24 | end_balance,
25 | });
26 |
27 | Ok(())
28 | }
29 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/processor/deposit.rs:
--------------------------------------------------------------------------------
1 | //! Deposit-related instructions.
2 |
3 | use crate::{events::*, QuarryStakePrimary, QuarryStakeReplica};
4 | use anchor_lang::prelude::*;
5 | use vipers::prelude::*;
6 |
7 | /// Deposits tokens into the [MergeMiner].
8 | /// Before calling this, the owner should call the [token::transfer] instruction
9 | /// to transfer to the [MergeMiner]'s [MergeMiner]::primary_token_account.
10 | pub fn stake_primary_miner(ctx: Context) -> Result<()> {
11 | let mm = &ctx.accounts.stake.mm;
12 | let amount = mm.stake_max_primary_miner(ctx.accounts)?;
13 |
14 | // Update [MergeMiner]/[MergePool]
15 | let mm = &mut ctx.accounts.stake.mm;
16 | mm.primary_balance = unwrap_int!(mm.primary_balance.checked_add(amount));
17 | let pool = &mut ctx.accounts.stake.pool;
18 | pool.total_primary_balance = unwrap_int!(pool.total_primary_balance.checked_add(amount));
19 |
20 | ctx.accounts.stake.miner.reload()?;
21 | invariant!(
22 | mm.primary_balance == ctx.accounts.stake.miner.balance,
23 | "after ix, mm balance must be miner balance"
24 | );
25 |
26 | emit!(StakePrimaryEvent {
27 | pool: pool.key(),
28 | mm: mm.key(),
29 | miner: ctx.accounts.stake.miner.key(),
30 | owner: mm.owner,
31 | amount,
32 | });
33 |
34 | Ok(())
35 | }
36 |
37 | /// Stakes all possible replica tokens into a [quarry_mine::Quarry].
38 | /// Before calling this, the owner should call the [anchor_spl::token::transfer] instruction
39 | /// to transfer to the [MergeMiner]'s primary token ATA.
40 | pub fn stake_replica_miner(ctx: Context) -> Result<()> {
41 | // ! IMPORTANT NOTE !
42 | // The replica mint issued to this pool could greatly exceed that of the balance.
43 | // However, withdrawals are only possible if all of the replica miners are unstaked,
44 | // since the total outstanding replica tokens issued to a given [MergeMiner] must be zero.
45 |
46 | // This only works because there can only be one Quarry per mint per rewarder.
47 | // If one desires multiple Quarries per mint per rewarder, one should incentivize a wrapper token.
48 |
49 | // pre-instruction checks
50 | let pre_replica_mint_supply = ctx.accounts.replica_mint.supply;
51 | invariant!(
52 | ctx.accounts.stake.miner.balance == ctx.accounts.stake.miner_vault.amount,
53 | "replica miner balance should equal miner_vault balance"
54 | );
55 |
56 | let mm = &ctx.accounts.stake.mm;
57 | let stake_amount = mm.stake_max_replica_miner(ctx.accounts)?;
58 |
59 | // Update replica balance
60 | let mm = &mut ctx.accounts.stake.mm;
61 | mm.replica_balance = unwrap_int!(mm.replica_balance.checked_add(stake_amount));
62 | let pool = &mut ctx.accounts.stake.pool;
63 | pool.total_replica_balance = unwrap_int!(pool.total_replica_balance.checked_add(stake_amount));
64 |
65 | emit!(StakeReplicaEvent {
66 | pool: pool.key(),
67 | mm: mm.key(),
68 | miner: ctx.accounts.stake.miner.key(),
69 | owner: mm.owner,
70 | amount: stake_amount,
71 | });
72 |
73 | // post-instruction checks
74 | post_stake_replica_miner(ctx, pre_replica_mint_supply, stake_amount)?;
75 |
76 | Ok(())
77 | }
78 |
79 | /// Checks run after [crate::quarry_merge_mine::stake_replica_miner].
80 | fn post_stake_replica_miner(
81 | ctx: Context,
82 | pre_replica_mint_supply: u64,
83 | stake_amount: u64,
84 | ) -> Result<()> {
85 | ctx.accounts.stake.miner.reload()?;
86 | ctx.accounts.stake.miner_vault.reload()?;
87 | ctx.accounts.replica_mint.reload()?;
88 | ctx.accounts.replica_mint_token_account.reload()?;
89 | invariant!(
90 | unwrap_int!(ctx
91 | .accounts
92 | .replica_mint
93 | .supply
94 | .checked_sub(pre_replica_mint_supply))
95 | == stake_amount,
96 | "supply increase should equal the stake amount"
97 | );
98 | invariant!(
99 | ctx.accounts.stake.mm.primary_balance == ctx.accounts.stake.miner.balance,
100 | "replica miner balance should equal primary_balance"
101 | );
102 | invariant!(
103 | ctx.accounts.stake.miner.balance == ctx.accounts.stake.miner_vault.amount,
104 | "replica miner balance should equal miner_vault balance"
105 | );
106 | invariant!(
107 | ctx.accounts.replica_mint_token_account.amount == 0,
108 | "mm replica mint balance should be zero"
109 | );
110 |
111 | Ok(())
112 | }
113 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/processor/init.rs:
--------------------------------------------------------------------------------
1 | //! Account initialization-related instructions.
2 |
3 | use crate::{events::*, InitMergeMiner, InitMiner, NewPool};
4 | use anchor_lang::prelude::*;
5 | use vipers::prelude::*;
6 |
7 | /// Creates a new [MergePool].
8 | /// Anyone can call this.
9 | pub fn new_pool(ctx: Context) -> Result<()> {
10 | let pool = &mut ctx.accounts.pool;
11 | pool.primary_mint = ctx.accounts.primary_mint.key();
12 | pool.bump = unwrap_bump!(ctx, "pool");
13 |
14 | pool.replica_mint = ctx.accounts.replica_mint.key();
15 |
16 | pool.mm_count = 0;
17 |
18 | pool.total_primary_balance = 0;
19 | pool.total_replica_balance = 0;
20 |
21 | emit!(NewMergePoolEvent {
22 | pool: pool.key(),
23 | primary_mint: pool.primary_mint,
24 | });
25 |
26 | Ok(())
27 | }
28 |
29 | /// Creates a new [MergeMiner].
30 | /// Anyone can call this.
31 | pub fn init_merge_miner(ctx: Context) -> Result<()> {
32 | let mm = &mut ctx.accounts.mm;
33 |
34 | mm.pool = ctx.accounts.pool.key();
35 | mm.owner = ctx.accounts.owner.key();
36 | mm.bump = unwrap_bump!(ctx, "mm");
37 |
38 | // Track total number of pools.
39 | let pool = &mut ctx.accounts.pool;
40 | mm.index = pool.mm_count;
41 | pool.mm_count = unwrap_int!(pool.mm_count.checked_add(1));
42 |
43 | mm.primary_balance = 0;
44 | mm.replica_balance = 0;
45 |
46 | let primary_mint = ctx.accounts.pool.primary_mint;
47 |
48 | emit!(InitMergeMinerEvent {
49 | mm: mm.key(),
50 | pool: mm.pool,
51 | primary_mint,
52 | owner: mm.owner,
53 | });
54 |
55 | Ok(())
56 | }
57 |
58 | /// Initializes a [quarry_mine::Miner] owned by the [MergeMiner].
59 | pub fn init_miner(ctx: Context) -> Result<()> {
60 | let mm = &ctx.accounts.mm;
61 | mm.init_miner(ctx.accounts)?;
62 |
63 | emit!(InitMinerEvent {
64 | pool: mm.pool,
65 | mm: mm.key(),
66 | miner: ctx.accounts.miner.key()
67 | });
68 |
69 | Ok(())
70 | }
71 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/processor/mod.rs:
--------------------------------------------------------------------------------
1 | //! Processes incoming instructions.
2 | #![deny(clippy::integer_arithmetic, clippy::float_arithmetic)]
3 |
4 | pub(crate) mod claim;
5 | pub(crate) mod deposit;
6 | pub(crate) mod init;
7 | pub mod rescue_tokens;
8 | pub(crate) mod withdraw;
9 |
10 | pub use rescue_tokens::*;
11 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/processor/rescue_tokens.rs:
--------------------------------------------------------------------------------
1 | //! Implementation of the [crate::quarry_merge_mine::rescue_tokens] instruction.
2 |
3 | use crate::*;
4 | use anchor_spl::token;
5 | use quarry_mine::{program::QuarryMine, Miner};
6 |
7 | /// Handler for the [crate::quarry_merge_mine::rescue_tokens] instruction.
8 | pub fn handler(ctx: Context) -> Result<()> {
9 | // Rescue tokens
10 | let seeds = gen_merge_miner_signer_seeds!(ctx.accounts.mm);
11 | let signer_seeds = &[&seeds[..]];
12 | quarry_mine::cpi::rescue_tokens(CpiContext::new_with_signer(
13 | ctx.accounts.quarry_mine_program.to_account_info(),
14 | quarry_mine::cpi::accounts::RescueTokens {
15 | miner: ctx.accounts.miner.to_account_info(),
16 | authority: ctx.accounts.mm.to_account_info(),
17 | miner_token_account: ctx.accounts.miner_token_account.to_account_info(),
18 | destination_token_account: ctx.accounts.destination_token_account.to_account_info(),
19 | token_program: ctx.accounts.token_program.to_account_info(),
20 | },
21 | signer_seeds,
22 | ))
23 | }
24 |
25 | /// Accounts for the [crate::quarry_merge_mine::rescue_tokens] instruction.
26 | #[derive(Accounts)]
27 | pub struct RescueTokens<'info> {
28 | /// The [MergeMiner::owner].
29 | pub mm_owner: Signer<'info>,
30 | /// The [MergePool].
31 | pub merge_pool: Account<'info, MergePool>,
32 | /// The [MergeMiner] (also the [quarry_mine::Miner] authority).
33 | #[account(constraint = mm.pool == merge_pool.key() && mm.owner == mm_owner.key())]
34 | pub mm: Account<'info, MergeMiner>,
35 |
36 | /// Miner holding tokens (owned by the [MergeMiner]).
37 | pub miner: Account<'info, Miner>,
38 | /// [TokenAccount] to withdraw tokens from.
39 | #[account(mut)]
40 | pub miner_token_account: Account<'info, TokenAccount>,
41 | /// [TokenAccount] to withdraw tokens into.
42 | #[account(mut)]
43 | pub destination_token_account: Account<'info, TokenAccount>,
44 | /// The [quarry_mine] program.
45 | pub quarry_mine_program: Program<'info, QuarryMine>,
46 | /// The SPL [token] program.
47 | pub token_program: Program<'info, token::Token>,
48 | }
49 |
50 | impl<'info> Validate<'info> for RescueTokens<'info> {
51 | fn validate(&self) -> Result<()> {
52 | // only callable by merge miner authority
53 | assert_keys_eq!(self.mm_owner, self.mm.owner, Unauthorized);
54 |
55 | // merge pool of the merge miner
56 | assert_keys_eq!(self.merge_pool, self.mm.pool);
57 |
58 | // mm must be authority of the miner
59 | assert_keys_eq!(self.miner.authority, self.mm);
60 |
61 | // miner token vault should be completely unrelated to all accounts
62 | assert_keys_neq!(self.miner.token_vault_key, self.miner_token_account);
63 | assert_keys_neq!(self.miner.token_vault_key, self.destination_token_account);
64 |
65 | // don't allow withdraws back to the miner
66 | assert_keys_neq!(self.miner.token_vault_key, self.destination_token_account);
67 |
68 | // miner token vault should be owned by the miner
69 | assert_keys_eq!(self.miner_token_account.owner, self.miner);
70 |
71 | // ensure correct mint
72 | assert_keys_eq!(
73 | self.miner_token_account.mint,
74 | self.destination_token_account.mint
75 | );
76 |
77 | // cannot be a primary or replica mint
78 | let mint = self.miner_token_account.mint;
79 | assert_keys_neq!(self.merge_pool.primary_mint, mint);
80 | assert_keys_neq!(self.merge_pool.replica_mint, mint);
81 |
82 | Ok(())
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/processor/withdraw.rs:
--------------------------------------------------------------------------------
1 | //! Withdraw-related instructions.
2 |
3 | use crate::{events::*, QuarryStakePrimary, QuarryStakeReplica, WithdrawTokens};
4 | use anchor_lang::prelude::*;
5 | use vipers::prelude::*;
6 |
7 | /// Withdraws tokens from the [MergeMiner].
8 | pub fn unstake_primary_miner(ctx: Context, amount: u64) -> Result<()> {
9 | // Check to see if the [MergeMiner] is fully collateralized after the withdraw
10 | let mm = &ctx.accounts.stake.mm;
11 | invariant!(amount <= mm.primary_balance, InsufficientBalance);
12 |
13 | // There must be zero replica tokens if there is an unstaking of the primary miner.
14 | // This is to ensure that the user cannot mine after withdrawing their stake.
15 | // To perform a partial unstake, one should do a full unstake of each replica miner,
16 | // unstake the desired amount, then stake back into each replica miner.
17 | invariant!(mm.replica_balance == 0, OutstandingReplicaTokens);
18 |
19 | // Withdraw tokens to the [MergeMiner]'s account
20 | mm.unstake_primary_miner(ctx.accounts, amount)?;
21 |
22 | // Update [MergeMiner]/[MergePool]
23 | let mm = &mut ctx.accounts.stake.mm;
24 | mm.primary_balance = unwrap_int!(mm.primary_balance.checked_sub(amount));
25 | let pool = &mut ctx.accounts.stake.pool;
26 | pool.total_primary_balance = unwrap_int!(pool.total_primary_balance.checked_sub(amount));
27 |
28 | ctx.accounts.stake.miner.reload()?;
29 | invariant!(
30 | mm.primary_balance == ctx.accounts.stake.miner.balance,
31 | "after ix, mm balance must be miner balance"
32 | );
33 |
34 | emit!(UnstakePrimaryEvent {
35 | pool: pool.key(),
36 | mm: mm.key(),
37 | miner: ctx.accounts.stake.miner.key(),
38 | owner: mm.owner,
39 | amount,
40 | });
41 |
42 | Ok(())
43 | }
44 |
45 | /// Unstakes all of a [crate::MergeMiner]'s replica tokens for a [quarry_mine::Miner].
46 | pub fn unstake_all_replica_miner(ctx: Context) -> Result<()> {
47 | // pre-instruction checks
48 | let pre_replica_mint_supply = ctx.accounts.replica_mint.supply;
49 | invariant!(
50 | ctx.accounts.stake.miner.balance == ctx.accounts.stake.miner_vault.amount,
51 | "replica miner balance should equal miner_vault balance"
52 | );
53 |
54 | // Check to see if the merge miner is fully collateralized after the withdraw
55 | let mm = &ctx.accounts.stake.mm;
56 |
57 | // Unstake and burn all replica tokens
58 | let amount = mm.unstake_all_and_burn_replica_miner(ctx.accounts)?;
59 |
60 | // Update replica balance
61 | let mm = &mut ctx.accounts.stake.mm;
62 | mm.replica_balance = unwrap_int!(mm.replica_balance.checked_sub(amount));
63 | let pool = &mut ctx.accounts.stake.pool;
64 | pool.total_replica_balance = unwrap_int!(pool.total_replica_balance.checked_sub(amount));
65 |
66 | emit!(UnstakeReplicaEvent {
67 | pool: pool.key(),
68 | mm: mm.key(),
69 | miner: ctx.accounts.stake.miner.key(),
70 | owner: mm.owner,
71 | amount,
72 | });
73 |
74 | // post-instruction checks
75 | post_unstake_replica_miner(ctx, pre_replica_mint_supply, amount)?;
76 |
77 | Ok(())
78 | }
79 |
80 | /// Withdraws tokens from the [MergeMiner].
81 | pub fn withdraw_tokens(ctx: Context) -> Result<()> {
82 | // skip withdrawal if there is nothing to claim
83 | if ctx.accounts.mm_token_account.amount == 0 {
84 | return Ok(());
85 | }
86 | let initial_balance = ctx.accounts.token_destination.amount;
87 |
88 | let mm = &ctx.accounts.mm;
89 | let event = mm.withdraw_tokens(ctx.accounts)?;
90 |
91 | ctx.accounts.mm_token_account.reload()?;
92 | invariant!(
93 | ctx.accounts.mm_token_account.amount == 0,
94 | "balance not empty"
95 | );
96 |
97 | ctx.accounts.token_destination.reload()?;
98 | let expected_dest_balance = unwrap_int!(initial_balance.checked_add(event.amount));
99 | invariant!(
100 | ctx.accounts.token_destination.amount == expected_dest_balance,
101 | "withdraw result invalid"
102 | );
103 |
104 | emit!(event);
105 | Ok(())
106 | }
107 |
108 | /// Checks run after [crate::quarry_merge_mine::unstake_replica_miner].
109 | fn post_unstake_replica_miner(
110 | ctx: Context,
111 | pre_replica_mint_supply: u64,
112 | burn_amount: u64,
113 | ) -> Result<()> {
114 | ctx.accounts.stake.miner.reload()?;
115 | ctx.accounts.stake.miner_vault.reload()?;
116 | ctx.accounts.replica_mint.reload()?;
117 | ctx.accounts.replica_mint_token_account.reload()?;
118 | invariant!(
119 | unwrap_int!(pre_replica_mint_supply.checked_sub(ctx.accounts.replica_mint.supply))
120 | == burn_amount,
121 | "supply increase should equal the stake amount"
122 | );
123 | invariant!(
124 | ctx.accounts.stake.miner.balance == 0,
125 | "replica miner balance should be zero"
126 | );
127 | invariant!(
128 | ctx.accounts.stake.miner.balance == ctx.accounts.stake.miner_vault.amount,
129 | "replica miner balance should equal miner_vault balance"
130 | );
131 | invariant!(
132 | ctx.accounts.replica_mint_token_account.amount == 0,
133 | "mm replica mint balance should be zero"
134 | );
135 |
136 | Ok(())
137 | }
138 |
--------------------------------------------------------------------------------
/programs/quarry-merge-mine/src/state.rs:
--------------------------------------------------------------------------------
1 | //! Struct definitions for accounts that hold state.
2 |
3 | use anchor_lang::prelude::*;
4 |
5 | /// A token that represents a locked other token.
6 | ///
7 | /// To derive the address, use the following code:
8 | /// ```ignore
9 | /// &[
10 | /// b"MergePool" as &[u8],
11 | /// &$pool.primary_mint.to_bytes(),
12 | /// &[$pool.bump],
13 | /// ]
14 | /// ```
15 | #[account]
16 | #[derive(Copy, Debug, Default)]
17 | pub struct MergePool {
18 | /// Mint of the underlying staked token, i.e. the [quarry_mine::Quarry::token_mint_key].
19 | pub primary_mint: Pubkey,
20 | /// Bump seed.
21 | pub bump: u8,
22 |
23 | /// Mint of the replica staked token, i.e. the [quarry_mine::Quarry::token_mint_key] of replicas.
24 | pub replica_mint: Pubkey,
25 | /// Number of [MergeMiner]s tracked by the [MergePool].
26 | pub mm_count: u64,
27 |
28 | /// Total number of primary tokens deposited.
29 | /// Used for TVL calculation.
30 | pub total_primary_balance: u64,
31 | /// Total number of replica tokens deposited.
32 | pub total_replica_balance: u64,
33 |
34 | /// Reserved for future program upgrades.
35 | pub reserved: [u64; 16],
36 | }
37 |
38 | impl MergePool {
39 | pub const LEN: usize = 32 + 1 + 32 + 8 + 8 + 8 + 8 * 16;
40 | }
41 |
42 | /// Enables mining multiple [quarry_mine::Quarry]s simultaneously with only one deposit.
43 | ///
44 | /// To derive the address, use the following code:
45 | /// ```ignore
46 | /// &[
47 | /// b"MergeMiner" as &[u8],
48 | /// &$mm.pool.key().to_bytes(),
49 | /// &$mm.owner.to_bytes(),
50 | /// &[$mm.bump],
51 | /// ]
52 | /// ```
53 | ///
54 | /// Deposits and withdrawals happen in token accounts owned by the [MergeMiner].
55 | /// These accounts do not need to be ATAs; however, ATAs make the accounts easier to
56 | /// locate on the frontend.
57 | ///
58 | /// The accounts serve as intermediate staging accounts for the merge miner: they are not staked
59 | /// and only exist to facilitate the merge miner's operations.
60 | #[account]
61 | #[derive(Copy, Debug, Default)]
62 | pub struct MergeMiner {
63 | /// [MergePool] to mint against.
64 | pub pool: Pubkey,
65 | /// Owner of the [MergeMiner].
66 | pub owner: Pubkey,
67 | /// Bump seed.
68 | pub bump: u8,
69 |
70 | /// The index of the [MergeMiner] within the [MergePool].
71 | pub index: u64,
72 |
73 | /// Amount of tokens staked into the primary quarry.
74 | pub primary_balance: u64,
75 | /// Amount of replica tokens that have been issued to this [MergeMiner].
76 | /// Primary tokens may only be withdrawn if [MergeMiner::primary_balance] == 0 and
77 | /// [MergeMiner::replica_balance] == 0.
78 | pub replica_balance: u64,
79 | }
80 |
81 | impl MergeMiner {
82 | pub const LEN: usize = 32 + 32 + 1 + 8 + 8 + 8;
83 | }
84 |
85 | #[cfg(test)]
86 | mod tests {
87 | use super::*;
88 |
89 | #[test]
90 | fn test_pool_len() {
91 | assert_eq!(
92 | MergePool::default().try_to_vec().unwrap().len(),
93 | MergePool::LEN
94 | );
95 | }
96 |
97 | #[test]
98 | fn test_miner_len() {
99 | assert_eq!(
100 | MergeMiner::default().try_to_vec().unwrap().len(),
101 | MergeMiner::LEN
102 | );
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/programs/quarry-mine/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quarry-mine"
3 | version = "5.3.0"
4 | description = "Distributes liquidity mining rewards to token stakers"
5 | edition = "2021"
6 | homepage = "https://quarry.so"
7 | repository = "https://github.com/QuarryProtocol/quarry"
8 | authors = ["Quarry Protocol "]
9 | license = "AGPL-3.0"
10 | keywords = ["solana", "quarry"]
11 |
12 | [lib]
13 | crate-type = ["cdylib", "lib"]
14 | name = "quarry_mine"
15 |
16 | [features]
17 | no-entrypoint = []
18 | no-idl = []
19 | no-log-ix-name = []
20 | cpi = ["no-entrypoint"]
21 | default = []
22 |
23 | [dependencies]
24 | anchor-lang = ">=0.22, <=0.25"
25 | anchor-spl = ">=0.22, <=0.25"
26 | num-traits = "0.2.14"
27 | quarry-mint-wrapper = { path = "../quarry-mint-wrapper", features = [
28 | "cpi"
29 | ], version = "5.3.0" }
30 | solana-security-txt = "1.0.1"
31 | spl-math = { version = "0.1.0", features = ["no-entrypoint"] }
32 | u128 = "0.1.0"
33 | vipers = "^2.0"
34 |
35 | [dev-dependencies]
36 | proptest = { version = "1.0" }
37 | rand = { version = "0.8.4" }
38 |
--------------------------------------------------------------------------------
/programs/quarry-mine/README.md:
--------------------------------------------------------------------------------
1 | # mine
2 |
3 | Distributes liquidity mining rewards to token stakers.
4 |
5 | ## Overview
6 |
7 | The Quarry mine program starts with a `Rewarder`. A `Rewarder` corresponds to a reward token e.g. SBR, MNDE.
8 |
9 | Once a `Rewarder` is created, we can add `Quarry`s to it. A `Quarry` corresponds to a staking token.
10 |
11 | Finally, users/programs can create `Miner`s which allow a user to stake/unstake tokens to a `Quarry` and claim rewards from a `Quarry`.
--------------------------------------------------------------------------------
/programs/quarry-mine/Xargo.toml:
--------------------------------------------------------------------------------
1 | [target.bpfel-unknown-unknown.dependencies.std]
2 | features = []
3 |
--------------------------------------------------------------------------------
/programs/quarry-mine/proptest-regressions/payroll.txt:
--------------------------------------------------------------------------------
1 | # Seeds for failure cases proptest has generated in the past. It is
2 | # automatically read and these particular cases re-run before any
3 | # novel cases are generated.
4 | #
5 | # It is recommended to check this file in to source control so that
6 | # everyone who runs the test benefits from these saved cases.
7 | cc 4ada3946a7ede73fee165d3b13cb53c3df351fbf723f48ad46c3d1ef0d0a256f # shrinks to num_updates = 0, (final_ts, initial_ts) = (15199438144291, 2738849259763), rewards_rate_per_second = 1371315255, token_decimals = 4, total_tokens_deposited = 3107761270932392106
8 | cc b64e5d674284a59682600ed0ea221b9465c6a69e2aeca3d671adc2ea73228e99 # shrinks to num_updates = 1, (final_ts, initial_ts) = (27111209533329, 1), rewards_rate_per_second = 3039136331, token_decimals = 4, total_tokens_deposited = 2788471575806626412
9 | cc f20f2b7824107ba13d17ca8173b861239de4edf4f30511fb001b40988ce91792 # shrinks to num_updates = 1, (final_ts, initial_ts) = (7625356439134, 1611), rewards_rate_per_second = 30138350, token_decimals = 8, total_tokens_deposited = 2157082376403927701
10 | cc 7c9ccddaccbb4f0a3a9109ca2cef9dd31e6a2a82c70fc745764b78a32a4c531c # shrinks to num_updates = 2, (final_ts, initial_ts) = (9613570958615, 343424694), rewards_rate_per_second = 2588968985, token_decimals = 0, total_tokens_deposited = 9836687756743697831
11 | cc a952fc40edf6c7eb928745ac0898b315f32c3c57d2fac966a8d7e91bebaaad0b # shrinks to num_updates = 1, (final_ts, initial_ts) = (3605737292043, 84), rewards_rate_per_second = 1030293898, token_decimals = 6, (my_tokens_deposited, total_tokens_deposited) = (10030, 16491541337291667669)
12 | cc 87c64cc0e5a3b59a3ca8e97b4bdabcdd9c3ae12026a422150e8a1113c1583570 # shrinks to num_updates = 1, (final_ts, initial_ts) = (3806250565090246311, 319858679), annual_rewards_rate = 5594790057520831918, (my_tokens_deposited, total_tokens_deposited) = (76444, 2737678569997154)
13 | cc 2fc312f5a99ef20fd0ad34cd039f3351d6cdc880ede9bb48cabd1aa920cdc124 # shrinks to num_updates = 1, (final_ts, initial_ts) = (9296907097054, 718167282587), annual_rewards_rate = 2208070880522977962, (my_tokens_deposited, total_tokens_deposited) = (0, 14955045807342075480)
14 | cc 2a292e968d89a1b582df861e518cca4baaa99a52f882bb8cbf380316bd2a6a5a # shrinks to num_updates = 1, (final_ts, initial_ts) = (537627756858611697, 0), annual_rewards_rate = 417008668215532856, (my_tokens_deposited, total_tokens_deposited) = (72078, 26557368220448)
15 | cc 2a7589510258f829772d6f5a8abcf2b6597740873a9cce73499126f681eaa16e # shrinks to annual_rewards_rate = 995219958336016470, rewards_already_earned = 0, (rewards_per_token_paid, rewards_per_token_stored) = (0, 589684104941973), (current_ts, last_checkpoint_ts) = (5359686513117554805, 5359686511981816289), (my_tokens_deposited, total_tokens_deposited) = (514467555477668, 999606550558171)
16 |
--------------------------------------------------------------------------------
/programs/quarry-mine/proptest-regressions/rewarder.txt:
--------------------------------------------------------------------------------
1 | # Seeds for failure cases proptest has generated in the past. It is
2 | # automatically read and these particular cases re-run before any
3 | # novel cases are generated.
4 | #
5 | # It is recommended to check this file in to source control so that
6 | # everyone who runs the test benefits from these saved cases.
7 | cc 8b8660a976cafbc2d2c60844fdddda7ea9311c036bf7a42a8c46e5a0315dfe25 # shrinks to annual_rewards_rate = 3332745090101094, num_quarries = 5535
8 | cc e54cee0080da142876389bb1403901607c5d2ddd6543c0585bc722f63918b752 # shrinks to annual_rewards_rate = 4468164296739927506, num_quarries = 43, total_rewards_shares = 12793151735638951419
9 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/account_validators.rs:
--------------------------------------------------------------------------------
1 | //! Validations for various accounts.
2 |
3 | use anchor_lang::prelude::*;
4 | use vipers::prelude::*;
5 |
6 | use crate::addresses;
7 | use crate::{
8 | AcceptAuthority, ExtractFees, MutableRewarderWithAuthority, MutableRewarderWithPauseAuthority,
9 | ReadOnlyRewarderWithAuthority, SetAnnualRewards, SetFamine, SetPauseAuthority, SetRewardsShare,
10 | TransferAuthority, UpdateQuarryRewards, UserStake,
11 | };
12 |
13 | // --------------------------------
14 | // Rewarder Functions
15 | // --------------------------------
16 |
17 | impl<'info> Validate<'info> for SetPauseAuthority<'info> {
18 | fn validate(&self) -> Result<()> {
19 | self.auth.validate()?;
20 | invariant!(!self.auth.rewarder.is_paused, Paused);
21 | Ok(())
22 | }
23 | }
24 |
25 | impl<'info> Validate<'info> for MutableRewarderWithPauseAuthority<'info> {
26 | fn validate(&self) -> Result<()> {
27 | invariant!(self.pause_authority.is_signer, Unauthorized);
28 | assert_keys_eq!(
29 | self.rewarder.pause_authority,
30 | self.pause_authority,
31 | Unauthorized
32 | );
33 | Ok(())
34 | }
35 | }
36 |
37 | impl<'info> Validate<'info> for TransferAuthority<'info> {
38 | fn validate(&self) -> Result<()> {
39 | self.rewarder.assert_not_paused()?;
40 | invariant!(self.authority.is_signer, Unauthorized);
41 | assert_keys_eq!(self.authority, self.rewarder.authority, Unauthorized);
42 | Ok(())
43 | }
44 | }
45 |
46 | impl<'info> Validate<'info> for AcceptAuthority<'info> {
47 | fn validate(&self) -> Result<()> {
48 | self.rewarder.assert_not_paused()?;
49 | invariant!(
50 | self.rewarder.pending_authority != Pubkey::default(),
51 | PendingAuthorityNotSet
52 | );
53 | assert_keys_eq!(
54 | self.rewarder.pending_authority,
55 | self.authority,
56 | Unauthorized
57 | );
58 | Ok(())
59 | }
60 | }
61 |
62 | impl<'info> Validate<'info> for SetAnnualRewards<'info> {
63 | fn validate(&self) -> Result<()> {
64 | self.auth.rewarder.assert_not_paused()?;
65 | self.auth.validate()?;
66 | Ok(())
67 | }
68 | }
69 |
70 | // --------------------------------
71 | // Quarry functions
72 | // --------------------------------
73 |
74 | impl<'info> Validate<'info> for SetRewardsShare<'info> {
75 | fn validate(&self) -> Result<()> {
76 | assert_keys_eq!(self.quarry.rewarder, self.auth.rewarder);
77 | self.auth.rewarder.assert_not_paused()?;
78 | self.auth.validate()?;
79 | Ok(())
80 | }
81 | }
82 |
83 | impl<'info> Validate<'info> for SetFamine<'info> {
84 | fn validate(&self) -> Result<()> {
85 | assert_keys_eq!(self.quarry.rewarder, self.auth.rewarder);
86 | self.auth.rewarder.assert_not_paused()?;
87 | self.auth.validate()?;
88 | Ok(())
89 | }
90 | }
91 |
92 | impl<'info> Validate<'info> for UpdateQuarryRewards<'info> {
93 | fn validate(&self) -> Result<()> {
94 | assert_keys_eq!(self.quarry.rewarder, self.rewarder);
95 | self.rewarder.assert_not_paused()?;
96 | Ok(())
97 | }
98 | }
99 |
100 | /// --------------------------------
101 | /// Miner functions
102 | /// --------------------------------
103 |
104 | impl<'info> Validate<'info> for UserStake<'info> {
105 | /// Validates the UserStake.
106 | fn validate(&self) -> Result<()> {
107 | self.rewarder.assert_not_paused()?;
108 |
109 | // authority
110 | invariant!(self.authority.is_signer, Unauthorized);
111 | assert_keys_eq!(self.authority, self.miner.authority);
112 |
113 | // quarry
114 | assert_keys_eq!(self.miner.quarry, self.quarry);
115 |
116 | // miner_vault
117 | let staked_mint = self.quarry.token_mint_key;
118 | assert_keys_eq!(self.miner.token_vault_key, self.miner_vault);
119 | assert_keys_eq!(self.miner_vault.mint, staked_mint);
120 | assert_keys_eq!(self.miner_vault.owner, self.miner);
121 |
122 | // token_account
123 | assert_keys_eq!(self.token_account.mint, staked_mint);
124 |
125 | // rewarder
126 | assert_keys_eq!(self.quarry.rewarder, self.rewarder);
127 |
128 | Ok(())
129 | }
130 | }
131 |
132 | impl<'info> Validate<'info> for ExtractFees<'info> {
133 | fn validate(&self) -> Result<()> {
134 | self.rewarder.assert_not_paused()?;
135 |
136 | assert_keys_eq!(
137 | self.claim_fee_token_account,
138 | self.rewarder.claim_fee_token_account
139 | );
140 | assert_keys_eq!(
141 | self.claim_fee_token_account.mint,
142 | self.rewarder.rewards_token_mint
143 | );
144 | invariant!(self.claim_fee_token_account.delegate.is_none());
145 | invariant!(self.claim_fee_token_account.close_authority.is_none());
146 |
147 | assert_keys_eq!(
148 | self.fee_to_token_account.mint,
149 | self.rewarder.rewards_token_mint
150 | );
151 | assert_keys_eq!(self.fee_to_token_account.owner, addresses::FEE_TO);
152 |
153 | assert_keys_eq!(
154 | self.fee_to_token_account.mint,
155 | self.rewarder.rewards_token_mint
156 | );
157 | invariant!(self.fee_to_token_account.delegate.is_none());
158 | invariant!(self.fee_to_token_account.close_authority.is_none());
159 |
160 | assert_keys_neq!(self.claim_fee_token_account, self.fee_to_token_account);
161 |
162 | Ok(())
163 | }
164 | }
165 |
166 | impl<'info> Validate<'info> for MutableRewarderWithAuthority<'info> {
167 | fn validate(&self) -> Result<()> {
168 | invariant!(self.authority.is_signer, Unauthorized);
169 | assert_keys_eq!(self.rewarder.authority, self.authority, Unauthorized);
170 | Ok(())
171 | }
172 | }
173 |
174 | impl<'info> Validate<'info> for ReadOnlyRewarderWithAuthority<'info> {
175 | /// Validates the [crate::Rewarder] is correct.
176 | fn validate(&self) -> Result<()> {
177 | invariant!(self.authority.is_signer, Unauthorized);
178 | assert_keys_eq!(self.authority, self.rewarder.authority);
179 | Ok(())
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/addresses.rs:
--------------------------------------------------------------------------------
1 | //! Contains addresses used for the Quarry program.
2 | //! These addresses are updated via program upgrades.
3 |
4 | use anchor_lang::prelude::*;
5 |
6 | /// Wrapper module.
7 | pub mod fee_to {
8 | use anchor_lang::declare_id;
9 |
10 | declare_id!("4MMZH3ih1aSty2nx4MC3kSR94Zb55XsXnqb5jfEcyHWQ");
11 | }
12 |
13 | /// Wrapper module.
14 | pub mod fee_setter {
15 | use anchor_lang::declare_id;
16 |
17 | declare_id!("4MMZH3ih1aSty2nx4MC3kSR94Zb55XsXnqb5jfEcyHWQ");
18 | }
19 |
20 | /// Account authorized to take fees.
21 | pub static FEE_TO: Pubkey = fee_to::ID;
22 |
23 | /// Account authorized to set fees of a rewarder. Currently unused.
24 | pub static FEE_SETTER: Pubkey = fee_setter::ID;
25 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/instructions/create_miner.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | /// Creates a [Miner] for the given authority.
4 | ///
5 | /// Anyone can call this; this is an associated account.
6 | pub fn handler(ctx: Context) -> Result<()> {
7 | let quarry = &mut ctx.accounts.quarry;
8 | let index = quarry.num_miners;
9 | quarry.num_miners = unwrap_int!(quarry.num_miners.checked_add(1));
10 |
11 | let miner = &mut ctx.accounts.miner;
12 | miner.authority = ctx.accounts.authority.key();
13 | miner.bump = unwrap_bump!(ctx, "miner");
14 | miner.quarry = ctx.accounts.quarry.key();
15 | miner.token_vault_key = ctx.accounts.miner_vault.key();
16 | miner.rewards_earned = 0;
17 | miner.rewards_per_token_paid = 0;
18 | miner.balance = 0;
19 | miner.index = index;
20 |
21 | emit!(MinerCreateEvent {
22 | authority: miner.authority,
23 | quarry: miner.quarry,
24 | miner: miner.key(),
25 | });
26 |
27 | Ok(())
28 | }
29 |
30 | /// Accounts for [quarry_mine::create_miner].
31 | #[derive(Accounts)]
32 | pub struct CreateMiner<'info> {
33 | /// Authority of the [Miner].
34 | pub authority: Signer<'info>,
35 |
36 | /// [Miner] to be created.
37 | #[account(
38 | init,
39 | seeds = [
40 | b"Miner".as_ref(),
41 | quarry.key().to_bytes().as_ref(),
42 | authority.key().to_bytes().as_ref()
43 | ],
44 | bump,
45 | payer = payer,
46 | space = 8 + Miner::LEN
47 | )]
48 | pub miner: Box>,
49 |
50 | /// [Quarry] to create a [Miner] for.
51 | #[account(mut)]
52 | pub quarry: Box>,
53 |
54 | /// [Rewarder].
55 | pub rewarder: Box>,
56 |
57 | /// System program.
58 | pub system_program: Program<'info, System>,
59 |
60 | /// Payer of [Miner] creation.
61 | #[account(mut)]
62 | pub payer: Signer<'info>,
63 |
64 | /// [Mint] of the token to create a [Quarry] for.
65 | pub token_mint: Account<'info, Mint>,
66 |
67 | /// [TokenAccount] holding the token [Mint].
68 | pub miner_vault: Account<'info, TokenAccount>,
69 |
70 | /// SPL Token program.
71 | pub token_program: Program<'info, Token>,
72 | }
73 |
74 | impl<'info> Validate<'info> for CreateMiner<'info> {
75 | fn validate(&self) -> Result<()> {
76 | invariant!(!self.rewarder.is_paused, Paused);
77 | assert_keys_eq!(self.miner_vault.owner, self.miner);
78 | assert_keys_eq!(self.miner_vault.mint, self.token_mint);
79 | invariant!(self.miner_vault.delegate.is_none());
80 | invariant!(self.miner_vault.close_authority.is_none());
81 |
82 | assert_keys_eq!(self.miner_vault.mint, self.quarry.token_mint_key);
83 | assert_keys_eq!(self.quarry.rewarder, self.rewarder);
84 |
85 | Ok(())
86 | }
87 | }
88 |
89 | /// Triggered when a new miner is created.
90 | #[event]
91 | pub struct MinerCreateEvent {
92 | /// Authority of the miner.
93 | #[index]
94 | pub authority: Pubkey,
95 | /// Quarry the miner was created on.
96 | #[index]
97 | pub quarry: Pubkey,
98 | /// The [Miner].
99 | pub miner: Pubkey,
100 | }
101 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/instructions/create_quarry.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | pub fn handler(ctx: Context) -> Result<()> {
4 | let rewarder = &mut ctx.accounts.auth.rewarder;
5 | // Update rewarder's quarry stats
6 | let index = rewarder.num_quarries;
7 | rewarder.num_quarries = unwrap_int!(rewarder.num_quarries.checked_add(1));
8 |
9 | let quarry = &mut ctx.accounts.quarry;
10 | quarry.bump = unwrap_bump!(ctx, "quarry");
11 |
12 | // Set quarry params
13 | quarry.index = index;
14 | quarry.famine_ts = i64::MAX;
15 | quarry.rewarder = rewarder.key();
16 | quarry.annual_rewards_rate = 0;
17 | quarry.rewards_share = 0;
18 | quarry.token_mint_decimals = ctx.accounts.token_mint.decimals;
19 | quarry.token_mint_key = ctx.accounts.token_mint.key();
20 |
21 | let current_ts = Clock::get()?.unix_timestamp;
22 | emit!(QuarryCreateEvent {
23 | token_mint: quarry.token_mint_key,
24 | timestamp: current_ts,
25 | });
26 |
27 | Ok(())
28 | }
29 |
30 | /// Accounts for [quarry_mine::create_quarry].
31 | #[derive(Accounts)]
32 | pub struct CreateQuarry<'info> {
33 | /// [Quarry].
34 | #[account(
35 | init,
36 | seeds = [
37 | b"Quarry".as_ref(),
38 | auth.rewarder.key().to_bytes().as_ref(),
39 | token_mint.key().to_bytes().as_ref()
40 | ],
41 | bump,
42 | payer = payer,
43 | space = 8 + Quarry::LEN
44 | )]
45 | pub quarry: Account<'info, Quarry>,
46 |
47 | /// [Rewarder] authority.
48 | pub auth: MutableRewarderWithAuthority<'info>,
49 |
50 | /// [Mint] of the token to create a [Quarry] for.
51 | pub token_mint: Account<'info, Mint>,
52 |
53 | /// Payer of [Quarry] creation.
54 | #[account(mut)]
55 | pub payer: Signer<'info>,
56 |
57 | /// Unused variable that held the clock. Placeholder.
58 | /// CHECK: OK
59 | pub unused_account: UncheckedAccount<'info>,
60 |
61 | /// System program.
62 | pub system_program: Program<'info, System>,
63 | }
64 |
65 | impl<'info> Validate<'info> for CreateQuarry<'info> {
66 | fn validate(&self) -> Result<()> {
67 | self.auth.validate()?;
68 | invariant!(!self.auth.rewarder.is_paused, Paused);
69 | Ok(())
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/instructions/create_quarry_v2.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | pub fn handler(ctx: Context) -> Result<()> {
4 | let rewarder = &mut ctx.accounts.auth.rewarder;
5 | // Update rewarder's quarry stats
6 | let index = rewarder.num_quarries;
7 | rewarder.num_quarries = unwrap_int!(rewarder.num_quarries.checked_add(1));
8 |
9 | let quarry = &mut ctx.accounts.quarry;
10 | quarry.bump = unwrap_bump!(ctx, "quarry");
11 |
12 | // Set quarry params
13 | quarry.index = index;
14 | quarry.famine_ts = i64::MAX;
15 | quarry.rewarder = rewarder.key();
16 | quarry.annual_rewards_rate = 0;
17 | quarry.rewards_share = 0;
18 | quarry.token_mint_decimals = ctx.accounts.token_mint.decimals;
19 | quarry.token_mint_key = ctx.accounts.token_mint.key();
20 |
21 | let current_ts = Clock::get()?.unix_timestamp;
22 | emit!(QuarryCreateEvent {
23 | token_mint: quarry.token_mint_key,
24 | timestamp: current_ts,
25 | });
26 |
27 | Ok(())
28 | }
29 |
30 | /// Accounts for [quarry_mine::create_quarry_v2].
31 | #[derive(Accounts)]
32 | pub struct CreateQuarryV2<'info> {
33 | /// [Quarry].
34 | #[account(
35 | init,
36 | seeds = [
37 | b"Quarry".as_ref(),
38 | auth.rewarder.key().to_bytes().as_ref(),
39 | token_mint.key().to_bytes().as_ref()
40 | ],
41 | bump,
42 | payer = payer,
43 | space = 8 + Quarry::LEN
44 | )]
45 | pub quarry: Account<'info, Quarry>,
46 |
47 | /// [Rewarder] authority.
48 | pub auth: MutableRewarderWithAuthority<'info>,
49 |
50 | /// [Mint] of the token to create a [Quarry] for.
51 | pub token_mint: Account<'info, Mint>,
52 |
53 | /// Payer of [Quarry] creation.
54 | #[account(mut)]
55 | pub payer: Signer<'info>,
56 |
57 | /// System program.
58 | pub system_program: Program<'info, System>,
59 | }
60 |
61 | impl<'info> Validate<'info> for CreateQuarryV2<'info> {
62 | fn validate(&self) -> Result<()> {
63 | self.auth.validate()?;
64 | invariant!(!self.auth.rewarder.is_paused, Paused);
65 | Ok(())
66 | }
67 | }
68 |
69 | /// Emitted when a new quarry is created.
70 | #[event]
71 | pub struct QuarryCreateEvent {
72 | /// [Mint] of the [Quarry] token.
73 | pub token_mint: Pubkey,
74 | /// When the event took place.
75 | pub timestamp: i64,
76 | }
77 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/instructions/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod claim_rewards;
2 | pub mod claim_rewards_v2;
3 | pub mod create_miner;
4 | pub mod create_quarry;
5 | pub mod create_quarry_v2;
6 | pub mod new_rewarder;
7 | pub mod new_rewarder_v2;
8 | pub mod rescue_tokens;
9 |
10 | pub use claim_rewards::*;
11 | pub use claim_rewards_v2::*;
12 | pub use create_miner::*;
13 | pub use create_quarry::*;
14 | pub use create_quarry_v2::*;
15 | pub use new_rewarder::*;
16 | pub use new_rewarder_v2::*;
17 | pub use rescue_tokens::*;
18 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/instructions/new_rewarder.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | pub fn handler(ctx: Context) -> Result<()> {
4 | let rewarder = &mut ctx.accounts.rewarder;
5 |
6 | rewarder.base = ctx.accounts.base.key();
7 | rewarder.bump = unwrap_bump!(ctx, "rewarder");
8 |
9 | rewarder.authority = ctx.accounts.initial_authority.key();
10 | rewarder.pending_authority = Pubkey::default();
11 |
12 | rewarder.annual_rewards_rate = 0;
13 | rewarder.num_quarries = 0;
14 | rewarder.total_rewards_shares = 0;
15 | rewarder.mint_wrapper = ctx.accounts.mint_wrapper.key();
16 |
17 | rewarder.rewards_token_mint = ctx.accounts.rewards_token_mint.key();
18 |
19 | rewarder.claim_fee_token_account = ctx.accounts.claim_fee_token_account.key();
20 | rewarder.max_claim_fee_millibps = DEFAULT_CLAIM_FEE_MILLIBPS;
21 |
22 | rewarder.pause_authority = Pubkey::default();
23 | rewarder.is_paused = false;
24 |
25 | let current_ts = Clock::get()?.unix_timestamp;
26 | emit!(NewRewarderEvent {
27 | authority: rewarder.authority,
28 | timestamp: current_ts,
29 | });
30 |
31 | Ok(())
32 | }
33 |
34 | /// Accounts for [quarry_mine::new_rewarder].
35 | #[derive(Accounts)]
36 | pub struct NewRewarder<'info> {
37 | /// Base. Arbitrary key.
38 | pub base: Signer<'info>,
39 |
40 | /// [Rewarder] of mines.
41 | #[account(
42 | init,
43 | seeds = [
44 | b"Rewarder".as_ref(),
45 | base.key().to_bytes().as_ref()
46 | ],
47 | bump,
48 | payer = payer,
49 | space = 8 + Rewarder::LEN
50 | )]
51 | pub rewarder: Account<'info, Rewarder>,
52 |
53 | /// Initial authority of the rewarder.
54 | /// CHECK: OK
55 | pub initial_authority: UncheckedAccount<'info>,
56 |
57 | /// Payer of the [Rewarder] initialization.
58 | #[account(mut)]
59 | pub payer: Signer<'info>,
60 |
61 | /// System program.
62 | pub system_program: Program<'info, System>,
63 |
64 | /// Unused variable that held the [Clock]. Placeholder.
65 | /// CHECK: OK
66 | pub unused_account: UncheckedAccount<'info>,
67 |
68 | /// Mint wrapper.
69 | pub mint_wrapper: Account<'info, quarry_mint_wrapper::MintWrapper>,
70 |
71 | /// Rewards token mint.
72 | pub rewards_token_mint: Account<'info, Mint>,
73 |
74 | /// Token account in which the rewards token fees are collected.
75 | pub claim_fee_token_account: Account<'info, TokenAccount>,
76 | }
77 |
78 | impl<'info> Validate<'info> for NewRewarder<'info> {
79 | fn validate(&self) -> Result<()> {
80 | invariant!(self.base.is_signer, Unauthorized);
81 |
82 | assert_keys_eq!(self.mint_wrapper.token_mint, self.rewards_token_mint);
83 | assert_keys_eq!(
84 | self.rewards_token_mint.mint_authority.unwrap(),
85 | self.mint_wrapper
86 | );
87 |
88 | assert_keys_eq!(self.claim_fee_token_account.owner, self.rewarder);
89 | assert_keys_eq!(self.claim_fee_token_account.mint, self.rewards_token_mint);
90 | invariant!(self.claim_fee_token_account.delegate.is_none());
91 | invariant!(self.claim_fee_token_account.close_authority.is_none());
92 |
93 | Ok(())
94 | }
95 | }
96 |
97 | /// Emitted when a new [Rewarder] is created.
98 | #[event]
99 | pub struct NewRewarderEvent {
100 | /// Authority of the rewarder
101 | #[index]
102 | pub authority: Pubkey,
103 | /// When the event occurred.
104 | pub timestamp: i64,
105 | }
106 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/instructions/new_rewarder_v2.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | pub fn handler(ctx: Context) -> Result<()> {
4 | let rewarder = &mut ctx.accounts.rewarder;
5 |
6 | rewarder.base = ctx.accounts.base.key();
7 | rewarder.bump = unwrap_bump!(ctx, "rewarder");
8 |
9 | rewarder.authority = ctx.accounts.initial_authority.key();
10 | rewarder.pending_authority = Pubkey::default();
11 |
12 | rewarder.annual_rewards_rate = 0;
13 | rewarder.num_quarries = 0;
14 | rewarder.total_rewards_shares = 0;
15 | rewarder.mint_wrapper = ctx.accounts.mint_wrapper.key();
16 |
17 | rewarder.rewards_token_mint = ctx.accounts.rewards_token_mint.key();
18 |
19 | rewarder.claim_fee_token_account = ctx.accounts.claim_fee_token_account.key();
20 | rewarder.max_claim_fee_millibps = DEFAULT_CLAIM_FEE_MILLIBPS;
21 |
22 | rewarder.pause_authority = Pubkey::default();
23 | rewarder.is_paused = false;
24 |
25 | let current_ts = Clock::get()?.unix_timestamp;
26 | emit!(NewRewarderEvent {
27 | authority: rewarder.authority,
28 | timestamp: current_ts,
29 | });
30 |
31 | Ok(())
32 | }
33 |
34 | /// Accounts for [quarry_mine::new_rewarder_v2].
35 | #[derive(Accounts)]
36 | pub struct NewRewarderV2<'info> {
37 | /// Base. Arbitrary key.
38 | pub base: Signer<'info>,
39 |
40 | /// [Rewarder] of mines.
41 | #[account(
42 | init,
43 | seeds = [
44 | b"Rewarder".as_ref(),
45 | base.key().to_bytes().as_ref()
46 | ],
47 | bump,
48 | payer = payer,
49 | space = 8 + Rewarder::LEN
50 | )]
51 | pub rewarder: Account<'info, Rewarder>,
52 |
53 | /// Initial authority of the rewarder.
54 | /// CHECK: OK
55 | pub initial_authority: UncheckedAccount<'info>,
56 |
57 | /// Payer of the [Rewarder] initialization.
58 | #[account(mut)]
59 | pub payer: Signer<'info>,
60 |
61 | /// System program.
62 | pub system_program: Program<'info, System>,
63 |
64 | /// Mint wrapper.
65 | pub mint_wrapper: Account<'info, quarry_mint_wrapper::MintWrapper>,
66 |
67 | /// Rewards token mint.
68 | pub rewards_token_mint: Account<'info, Mint>,
69 |
70 | /// Token account in which the rewards token fees are collected.
71 | pub claim_fee_token_account: Account<'info, TokenAccount>,
72 | }
73 |
74 | impl<'info> Validate<'info> for NewRewarderV2<'info> {
75 | fn validate(&self) -> Result<()> {
76 | invariant!(self.base.is_signer, Unauthorized);
77 |
78 | assert_keys_eq!(self.mint_wrapper.token_mint, self.rewards_token_mint);
79 | assert_keys_eq!(
80 | self.rewards_token_mint.mint_authority.unwrap(),
81 | self.mint_wrapper
82 | );
83 |
84 | assert_keys_eq!(self.claim_fee_token_account.owner, self.rewarder);
85 | assert_keys_eq!(self.claim_fee_token_account.mint, self.rewards_token_mint);
86 | invariant!(self.claim_fee_token_account.delegate.is_none());
87 | invariant!(self.claim_fee_token_account.close_authority.is_none());
88 |
89 | Ok(())
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/instructions/rescue_tokens.rs:
--------------------------------------------------------------------------------
1 | //! Implementation of the [crate::quarry_mine::rescue_tokens] instruction.
2 |
3 | use crate::*;
4 |
5 | /// Handler for the [crate::quarry_mine::rescue_tokens] instruction.
6 | pub fn handler(ctx: Context) -> Result<()> {
7 | let seeds = gen_miner_signer_seeds!(ctx.accounts.miner);
8 | let signer_seeds = &[&seeds[..]];
9 |
10 | // Transfer the tokens to the owner of the miner.
11 | token::transfer(
12 | CpiContext::new_with_signer(
13 | ctx.accounts.token_program.to_account_info(),
14 | token::Transfer {
15 | from: ctx.accounts.miner_token_account.to_account_info(),
16 | to: ctx.accounts.destination_token_account.to_account_info(),
17 | authority: ctx.accounts.miner.to_account_info(),
18 | },
19 | signer_seeds,
20 | ),
21 | ctx.accounts.miner_token_account.amount,
22 | )?;
23 |
24 | Ok(())
25 | }
26 |
27 | /// Accounts for the [crate::quarry_mine::rescue_tokens] instruction.
28 | #[derive(Accounts)]
29 | pub struct RescueTokens<'info> {
30 | /// Miner holding tokens.
31 | pub miner: Account<'info, Miner>,
32 | /// Miner authority (i.e. the user).
33 | pub authority: Signer<'info>,
34 | /// [TokenAccount] to withdraw tokens from.
35 | #[account(mut)]
36 | pub miner_token_account: Account<'info, TokenAccount>,
37 | /// [TokenAccount] to withdraw tokens into.
38 | #[account(mut)]
39 | pub destination_token_account: Account<'info, TokenAccount>,
40 | /// The SPL [token] program.
41 | pub token_program: Program<'info, token::Token>,
42 | }
43 |
44 | impl<'info> Validate<'info> for RescueTokens<'info> {
45 | fn validate(&self) -> Result<()> {
46 | // only callable by miner authority
47 | assert_keys_eq!(self.miner.authority, self.authority);
48 |
49 | // miner token vault should be completely unrelated to all accounts
50 | assert_keys_neq!(self.miner.token_vault_key, self.miner_token_account);
51 | assert_keys_neq!(self.miner.token_vault_key, self.destination_token_account);
52 |
53 | // miner token vault should be owned by the miner
54 | assert_keys_eq!(self.miner_token_account.owner, self.miner);
55 |
56 | // ensure correct mint
57 | assert_keys_eq!(
58 | self.miner_token_account.mint,
59 | self.destination_token_account.mint
60 | );
61 | Ok(())
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/macros.rs:
--------------------------------------------------------------------------------
1 | //! Macros.
2 |
3 | /// Generates the signer seeds for a [crate::Rewarder].
4 | #[macro_export]
5 | macro_rules! gen_rewarder_signer_seeds {
6 | ($rewarder:expr) => {
7 | &[
8 | b"Rewarder".as_ref(),
9 | $rewarder.base.as_ref(),
10 | &[$rewarder.bump],
11 | ]
12 | };
13 | }
14 |
15 | /// Generates the signer seeds for a [crate::Miner].
16 | #[macro_export]
17 | macro_rules! gen_miner_signer_seeds {
18 | ($miner:expr) => {
19 | &[
20 | b"Miner".as_ref(),
21 | $miner.quarry.as_ref(),
22 | $miner.authority.as_ref(),
23 | &[$miner.bump],
24 | ]
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/programs/quarry-mine/src/state.rs:
--------------------------------------------------------------------------------
1 | //! State structs.
2 |
3 | use crate::*;
4 |
5 | /// Controls token rewards distribution to all [Quarry]s.
6 | /// The [Rewarder] is also the [quarry_mint_wrapper::Minter] registered to the [quarry_mint_wrapper::MintWrapper].
7 | #[account]
8 | #[derive(Copy, Default, Debug)]
9 | pub struct Rewarder {
10 | /// Random pubkey used for generating the program address.
11 | pub base: Pubkey,
12 | /// Bump seed for program address.
13 | pub bump: u8,
14 |
15 | /// Authority who controls the rewarder
16 | pub authority: Pubkey,
17 | /// Pending authority which must accept the authority
18 | pub pending_authority: Pubkey,
19 |
20 | /// Number of [Quarry]s the [Rewarder] manages.
21 | /// If more than this many [Quarry]s are desired, one can create
22 | /// a second rewarder.
23 | pub num_quarries: u16,
24 | /// Amount of reward tokens distributed per day
25 | pub annual_rewards_rate: u64,
26 | /// Total amount of rewards shares allocated to [Quarry]s
27 | pub total_rewards_shares: u64,
28 | /// Mint wrapper.
29 | pub mint_wrapper: Pubkey,
30 | /// Mint of the rewards token for this [Rewarder].
31 | pub rewards_token_mint: Pubkey,
32 |
33 | /// Claim fees are placed in this account.
34 | pub claim_fee_token_account: Pubkey,
35 | /// Maximum amount of tokens to send to the Quarry DAO on each claim,
36 | /// in terms of milliBPS. 1,000 milliBPS = 1 BPS = 0.01%
37 | /// This is stored on the [Rewarder] to ensure that the fee will
38 | /// not exceed this in the future.
39 | pub max_claim_fee_millibps: u64,
40 |
41 | /// Authority allowed to pause a [Rewarder].
42 | pub pause_authority: Pubkey,
43 | /// If true, all instructions on the [Rewarder] are paused other than [quarry_mine::unpause].
44 | pub is_paused: bool,
45 | }
46 |
47 | impl Rewarder {
48 | pub const LEN: usize = 32 + 1 + 32 + 32 + 2 + 8 + 8 + 32 + 32 + 32 + 8 + 32 + 1;
49 |
50 | /// Asserts that this [Rewarder] is not paused.
51 | pub fn assert_not_paused(&self) -> Result<()> {
52 | invariant!(!self.is_paused, Paused);
53 | Ok(())
54 | }
55 | }
56 |
57 | /// A pool which distributes tokens to its [Miner]s.
58 | #[account]
59 | #[derive(Copy, Default)]
60 | pub struct Quarry {
61 | /// Rewarder which manages this quarry
62 | pub rewarder: Pubkey,
63 | /// LP token this quarry is designated to
64 | pub token_mint_key: Pubkey,
65 | /// Bump.
66 | pub bump: u8,
67 |
68 | /// Index of the [Quarry].
69 | pub index: u16,
70 | /// Decimals on the token [Mint].
71 | pub token_mint_decimals: u8, // This field is never used.
72 | /// Timestamp when quarry rewards cease
73 | pub famine_ts: i64,
74 | /// Timestamp of last checkpoint
75 | pub last_update_ts: i64,
76 | /// Rewards per token stored in the quarry
77 | pub rewards_per_token_stored: u128,
78 | /// Amount of rewards distributed to the quarry per year.
79 | pub annual_rewards_rate: u64,
80 | /// Rewards shared allocated to this quarry
81 | pub rewards_share: u64,
82 |
83 | /// Total number of tokens deposited into the quarry.
84 | pub total_tokens_deposited: u64,
85 | /// Number of [Miner]s.
86 | pub num_miners: u64,
87 | }
88 |
89 | impl Quarry {
90 | pub const LEN: usize = 32 + 32 + 1 + 2 + 1 + 8 + 8 + 16 + 8 + 8 + 8 + 8;
91 | }
92 |
93 | /// An account that has staked tokens into a [Quarry].
94 | #[account]
95 | #[derive(Copy, Default, Debug)]
96 | pub struct Miner {
97 | /// Key of the [Quarry] this [Miner] works on.
98 | pub quarry: Pubkey,
99 | /// Authority who manages this [Miner].
100 | /// All withdrawals of tokens must accrue to [TokenAccount]s owned by this account.
101 | pub authority: Pubkey,
102 |
103 | /// Bump.
104 | pub bump: u8,
105 |
106 | /// [TokenAccount] to hold the [Miner]'s staked LP tokens.
107 | pub token_vault_key: Pubkey,
108 |
109 | /// Stores the amount of tokens that the [Miner] may claim.
110 | /// Whenever the [Miner] claims tokens, this is reset to 0.
111 | pub rewards_earned: u64,
112 |
113 | /// A checkpoint of the [Quarry]'s reward tokens paid per staked token.
114 | ///
115 | /// When the [Miner] is initialized, this number starts at 0.
116 | /// On the first [quarry_mine::stake_tokens], the [Quarry]#update_rewards_and_miner
117 | /// method is called, which updates this checkpoint to the current quarry value.
118 | ///
119 | /// On a [quarry_mine::claim_rewards], the difference in checkpoints is used to calculate
120 | /// the amount of tokens owed.
121 | pub rewards_per_token_paid: u128,
122 |
123 | /// Number of tokens the [Miner] holds.
124 | pub balance: u64,
125 |
126 | /// Index of the [Miner].
127 | pub index: u64,
128 | }
129 |
130 | impl Miner {
131 | pub const LEN: usize = 32 + 32 + 1 + 32 + 8 + 16 + 8 + 8;
132 | }
133 |
134 | #[cfg(test)]
135 | mod tests {
136 | use super::*;
137 |
138 | #[test]
139 | fn test_rewarder_len() {
140 | assert_eq!(
141 | Rewarder::default().try_to_vec().unwrap().len(),
142 | Rewarder::LEN
143 | );
144 | }
145 |
146 | #[test]
147 | fn test_quarry_len() {
148 | assert_eq!(Quarry::default().try_to_vec().unwrap().len(), Quarry::LEN);
149 | }
150 |
151 | #[test]
152 | fn test_miner_len() {
153 | assert_eq!(Miner::default().try_to_vec().unwrap().len(), Miner::LEN);
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/programs/quarry-mint-wrapper/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quarry-mint-wrapper"
3 | version = "5.3.0"
4 | description = "Mints tokens to authorized accounts"
5 | edition = "2021"
6 | homepage = "https://quarry.so"
7 | repository = "https://github.com/QuarryProtocol/quarry"
8 | authors = ["Quarry Protocol "]
9 | license = "AGPL-3.0"
10 | keywords = ["solana", "quarry"]
11 |
12 | [lib]
13 | crate-type = ["cdylib", "lib"]
14 | name = "quarry_mint_wrapper"
15 |
16 | [features]
17 | no-entrypoint = []
18 | no-idl = []
19 | cpi = ["no-entrypoint"]
20 | default = []
21 |
22 | [dependencies]
23 | anchor-lang = ">=0.22, <=0.25"
24 | anchor-spl = ">=0.22, <=0.25"
25 | solana-security-txt = "1.0.1"
26 | vipers = "^2.0"
27 |
--------------------------------------------------------------------------------
/programs/quarry-mint-wrapper/README.md:
--------------------------------------------------------------------------------
1 | # mint-wrapper
2 |
3 | Mints tokens to authorized accounts.
4 |
5 | ## Description
6 |
7 | The `mint-wrapper` program wraps a token mint and authorizes specific accounts to mint tokens up to given allowances.
8 |
9 | The `mint-wrapper` also enforces a hard cap of a token.
10 |
11 | Within the Quarry protocol, this should be used to prevent the `Rewarder` from over-issuing tokens.
12 |
13 | This can also be used for several other use cases, including but not limited to:
14 |
15 | - Allocating funds to a DAO
16 | - Allocating team lockups
17 |
18 | If you're building a use case, please [get in touch with us](mailto:team@quarry.so)!
19 |
20 | ## Roadmap
21 |
22 | Future improvements may include:
23 |
24 | - Allowing transfer of the `mint_authority` to a different address
25 |
--------------------------------------------------------------------------------
/programs/quarry-mint-wrapper/Xargo.toml:
--------------------------------------------------------------------------------
1 | [target.bpfel-unknown-unknown.dependencies.std]
2 | features = []
3 |
--------------------------------------------------------------------------------
/programs/quarry-mint-wrapper/src/account_validators.rs:
--------------------------------------------------------------------------------
1 | //! Validators for mint wrapper accounts.
2 |
3 | use crate::*;
4 |
5 | // --------------------------------
6 | // Instruction account structs
7 | // --------------------------------
8 |
9 | impl<'info> Validate<'info> for MinterUpdate<'info> {
10 | fn validate(&self) -> Result<()> {
11 | self.auth.validate()?;
12 | assert_keys_eq!(self.minter.mint_wrapper, self.auth.mint_wrapper);
13 | Ok(())
14 | }
15 | }
16 |
17 | impl<'info> Validate<'info> for TransferAdmin<'info> {
18 | fn validate(&self) -> Result<()> {
19 | invariant!(self.admin.is_signer, Unauthorized);
20 | assert_keys_eq!(self.admin, self.mint_wrapper.admin);
21 | assert_keys_neq!(self.next_admin, self.mint_wrapper.admin);
22 |
23 | Ok(())
24 | }
25 | }
26 |
27 | impl<'info> Validate<'info> for AcceptAdmin<'info> {
28 | fn validate(&self) -> Result<()> {
29 | invariant!(self.pending_admin.is_signer, Unauthorized);
30 | assert_keys_eq!(self.pending_admin, self.mint_wrapper.pending_admin);
31 | Ok(())
32 | }
33 | }
34 |
35 | impl<'info> Validate<'info> for PerformMint<'info> {
36 | fn validate(&self) -> Result<()> {
37 | invariant!(
38 | self.mint_wrapper.to_account_info().is_writable,
39 | Unauthorized
40 | );
41 | invariant!(self.minter.to_account_info().is_writable, Unauthorized);
42 |
43 | invariant!(self.minter_authority.is_signer, Unauthorized);
44 | invariant!(self.minter.allowance > 0, MinterAllowanceExceeded);
45 | assert_keys_eq!(self.minter.mint_wrapper, self.mint_wrapper);
46 | assert_keys_eq!(
47 | self.minter_authority,
48 | self.minter.minter_authority,
49 | Unauthorized
50 | );
51 | assert_keys_eq!(self.token_mint, self.mint_wrapper.token_mint);
52 | assert_keys_eq!(self.destination.mint, self.token_mint);
53 | Ok(())
54 | }
55 | }
56 |
57 | /// --------------------------------
58 | /// Account Structs
59 | /// --------------------------------
60 |
61 | impl<'info> Validate<'info> for OnlyAdmin<'info> {
62 | fn validate(&self) -> Result<()> {
63 | invariant!(self.admin.is_signer, Unauthorized);
64 | assert_keys_eq!(self.admin, self.mint_wrapper.admin);
65 | Ok(())
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/programs/quarry-mint-wrapper/src/instructions/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod new_minter;
2 | pub mod new_wrapper;
3 | pub use new_minter::*;
4 | pub use new_wrapper::*;
5 |
--------------------------------------------------------------------------------
/programs/quarry-mint-wrapper/src/instructions/new_minter.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | pub fn handler(ctx: Context) -> Result<()> {
4 | let minter = &mut ctx.accounts.minter;
5 |
6 | minter.mint_wrapper = ctx.accounts.auth.mint_wrapper.key();
7 | minter.minter_authority = ctx.accounts.new_minter_authority.key();
8 | minter.bump = unwrap_bump!(ctx, "minter");
9 |
10 | let index = ctx.accounts.auth.mint_wrapper.num_minters;
11 | minter.index = index;
12 |
13 | // update num minters
14 | let mint_wrapper = &mut ctx.accounts.auth.mint_wrapper;
15 | mint_wrapper.num_minters = unwrap_int!(index.checked_add(1));
16 |
17 | minter.allowance = 0;
18 | minter.total_minted = 0;
19 |
20 | emit!(NewMinterEvent {
21 | mint_wrapper: minter.mint_wrapper,
22 | minter: minter.key(),
23 | index: minter.index,
24 | minter_authority: minter.minter_authority,
25 | });
26 | Ok(())
27 | }
28 |
29 | /// Adds a minter.
30 | #[derive(Accounts)]
31 | pub struct NewMinter<'info> {
32 | /// Owner of the [MintWrapper].
33 | pub auth: OnlyAdmin<'info>,
34 |
35 | /// Account to authorize as a minter.
36 | /// CHECK: Can be any Solana account.
37 | pub new_minter_authority: UncheckedAccount<'info>,
38 |
39 | /// Information about the minter.
40 | #[account(
41 | init,
42 | seeds = [
43 | b"MintWrapperMinter".as_ref(),
44 | auth.mint_wrapper.key().to_bytes().as_ref(),
45 | new_minter_authority.key().to_bytes().as_ref()
46 | ],
47 | bump,
48 | payer = payer,
49 | space = 8 + Minter::LEN
50 | )]
51 | pub minter: Account<'info, Minter>,
52 |
53 | /// Payer for creating the minter.
54 | #[account(mut)]
55 | pub payer: Signer<'info>,
56 |
57 | /// System program.
58 | pub system_program: Program<'info, System>,
59 | }
60 |
61 | impl<'info> Validate<'info> for NewMinter<'info> {
62 | fn validate(&self) -> Result<()> {
63 | self.auth.validate()?;
64 | Ok(())
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/programs/quarry-mint-wrapper/src/instructions/new_wrapper.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | pub fn handler(ctx: Context, hard_cap: u64) -> Result<()> {
4 | let mint_wrapper = &mut ctx.accounts.mint_wrapper;
5 | mint_wrapper.base = ctx.accounts.base.key();
6 | mint_wrapper.bump = unwrap_bump!(ctx, "mint_wrapper");
7 | mint_wrapper.hard_cap = hard_cap;
8 | mint_wrapper.admin = ctx.accounts.admin.key();
9 | mint_wrapper.pending_admin = Pubkey::default();
10 | mint_wrapper.token_mint = ctx.accounts.token_mint.key();
11 | mint_wrapper.num_minters = 0;
12 |
13 | mint_wrapper.total_allowance = 0;
14 | mint_wrapper.total_minted = 0;
15 |
16 | emit!(NewMintWrapperEvent {
17 | mint_wrapper: mint_wrapper.key(),
18 | hard_cap,
19 | admin: ctx.accounts.admin.key(),
20 | token_mint: ctx.accounts.token_mint.key()
21 | });
22 |
23 | Ok(())
24 | }
25 |
26 | #[derive(Accounts)]
27 | pub struct NewWrapper<'info> {
28 | /// Base account.
29 | pub base: Signer<'info>,
30 |
31 | #[account(
32 | init,
33 | seeds = [
34 | b"MintWrapper".as_ref(),
35 | base.key().to_bytes().as_ref()
36 | ],
37 | bump,
38 | payer = payer,
39 | space = 8 + MintWrapper::LEN
40 | )]
41 | pub mint_wrapper: Account<'info, MintWrapper>,
42 |
43 | /// CHECK: Admin-to-be of the [MintWrapper].
44 | pub admin: UncheckedAccount<'info>,
45 |
46 | /// Token mint to mint.
47 | pub token_mint: Account<'info, Mint>,
48 |
49 | /// Token program.
50 | pub token_program: Program<'info, Token>,
51 |
52 | /// Payer.
53 | #[account(mut)]
54 | pub payer: Signer<'info>,
55 |
56 | /// System program.
57 | pub system_program: Program<'info, System>,
58 | }
59 |
60 | impl<'info> Validate<'info> for NewWrapper<'info> {
61 | fn validate(&self) -> Result<()> {
62 | assert_keys_eq!(self.token_mint.mint_authority.unwrap(), self.mint_wrapper);
63 | assert_keys_eq!(self.token_mint.freeze_authority.unwrap(), self.mint_wrapper);
64 | Ok(())
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/programs/quarry-mint-wrapper/src/macros.rs:
--------------------------------------------------------------------------------
1 | //! Macros.
2 |
3 | #[macro_export]
4 | macro_rules! gen_wrapper_signer_seeds {
5 | ($wrapper:expr) => {
6 | &[
7 | b"MintWrapper" as &[u8],
8 | &$wrapper.base.to_bytes(),
9 | &[$wrapper.bump],
10 | ]
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/programs/quarry-mint-wrapper/src/state.rs:
--------------------------------------------------------------------------------
1 | //! State structs.
2 |
3 | use crate::*;
4 |
5 | /// Mint wrapper
6 | ///
7 | /// ```ignore
8 | /// seeds = [
9 | /// b"MintWrapper",
10 | /// base.key().to_bytes().as_ref(),
11 | /// &[bump]
12 | /// ],
13 | ///
14 | #[account]
15 | #[derive(Copy, Default, Debug)]
16 | pub struct MintWrapper {
17 | /// Base account.
18 | pub base: Pubkey,
19 | /// Bump for allowing the proxy mint authority to sign.
20 | pub bump: u8,
21 | /// Maximum number of tokens that can be issued.
22 | pub hard_cap: u64,
23 |
24 | /// Admin account.
25 | pub admin: Pubkey,
26 | /// Next admin account.
27 | pub pending_admin: Pubkey,
28 |
29 | /// Mint of the token.
30 | pub token_mint: Pubkey,
31 | /// Number of [Minter]s.
32 | pub num_minters: u64,
33 |
34 | /// Total allowance outstanding.
35 | pub total_allowance: u64,
36 | /// Total amount of tokens minted through the [MintWrapper].
37 | pub total_minted: u64,
38 | }
39 |
40 | impl MintWrapper {
41 | /// Number of bytes that a [MintWrapper] struct takes up.
42 | pub const LEN: usize = 32 + 1 + 8 + 32 + 32 + 32 + 8 + 8 + 8;
43 | }
44 |
45 | /// One who can mint.
46 | ///
47 | /// ```ignore
48 | /// seeds = [
49 | /// b"MintWrapperMinter",
50 | /// auth.mint_wrapper.key().to_bytes().as_ref(),
51 | /// minter_authority.key().to_bytes().as_ref(),
52 | /// &[bump]
53 | /// ],
54 | /// ```
55 | #[account]
56 | #[derive(Copy, Default, Debug)]
57 | pub struct Minter {
58 | /// The mint wrapper.
59 | pub mint_wrapper: Pubkey,
60 | /// Address that can mint.
61 | pub minter_authority: Pubkey,
62 | /// Bump seed.
63 | pub bump: u8,
64 |
65 | /// Auto-incrementing index of the [Minter].
66 | pub index: u64,
67 |
68 | /// Limit of number of tokens that this [Minter] can mint.
69 | pub allowance: u64,
70 | /// Cumulative sum of the number of tokens ever minted by this [Minter].
71 | pub total_minted: u64,
72 | }
73 |
74 | impl Minter {
75 | /// Number of bytes that a [Minter] struct takes up.
76 | pub const LEN: usize = 32 + 32 + 1 + 8 + 8 + 8;
77 | }
78 |
79 | #[cfg(test)]
80 | mod tests {
81 | use super::*;
82 |
83 | #[test]
84 | fn test_mint_wrapper_len() {
85 | assert_eq!(
86 | MintWrapper::default().try_to_vec().unwrap().len(),
87 | MintWrapper::LEN
88 | );
89 | }
90 |
91 | #[test]
92 | fn test_minter_len() {
93 | assert_eq!(Minter::default().try_to_vec().unwrap().len(), Minter::LEN);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/programs/quarry-operator/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quarry-operator"
3 | version = "5.3.0"
4 | description = "Delegates Quarry Rewarder authority roles."
5 | edition = "2021"
6 | homepage = "https://quarry.so"
7 | repository = "https://github.com/QuarryProtocol/quarry"
8 | authors = ["Quarry Protocol "]
9 | license = "AGPL-3.0"
10 | keywords = ["solana", "quarry"]
11 |
12 | [lib]
13 | crate-type = ["cdylib", "lib"]
14 | name = "quarry_operator"
15 |
16 | [features]
17 | no-entrypoint = []
18 | no-idl = []
19 | cpi = ["no-entrypoint"]
20 | default = []
21 |
22 | [dependencies]
23 | anchor-lang = ">=0.22, <=0.25"
24 | anchor-spl = ">=0.22, <=0.25"
25 | quarry-mine = { path = "../quarry-mine", features = ["cpi"], version = "5.3.0" }
26 | solana-security-txt = "1.0.1"
27 | vipers = "^2.0"
28 |
--------------------------------------------------------------------------------
/programs/quarry-operator/README.md:
--------------------------------------------------------------------------------
1 | # `quarry-operator`
2 |
3 | Delegates Quarry Rewarder authority roles.
4 |
5 | This program defines four roles:
6 |
7 | - `admin`, which can update the three authorized roles.
8 | - `rate_setter`, which can modify rates.
9 | - `quarry_creator`, which can create new quarries.
10 | - `share_allocator`, which can choose the number of rewards shares each quarry receives.
11 |
12 | ## Usage
13 |
14 | To use this program
15 |
16 | 1. Generate the PDA of the Operator.
17 | 2. Set the rewarder authority via `quarry_mine::transfer_authority`.
18 | 3. Create the Operator and accept the authority via `quarry_operator::create_operator`.
19 |
--------------------------------------------------------------------------------
/programs/quarry-operator/Xargo.toml:
--------------------------------------------------------------------------------
1 | [target.bpfel-unknown-unknown.dependencies.std]
2 | features = []
3 |
--------------------------------------------------------------------------------
/programs/quarry-operator/src/account_validators.rs:
--------------------------------------------------------------------------------
1 | //! Validators for Quarry operator accounts.
2 |
3 | use crate::*;
4 |
5 | impl<'info> Validate<'info> for SetRole<'info> {
6 | fn validate(&self) -> Result<()> {
7 | assert_keys_eq!(self.operator.admin, self.admin, Unauthorized);
8 | Ok(())
9 | }
10 | }
11 |
12 | impl<'info> Validate<'info> for DelegateSetAnnualRewards<'info> {
13 | fn validate(&self) -> Result<()> {
14 | assert_keys_eq!(
15 | self.with_delegate.operator.rate_setter,
16 | self.with_delegate.delegate,
17 | Unauthorized
18 | );
19 | self.with_delegate.validate()?;
20 | Ok(())
21 | }
22 | }
23 |
24 | impl<'info> Validate<'info> for DelegateSetRewardsShare<'info> {
25 | fn validate(&self) -> Result<()> {
26 | assert_keys_eq!(
27 | self.quarry.rewarder,
28 | self.with_delegate.rewarder,
29 | Unauthorized
30 | );
31 | assert_keys_eq!(
32 | self.with_delegate.operator.share_allocator,
33 | self.with_delegate.delegate,
34 | Unauthorized
35 | );
36 | self.with_delegate.validate()?;
37 | Ok(())
38 | }
39 | }
40 |
41 | impl<'info> Validate<'info> for DelegateSetFamine<'info> {
42 | fn validate(&self) -> Result<()> {
43 | assert_keys_eq!(
44 | self.quarry.rewarder,
45 | self.with_delegate.rewarder,
46 | Unauthorized
47 | );
48 | assert_keys_eq!(
49 | self.with_delegate.operator.share_allocator,
50 | self.with_delegate.delegate,
51 | Unauthorized
52 | );
53 | self.with_delegate.validate()?;
54 | Ok(())
55 | }
56 | }
57 |
58 | impl<'info> Validate<'info> for WithDelegate<'info> {
59 | fn validate(&self) -> Result<()> {
60 | assert_keys_eq!(self.operator.rewarder, self.rewarder);
61 | assert_keys_eq!(self.operator, self.rewarder.authority);
62 | Ok(())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/programs/quarry-operator/src/instructions/create_operator.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | pub fn handler(ctx: Context) -> Result<()> {
4 | let operator = &mut ctx.accounts.operator;
5 | operator.base = ctx.accounts.base.key();
6 | operator.bump = unwrap_bump!(ctx, "operator");
7 |
8 | operator.rewarder = ctx.accounts.rewarder.key();
9 | operator.admin = ctx.accounts.admin.key();
10 |
11 | operator.rate_setter = operator.admin;
12 | operator.quarry_creator = operator.admin;
13 | operator.share_allocator = operator.admin;
14 | operator.record_update()?;
15 |
16 | let signer_seeds: &[&[&[u8]]] = &[gen_operator_signer_seeds!(operator)];
17 | quarry_mine::cpi::accept_authority(CpiContext::new_with_signer(
18 | ctx.accounts.quarry_mine_program.to_account_info(),
19 | quarry_mine::cpi::accounts::AcceptAuthority {
20 | authority: ctx.accounts.operator.to_account_info(),
21 | rewarder: ctx.accounts.rewarder.to_account_info(),
22 | },
23 | signer_seeds,
24 | ))?;
25 |
26 | Ok(())
27 | }
28 |
29 | /// Accounts for [crate::quarry_operator::create_operator].
30 | #[derive(Accounts)]
31 | pub struct CreateOperator<'info> {
32 | /// Base key used to create the [Operator].
33 | pub base: Signer<'info>,
34 | /// Operator PDA.
35 | #[account(
36 | init,
37 | seeds = [
38 | b"Operator".as_ref(),
39 | base.key().to_bytes().as_ref()
40 | ],
41 | bump,
42 | payer = payer,
43 | space = 8 + Operator::LEN
44 | )]
45 | pub operator: Account<'info, Operator>,
46 | /// [Rewarder] of the token.
47 | #[account(mut)]
48 | pub rewarder: Box>,
49 | /// CHECK: The admin to set.
50 | pub admin: UncheckedAccount<'info>,
51 | /// Payer.
52 | #[account(mut)]
53 | pub payer: Signer<'info>,
54 | /// [System] program.
55 | pub system_program: Program<'info, System>,
56 | /// Quarry mine
57 | pub quarry_mine_program: Program<'info, quarry_mine::program::QuarryMine>,
58 | }
59 |
60 | impl<'info> Validate<'info> for CreateOperator<'info> {
61 | fn validate(&self) -> Result<()> {
62 | assert_keys_eq!(
63 | self.operator,
64 | self.rewarder.pending_authority,
65 | PendingAuthorityNotSet
66 | );
67 | Ok(())
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/programs/quarry-operator/src/instructions/delegate_create_quarry.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | /// Calls [quarry_mine::quarry_mine::create_quarry].
4 | pub fn handler(ctx: Context) -> Result<()> {
5 | let operator = &ctx.accounts.with_delegate.operator;
6 | let signer_seeds: &[&[&[u8]]] = &[gen_operator_signer_seeds!(operator)];
7 | quarry_mine::cpi::create_quarry_v2(CpiContext::new_with_signer(
8 | ctx.accounts
9 | .with_delegate
10 | .quarry_mine_program
11 | .to_account_info(),
12 | quarry_mine::cpi::accounts::CreateQuarryV2 {
13 | quarry: ctx.accounts.quarry.to_account_info(),
14 | auth: ctx.accounts.with_delegate.to_auth_accounts(),
15 | token_mint: ctx.accounts.token_mint.to_account_info(),
16 | payer: ctx.accounts.payer.to_account_info(),
17 | system_program: ctx.accounts.system_program.to_account_info(),
18 | },
19 | signer_seeds,
20 | ))?;
21 | Ok(())
22 | }
23 |
24 | /// Accounts for [crate::quarry_operator::delegate_create_quarry_v2].
25 | #[derive(Accounts)]
26 | pub struct DelegateCreateQuarry<'info> {
27 | /// Delegate information.
28 | pub with_delegate: WithDelegate<'info>,
29 |
30 | /// The Quarry to create.
31 | #[account(mut)]
32 | pub quarry: SystemAccount<'info>,
33 |
34 | /// Mint of the Quarry being created.
35 | pub token_mint: Box>,
36 |
37 | /// Payer of [Quarry] creation.
38 | #[account(mut)]
39 | pub payer: Signer<'info>,
40 |
41 | /// Unused variable that held the clock. Placeholder.
42 | /// CHECK: OK
43 | pub unused_account: UncheckedAccount<'info>,
44 |
45 | /// System program.
46 | pub system_program: Program<'info, System>,
47 | }
48 |
49 | impl<'info> Validate<'info> for DelegateCreateQuarry<'info> {
50 | fn validate(&self) -> Result<()> {
51 | assert_keys_eq!(
52 | self.with_delegate.operator.quarry_creator,
53 | self.with_delegate.delegate,
54 | Unauthorized
55 | );
56 | self.with_delegate.validate()?;
57 | Ok(())
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/programs/quarry-operator/src/instructions/delegate_create_quarry_v2.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 |
3 | /// Calls [quarry_mine::quarry_mine::create_quarry].
4 | pub fn handler(ctx: Context) -> Result<()> {
5 | let operator = &ctx.accounts.with_delegate.operator;
6 | let signer_seeds: &[&[&[u8]]] = &[gen_operator_signer_seeds!(operator)];
7 | quarry_mine::cpi::create_quarry_v2(CpiContext::new_with_signer(
8 | ctx.accounts
9 | .with_delegate
10 | .quarry_mine_program
11 | .to_account_info(),
12 | quarry_mine::cpi::accounts::CreateQuarryV2 {
13 | quarry: ctx.accounts.quarry.to_account_info(),
14 | auth: ctx.accounts.with_delegate.to_auth_accounts(),
15 | token_mint: ctx.accounts.token_mint.to_account_info(),
16 | payer: ctx.accounts.payer.to_account_info(),
17 | system_program: ctx.accounts.system_program.to_account_info(),
18 | },
19 | signer_seeds,
20 | ))?;
21 | Ok(())
22 | }
23 |
24 | /// Accounts for [crate::quarry_operator::delegate_create_quarry_v2].
25 | #[derive(Accounts)]
26 | pub struct DelegateCreateQuarryV2<'info> {
27 | /// Delegate information.
28 | pub with_delegate: WithDelegate<'info>,
29 |
30 | /// The Quarry to create.
31 | #[account(mut)]
32 | pub quarry: SystemAccount<'info>,
33 |
34 | /// Mint of the Quarry being created.
35 | pub token_mint: Box>,
36 |
37 | /// Payer of [Quarry] creation.
38 | #[account(mut)]
39 | pub payer: Signer<'info>,
40 |
41 | /// System program.
42 | pub system_program: Program<'info, System>,
43 | }
44 |
45 | impl<'info> Validate<'info> for DelegateCreateQuarryV2<'info> {
46 | fn validate(&self) -> Result<()> {
47 | assert_keys_eq!(
48 | self.with_delegate.operator.quarry_creator,
49 | self.with_delegate.delegate,
50 | Unauthorized
51 | );
52 | self.with_delegate.validate()?;
53 | Ok(())
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/programs/quarry-operator/src/instructions/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod create_operator;
2 | pub mod delegate_create_quarry;
3 | pub mod delegate_create_quarry_v2;
4 |
5 | pub use create_operator::*;
6 | pub use delegate_create_quarry::*;
7 | pub use delegate_create_quarry_v2::*;
8 |
--------------------------------------------------------------------------------
/programs/quarry-operator/src/macros.rs:
--------------------------------------------------------------------------------
1 | //! Macros.
2 |
3 | /// Generates the signer seeds for the [crate::Operator].
4 | #[macro_export]
5 | macro_rules! gen_operator_signer_seeds {
6 | ($operator:expr) => {
7 | &[
8 | b"Operator" as &[u8],
9 | &$operator.base.to_bytes(),
10 | &[$operator.bump],
11 | ]
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/programs/quarry-operator/src/state.rs:
--------------------------------------------------------------------------------
1 | //! State structs.
2 |
3 | pub use crate::*;
4 |
5 | /// Operator state
6 | #[account]
7 | #[derive(Copy, Default, Debug, PartialEq, Eq)]
8 | pub struct Operator {
9 | /// The base.
10 | pub base: Pubkey,
11 | /// Bump seed.
12 | pub bump: u8,
13 |
14 | /// The [Rewarder].
15 | pub rewarder: Pubkey,
16 | /// Can modify the authorities below.
17 | pub admin: Pubkey,
18 |
19 | /// Can call [quarry_mine::quarry_mine::set_annual_rewards].
20 | pub rate_setter: Pubkey,
21 | /// Can call [quarry_mine::quarry_mine::create_quarry].
22 | pub quarry_creator: Pubkey,
23 | /// Can call [quarry_mine::quarry_mine::set_rewards_share].
24 | pub share_allocator: Pubkey,
25 |
26 | /// When the [Operator] was last modified.
27 | pub last_modified_ts: i64,
28 | /// Auto-incrementing sequence number of the set of authorities.
29 | /// Useful for checking if things were updated.
30 | pub generation: u64,
31 | }
32 |
33 | impl Operator {
34 | /// Number of bytes in an [Operator].
35 | pub const LEN: usize = 32 + 1 + 32 + 32 + 32 + 32 + 32 + 8 + 8;
36 |
37 | pub(crate) fn record_update(&mut self) -> Result<()> {
38 | self.last_modified_ts = Clock::get()?.unix_timestamp;
39 | self.generation = unwrap_int!(self.generation.checked_add(1));
40 | Ok(())
41 | }
42 | }
43 |
44 | #[cfg(test)]
45 | mod tests {
46 | use super::*;
47 |
48 | #[test]
49 | fn test_operator_len() {
50 | assert_eq!(
51 | Operator::default().try_to_vec().unwrap().len(),
52 | Operator::LEN
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/programs/quarry-redeemer/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quarry-redeemer"
3 | version = "5.3.0"
4 | description = "Redeems one token for another"
5 | edition = "2021"
6 | homepage = "https://quarry.so"
7 | repository = "https://github.com/QuarryProtocol/quarry"
8 | authors = ["Quarry Protocol "]
9 | license = "AGPL-3.0"
10 | keywords = ["solana", "quarry"]
11 |
12 | [lib]
13 | crate-type = ["cdylib", "lib"]
14 | name = "quarry_redeemer"
15 |
16 | [features]
17 | no-entrypoint = []
18 | no-idl = []
19 | cpi = ["no-entrypoint"]
20 | default = []
21 |
22 | [dependencies]
23 | anchor-lang = ">=0.22, <=0.25"
24 | anchor-spl = ">=0.22, <=0.25"
25 | solana-security-txt = "1.0.1"
26 | vipers = "^2.0"
27 |
--------------------------------------------------------------------------------
/programs/quarry-redeemer/README.md:
--------------------------------------------------------------------------------
1 | # `quarry-redeemer`
2 |
3 | Allows redeeming "IOU" tokens for some underlying token.
4 |
--------------------------------------------------------------------------------
/programs/quarry-redeemer/Xargo.toml:
--------------------------------------------------------------------------------
1 | [target.bpfel-unknown-unknown.dependencies.std]
2 | features = []
3 |
--------------------------------------------------------------------------------
/programs/quarry-redeemer/src/account_validators.rs:
--------------------------------------------------------------------------------
1 | use crate::{CreateRedeemer, RedeemTokens};
2 | use anchor_lang::prelude::*;
3 | use vipers::prelude::*;
4 |
5 | impl<'info> Validate<'info> for CreateRedeemer<'info> {
6 | fn validate(&self) -> Result<()> {
7 | invariant!(
8 | self.iou_mint.decimals == self.redemption_mint.decimals,
9 | "decimals mismatch"
10 | );
11 | Ok(())
12 | }
13 | }
14 |
15 | impl<'info> Validate<'info> for RedeemTokens<'info> {
16 | fn validate(&self) -> Result<()> {
17 | invariant!(self.source_authority.is_signer, Unauthorized);
18 |
19 | assert_keys_eq!(self.iou_mint, self.redeemer.iou_mint);
20 | assert_keys_eq!(self.iou_source.mint, self.redeemer.iou_mint);
21 | assert_keys_eq!(self.iou_source.owner, self.source_authority);
22 |
23 | assert_keys_eq!(self.redemption_vault.owner, self.redeemer);
24 | assert_keys_eq!(self.redemption_vault.mint, self.redeemer.redemption_mint);
25 | invariant!(self.redemption_vault.delegate.is_none());
26 | invariant!(self.redemption_vault.close_authority.is_none());
27 |
28 | assert_keys_neq!(self.redemption_destination, self.redemption_vault);
29 | assert_keys_eq!(
30 | self.redemption_destination.mint,
31 | self.redeemer.redemption_mint
32 | );
33 | assert_keys_eq!(self.redemption_destination.owner, self.source_authority);
34 |
35 | Ok(())
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/programs/quarry-redeemer/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Program for redeeming IOU tokens for an underlying token.
2 | #![deny(rustdoc::all)]
3 | #![allow(rustdoc::missing_doc_code_examples)]
4 |
5 | use anchor_lang::prelude::*;
6 | use anchor_spl::token::{Mint, Token, TokenAccount};
7 | use vipers::prelude::*;
8 |
9 | mod account_validators;
10 | mod macros;
11 | mod redeem_cpi;
12 | mod state;
13 |
14 | pub use state::*;
15 |
16 | declare_id!("QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9");
17 |
18 | #[cfg(not(feature = "no-entrypoint"))]
19 | solana_security_txt::security_txt! {
20 | name: "Quarry Redeemer",
21 | project_url: "https://quarry.so",
22 | contacts: "email:team@quarry.so",
23 | policy: "https://github.com/QuarryProtocol/quarry/blob/master/SECURITY.md",
24 |
25 | source_code: "https://github.com/QuarryProtocol/quarry",
26 | auditors: "Quantstamp"
27 | }
28 |
29 | /// Quarry Redeemer program.
30 | #[program]
31 | pub mod quarry_redeemer {
32 | use super::*;
33 |
34 | /// Creates a new [Redeemer].
35 | #[access_control(ctx.accounts.validate())]
36 | pub fn create_redeemer(ctx: Context, _bump: u8) -> Result<()> {
37 | let redeemer = &mut ctx.accounts.redeemer;
38 | redeemer.iou_mint = ctx.accounts.iou_mint.key();
39 | redeemer.redemption_mint = ctx.accounts.redemption_mint.key();
40 | redeemer.bump = unwrap_bump!(ctx, "redeemer");
41 |
42 | redeemer.total_tokens_redeemed = 0;
43 | Ok(())
44 | }
45 |
46 | /// Redeems some of a user's tokens from the redemption vault.
47 | #[access_control(ctx.accounts.validate())]
48 | pub fn redeem_tokens(ctx: Context, amount: u64) -> Result<()> {
49 | invariant!(
50 | amount <= ctx.accounts.iou_source.amount,
51 | "insufficient iou_source balance"
52 | );
53 | invariant!(
54 | amount <= ctx.accounts.redemption_vault.amount,
55 | "insufficient redemption_vault balance"
56 | );
57 |
58 | ctx.accounts.burn_iou_tokens(amount)?;
59 | ctx.accounts.transfer_redemption_tokens(amount)?;
60 |
61 | let redeemer = &mut ctx.accounts.redeemer;
62 | redeemer.total_tokens_redeemed =
63 | unwrap_int!(redeemer.total_tokens_redeemed.checked_add(amount));
64 |
65 | let redeemer = &ctx.accounts.redeemer;
66 | emit!(RedeemTokensEvent {
67 | user: ctx.accounts.source_authority.key(),
68 | iou_mint: redeemer.iou_mint,
69 | redemption_mint: redeemer.redemption_mint,
70 | amount,
71 | timestamp: Clock::get()?.unix_timestamp
72 | });
73 |
74 | Ok(())
75 | }
76 |
77 | /// Redeems all of a user's tokens against the redemption vault.
78 | pub fn redeem_all_tokens(ctx: Context) -> Result<()> {
79 | let amount = ctx.accounts.iou_source.amount;
80 | redeem_tokens(ctx, amount)
81 | }
82 | }
83 |
84 | // --------------------------------
85 | // Accounts
86 | // --------------------------------
87 |
88 | // --------------------------------
89 | // Instructions
90 | // --------------------------------
91 |
92 | #[derive(Accounts)]
93 | pub struct CreateRedeemer<'info> {
94 | /// Redeemer PDA.
95 | #[account(
96 | init,
97 | seeds = [
98 | b"Redeemer".as_ref(),
99 | iou_mint.to_account_info().key.as_ref(),
100 | redemption_mint.to_account_info().key.as_ref()
101 | ],
102 | bump,
103 | payer = payer,
104 | space = 8 + Redeemer::LEN
105 | )]
106 | pub redeemer: Account<'info, Redeemer>,
107 | /// [Mint] of the IOU token.
108 | pub iou_mint: Account<'info, Mint>,
109 | /// [Mint] of the redemption token.
110 | pub redemption_mint: Account<'info, Mint>,
111 | /// Payer.
112 | #[account(mut)]
113 | pub payer: Signer<'info>,
114 | /// [System] program.
115 | pub system_program: Program<'info, System>,
116 | }
117 |
118 | #[derive(Accounts)]
119 | pub struct RedeemTokens<'info> {
120 | /// Redeemer PDA.
121 | #[account(mut)]
122 | pub redeemer: Account<'info, Redeemer>,
123 |
124 | /// Authority of the source of the redeemed tokens.
125 | pub source_authority: Signer<'info>,
126 | /// [Mint] of the IOU token.
127 | #[account(mut)]
128 | pub iou_mint: Account<'info, Mint>,
129 | /// Source of the IOU tokens.
130 | #[account(mut)]
131 | pub iou_source: Account<'info, TokenAccount>,
132 |
133 | /// [TokenAccount] holding the [Redeemer]'s redemption tokens.
134 | #[account(mut)]
135 | pub redemption_vault: Account<'info, TokenAccount>,
136 | /// Destination of the IOU tokens.
137 | #[account(mut, constraint = redemption_destination.key() != redemption_vault.key())]
138 | pub redemption_destination: Account<'info, TokenAccount>,
139 |
140 | /// The spl_token program corresponding to [Token].
141 | pub token_program: Program<'info, Token>,
142 | }
143 |
144 | // --------------------------------
145 | // Events
146 | // --------------------------------
147 |
148 | #[event]
149 | /// Emitted when tokens are redeemed.
150 | pub struct RedeemTokensEvent {
151 | /// User which redeemed.
152 | #[index]
153 | pub user: Pubkey,
154 | /// IOU
155 | pub iou_mint: Pubkey,
156 | /// Redemption mint
157 | pub redemption_mint: Pubkey,
158 | /// Amount of tokens
159 | pub amount: u64,
160 | /// When the tokens were redeemed.
161 | pub timestamp: i64,
162 | }
163 |
164 | /// Errors
165 | #[error_code]
166 | pub enum ErrorCode {
167 | #[msg("Unauthorized.")]
168 | Unauthorized,
169 | }
170 |
--------------------------------------------------------------------------------
/programs/quarry-redeemer/src/macros.rs:
--------------------------------------------------------------------------------
1 | //! Macros.
2 |
3 | #[macro_export]
4 | macro_rules! gen_redeemer_signer_seeds {
5 | ($redeemer:expr) => {
6 | &[
7 | b"Redeemer" as &[u8],
8 | &$redeemer.iou_mint.to_bytes(),
9 | &$redeemer.redemption_mint.to_bytes(),
10 | &[$redeemer.bump],
11 | ]
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/programs/quarry-redeemer/src/redeem_cpi.rs:
--------------------------------------------------------------------------------
1 | use crate::{gen_redeemer_signer_seeds, RedeemTokens};
2 | use anchor_lang::prelude::*;
3 | use anchor_spl::token;
4 |
5 | impl<'info> RedeemTokens<'info> {
6 | /// Burn IOU tokens from source account.
7 | pub fn burn_iou_tokens(&self, amount: u64) -> Result<()> {
8 | let cpi_ctx = CpiContext::new(
9 | self.token_program.to_account_info(),
10 | token::Burn {
11 | mint: self.iou_mint.to_account_info(),
12 | from: self.iou_source.to_account_info(),
13 | authority: self.source_authority.to_account_info(),
14 | },
15 | );
16 | token::burn(cpi_ctx, amount)
17 | }
18 |
19 | /// Transfer redemption tokens from the redemption vault to the user.
20 | pub fn transfer_redemption_tokens(&self, amount: u64) -> Result<()> {
21 | let seeds = gen_redeemer_signer_seeds!(self.redeemer);
22 | let signer_seeds = &[&seeds[..]];
23 | let cpi_ctx = CpiContext::new_with_signer(
24 | self.token_program.to_account_info(),
25 | token::Transfer {
26 | from: self.redemption_vault.to_account_info(),
27 | to: self.redemption_destination.to_account_info(),
28 | authority: self.redeemer.to_account_info(),
29 | },
30 | signer_seeds,
31 | );
32 | token::transfer(cpi_ctx, amount)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/programs/quarry-redeemer/src/state.rs:
--------------------------------------------------------------------------------
1 | //! State structs.
2 |
3 | use crate::*;
4 |
5 | /// Redeemer state
6 | #[account]
7 | #[derive(Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
8 | pub struct Redeemer {
9 | /// [Mint] of the IOU token.
10 | pub iou_mint: Pubkey,
11 | /// [Mint] of the token to redeem.
12 | pub redemption_mint: Pubkey,
13 | /// Bump seed.
14 | pub bump: u8,
15 |
16 | /// Lifetime number of IOU tokens redeemed for redemption tokens.
17 | pub total_tokens_redeemed: u64,
18 | }
19 |
20 | impl Redeemer {
21 | /// Number of bytes in a [Redeemer].
22 | pub const LEN: usize = 32 + 32 + 1 + 8;
23 | }
24 |
25 | #[cfg(test)]
26 | mod tests {
27 | use super::*;
28 |
29 | #[test]
30 | fn test_redeemer_len() {
31 | assert_eq!(
32 | Redeemer::default().try_to_vec().unwrap().len(),
33 | Redeemer::LEN
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/programs/quarry-registry/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quarry-registry"
3 | version = "5.3.0"
4 | description = "Registry of all quarries associated with a rewarder."
5 | edition = "2021"
6 | homepage = "https://quarry.so"
7 | repository = "https://github.com/QuarryProtocol/quarry"
8 | authors = ["Quarry Protocol "]
9 | license = "AGPL-3.0"
10 | keywords = ["solana", "quarry"]
11 |
12 | [lib]
13 | crate-type = ["cdylib", "lib"]
14 | name = "quarry_registry"
15 |
16 | [features]
17 | no-entrypoint = []
18 | no-idl = []
19 | cpi = ["no-entrypoint"]
20 | default = []
21 |
22 | [dependencies]
23 | anchor-lang = ">=0.22, <=0.25"
24 | quarry-mine = { path = "../quarry-mine", features = ["cpi"], version = "5.3.0" }
25 | solana-security-txt = "1.0.1"
26 | vipers = "^2.0"
27 |
--------------------------------------------------------------------------------
/programs/quarry-registry/README.md:
--------------------------------------------------------------------------------
1 | # registry
2 |
3 | Registry of all quarries associated with a rewarder.
4 |
--------------------------------------------------------------------------------
/programs/quarry-registry/Xargo.toml:
--------------------------------------------------------------------------------
1 | [target.bpfel-unknown-unknown.dependencies.std]
2 | features = []
3 |
--------------------------------------------------------------------------------
/programs/quarry-registry/src/account_validators.rs:
--------------------------------------------------------------------------------
1 | //! Validations for various accounts.
2 |
3 | use anchor_lang::prelude::*;
4 | use vipers::prelude::*;
5 |
6 | use crate::{NewRegistry, SyncQuarry};
7 |
8 | impl<'info> Validate<'info> for NewRegistry<'info> {
9 | fn validate(&self) -> Result<()> {
10 | Ok(())
11 | }
12 | }
13 |
14 | impl<'info> Validate<'info> for SyncQuarry<'info> {
15 | fn validate(&self) -> Result<()> {
16 | assert_keys_eq!(self.quarry.rewarder, self.registry.rewarder);
17 | Ok(())
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/programs/quarry-registry/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Registry to help the frontend quickly locate all active quarries.
2 | #![deny(rustdoc::all)]
3 | #![allow(rustdoc::missing_doc_code_examples)]
4 |
5 | use anchor_lang::prelude::*;
6 | use quarry_mine::{Quarry, Rewarder};
7 | use vipers::prelude::*;
8 |
9 | mod account_validators;
10 |
11 | declare_id!("QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc");
12 |
13 | #[cfg(not(feature = "no-entrypoint"))]
14 | solana_security_txt::security_txt! {
15 | name: "Quarry Registry",
16 | project_url: "https://quarry.so",
17 | contacts: "email:team@quarry.so",
18 | policy: "https://github.com/QuarryProtocol/quarry/blob/master/SECURITY.md",
19 |
20 | source_code: "https://github.com/QuarryProtocol/quarry",
21 | auditors: "Quantstamp"
22 | }
23 |
24 | /// Registry to help frontends quickly locate all active quarries.
25 | #[program]
26 | pub mod quarry_registry {
27 |
28 | use super::*;
29 |
30 | /// Provisions a new registry for a [Rewarder].
31 | ///
32 | /// # Arguments
33 | ///
34 | /// * `max_quarries` - The maximum number of quarries that can be held in the registry.
35 | /// * `bump` - Bump seed.
36 | pub fn new_registry(ctx: Context, max_quarries: u16, _bump: u8) -> Result<()> {
37 | ctx.accounts.validate()?;
38 | let registry = &mut ctx.accounts.registry;
39 | registry.bump = unwrap_bump!(ctx, "registry");
40 | registry.rewarder = ctx.accounts.rewarder.key();
41 | registry
42 | .tokens
43 | .resize(max_quarries as usize, Pubkey::default());
44 | Ok(())
45 | }
46 |
47 | /// Synchronizes a [Quarry]'s token mint with the registry of its [Rewarder].
48 | pub fn sync_quarry(ctx: Context) -> Result<()> {
49 | ctx.accounts.validate()?;
50 | let quarry = &ctx.accounts.quarry;
51 | let registry = &mut ctx.accounts.registry;
52 | registry.tokens[quarry.index as usize] = quarry.token_mint_key;
53 | Ok(())
54 | }
55 | }
56 |
57 | /// Accounts for [quarry_registry::new_registry].
58 | #[derive(Accounts)]
59 | #[instruction(max_quarries: u16)]
60 | pub struct NewRegistry<'info> {
61 | /// [Rewarder].
62 | pub rewarder: Account<'info, Rewarder>,
63 |
64 | /// [Rewarder] of mines.
65 | #[account(
66 | init,
67 | seeds = [
68 | b"QuarryRegistry".as_ref(),
69 | rewarder.key().to_bytes().as_ref()
70 | ],
71 | bump,
72 | payer = payer,
73 | space = (8 + 1 + 32 + 32 * max_quarries + 100) as usize
74 | )]
75 | pub registry: Account<'info, Registry>,
76 |
77 | /// Payer of the [Registry] initialization.
78 | #[account(mut)]
79 | pub payer: Signer<'info>,
80 |
81 | /// System program.
82 | pub system_program: Program<'info, System>,
83 | }
84 |
85 | /// Accounts for [quarry_registry::sync_quarry].
86 | #[derive(Accounts)]
87 | pub struct SyncQuarry<'info> {
88 | /// [Quarry] to sync.
89 | pub quarry: Account<'info, Quarry>,
90 | /// [Registry] to write to.
91 | #[account(mut)]
92 | pub registry: Account<'info, Registry>,
93 | }
94 |
95 | /// The [Registry] of all token mints associated with a [Rewarder].
96 | #[account]
97 | #[derive(Default, Debug)]
98 | pub struct Registry {
99 | /// Bump seed
100 | pub bump: u8,
101 | /// Rewarder
102 | pub rewarder: Pubkey,
103 | /// Tokens
104 | pub tokens: Vec,
105 | }
106 |
107 | impl Registry {
108 | /// Number of bytes a [Registry] takes up when serialized.
109 | pub fn byte_length(max_quarries: u16) -> usize {
110 | (1 + 32 + 4 + 32 * max_quarries) as usize
111 | }
112 | }
113 |
114 | #[cfg(test)]
115 | mod tests {
116 | use anchor_lang::system_program;
117 |
118 | use super::*;
119 |
120 | #[test]
121 | fn test_registry_len() {
122 | let registry = Registry {
123 | tokens: vec![system_program::ID, system_program::ID],
124 | ..Default::default()
125 | };
126 | assert_eq!(
127 | registry.try_to_vec().unwrap().len(),
128 | Registry::byte_length(2)
129 | );
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/scripts/diff-v2.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Helper script for checking to see that instruction handlers have
4 | # isomorphic logic.
5 |
6 | cd $(dirname $0)/..
7 |
8 | diff --color programs/quarry-mine/src/instructions/new_rewarder.rs \
9 | programs/quarry-mine/src/instructions/new_rewarder_v2.rs
10 |
11 | diff --color programs/quarry-mine/src/instructions/claim_rewards.rs \
12 | programs/quarry-mine/src/instructions/claim_rewards_v2.rs
13 |
14 | diff --color programs/quarry-mine/src/instructions/create_quarry.rs \
15 | programs/quarry-mine/src/instructions/create_quarry_v2.rs
16 |
17 | diff --color programs/quarry-operator/src/instructions/delegate_create_quarry.rs \
18 | programs/quarry-operator/src/instructions/delegate_create_quarry_v2.rs
19 |
--------------------------------------------------------------------------------
/scripts/generate-idl-types.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | shopt -s extglob
4 |
5 | cd $(dirname $0)/..
6 |
7 | generate_declaration_file() {
8 | PROGRAM_SO=$1
9 | OUT_DIR=$2
10 |
11 | prog="$(basename $PROGRAM_SO .json)"
12 | OUT_PATH="$OUT_DIR/$prog.ts"
13 | if [ ! $(which gsed) ]; then
14 | PREFIX=$(echo $prog | sed -E 's/(^|_)([a-z])/\U\2/g')
15 | else
16 | PREFIX=$(echo $prog | gsed -E 's/(^|_)([a-z])/\U\2/g')
17 | fi
18 | typename="${PREFIX}IDL"
19 | rawName="${PREFIX}JSON"
20 |
21 | # types
22 | echo "export type $typename =" >>$OUT_PATH
23 | cat $PROGRAM_SO >>$OUT_PATH
24 | echo ";" >>$OUT_PATH
25 |
26 | cat artifacts/idl/$prog.ts | sed -E "s/${PREFIX}/Anchor${PREFIX}/" | sed -E "s/IDL: /Anchor${PREFIX}IDL: /" >>$OUT_PATH
27 |
28 | # raw json
29 | echo "export const $rawName: $typename =" >>$OUT_PATH
30 | cat $PROGRAM_SO >>$OUT_PATH
31 | echo ";" >>$OUT_PATH
32 |
33 | # error type
34 | echo "import { generateErrorMap } from '@saberhq/anchor-contrib';" >>$OUT_PATH
35 | echo "export const ${PREFIX}Errors = generateErrorMap($rawName);" >>$OUT_PATH
36 | }
37 |
38 | generate_sdk_idls() {
39 | SDK_DIR=${1:-"./packages/sdk/src/idls"}
40 | IDL_JSONS=$2
41 |
42 | echo "Generating IDLs for the following programs:"
43 | echo $IDL_JSONS
44 | echo ""
45 |
46 | rm -rf $SDK_DIR
47 | mkdir -p $SDK_DIR
48 | if [ $(ls -l artifacts/idl/ | wc -l) -ne 0 ]; then
49 | for f in $IDL_JSONS; do
50 | generate_declaration_file $f $SDK_DIR
51 | done
52 | if [[ $RUN_ESLINT != "none" ]]; then
53 | yarn eslint --fix $SDK_DIR
54 | fi
55 | else
56 | echo "Warning: no IDLs found. Make sure you ran ./scripts/idl.sh first."
57 | fi
58 | }
59 |
60 | generate_sdk_idls ./src/idls 'artifacts/idl/*.json'
61 |
--------------------------------------------------------------------------------
/scripts/parse-idls.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # This script generates the IDL JSONs without buildling the full packages.
4 |
5 | rm -fr artifacts/idl/
6 | mkdir -p artifacts/idl/
7 |
8 | for PROGRAM in $(find programs/ -maxdepth 3 -name lib.rs); do
9 | PROGRAM_NAME=$(dirname $PROGRAM | xargs dirname | xargs basename | tr '-' '_')
10 | echo "Parsing IDL for $PROGRAM_NAME"
11 | anchor idl parse --file $PROGRAM --out artifacts/idl/$PROGRAM_NAME.json --out-ts artifacts/idl/$PROGRAM_NAME.ts || {
12 | echo "Could not parse IDL"
13 | exit 1
14 | }
15 | done
16 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | import { buildCoderMap } from "@saberhq/anchor-contrib";
2 | import { PublicKey } from "@solana/web3.js";
3 |
4 | import { QuarryMineJSON } from "./idls/quarry_mine";
5 | import { QuarryMintWrapperJSON } from "./idls/quarry_mint_wrapper";
6 | import { QuarryRedeemerJSON } from "./idls/quarry_redeemer";
7 | import type {
8 | MineProgram,
9 | MineTypes,
10 | MintWrapperProgram,
11 | MintWrapperTypes,
12 | QuarryMergeMineProgram,
13 | QuarryMergeMineTypes,
14 | QuarryOperatorProgram,
15 | QuarryOperatorTypes,
16 | } from "./programs";
17 | import { QuarryMergeMineJSON, QuarryOperatorJSON } from "./programs";
18 | import type { RedeemerProgram, RedeemerTypes } from "./programs/redeemer";
19 | import type { RegistryProgram, RegistryTypes } from "./programs/registry";
20 | import { QuarryRegistryJSON } from "./programs/registry";
21 |
22 | /**
23 | * Types of all programs.
24 | */
25 | export interface Programs {
26 | MergeMine: QuarryMergeMineProgram;
27 | Mine: MineProgram;
28 | MintWrapper: MintWrapperProgram;
29 | Operator: QuarryOperatorProgram;
30 | Redeemer: RedeemerProgram;
31 | Registry: RegistryProgram;
32 | }
33 |
34 | /**
35 | * Quarry program addresses.
36 | */
37 | export const QUARRY_ADDRESSES = {
38 | MergeMine: new PublicKey("QMMD16kjauP5knBwxNUJRZ1Z5o3deBuFrqVjBVmmqto"),
39 | Mine: new PublicKey("QMNeHCGYnLVDn1icRAfQZpjPLBNkfGbSKRB83G5d8KB"),
40 | MintWrapper: new PublicKey("QMWoBmAyJLAsA1Lh9ugMTw2gciTihncciphzdNzdZYV"),
41 | Operator: new PublicKey("QoP6NfrQbaGnccXQrMLUkog2tQZ4C1RFgJcwDnT8Kmz"),
42 | Redeemer: new PublicKey("QRDxhMw1P2NEfiw5mYXG79bwfgHTdasY2xNP76XSea9"),
43 | Registry: new PublicKey("QREGBnEj9Sa5uR91AV8u3FxThgP5ZCvdZUW2bHAkfNc"),
44 | };
45 |
46 | /**
47 | * Quarry program IDLs.
48 | */
49 | export const QUARRY_IDLS = {
50 | MergeMine: QuarryMergeMineJSON,
51 | Mine: QuarryMineJSON,
52 | MintWrapper: QuarryMintWrapperJSON,
53 | Operator: QuarryOperatorJSON,
54 | Redeemer: QuarryRedeemerJSON,
55 | Registry: QuarryRegistryJSON,
56 | };
57 |
58 | /**
59 | * Quarry program IDLs.
60 | */
61 | export const QUARRY_CODERS = buildCoderMap<{
62 | MergeMine: QuarryMergeMineTypes;
63 | Mine: MineTypes;
64 | MintWrapper: MintWrapperTypes;
65 | Operator: QuarryOperatorTypes;
66 | Redeemer: RedeemerTypes;
67 | Registry: RegistryTypes;
68 | }>(QUARRY_IDLS, QUARRY_ADDRESSES);
69 |
70 | /**
71 | * Recipient of protocol fees.
72 | */
73 | export const QUARRY_FEE_TO = new PublicKey(
74 | "4MMZH3ih1aSty2nx4MC3kSR94Zb55XsXnqb5jfEcyHWQ"
75 | );
76 |
77 | /**
78 | * Sets the protocol fees.
79 | */
80 | export const QUARRY_FEE_SETTER = new PublicKey(
81 | "4MMZH3ih1aSty2nx4MC3kSR94Zb55XsXnqb5jfEcyHWQ"
82 | );
83 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./constants";
2 | export * from "./programs";
3 | export * from "./sdk";
4 | export * from "./wrappers";
5 |
--------------------------------------------------------------------------------
/src/programs/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./mine";
2 | export * from "./mintWrapper";
3 | export * from "./operator";
4 | export * from "./quarryMergeMine";
5 | export * from "./redeemer";
6 | export * from "./registry";
7 |
--------------------------------------------------------------------------------
/src/programs/mine.ts:
--------------------------------------------------------------------------------
1 | import type { Program } from "@project-serum/anchor";
2 | import type { AnchorTypes } from "@saberhq/anchor-contrib";
3 |
4 | import type { AnchorQuarryMine } from "../idls/quarry_mine";
5 |
6 | export * from "../idls/quarry_mine";
7 |
8 | export type MineTypes = AnchorTypes<
9 | AnchorQuarryMine,
10 | {
11 | rewarder: RewarderData;
12 | quarry: QuarryData;
13 | miner: MinerData;
14 | }
15 | >;
16 |
17 | type Accounts = MineTypes["Accounts"];
18 | export type RewarderData = Accounts["rewarder"];
19 | export type QuarryData = Accounts["quarry"];
20 | export type MinerData = Accounts["miner"];
21 |
22 | export type MineError = MineTypes["Error"];
23 | export type MineEvents = MineTypes["Events"];
24 | export type MineProgram = Omit, "account"> &
25 | MineTypes["Program"];
26 |
27 | export type ClaimEvent = MineEvents["ClaimEvent"];
28 | export type StakeEvent = MineEvents["StakeEvent"];
29 | export type WithdrawEvent = MineEvents["WithdrawEvent"];
30 | export type QuarryRewardsUpdateEvent = MineEvents["QuarryRewardsUpdateEvent"];
31 |
--------------------------------------------------------------------------------
/src/programs/mintWrapper.ts:
--------------------------------------------------------------------------------
1 | import type { AnchorTypes } from "@saberhq/anchor-contrib";
2 |
3 | import type { QuarryMintWrapperIDL } from "../idls/quarry_mint_wrapper";
4 |
5 | export * from "../idls/quarry_mint_wrapper";
6 |
7 | export type MintWrapperTypes = AnchorTypes<
8 | QuarryMintWrapperIDL,
9 | {
10 | mintWrapper: MintWrapperData;
11 | minter: MinterData;
12 | }
13 | >;
14 |
15 | type Accounts = MintWrapperTypes["Accounts"];
16 |
17 | export type MintWrapperData = Accounts["MintWrapper"];
18 | export type MinterData = Accounts["Minter"];
19 |
20 | export type MintWrapperError = MintWrapperTypes["Error"];
21 |
22 | export type MintWrapperProgram = MintWrapperTypes["Program"];
23 |
--------------------------------------------------------------------------------
/src/programs/operator.ts:
--------------------------------------------------------------------------------
1 | import type { AnchorTypes } from "@saberhq/anchor-contrib";
2 |
3 | import type { QuarryOperatorIDL } from "../idls/quarry_operator";
4 |
5 | export * from "../idls/quarry_operator";
6 |
7 | export type QuarryOperatorTypes = AnchorTypes<
8 | QuarryOperatorIDL,
9 | {
10 | operator: OperatorData;
11 | }
12 | >;
13 |
14 | type Accounts = QuarryOperatorTypes["Accounts"];
15 | export type OperatorData = Accounts["Operator"];
16 |
17 | export type QuarryOperatorError = QuarryOperatorTypes["Error"];
18 | export type QuarryOperatorProgram = QuarryOperatorTypes["Program"];
19 |
--------------------------------------------------------------------------------
/src/programs/quarryMergeMine.ts:
--------------------------------------------------------------------------------
1 | import type { Program } from "@project-serum/anchor";
2 | import type { AnchorTypes } from "@saberhq/anchor-contrib";
3 | import type { PublicKey } from "@solana/web3.js";
4 |
5 | import type { AnchorQuarryMergeMine } from "../idls/quarry_merge_mine";
6 |
7 | export * from "../idls/quarry_merge_mine";
8 |
9 | export type QuarryMergeMineTypes = AnchorTypes<
10 | AnchorQuarryMergeMine,
11 | {
12 | mergePool: MergePoolData;
13 | mergeMiner: MergeMinerData;
14 | }
15 | >;
16 |
17 | type Accounts = QuarryMergeMineTypes["Accounts"];
18 | export type MergePoolData = Accounts["mergePool"];
19 | export type MergeMinerData = Accounts["mergeMiner"];
20 |
21 | export type QuarryMergeMineError = QuarryMergeMineTypes["Error"];
22 | export type QuarryMergeMineProgram = Omit<
23 | Program,
24 | "account"
25 | > &
26 | QuarryMergeMineTypes["Program"];
27 |
28 | export type QuarryStakeAccounts = {
29 | [A in keyof Parameters<
30 | QuarryMergeMineProgram["instruction"]["stakePrimaryMiner"]["accounts"]
31 | >[0]["stake"]]: PublicKey;
32 | };
33 |
--------------------------------------------------------------------------------
/src/programs/redeemer.ts:
--------------------------------------------------------------------------------
1 | import type { AnchorTypes } from "@saberhq/anchor-contrib";
2 | import type { TransactionEnvelope } from "@saberhq/solana-contrib";
3 | import type { u64 } from "@saberhq/token-utils";
4 | import type { PublicKey } from "@solana/web3.js";
5 |
6 | import type { QuarryRedeemerIDL } from "../idls/quarry_redeemer";
7 |
8 | export * from "../idls/quarry_redeemer";
9 |
10 | export type RedeemerTypes = AnchorTypes<
11 | QuarryRedeemerIDL,
12 | {
13 | redeemer: RedeemerData;
14 | }
15 | >;
16 |
17 | type Accounts = RedeemerTypes["Accounts"];
18 | export type RedeemerData = Accounts["Redeemer"];
19 |
20 | export type RedeemerError = RedeemerTypes["Error"];
21 | export type RedeemerEvents = RedeemerTypes["Events"];
22 | export type RedeemerProgram = RedeemerTypes["Program"];
23 |
24 | export type PendingRedeemer = {
25 | bump: number;
26 | vaultTokenAccount: PublicKey;
27 | tx: TransactionEnvelope;
28 | };
29 |
30 | export type RedeemTokenArgs = {
31 | tokenAmount: u64;
32 | sourceAuthority: PublicKey;
33 | iouSource: PublicKey;
34 | redemptionDestination: PublicKey;
35 | };
36 |
--------------------------------------------------------------------------------
/src/programs/registry.ts:
--------------------------------------------------------------------------------
1 | import type { AnchorTypes } from "@saberhq/anchor-contrib";
2 |
3 | import type { QuarryRegistryIDL } from "../idls/quarry_registry";
4 |
5 | export * from "../idls/quarry_registry";
6 |
7 | export type RegistryTypes = AnchorTypes<
8 | QuarryRegistryIDL,
9 | {
10 | registry: RegistryData;
11 | }
12 | >;
13 |
14 | type Accounts = RegistryTypes["Accounts"];
15 |
16 | export type RegistryData = Accounts["Registry"];
17 |
18 | export type RegistryError = RegistryTypes["Error"];
19 |
20 | export type RegistryProgram = RegistryTypes["Program"];
21 |
--------------------------------------------------------------------------------
/src/sdk.ts:
--------------------------------------------------------------------------------
1 | import type { Program } from "@project-serum/anchor";
2 | import { newProgramMap } from "@saberhq/anchor-contrib";
3 | import type {
4 | AugmentedProvider,
5 | Provider,
6 | TransactionEnvelope,
7 | } from "@saberhq/solana-contrib";
8 | import { SolanaAugmentedProvider } from "@saberhq/solana-contrib";
9 | import type {
10 | PublicKey,
11 | Signer,
12 | TransactionInstruction,
13 | } from "@solana/web3.js";
14 | import { Keypair } from "@solana/web3.js";
15 |
16 | import type { Programs } from "./constants";
17 | import { QUARRY_ADDRESSES, QUARRY_IDLS } from "./constants";
18 | import type { PendingRedeemer } from "./programs/redeemer";
19 | import {
20 | MergeMine,
21 | MineWrapper,
22 | MintWrapper,
23 | QuarryRegistry,
24 | } from "./wrappers";
25 | import { Operator } from "./wrappers/operator";
26 | import { RedeemerWrapper } from "./wrappers/redeemer";
27 |
28 | /**
29 | * Quarry SDK.
30 | */
31 | export class QuarrySDK {
32 | constructor(
33 | readonly provider: AugmentedProvider,
34 | readonly programs: Programs
35 | ) {}
36 |
37 | /**
38 | * Creates a new instance of the SDK with the given keypair.
39 | */
40 | withSigner(signer: Signer): QuarrySDK {
41 | return QuarrySDK.load({
42 | provider: this.provider.withSigner(signer),
43 | });
44 | }
45 |
46 | get programList(): Program[] {
47 | return Object.values(this.programs) as Program[];
48 | }
49 |
50 | get mintWrapper(): MintWrapper {
51 | return new MintWrapper(this);
52 | }
53 |
54 | get mine(): MineWrapper {
55 | return new MineWrapper(this);
56 | }
57 |
58 | get registry(): QuarryRegistry {
59 | return new QuarryRegistry(this);
60 | }
61 |
62 | get mergeMine(): MergeMine {
63 | return new MergeMine(this);
64 | }
65 |
66 | /**
67 | * Constructs a new transaction envelope.
68 | * @param instructions
69 | * @param signers
70 | * @returns
71 | */
72 | newTx(
73 | instructions: TransactionInstruction[],
74 | signers?: Signer[]
75 | ): TransactionEnvelope {
76 | return this.provider.newTX(instructions, signers);
77 | }
78 |
79 | /**
80 | * Loads the SDK.
81 | * @returns
82 | */
83 | static load({
84 | provider,
85 | addresses = QUARRY_ADDRESSES,
86 | }: {
87 | // Provider
88 | provider: Provider;
89 | // Addresses of each program.
90 | addresses?: { [K in keyof Programs]?: PublicKey };
91 | }): QuarrySDK {
92 | const allAddresses = { ...QUARRY_ADDRESSES, ...addresses };
93 | const programs = newProgramMap(
94 | provider,
95 | QUARRY_IDLS,
96 | allAddresses
97 | );
98 | return new QuarrySDK(new SolanaAugmentedProvider(provider), programs);
99 | }
100 |
101 | async loadRedeemer({
102 | iouMint,
103 | redemptionMint,
104 | }: {
105 | iouMint: PublicKey;
106 | redemptionMint: PublicKey;
107 | }): Promise {
108 | return await RedeemerWrapper.load({ iouMint, redemptionMint, sdk: this });
109 | }
110 |
111 | async createRedeemer({
112 | iouMint,
113 | redemptionMint,
114 | }: {
115 | iouMint: PublicKey;
116 | redemptionMint: PublicKey;
117 | }): Promise {
118 | return await RedeemerWrapper.createRedeemer({
119 | iouMint,
120 | redemptionMint,
121 | sdk: this,
122 | });
123 | }
124 |
125 | /**
126 | * Loads an operator.
127 | * @param key
128 | * @returns
129 | */
130 | async loadOperator(key: PublicKey): Promise {
131 | return await Operator.load({
132 | sdk: this,
133 | key,
134 | });
135 | }
136 |
137 | /**
138 | * Creates an Operator.
139 | * @returns
140 | */
141 | async createOperator({
142 | rewarder,
143 | baseKP = Keypair.generate(),
144 | admin = this.provider.wallet.publicKey,
145 | payer = this.provider.wallet.publicKey,
146 | }: {
147 | rewarder: PublicKey;
148 | admin?: PublicKey;
149 | baseKP?: Signer;
150 | payer?: PublicKey;
151 | }): Promise<{ key: PublicKey; tx: TransactionEnvelope }> {
152 | return await Operator.createOperator({
153 | sdk: this,
154 | rewarder,
155 | baseKP,
156 | admin,
157 | payer,
158 | });
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/wrappers/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./mergeMine";
2 | export * from "./mine";
3 | export * from "./mintWrapper";
4 | export * from "./operator";
5 | export * from "./redeemer";
6 | export * from "./registry";
7 |
--------------------------------------------------------------------------------
/src/wrappers/mergeMine/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./mergeMiner";
2 | export * from "./mergePool";
3 | export * from "./pda";
4 | export * from "./quarryMergeMine";
5 |
--------------------------------------------------------------------------------
/src/wrappers/mergeMine/pda.ts:
--------------------------------------------------------------------------------
1 | import { utils } from "@project-serum/anchor";
2 | import { PublicKey } from "@solana/web3.js";
3 |
4 | import { QUARRY_ADDRESSES } from "../../constants";
5 |
6 | /**
7 | * Finds the address of the Pool.
8 | * @returns
9 | */
10 | export const findPoolAddress = async ({
11 | programId = QUARRY_ADDRESSES.MergeMine,
12 | primaryMint,
13 | }: {
14 | programId?: PublicKey;
15 | primaryMint: PublicKey;
16 | }): Promise<[PublicKey, number]> => {
17 | return await PublicKey.findProgramAddress(
18 | [utils.bytes.utf8.encode("MergePool"), primaryMint.toBuffer()],
19 | programId
20 | );
21 | };
22 |
23 | /**
24 | * Finds the address of the Pool.
25 | * @returns
26 | */
27 | export const findReplicaMintAddress = async ({
28 | programId = QUARRY_ADDRESSES.MergeMine,
29 | primaryMint,
30 | }: {
31 | programId?: PublicKey;
32 | primaryMint: PublicKey;
33 | }): Promise<[PublicKey, number]> => {
34 | const [pool] = await findPoolAddress({ programId, primaryMint });
35 | return await PublicKey.findProgramAddress(
36 | [utils.bytes.utf8.encode("ReplicaMint"), pool.toBuffer()],
37 | programId
38 | );
39 | };
40 |
41 | /**
42 | * Finds the address of the Merge Miner.
43 | * @returns
44 | */
45 | export const findMergeMinerAddress = async ({
46 | programId = QUARRY_ADDRESSES.MergeMine,
47 | pool,
48 | owner,
49 | }: {
50 | programId?: PublicKey;
51 | pool: PublicKey;
52 | owner: PublicKey;
53 | }): Promise<[PublicKey, number]> => {
54 | return await PublicKey.findProgramAddress(
55 | [utils.bytes.utf8.encode("MergeMiner"), pool.toBuffer(), owner.toBuffer()],
56 | programId
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/src/wrappers/mine/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./mine";
2 | export * from "./miner";
3 | export * from "./payroll";
4 | export * from "./pda";
5 | export * from "./quarry";
6 | export * from "./rewarder";
7 | export * from "./types";
8 |
--------------------------------------------------------------------------------
/src/wrappers/mine/mine.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | AugmentedProvider,
3 | TransactionEnvelope,
4 | } from "@saberhq/solana-contrib";
5 | import { getOrCreateATA, TOKEN_PROGRAM_ID } from "@saberhq/token-utils";
6 | import type {
7 | PublicKey,
8 | Signer,
9 | TransactionInstruction,
10 | } from "@solana/web3.js";
11 | import { Keypair, SystemProgram, SYSVAR_CLOCK_PUBKEY } from "@solana/web3.js";
12 |
13 | import { QUARRY_CODERS } from "../../constants";
14 | import type { MineProgram } from "../../programs/mine";
15 | import type { QuarrySDK } from "../../sdk";
16 | import { findRewarderAddress } from "./pda";
17 | import { RewarderWrapper } from "./rewarder";
18 |
19 | export class MineWrapper {
20 | constructor(readonly sdk: QuarrySDK) {}
21 |
22 | get provider(): AugmentedProvider {
23 | return this.sdk.provider;
24 | }
25 |
26 | get program(): MineProgram {
27 | return this.sdk.programs.Mine;
28 | }
29 |
30 | /**
31 | *
32 | * @deprecated Use {@link createRewarder}.
33 | * @param param0
34 | * @returns
35 | */
36 | async createRewarderV1({
37 | mintWrapper,
38 | baseKP = Keypair.generate(),
39 | authority = this.provider.wallet.publicKey,
40 | }: {
41 | mintWrapper: PublicKey;
42 | baseKP?: Signer;
43 | authority?: PublicKey;
44 | }): Promise<{
45 | key: PublicKey;
46 | tx: TransactionEnvelope;
47 | }> {
48 | const [rewarderKey, bump] = await findRewarderAddress(
49 | baseKP.publicKey,
50 | this.program.programId
51 | );
52 |
53 | const mintWrapperDataRaw = await this.provider.getAccountInfo(mintWrapper);
54 | if (!mintWrapperDataRaw) {
55 | throw new Error(
56 | `mint wrapper does not exist at ${mintWrapper.toString()}`
57 | );
58 | }
59 | const mintWrapperData =
60 | QUARRY_CODERS.MintWrapper.accounts.mintWrapper.parse(
61 | mintWrapperDataRaw.accountInfo.data
62 | );
63 |
64 | const { address: claimFeeTokenAccount, instruction: createATAInstruction } =
65 | await getOrCreateATA({
66 | provider: this.provider,
67 | mint: mintWrapperData.tokenMint,
68 | owner: rewarderKey,
69 | });
70 |
71 | return {
72 | key: rewarderKey,
73 | tx: this.provider.newTX(
74 | [
75 | createATAInstruction,
76 | this.program.instruction.newRewarder(bump, {
77 | accounts: {
78 | base: baseKP.publicKey,
79 | initialAuthority: authority,
80 | rewarder: rewarderKey,
81 | payer: this.provider.wallet.publicKey,
82 | systemProgram: SystemProgram.programId,
83 | unusedAccount: SYSVAR_CLOCK_PUBKEY,
84 | mintWrapper,
85 | rewardsTokenMint: mintWrapperData.tokenMint,
86 | claimFeeTokenAccount,
87 | },
88 | }),
89 | ],
90 | [baseKP]
91 | ),
92 | };
93 | }
94 |
95 | /**
96 | * Creates a new Rewarder.
97 | * @param param0
98 | * @returns
99 | */
100 | async createRewarder({
101 | mintWrapper,
102 | baseKP = Keypair.generate(),
103 | authority = this.provider.wallet.publicKey,
104 | }: {
105 | mintWrapper: PublicKey;
106 | baseKP?: Signer;
107 | authority?: PublicKey;
108 | }): Promise<{
109 | key: PublicKey;
110 | tx: TransactionEnvelope;
111 | }> {
112 | const [rewarderKey] = await findRewarderAddress(
113 | baseKP.publicKey,
114 | this.program.programId
115 | );
116 |
117 | const mintWrapperDataRaw = await this.provider.getAccountInfo(mintWrapper);
118 | if (!mintWrapperDataRaw) {
119 | throw new Error(
120 | `mint wrapper does not exist at ${mintWrapper.toString()}`
121 | );
122 | }
123 |
124 | const mintWrapperData =
125 | QUARRY_CODERS.MintWrapper.accounts.mintWrapper.parse(
126 | mintWrapperDataRaw.accountInfo.data
127 | );
128 |
129 | const { address: claimFeeTokenAccount, instruction: createATAInstruction } =
130 | await getOrCreateATA({
131 | provider: this.provider,
132 | mint: mintWrapperData.tokenMint,
133 | owner: rewarderKey,
134 | });
135 |
136 | return {
137 | key: rewarderKey,
138 | tx: this.provider.newTX(
139 | [
140 | createATAInstruction,
141 | this.program.instruction.newRewarderV2({
142 | accounts: {
143 | base: baseKP.publicKey,
144 | initialAuthority: authority,
145 | rewarder: rewarderKey,
146 | payer: this.provider.wallet.publicKey,
147 | systemProgram: SystemProgram.programId,
148 | mintWrapper,
149 | rewardsTokenMint: mintWrapperData.tokenMint,
150 | claimFeeTokenAccount,
151 | },
152 | }),
153 | ],
154 | [baseKP]
155 | ),
156 | };
157 | }
158 |
159 | /**
160 | * Loads the rewarder wrapper.
161 | * @param rewarder
162 | * @returns
163 | */
164 | async loadRewarderWrapper(rewarder: PublicKey): Promise {
165 | const rewarderData = await this.program.account.rewarder.fetch(rewarder);
166 | return new RewarderWrapper(this, rewarder, rewarderData);
167 | }
168 |
169 | /**
170 | * Rescue stuck tokens in a miner.
171 | * @returns
172 | */
173 | async rescueTokens({
174 | mint,
175 | miner,
176 | minerTokenAccount,
177 | owner = this.provider.wallet.publicKey,
178 | }: {
179 | mint: PublicKey;
180 | miner: PublicKey;
181 | minerTokenAccount: PublicKey;
182 | owner?: PublicKey;
183 | }): Promise {
184 | const instructions: TransactionInstruction[] = [];
185 | const { address: destinationTokenAccount, instruction: ataInstruction } =
186 | await getOrCreateATA({
187 | provider: this.provider,
188 | mint,
189 | owner,
190 | });
191 | if (ataInstruction) {
192 | instructions.push(ataInstruction);
193 | }
194 |
195 | instructions.push(
196 | this.program.instruction.rescueTokens({
197 | accounts: {
198 | authority: owner,
199 | miner,
200 | minerTokenAccount,
201 | destinationTokenAccount,
202 | tokenProgram: TOKEN_PROGRAM_ID,
203 | },
204 | })
205 | );
206 |
207 | return this.sdk.newTx(instructions);
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/wrappers/mine/payroll.ts:
--------------------------------------------------------------------------------
1 | import { MAX_U64 } from "@saberhq/token-utils";
2 | import BN from "bn.js";
3 |
4 | export const ZERO = new BN(0);
5 | export const BASE_TEN = new BN(10);
6 |
7 | export const MAX_U64_BN = new BN(MAX_U64.toString());
8 |
9 | export const SECONDS_PER_YEAR = new BN(365 * 86_400);
10 |
11 | export class Payroll {
12 | constructor(
13 | readonly famineTs: BN,
14 | readonly lastCheckpointTs: BN,
15 | readonly annualRewardsRate: BN,
16 | readonly rewardsPerTokenStored: BN,
17 | readonly totalTokensDeposited: BN
18 | ) {}
19 |
20 | /**
21 | * Calculates the amount of tokens that this user can receive.
22 | * @param currentTs
23 | * @returns
24 | */
25 | calculateRewardPerToken(currentTs: BN): BN {
26 | if (this.totalTokensDeposited.isZero()) {
27 | return this.rewardsPerTokenStored;
28 | }
29 |
30 | const lastTimeRewardsApplicable = BN.min(currentTs, this.famineTs);
31 | const timeWorked = BN.max(
32 | ZERO,
33 | lastTimeRewardsApplicable.sub(this.lastCheckpointTs)
34 | );
35 | const reward = timeWorked
36 | .mul(MAX_U64_BN)
37 | .mul(this.annualRewardsRate)
38 | .div(SECONDS_PER_YEAR)
39 | .div(this.totalTokensDeposited);
40 | return this.rewardsPerTokenStored.add(reward);
41 | }
42 |
43 | /**
44 | * Calculates the amount of tokens that this user can claim.
45 | * @param currentTs
46 | * @param tokensDeposited
47 | * @param rewardsPerTokenPaid
48 | * @param rewardsEarned
49 | * @returns
50 | */
51 | calculateRewardsEarned(
52 | currentTs: BN,
53 | tokensDeposited: BN,
54 | rewardsPerTokenPaid: BN,
55 | rewardsEarned: BN
56 | ): BN {
57 | const netNewRewards =
58 | this.calculateRewardPerToken(currentTs).sub(rewardsPerTokenPaid);
59 | const earnedRewards = tokensDeposited.mul(netNewRewards).div(MAX_U64_BN);
60 | return earnedRewards.add(rewardsEarned);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/wrappers/mine/pda.ts:
--------------------------------------------------------------------------------
1 | import { utils } from "@project-serum/anchor";
2 | import { PublicKey } from "@solana/web3.js";
3 |
4 | import { QUARRY_ADDRESSES } from "../../constants";
5 |
6 | export const findRewarderAddress = async (
7 | base: PublicKey,
8 | programID: PublicKey = QUARRY_ADDRESSES.Mine
9 | ): Promise<[PublicKey, number]> => {
10 | return await PublicKey.findProgramAddress(
11 | [Buffer.from(utils.bytes.utf8.encode("Rewarder")), base.toBytes()],
12 | programID
13 | );
14 | };
15 |
16 | export const findQuarryAddress = async (
17 | rewarder: PublicKey,
18 | tokenMint: PublicKey,
19 | programID: PublicKey = QUARRY_ADDRESSES.Mine
20 | ): Promise<[PublicKey, number]> => {
21 | return await PublicKey.findProgramAddress(
22 | [
23 | Buffer.from(utils.bytes.utf8.encode("Quarry")),
24 | rewarder.toBytes(),
25 | tokenMint.toBytes(),
26 | ],
27 | programID
28 | );
29 | };
30 |
31 | export const findMinerAddress = async (
32 | quarry: PublicKey,
33 | authority: PublicKey,
34 | programID: PublicKey = QUARRY_ADDRESSES.Mine
35 | ): Promise<[PublicKey, number]> => {
36 | return await PublicKey.findProgramAddress(
37 | [
38 | Buffer.from(utils.bytes.utf8.encode("Miner")),
39 | quarry.toBytes(),
40 | authority.toBytes(),
41 | ],
42 | programID
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/src/wrappers/mine/types.ts:
--------------------------------------------------------------------------------
1 | import type { TransactionEnvelope } from "@saberhq/solana-contrib";
2 | import type { PublicKey } from "@solana/web3.js";
3 |
4 | import type { MinerWrapper } from "./miner";
5 |
6 | export interface PendingRewarder {
7 | rewarder: PublicKey;
8 | base: PublicKey;
9 | tx: TransactionEnvelope;
10 | }
11 |
12 | export interface PendingQuarry {
13 | rewarder: PublicKey;
14 | quarry: PublicKey;
15 | tx: TransactionEnvelope;
16 | }
17 |
18 | export interface PendingMiner {
19 | miner: PublicKey;
20 | wrapper: MinerWrapper;
21 | tx: TransactionEnvelope;
22 | }
23 |
--------------------------------------------------------------------------------
/src/wrappers/mintWrapper/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./mintWrapper";
2 | export * from "./pda";
3 | export * from "./types";
4 |
--------------------------------------------------------------------------------
/src/wrappers/mintWrapper/pda.ts:
--------------------------------------------------------------------------------
1 | import { utils } from "@project-serum/anchor";
2 | import { PublicKey } from "@solana/web3.js";
3 |
4 | import { QUARRY_ADDRESSES } from "../../constants";
5 |
6 | export const findMintWrapperAddress = async (
7 | base: PublicKey,
8 | programID: PublicKey = QUARRY_ADDRESSES.MintWrapper
9 | ): Promise<[PublicKey, number]> => {
10 | return await PublicKey.findProgramAddress(
11 | [Buffer.from(utils.bytes.utf8.encode("MintWrapper")), base.toBytes()],
12 | programID
13 | );
14 | };
15 |
16 | export const findMinterAddress = async (
17 | wrapper: PublicKey,
18 | authority: PublicKey,
19 | programID: PublicKey = QUARRY_ADDRESSES.MintWrapper
20 | ): Promise<[PublicKey, number]> => {
21 | return await PublicKey.findProgramAddress(
22 | [
23 | Buffer.from(utils.bytes.utf8.encode("MintWrapperMinter")),
24 | wrapper.toBytes(),
25 | authority.toBytes(),
26 | ],
27 | programID
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/src/wrappers/mintWrapper/types.ts:
--------------------------------------------------------------------------------
1 | import type { TransactionEnvelope } from "@saberhq/solana-contrib";
2 | import type { PublicKey } from "@solana/web3.js";
3 |
4 | export interface PendingMintWrapper {
5 | mintWrapper: PublicKey;
6 | tx: TransactionEnvelope;
7 | }
8 |
9 | export interface PendingMintAndWrapper {
10 | mint: PublicKey;
11 | mintWrapper: PublicKey;
12 | tx: TransactionEnvelope;
13 | }
14 |
--------------------------------------------------------------------------------
/src/wrappers/operator/index.ts:
--------------------------------------------------------------------------------
1 | import { TransactionEnvelope } from "@saberhq/solana-contrib";
2 | import { u64 } from "@saberhq/token-utils";
3 | import type { PublicKey, Signer } from "@solana/web3.js";
4 | import { Keypair, SystemProgram } from "@solana/web3.js";
5 |
6 | import type { OperatorData, QuarryOperatorProgram, QuarrySDK } from "../..";
7 | import { findQuarryAddress } from "..";
8 | import { findOperatorAddress } from "./pda";
9 |
10 | /**
11 | * Operator helper functions.
12 | */
13 | export class Operator {
14 | constructor(
15 | readonly sdk: QuarrySDK,
16 | readonly key: PublicKey,
17 | readonly data: OperatorData
18 | ) {}
19 |
20 | get program(): QuarryOperatorProgram {
21 | return this.sdk.programs.Operator;
22 | }
23 |
24 | /**
25 | * Reloads the Operator's data.
26 | * @returns
27 | */
28 | async reload(): Promise {
29 | const data = await this.program.account.operator.fetch(this.key);
30 | return new Operator(this.sdk, this.key, data);
31 | }
32 |
33 | static async load({
34 | sdk,
35 | key,
36 | }: {
37 | sdk: QuarrySDK;
38 | key: PublicKey;
39 | }): Promise {
40 | const program = sdk.programs.Operator;
41 | const data = (await program.account.operator.fetchNullable(
42 | key
43 | )) as OperatorData;
44 | if (!data) {
45 | return null;
46 | }
47 | return new Operator(sdk, key, data);
48 | }
49 |
50 | static async createOperator({
51 | sdk,
52 | rewarder,
53 | baseKP = Keypair.generate(),
54 | admin = sdk.provider.wallet.publicKey,
55 | payer = sdk.provider.wallet.publicKey,
56 | }: {
57 | sdk: QuarrySDK;
58 | rewarder: PublicKey;
59 | admin?: PublicKey;
60 | baseKP?: Signer;
61 | payer?: PublicKey;
62 | }): Promise<{
63 | key: PublicKey;
64 | tx: TransactionEnvelope;
65 | }> {
66 | const [operatorKey] = await findOperatorAddress(
67 | baseKP.publicKey,
68 | sdk.programs.Operator.programId
69 | );
70 | return {
71 | key: operatorKey,
72 | tx: new TransactionEnvelope(
73 | sdk.provider,
74 | [
75 | sdk.programs.Operator.instruction.createOperatorV2({
76 | accounts: {
77 | base: baseKP.publicKey,
78 | operator: operatorKey,
79 | rewarder,
80 | admin,
81 |
82 | payer,
83 | systemProgram: SystemProgram.programId,
84 | quarryMineProgram: sdk.programs.Mine.programId,
85 | },
86 | }),
87 | ],
88 | [baseKP]
89 | ),
90 | };
91 | }
92 |
93 | setAdmin(delegate: PublicKey): TransactionEnvelope {
94 | return new TransactionEnvelope(this.sdk.provider, [
95 | this.program.instruction.setAdmin({
96 | accounts: {
97 | operator: this.key,
98 | admin: this.sdk.provider.wallet.publicKey,
99 | delegate,
100 | },
101 | }),
102 | ]);
103 | }
104 |
105 | setRateSetter(delegate: PublicKey): TransactionEnvelope {
106 | return new TransactionEnvelope(this.sdk.provider, [
107 | this.program.instruction.setRateSetter({
108 | accounts: {
109 | operator: this.key,
110 | admin: this.sdk.provider.wallet.publicKey,
111 | delegate,
112 | },
113 | }),
114 | ]);
115 | }
116 |
117 | setQuarryCreator(delegate: PublicKey): TransactionEnvelope {
118 | return new TransactionEnvelope(this.sdk.provider, [
119 | this.program.instruction.setQuarryCreator({
120 | accounts: {
121 | operator: this.key,
122 | admin: this.sdk.provider.wallet.publicKey,
123 | delegate,
124 | },
125 | }),
126 | ]);
127 | }
128 |
129 | setShareAllocator(delegate: PublicKey): TransactionEnvelope {
130 | return new TransactionEnvelope(this.sdk.provider, [
131 | this.program.instruction.setShareAllocator({
132 | accounts: {
133 | operator: this.key,
134 | admin: this.sdk.provider.wallet.publicKey,
135 | delegate,
136 | },
137 | }),
138 | ]);
139 | }
140 |
141 | get withDelegateAccounts(): {
142 | operator: PublicKey;
143 | delegate: PublicKey;
144 | rewarder: PublicKey;
145 | quarryMineProgram: PublicKey;
146 | } {
147 | return {
148 | operator: this.key,
149 | delegate: this.sdk.provider.wallet.publicKey,
150 | rewarder: this.data.rewarder,
151 | quarryMineProgram: this.sdk.programs.Mine.programId,
152 | };
153 | }
154 |
155 | delegateSetAnnualRewards(newAnnualRate: u64): TransactionEnvelope {
156 | return new TransactionEnvelope(this.sdk.provider, [
157 | this.program.instruction.delegateSetAnnualRewards(newAnnualRate, {
158 | accounts: {
159 | withDelegate: this.withDelegateAccounts,
160 | },
161 | }),
162 | ]);
163 | }
164 |
165 | delegateSetFamine(newFamineTs: u64, quarry: PublicKey): TransactionEnvelope {
166 | return new TransactionEnvelope(this.sdk.provider, [
167 | this.program.instruction.delegateSetFamine(newFamineTs, {
168 | accounts: {
169 | withDelegate: this.withDelegateAccounts,
170 | quarry,
171 | },
172 | }),
173 | ]);
174 | }
175 |
176 | async delegateCreateQuarry({
177 | tokenMint,
178 | payer = this.sdk.provider.wallet.publicKey,
179 | }: {
180 | tokenMint: PublicKey;
181 | payer?: PublicKey;
182 | }): Promise<{ tx: TransactionEnvelope; quarry: PublicKey }> {
183 | const [quarry] = await findQuarryAddress(
184 | this.data.rewarder,
185 | tokenMint,
186 | this.sdk.programs.Mine.programId
187 | );
188 | return {
189 | quarry,
190 | tx: new TransactionEnvelope(this.sdk.provider, [
191 | this.program.instruction.delegateCreateQuarryV2({
192 | accounts: {
193 | withDelegate: this.withDelegateAccounts,
194 | quarry,
195 | tokenMint,
196 | payer,
197 | systemProgram: SystemProgram.programId,
198 | },
199 | }),
200 | ]),
201 | };
202 | }
203 |
204 | delegateSetRewardsShare({
205 | share,
206 | quarry,
207 | }: {
208 | share: number;
209 | quarry: PublicKey;
210 | }): TransactionEnvelope {
211 | return new TransactionEnvelope(this.sdk.provider, [
212 | this.program.instruction.delegateSetRewardsShare(new u64(share), {
213 | accounts: {
214 | withDelegate: this.withDelegateAccounts,
215 | quarry,
216 | },
217 | }),
218 | ]);
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/src/wrappers/operator/pda.ts:
--------------------------------------------------------------------------------
1 | import { utils } from "@project-serum/anchor";
2 | import { PublicKey } from "@solana/web3.js";
3 |
4 | import { QUARRY_ADDRESSES } from "../../constants";
5 |
6 | export const findOperatorAddress = async (
7 | base: PublicKey,
8 | programID: PublicKey = QUARRY_ADDRESSES.Operator
9 | ): Promise<[PublicKey, number]> => {
10 | return await PublicKey.findProgramAddress(
11 | [utils.bytes.utf8.encode("Operator"), base.toBytes()],
12 | programID
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/wrappers/redeemer/index.ts:
--------------------------------------------------------------------------------
1 | import { TransactionEnvelope } from "@saberhq/solana-contrib";
2 | import {
3 | getATAAddress,
4 | getOrCreateATA,
5 | TOKEN_PROGRAM_ID,
6 | } from "@saberhq/token-utils";
7 | import type { PublicKey, TransactionInstruction } from "@solana/web3.js";
8 | import { SystemProgram } from "@solana/web3.js";
9 |
10 | import type { QuarrySDK } from "../..";
11 | import type {
12 | PendingRedeemer,
13 | RedeemerData,
14 | RedeemerProgram,
15 | RedeemTokenArgs,
16 | } from "../../programs/redeemer";
17 | import { findRedeemerKey } from "./pda";
18 |
19 | export * from "./pda";
20 |
21 | export class RedeemerWrapper {
22 | constructor(
23 | readonly sdk: QuarrySDK,
24 | readonly iouMint: PublicKey,
25 | readonly redemptionMint: PublicKey,
26 | readonly key: PublicKey,
27 | readonly data: RedeemerData
28 | ) {}
29 |
30 | get program(): RedeemerProgram {
31 | return this.sdk.programs.Redeemer;
32 | }
33 |
34 | static async load({
35 | sdk,
36 | iouMint,
37 | redemptionMint,
38 | }: {
39 | sdk: QuarrySDK;
40 | iouMint: PublicKey;
41 | redemptionMint: PublicKey;
42 | }): Promise {
43 | const [redeemer] = await findRedeemerKey({ iouMint, redemptionMint });
44 | const program = sdk.programs.Redeemer;
45 | const data = await program.account.redeemer.fetch(redeemer);
46 | return new RedeemerWrapper(sdk, iouMint, redemptionMint, redeemer, data);
47 | }
48 |
49 | static async createRedeemer({
50 | sdk,
51 | iouMint,
52 | redemptionMint,
53 | }: {
54 | sdk: QuarrySDK;
55 | iouMint: PublicKey;
56 | redemptionMint: PublicKey;
57 | }): Promise {
58 | const { provider } = sdk;
59 | const [redeemer, bump] = await findRedeemerKey({ iouMint, redemptionMint });
60 | const ata = await getOrCreateATA({
61 | provider,
62 | mint: redemptionMint,
63 | owner: redeemer,
64 | });
65 | return {
66 | bump,
67 | vaultTokenAccount: ata.address,
68 | tx: new TransactionEnvelope(sdk.provider, [
69 | ...(ata.instruction ? [ata.instruction] : []),
70 | sdk.programs.Redeemer.instruction.createRedeemer(bump, {
71 | accounts: {
72 | redeemer,
73 | iouMint,
74 | redemptionMint,
75 | payer: provider.wallet.publicKey,
76 | systemProgram: SystemProgram.programId,
77 | },
78 | }),
79 | ]),
80 | };
81 | }
82 |
83 | /**
84 | * redeemTokensIx
85 | */
86 | async redeemTokensIx(args: RedeemTokenArgs): Promise {
87 | return this.program.instruction.redeemTokens(args.tokenAmount, {
88 | accounts: await this.getRedeemTokenAccounts(args),
89 | });
90 | }
91 |
92 | async redeemTokens(args: RedeemTokenArgs): Promise {
93 | return new TransactionEnvelope(this.sdk.provider, [
94 | await this.redeemTokensIx(args),
95 | ]);
96 | }
97 |
98 | async getVaultAddress(): Promise {
99 | return await getATAAddress({
100 | mint: this.redemptionMint,
101 | owner: this.key,
102 | });
103 | }
104 |
105 | async getRedeemTokenAccounts(
106 | args: Omit
107 | ): Promise<{
108 | redeemer: PublicKey;
109 | iouMint: PublicKey;
110 | redemptionMint: PublicKey;
111 | redemptionVault: PublicKey;
112 | tokenProgram: PublicKey;
113 | sourceAuthority: PublicKey;
114 | iouSource: PublicKey;
115 | redemptionDestination: PublicKey;
116 | }> {
117 | const { iouSource, redemptionDestination, sourceAuthority } = args;
118 | return {
119 | redeemer: this.key,
120 | iouMint: this.data.iouMint,
121 | redemptionMint: this.data.redemptionMint,
122 | redemptionVault: await getATAAddress({
123 | mint: this.data.redemptionMint,
124 | owner: this.key,
125 | }),
126 | tokenProgram: TOKEN_PROGRAM_ID,
127 | sourceAuthority,
128 | iouSource,
129 | redemptionDestination,
130 | };
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/wrappers/redeemer/pda.ts:
--------------------------------------------------------------------------------
1 | import { utils } from "@project-serum/anchor";
2 | import { PublicKey } from "@solana/web3.js";
3 |
4 | import { QUARRY_ADDRESSES } from "../..";
5 |
6 | export const findRedeemerKey = async ({
7 | iouMint,
8 | redemptionMint,
9 | }: {
10 | iouMint: PublicKey;
11 | redemptionMint: PublicKey;
12 | }): Promise<[PublicKey, number]> => {
13 | return PublicKey.findProgramAddress(
14 | [
15 | utils.bytes.utf8.encode("Redeemer"),
16 | iouMint.toBytes(),
17 | redemptionMint.toBytes(),
18 | ],
19 | QUARRY_ADDRESSES.Redeemer
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/wrappers/registry/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./pda";
2 | export * from "./registry";
3 |
--------------------------------------------------------------------------------
/src/wrappers/registry/pda.ts:
--------------------------------------------------------------------------------
1 | import { utils } from "@project-serum/anchor";
2 | import { PublicKey } from "@solana/web3.js";
3 |
4 | import { QUARRY_ADDRESSES } from "../../constants";
5 |
6 | export const findRegistryAddress = async (
7 | rewarderKey: PublicKey,
8 | programID: PublicKey = QUARRY_ADDRESSES.Registry
9 | ): Promise<[PublicKey, number]> => {
10 | return await PublicKey.findProgramAddress(
11 | [utils.bytes.utf8.encode("QuarryRegistry"), rewarderKey.toBytes()],
12 | programID
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/wrappers/registry/registry.ts:
--------------------------------------------------------------------------------
1 | import type { Provider } from "@saberhq/solana-contrib";
2 | import { TransactionEnvelope } from "@saberhq/solana-contrib";
3 | import type { PublicKey } from "@solana/web3.js";
4 | import { SystemProgram } from "@solana/web3.js";
5 |
6 | import type { RegistryProgram } from "../../programs";
7 | import type { QuarrySDK } from "../../sdk";
8 | import { findQuarryAddress } from "../mine";
9 | import { findRegistryAddress } from "./pda";
10 |
11 | export class QuarryRegistry {
12 | readonly program: RegistryProgram;
13 | constructor(readonly sdk: QuarrySDK) {
14 | this.program = sdk.programs.Registry;
15 | }
16 |
17 | get provider(): Provider {
18 | return this.sdk.provider;
19 | }
20 |
21 | /**
22 | * Creates a new Registry.
23 | * @returns
24 | */
25 | async newRegistry({
26 | numQuarries,
27 | rewarderKey,
28 | payer = this.provider.wallet.publicKey,
29 | }: {
30 | numQuarries: number;
31 | rewarderKey: PublicKey;
32 | payer?: PublicKey;
33 | }): Promise<{ tx: TransactionEnvelope; registry: PublicKey }> {
34 | const [registry, bump] = await findRegistryAddress(
35 | rewarderKey,
36 | this.program.programId
37 | );
38 | const createRegistryTX = new TransactionEnvelope(this.provider, [
39 | this.program.instruction.newRegistry(numQuarries, bump, {
40 | accounts: {
41 | rewarder: rewarderKey,
42 | registry,
43 | payer,
44 | systemProgram: SystemProgram.programId,
45 | },
46 | }),
47 | ]);
48 | return {
49 | tx: createRegistryTX,
50 | registry,
51 | };
52 | }
53 |
54 | async syncQuarry({
55 | tokenMint,
56 | rewarderKey,
57 | }: {
58 | tokenMint: PublicKey;
59 | rewarderKey: PublicKey;
60 | }): Promise {
61 | const [registry] = await findRegistryAddress(
62 | rewarderKey,
63 | this.program.programId
64 | );
65 | const [quarry] = await findQuarryAddress(
66 | rewarderKey,
67 | tokenMint,
68 | this.sdk.programs.Mine.programId
69 | );
70 | return new TransactionEnvelope(this.provider, [
71 | this.program.instruction.syncQuarry({
72 | accounts: { quarry, registry },
73 | }),
74 | ]);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/tests/quarryRedeemer.spec.ts:
--------------------------------------------------------------------------------
1 | import { chaiSolana, expectTX } from "@saberhq/chai-solana";
2 | import {
3 | createMint,
4 | createMintToInstruction,
5 | getOrCreateATAs,
6 | getTokenAccount,
7 | u64,
8 | ZERO,
9 | } from "@saberhq/token-utils";
10 | import type { PublicKey } from "@solana/web3.js";
11 | import { Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
12 | import { expect, use } from "chai";
13 | import invariant from "tiny-invariant";
14 |
15 | import type { QuarrySDK } from "../src";
16 | import { DEFAULT_DECIMALS } from "./utils";
17 | import { makeSDK } from "./workspace";
18 |
19 | use(chaiSolana);
20 |
21 | describe("Redeemer", () => {
22 | const sdk: QuarrySDK = makeSDK();
23 | const provider = sdk.provider;
24 |
25 | let redeemerBump: number;
26 |
27 | let userAuthority: Keypair;
28 |
29 | let iouMint: PublicKey;
30 | let iouMintAuthority: Keypair;
31 | let iouSource: PublicKey;
32 |
33 | let redemptionMint: PublicKey;
34 | let redemptionMintAuthority: Keypair;
35 | let redemptionDestination: PublicKey;
36 |
37 | beforeEach(async () => {
38 | iouMintAuthority = Keypair.generate();
39 | iouMint = await createMint(sdk.provider, iouMintAuthority.publicKey);
40 |
41 | redemptionMintAuthority = Keypair.generate();
42 | redemptionMint = await createMint(
43 | sdk.provider,
44 | redemptionMintAuthority.publicKey
45 | );
46 |
47 | userAuthority = Keypair.generate();
48 | // Airdrop to user
49 | await sdk.provider.connection.requestAirdrop(
50 | userAuthority.publicKey,
51 | 10 * LAMPORTS_PER_SOL
52 | );
53 |
54 | const { bump, tx, vaultTokenAccount } = await sdk.createRedeemer({
55 | iouMint,
56 | redemptionMint,
57 | });
58 |
59 | const { accounts, createAccountInstructions } = await getOrCreateATAs({
60 | provider,
61 | mints: {
62 | iouMint,
63 | redemptionMint,
64 | },
65 | owner: userAuthority.publicKey,
66 | });
67 |
68 | invariant(
69 | createAccountInstructions.iouMint,
70 | "create user ATA account for iouMint"
71 | );
72 | invariant(
73 | createAccountInstructions.redemptionMint,
74 | "create user ATA account for redemptionMint"
75 | );
76 | tx.instructions.push(
77 | createAccountInstructions.iouMint,
78 | createAccountInstructions.redemptionMint
79 | );
80 | tx.instructions.push(
81 | ...createMintToInstruction({
82 | provider,
83 | mint: redemptionMint,
84 | mintAuthorityKP: redemptionMintAuthority,
85 | to: vaultTokenAccount,
86 | amount: new u64(1_000 * DEFAULT_DECIMALS),
87 | }).instructions,
88 | ...createMintToInstruction({
89 | provider,
90 | mint: iouMint,
91 | mintAuthorityKP: iouMintAuthority,
92 | to: accounts.iouMint,
93 | amount: new u64(1_000 * DEFAULT_DECIMALS),
94 | }).instructions
95 | );
96 | tx.addSigners(iouMintAuthority, redemptionMintAuthority);
97 | await expectTX(tx, "create redeemer").to.be.fulfilled;
98 |
99 | iouSource = accounts.iouMint;
100 |
101 | redeemerBump = bump;
102 | redemptionDestination = accounts.redemptionMint;
103 | });
104 |
105 | it("Redeemer was initialized", async () => {
106 | const { data } = await sdk.loadRedeemer({
107 | iouMint,
108 | redemptionMint,
109 | });
110 | expect(data.bump).to.equal(redeemerBump);
111 | expect(data.iouMint).to.eqAddress(iouMint);
112 | expect(data.redemptionMint).to.eqAddress(redemptionMint);
113 | });
114 |
115 | it("Redeem tokens", async () => {
116 | const redeemerWrapper = await sdk.loadRedeemer({
117 | iouMint,
118 | redemptionMint,
119 | });
120 |
121 | let iouSourceAccount = await getTokenAccount(provider, iouSource);
122 | const expectedAmount = iouSourceAccount.amount;
123 |
124 | const tx = await redeemerWrapper.redeemTokens({
125 | tokenAmount: new u64(1_000 * DEFAULT_DECIMALS),
126 | sourceAuthority: userAuthority.publicKey,
127 | iouSource,
128 | redemptionDestination,
129 | });
130 | await expectTX(tx.addSigners(userAuthority), "redeem").to.be.fulfilled;
131 |
132 | iouSourceAccount = await getTokenAccount(provider, iouSource);
133 | expect(iouSourceAccount.amount.toString()).to.equal(ZERO.toString());
134 | const redemptionDestinationAccount = await getTokenAccount(
135 | provider,
136 | redemptionDestination
137 | );
138 | expect(redemptionDestinationAccount.amount.toString()).to.equal(
139 | expectedAmount.toString()
140 | );
141 | });
142 | });
143 |
--------------------------------------------------------------------------------
/tests/quarryUtils.ts:
--------------------------------------------------------------------------------
1 | import { expectTX } from "@saberhq/chai-solana";
2 | import { SignerWallet, SolanaProvider } from "@saberhq/solana-contrib";
3 | import { Token, u64 } from "@saberhq/token-utils";
4 | import type { Connection, PublicKey, Signer } from "@solana/web3.js";
5 | import { Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
6 |
7 | import type { RewarderWrapper } from "../src";
8 | import { QuarrySDK, QuarryWrapper } from "../src";
9 |
10 | export type RewarderAndQuarry = {
11 | adminKP: Signer;
12 | /**
13 | * Token issued as Quarry rewards.
14 | */
15 | rewardsToken: Token;
16 |
17 | quarry: PublicKey;
18 | /**
19 | * Quarry wrapper
20 | */
21 | quarryW: QuarryWrapper;
22 |
23 | rewarder: PublicKey;
24 | /**
25 | * Rewarder wrapper
26 | */
27 | rewarderW: RewarderWrapper;
28 | };
29 |
30 | export const createRewarderAndQuarry = async ({
31 | connection,
32 | stakedToken,
33 | annualRate,
34 | adminKP = Keypair.generate(),
35 | }: {
36 | adminKP?: Signer;
37 | connection: Connection;
38 | /**
39 | * Token to stake in the Quarry.
40 | */
41 | stakedToken: Token;
42 | annualRate: u64;
43 | }): Promise => {
44 | const { rewardsToken, rewarder, rewarderW, quarrySDK } = await createRewarder(
45 | {
46 | connection,
47 | adminKP,
48 | }
49 | );
50 |
51 | const { tx: createQuarryTX, quarry: quarryKey } =
52 | await rewarderW.createQuarry({
53 | token: stakedToken,
54 | });
55 | await expectTX(createQuarryTX, "Create quarry").to.be.fulfilled;
56 |
57 | const quarryW = await QuarryWrapper.load({
58 | sdk: quarrySDK,
59 | token: stakedToken,
60 | key: quarryKey,
61 | });
62 | const setShareTX = quarryW.setRewardsShare(new u64(1));
63 | const syncRewardsTX = await rewarderW.setAndSyncAnnualRewards(annualRate, [
64 | stakedToken.mintAccount,
65 | ]);
66 | await expectTX(setShareTX.combine(syncRewardsTX), "set rewards").to.be
67 | .fulfilled;
68 |
69 | return {
70 | adminKP,
71 | rewardsToken,
72 | quarry: quarryKey,
73 | quarryW,
74 | rewarder,
75 | rewarderW,
76 | };
77 | };
78 |
79 | export const createRewarder = async ({
80 | connection,
81 | adminKP = Keypair.generate(),
82 | }: {
83 | adminKP?: Signer;
84 | connection: Connection;
85 | }): Promise<{
86 | quarrySDK: QuarrySDK;
87 | adminKP: Signer;
88 | /**
89 | * Token issued as Quarry rewards.
90 | */
91 | rewardsToken: Token;
92 | rewarder: PublicKey;
93 | rewarderW: RewarderWrapper;
94 | }> => {
95 | await connection.confirmTransaction(
96 | await connection.requestAirdrop(adminKP.publicKey, 10 * LAMPORTS_PER_SOL)
97 | );
98 |
99 | const primaryQuarrySDK = QuarrySDK.load({
100 | provider: SolanaProvider.load({
101 | connection,
102 | sendConnection: connection,
103 | wallet: new SignerWallet(adminKP),
104 | }),
105 | });
106 |
107 | const primaryMintWrapper =
108 | await primaryQuarrySDK.mintWrapper.newWrapperAndMint({
109 | // 1B
110 | hardcap: new u64("1000000000000000"),
111 | decimals: 6,
112 | });
113 | await expectTX(primaryMintWrapper.tx, "primary mint wrapper").to.be.fulfilled;
114 | const primaryRewarder = await primaryQuarrySDK.mine.createRewarder({
115 | mintWrapper: primaryMintWrapper.mintWrapper,
116 | });
117 | await expectTX(primaryRewarder.tx, "primary rewarder").to.be.fulfilled;
118 |
119 | // create minter info
120 | const minterAddTX = await primaryQuarrySDK.mintWrapper.newMinterWithAllowance(
121 | primaryMintWrapper.mintWrapper,
122 | primaryRewarder.key,
123 | new u64(1_000_000000)
124 | );
125 | await expectTX(minterAddTX, "Minter add").to.be.fulfilled;
126 |
127 | // create quarry
128 | const rewarderW = await primaryQuarrySDK.mine.loadRewarderWrapper(
129 | primaryRewarder.key
130 | );
131 |
132 | return {
133 | quarrySDK: primaryQuarrySDK,
134 | adminKP,
135 | rewardsToken: Token.fromMint(primaryMintWrapper.mint, 6),
136 | rewarder: rewarderW.rewarderKey,
137 | rewarderW,
138 | };
139 | };
140 |
--------------------------------------------------------------------------------
/tests/registry.spec.ts:
--------------------------------------------------------------------------------
1 | import type * as anchor from "@project-serum/anchor";
2 | import { expectTX } from "@saberhq/chai-solana";
3 | import type { Provider } from "@saberhq/solana-contrib";
4 | import {
5 | createInitMintInstructions,
6 | createMint,
7 | Token,
8 | TokenAmount,
9 | } from "@saberhq/token-utils";
10 | import type { PublicKey } from "@solana/web3.js";
11 | import { Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
12 | import { BN } from "bn.js";
13 |
14 | import type {
15 | MineWrapper,
16 | MintWrapper,
17 | QuarrySDK,
18 | RewarderWrapper,
19 | } from "../src";
20 | import { DEFAULT_DECIMALS, DEFAULT_HARD_CAP } from "./utils";
21 | import { makeSDK } from "./workspace";
22 |
23 | describe("Registry", () => {
24 | const dailyRewardsRate = new BN(1_000 * LAMPORTS_PER_SOL);
25 | const annualRewardsRate = dailyRewardsRate.mul(new BN(365));
26 |
27 | let sdk: QuarrySDK;
28 | let provider: Provider;
29 | let mintWrapper: MintWrapper;
30 | let mine: MineWrapper;
31 |
32 | let stakedMintAuthority: anchor.web3.Keypair;
33 | let stakeTokenMint: anchor.web3.PublicKey;
34 | let stakeToken: Token;
35 |
36 | let rewarder: PublicKey;
37 | let rewarderWrapper: RewarderWrapper;
38 |
39 | before("Initialize SDK", () => {
40 | sdk = makeSDK();
41 | provider = sdk.provider;
42 | mintWrapper = sdk.mintWrapper;
43 | mine = sdk.mine;
44 | });
45 |
46 | let rewardsMint: PublicKey;
47 | let token: Token;
48 | let mintWrapperKey: PublicKey;
49 | let hardCap: TokenAmount;
50 |
51 | beforeEach("Initialize mint", async () => {
52 | const rewardsMintKP = Keypair.generate();
53 | rewardsMint = rewardsMintKP.publicKey;
54 | token = Token.fromMint(rewardsMint, DEFAULT_DECIMALS);
55 | hardCap = TokenAmount.parse(token, DEFAULT_HARD_CAP.toString());
56 | const { tx, mintWrapper: wrapperKey } = await mintWrapper.newWrapper({
57 | hardcap: hardCap.toU64(),
58 | tokenMint: rewardsMint,
59 | });
60 |
61 | await expectTX(
62 | await createInitMintInstructions({
63 | provider,
64 | mintKP: rewardsMintKP,
65 | decimals: DEFAULT_DECIMALS,
66 | mintAuthority: wrapperKey,
67 | freezeAuthority: wrapperKey,
68 | })
69 | ).to.be.fulfilled;
70 |
71 | mintWrapperKey = wrapperKey;
72 | await expectTX(tx, "Initialize mint").to.be.fulfilled;
73 | });
74 |
75 | beforeEach(async () => {
76 | stakedMintAuthority = Keypair.generate();
77 | stakeTokenMint = await createMint(
78 | provider,
79 | stakedMintAuthority.publicKey,
80 | DEFAULT_DECIMALS
81 | );
82 | stakeToken = Token.fromMint(stakeTokenMint, DEFAULT_DECIMALS, {
83 | name: "stake token",
84 | });
85 |
86 | const { tx: rewarderTx, key: rewarderKey } = await mine.createRewarder({
87 | mintWrapper: mintWrapperKey,
88 | });
89 | rewarder = rewarderKey;
90 | await expectTX(rewarderTx).eventually.to.be.fulfilled;
91 |
92 | rewarderWrapper = await mine.loadRewarderWrapper(rewarder);
93 |
94 | const setAnnualRewardsTX = await rewarderWrapper.setAndSyncAnnualRewards(
95 | annualRewardsRate,
96 | []
97 | );
98 | await expectTX(setAnnualRewardsTX).eventually.to.be.fulfilled;
99 |
100 | const { tx: createQuarryTX } = await rewarderWrapper.createQuarry({
101 | token: stakeToken,
102 | });
103 | await expectTX(createQuarryTX, "create quarry for stake token").to.be
104 | .fulfilled;
105 | });
106 |
107 | it("create registry", async () => {
108 | const { tx: createRegistryTX, registry } = await sdk.registry.newRegistry({
109 | numQuarries: 100,
110 | rewarderKey: rewarder,
111 | });
112 | await expectTX(createRegistryTX, "create registry").to.eventually.be
113 | .fulfilled;
114 |
115 | const syncQuarryTX = await sdk.registry.syncQuarry({
116 | tokenMint: stakeToken.mintAccount,
117 | rewarderKey: rewarder,
118 | });
119 | await expectTX(syncQuarryTX, "sync quarry").to.eventually.be.fulfilled;
120 |
121 | console.log(
122 | (await sdk.registry.program.account.registry.fetch(registry)).tokens.map(
123 | (tok) => tok.toString()
124 | )
125 | );
126 | });
127 | });
128 |
--------------------------------------------------------------------------------
/tests/test-key.json:
--------------------------------------------------------------------------------
1 | [
2 | 59, 119, 22, 144, 22, 26, 228, 82, 93, 84, 233, 133, 253, 173, 250, 29, 128,
3 | 206, 192, 108, 44, 89, 3, 99, 195, 15, 164, 69, 158, 156, 214, 110, 98, 163,
4 | 186, 118, 187, 232, 79, 243, 157, 213, 39, 116, 55, 228, 243, 129, 80, 71, 35,
5 | 118, 166, 156, 181, 182, 152, 132, 219, 182, 206, 102, 55, 210
6 | ]
7 |
--------------------------------------------------------------------------------
/tests/utils.ts:
--------------------------------------------------------------------------------
1 | import { AnchorProvider } from "@project-serum/anchor";
2 | import { expectTX } from "@saberhq/chai-solana";
3 | import type { Token } from "@saberhq/token-utils";
4 | import {
5 | ASSOCIATED_TOKEN_PROGRAM_ID,
6 | SPLToken,
7 | TOKEN_PROGRAM_ID,
8 | } from "@saberhq/token-utils";
9 | import type { PublicKey, Signer } from "@solana/web3.js";
10 | import { Transaction } from "@solana/web3.js";
11 |
12 | import type { QuarrySDK, QuarryWrapper } from "../src";
13 |
14 | export const DEFAULT_DECIMALS = 6;
15 | export const DEFAULT_HARD_CAP = 1_000_000_000_000;
16 |
17 | export const newUserStakeTokenAccount = async (
18 | sdk: QuarrySDK,
19 | quarry: QuarryWrapper,
20 | stakeToken: Token,
21 | stakedMintAuthority: Signer,
22 | amount: number
23 | ): Promise => {
24 | const minerActions = await quarry.getMinerActions(
25 | sdk.provider.wallet.publicKey
26 | );
27 | const createATA = await minerActions.createATAIfNotExists();
28 | if (createATA) {
29 | await expectTX(createATA, "create ATA").to.be.fulfilled;
30 | }
31 |
32 | const userStakeTokenAccount = minerActions.stakedTokenATA;
33 | await expectTX(
34 | sdk.newTx(
35 | [
36 | SPLToken.createMintToInstruction(
37 | TOKEN_PROGRAM_ID,
38 | stakeToken.mintAccount,
39 | userStakeTokenAccount,
40 | stakedMintAuthority.publicKey,
41 | [],
42 | amount
43 | ),
44 | ],
45 | [stakedMintAuthority]
46 | ),
47 | "mint initial"
48 | ).to.be.fulfilled;
49 |
50 | return userStakeTokenAccount;
51 | };
52 |
53 | export const initATA = async (
54 | token: Token,
55 | owner: Signer,
56 | mint?: { minter: Signer; mintAmount: number }
57 | ): Promise => {
58 | const account = await SPLToken.getAssociatedTokenAddress(
59 | ASSOCIATED_TOKEN_PROGRAM_ID,
60 | TOKEN_PROGRAM_ID,
61 | token.mintAccount,
62 | owner.publicKey
63 | );
64 |
65 | const tx = new Transaction().add(
66 | SPLToken.createAssociatedTokenAccountInstruction(
67 | ASSOCIATED_TOKEN_PROGRAM_ID,
68 | TOKEN_PROGRAM_ID,
69 | token.mintAccount,
70 | account,
71 | owner.publicKey,
72 | AnchorProvider.env().wallet.publicKey
73 | )
74 | );
75 |
76 | if (mint) {
77 | tx.add(
78 | SPLToken.createMintToInstruction(
79 | TOKEN_PROGRAM_ID,
80 | token.mintAccount,
81 | account,
82 | mint.minter.publicKey,
83 | [],
84 | mint.mintAmount
85 | )
86 | );
87 | }
88 | // mint tokens
89 | await AnchorProvider.env().sendAndConfirm(
90 | tx,
91 | mint ? [mint.minter] : undefined,
92 | {
93 | commitment: "confirmed",
94 | }
95 | );
96 | return account;
97 | };
98 |
--------------------------------------------------------------------------------
/tests/workspace.ts:
--------------------------------------------------------------------------------
1 | import type { Idl } from "@project-serum/anchor";
2 | import * as anchor from "@project-serum/anchor";
3 | import { chaiSolana } from "@saberhq/chai-solana";
4 | import { SolanaProvider } from "@saberhq/solana-contrib";
5 | import chai, { assert } from "chai";
6 |
7 | import type { Programs } from "../src";
8 | import { QuarrySDK } from "../src";
9 |
10 | chai.use(chaiSolana);
11 |
12 | export type Workspace = Programs;
13 |
14 | export const makeSDK = (): QuarrySDK => {
15 | const anchorProvider = anchor.AnchorProvider.env();
16 | anchor.setProvider(anchorProvider);
17 |
18 | const provider = SolanaProvider.load({
19 | connection: anchorProvider.connection,
20 | sendConnection: anchorProvider.connection,
21 | wallet: anchorProvider.wallet,
22 | opts: anchorProvider.opts,
23 | });
24 | return QuarrySDK.load({
25 | provider,
26 | });
27 | };
28 |
29 | type IDLError = NonNullable[number];
30 |
31 | export const assertError = (error: IDLError, other: IDLError): void => {
32 | assert.strictEqual(error.code, other.code);
33 | assert.strictEqual(error.msg, other.msg);
34 | };
35 |
--------------------------------------------------------------------------------
/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@saberhq/tsconfig/tsconfig.mono.cjs.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist/cjs/"
6 | },
7 | "include": ["src/"]
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@saberhq/tsconfig/tsconfig.mono.json",
3 | "compilerOptions": {
4 | "rootDir": "src",
5 | "outDir": "dist/esm/"
6 | },
7 | "include": ["src/"]
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@saberhq/tsconfig/tsconfig.lib.json",
3 | "compilerOptions": {
4 | "module": "CommonJS",
5 | "noEmit": true,
6 | "types": ["mocha"]
7 | },
8 | "include": ["src/", "tests/"],
9 | "files": [".eslintrc.cjs"]
10 | }
11 |
--------------------------------------------------------------------------------