├── .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 | [![License](https://img.shields.io/badge/license-AGPL%203.0-blue)](https://github.com/QuarryProtocol/quarry/blob/master/LICENSE) 4 | [![Build Status](https://img.shields.io/github/workflow/status/QuarryProtocol/quarry/E2E/master)](https://github.com/QuarryProtocol/quarry/actions/workflows/programs-e2e.yml?query=branch%3Amaster) 5 | [![Contributors](https://img.shields.io/github/contributors/QuarryProtocol/quarry)](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 | [![Crates.io](https://img.shields.io/crates/v/quarry-merge-mine)](https://crates.io/crates/quarry-merge-mine) | [![Docs.rs](https://docs.rs/quarry-merge-mine/badge.svg)](https://docs.rs/quarry-merge-mine) | 29 | | `quarry-mine` | Distributes liquidity mining rewards to token stakers | [![crates](https://img.shields.io/crates/v/quarry-mine)](https://crates.io/crates/quarry-mine) | [![Docs.rs](https://docs.rs/quarry-mine/badge.svg)](https://docs.rs/quarry-mine) | 30 | | `quarry-mint-wrapper` | Mints tokens to authorized accounts | [![Crates.io](https://img.shields.io/crates/v/quarry-mint-wrapper)](https://crates.io/crates/quarry-mint-wrapper) | [![Docs.rs](https://docs.rs/quarry-mint-wrapper/badge.svg)](https://docs.rs/quarry-mint-wrapper) | 31 | | `quarry-operator` | Delegates Quarry Rewarder authority roles. | [![crates](https://img.shields.io/crates/v/quarry-operator)](https://crates.io/crates/quarry-operator) | [![Docs.rs](https://docs.rs/quarry-operator/badge.svg)](https://docs.rs/quarry-operator) | 32 | | `quarry-redeemer` | Redeems one token for another | [![crates](https://img.shields.io/crates/v/quarry-redeemer)](https://crates.io/crates/quarry-redeemer) | [![Docs.rs](https://docs.rs/quarry-redeemer/badge.svg)](https://docs.rs/quarry-redeemer) | 33 | | `quarry-registry` | Registry to index all quarries of a rewarder. | [![crates](https://img.shields.io/crates/v/quarry-registry)](https://crates.io/crates/quarry-registry) | [![Docs.rs](https://docs.rs/quarry-registry/badge.svg)](https://docs.rs/quarry-registry) | 34 | | `@quarryprotocol/quarry-sdk` | TypeScript SDK for Quarry | [![npm](https://img.shields.io/npm/v/@quarryprotocol/quarry-sdk.svg)](https://www.npmjs.com/package/@quarryprotocol/quarry-sdk) | [![Docs](https://img.shields.io/badge/docs-typedoc-blue)](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 | --------------------------------------------------------------------------------