Compare commits
No commits in common. "stake-pool-v0.3.0" and "master" have entirely different histories.
stake-pool
...
master
|
@ -1,59 +0,0 @@
|
|||
name: Binary Oracle Pair Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'binary-oracle-pair/**'
|
||||
- 'token/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'binary-oracle-pair/**'
|
||||
- 'token/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh binary-oracle-pair
|
|
@ -10,34 +10,7 @@ on:
|
|||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
check_non_docs:
|
||||
outputs:
|
||||
run_all_github_action_checks: ${{ steps.check_files.outputs.run_all_github_action_checks }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: check modified files
|
||||
id: check_files
|
||||
run: |
|
||||
echo "========== check paths of modified files =========="
|
||||
echo "::set-output name=run_all_github_action_checks::true"
|
||||
git diff --name-only HEAD^ HEAD > files.txt
|
||||
while IFS= read -r file
|
||||
do
|
||||
if [[ $file != docs/** ]]; then
|
||||
echo "Found modified non-'docs' file(s)"
|
||||
echo "::set-output name=run_all_github_action_checks::false"
|
||||
break
|
||||
fi
|
||||
done < files.txt
|
||||
|
||||
all_github_action_checks:
|
||||
runs-on: ubuntu-latest
|
||||
needs: check_non_docs
|
||||
if: needs.check_non_docs.outputs.run_all_github_action_checks == 'true'
|
||||
steps:
|
||||
- run: echo "Done"
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
name: Examples Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'examples/rust/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'examples/rust/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh examples/rust
|
|
@ -1,59 +0,0 @@
|
|||
name: Feature Proposal Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'feature-proposal/**'
|
||||
- 'token/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'feature-proposal/**'
|
||||
- 'token/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh feature-proposal
|
|
@ -1,59 +0,0 @@
|
|||
name: Governance Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'governance/**'
|
||||
- 'token/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'governance/**'
|
||||
- 'token/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh governance
|
|
@ -1,57 +0,0 @@
|
|||
name: Libraries Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'libraries/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'libraries/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh libraries
|
|
@ -1,57 +0,0 @@
|
|||
name: Memo Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'memo/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'memo/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh memo
|
|
@ -1,57 +0,0 @@
|
|||
name: Name Service Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'name-service/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'name-service/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh name-service
|
|
@ -1,57 +0,0 @@
|
|||
name: Record Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'record/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'record/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh record
|
|
@ -1,57 +0,0 @@
|
|||
name: Shared Memory Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'shared-memory/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'shared-memory/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh shared-memory
|
|
@ -1,59 +0,0 @@
|
|||
name: Stake Pool Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'stake-pool/**'
|
||||
- 'token/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'stake-pool/**'
|
||||
- 'token/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh stake-pool
|
|
@ -1,90 +0,0 @@
|
|||
name: Token Lending Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'token-lending/**'
|
||||
- 'token/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'token-lending/**'
|
||||
- 'token/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh token-lending
|
||||
|
||||
- name: Upload programs
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: token-lending-programs
|
||||
path: "target/deploy/*.so"
|
||||
if-no-files-found: error
|
||||
|
||||
js-test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_VERSION: 12.x
|
||||
needs: cargo-test-bpf
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-${{ hashFiles('token-lending/js/package-lock.json') }}
|
||||
restore-keys: |
|
||||
node-
|
||||
- name: Download programs
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: token-lending-programs
|
||||
path: target/deploy
|
||||
- run: ./ci/js-test-token-lending.sh
|
|
@ -1,154 +0,0 @@
|
|||
name: Token Swap Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'token-swap/**'
|
||||
- 'token/**'
|
||||
- 'libraries/math/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'token-swap/**'
|
||||
- 'token/**'
|
||||
- 'libraries/math/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh token-swap
|
||||
|
||||
- name: Build production version
|
||||
run: |
|
||||
cargo +"$RUST_STABLE" build-bpf \
|
||||
--manifest-path=token-swap/program/Cargo.toml \
|
||||
--features production \
|
||||
--bpf-out-dir target/deploy-production
|
||||
env:
|
||||
SWAP_PROGRAM_OWNER_FEE_ADDRESS: HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN
|
||||
|
||||
- name: Move production version for upload
|
||||
run: |
|
||||
mv target/deploy-production/spl_token_swap.so target/deploy/spl_token_swap_production.so
|
||||
|
||||
- name: Upload programs
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: token-swap-programs
|
||||
path: "target/deploy/*.so"
|
||||
if-no-files-found: error
|
||||
|
||||
js-test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_VERSION: 12.x
|
||||
needs: cargo-test-bpf
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-${{ hashFiles('token-swap/js/package-lock.json') }}
|
||||
restore-keys: |
|
||||
node-
|
||||
- name: Download programs
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: token-swap-programs
|
||||
path: target/deploy
|
||||
- run: ./ci/js-test-token-swap.sh
|
||||
|
||||
fuzz:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: token-swap-fuzz-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/cargo-hfuzz
|
||||
~/.cargo/bin/cargo-honggfuzz
|
||||
key: cargo-fuzz-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
restore-keys: |
|
||||
solana-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Run fuzz target
|
||||
run: ./ci/fuzz.sh token-swap-instructions 30 # 30 seconds, just to check everything is ok
|
|
@ -1,93 +0,0 @@
|
|||
name: Token Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'associated-token-account/**'
|
||||
- 'token/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'associated-token-account/**'
|
||||
- 'token/**'
|
||||
|
||||
jobs:
|
||||
cargo-test-bpf:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/rustfilt
|
||||
key: cargo-bpf-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh token
|
||||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-test-bpf.sh associated-token-account
|
||||
|
||||
- name: Upload programs
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: token-programs
|
||||
path: "target/deploy/*.so"
|
||||
if-no-files-found: error
|
||||
|
||||
js-test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_VERSION: 12.x
|
||||
needs: cargo-test-bpf
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-${{ hashFiles('token/js/package-lock.json') }}
|
||||
restore-keys: |
|
||||
node-
|
||||
- name: Download programs
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: token-programs
|
||||
path: target/deploy
|
||||
- run: ./ci/js-test-token.sh
|
|
@ -16,6 +16,10 @@ jobs:
|
|||
- rustfmt
|
||||
- clippy
|
||||
- cargo-build-test
|
||||
- js-test-token
|
||||
- js-test-token-swap
|
||||
- js-test-token-lending
|
||||
- fuzz
|
||||
steps:
|
||||
- run: echo "Done"
|
||||
|
||||
|
@ -124,3 +128,135 @@ jobs:
|
|||
|
||||
- name: Build and test
|
||||
run: ./ci/cargo-build-test.sh
|
||||
|
||||
- name: Upload programs
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: programs
|
||||
path: "target/deploy/*.so"
|
||||
if-no-files-found: error
|
||||
|
||||
js-test-token:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_VERSION: 12.x
|
||||
needs: cargo-build-test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-token-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
node-token-
|
||||
- name: Download programs
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: programs
|
||||
path: target/bpfel-unknown-unknown/release
|
||||
- run: ./ci/js-test-token.sh
|
||||
|
||||
js-test-token-swap:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_VERSION: 12.x
|
||||
needs: cargo-build-test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-token-swap-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
node-token-swap-
|
||||
- name: Download programs
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: programs
|
||||
path: target/deploy
|
||||
- run: ./ci/js-test-token-swap.sh
|
||||
|
||||
js-test-token-lending:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_VERSION: 12.x
|
||||
needs: cargo-build-test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-token-lending-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
node-token-lending-
|
||||
- name: Download programs
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: programs
|
||||
path: target/deploy
|
||||
- run: ./ci/js-test-token-lending.sh
|
||||
|
||||
fuzz:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
fuzz_target: [token-swap-instructions]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set env vars
|
||||
run: |
|
||||
source ci/rust-version.sh
|
||||
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
|
||||
source ci/solana-version.sh
|
||||
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_STABLE }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-fuzz-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/cargo-hfuzz
|
||||
~/.cargo/bin/cargo-honggfuzz
|
||||
key: cargo-fuzz-bins-${{ runner.os }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cache
|
||||
key: solana-${{ env.SOLANA_VERSION }}
|
||||
restore-keys: |
|
||||
solana-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
./ci/install-build-deps.sh
|
||||
./ci/install-program-deps.sh
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Run fuzz target
|
||||
run: ./ci/fuzz.sh ${{ matrix.fuzz_target }} 30 # 30 seconds, just to check everything is ok
|
||||
|
|
|
@ -8,5 +8,3 @@ node_modules
|
|||
hfuzz_target
|
||||
hfuzz_workspace
|
||||
**/*.so
|
||||
**/.DS_Store
|
||||
test-ledger
|
||||
|
|
13
.mergify.yml
13
.mergify.yml
|
@ -4,21 +4,10 @@
|
|||
#
|
||||
# https://doc.mergify.io/
|
||||
pull_request_rules:
|
||||
- name: label changes from community
|
||||
conditions:
|
||||
- author≠@core-contributors
|
||||
- author≠mergify[bot]
|
||||
- author≠dependabot[bot]
|
||||
actions:
|
||||
label:
|
||||
add:
|
||||
- community
|
||||
- name: automatic merge (squash) on CI success
|
||||
conditions:
|
||||
- check-success=Travis CI - Pull Request
|
||||
- check-success=all_github_action_checks
|
||||
- "#status-failure=0"
|
||||
- "#status-neutral=0"
|
||||
- label=automerge
|
||||
- author≠@dont-squash-my-commits
|
||||
actions:
|
||||
|
@ -29,8 +18,6 @@ pull_request_rules:
|
|||
conditions:
|
||||
- check-success=Travis CI - Pull Request
|
||||
- check-success=all_github_action_checks
|
||||
- "#status-failure=0"
|
||||
- "#status-neutral=0"
|
||||
- label=automerge
|
||||
- author=@dont-squash-my-commits
|
||||
actions:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,16 +9,14 @@ members = [
|
|||
"examples/rust/transfer-lamports",
|
||||
"feature-proposal/program",
|
||||
"feature-proposal/cli",
|
||||
"governance/program",
|
||||
"libraries/math",
|
||||
"memo/program",
|
||||
"name-service/program",
|
||||
"record/program",
|
||||
"shared-memory/program",
|
||||
"stake-pool/cli",
|
||||
"stake-pool/program",
|
||||
"token-lending/cli",
|
||||
"token-lending/program",
|
||||
"token-lending/client",
|
||||
"token-swap/program",
|
||||
"token-swap/program/fuzz",
|
||||
"token/cli",
|
||||
|
@ -31,6 +29,3 @@ exclude = [
|
|||
"themis/program_ristretto",
|
||||
"token/perf-monitor", # TODO: Rework perf-monitor to use solana-program-test, avoiding the need to link directly with the BPF VM
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
|
|
@ -12,12 +12,12 @@ no-entrypoint = []
|
|||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
solana-program = "1.7.3"
|
||||
solana-program = "1.6.2"
|
||||
spl-token = { version = "3.1", path = "../../token/program", features = ["no-entrypoint"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
solana-program-test = "1.6.2"
|
||||
solana-sdk = "1.6.2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
|
|
@ -34,7 +34,7 @@ pub fn get_associated_token_address(
|
|||
wallet_address: &Pubkey,
|
||||
spl_token_mint_address: &Pubkey,
|
||||
) -> Pubkey {
|
||||
get_associated_token_address_and_bump_seed(wallet_address, spl_token_mint_address, &id()).0
|
||||
get_associated_token_address_and_bump_seed(&wallet_address, &spl_token_mint_address, &id()).0
|
||||
}
|
||||
|
||||
fn get_associated_token_address_and_bump_seed_internal(
|
||||
|
|
|
@ -31,10 +31,10 @@ pub fn process_instruction(
|
|||
let rent_sysvar_info = next_account_info(account_info_iter)?;
|
||||
|
||||
let (associated_token_address, bump_seed) = get_associated_token_address_and_bump_seed_internal(
|
||||
wallet_account_info.key,
|
||||
spl_token_mint_info.key,
|
||||
&wallet_account_info.key,
|
||||
&spl_token_mint_info.key,
|
||||
program_id,
|
||||
spl_token_program_id,
|
||||
&spl_token_program_id,
|
||||
);
|
||||
if associated_token_address != *associated_token_account_info.key {
|
||||
msg!("Error: Associated address does not match seed derivation");
|
||||
|
@ -62,7 +62,7 @@ pub fn process_instruction(
|
|||
);
|
||||
invoke(
|
||||
&system_instruction::transfer(
|
||||
funder_info.key,
|
||||
&funder_info.key,
|
||||
associated_token_account_info.key,
|
||||
required_lamports,
|
||||
),
|
||||
|
@ -84,23 +84,23 @@ pub fn process_instruction(
|
|||
associated_token_account_info.clone(),
|
||||
system_program_info.clone(),
|
||||
],
|
||||
&[associated_token_account_signer_seeds],
|
||||
&[&associated_token_account_signer_seeds],
|
||||
)?;
|
||||
|
||||
msg!("Assign the associated token account to the SPL Token program");
|
||||
invoke_signed(
|
||||
&system_instruction::assign(associated_token_account_info.key, spl_token_program_id),
|
||||
&system_instruction::assign(associated_token_account_info.key, &spl_token_program_id),
|
||||
&[
|
||||
associated_token_account_info.clone(),
|
||||
system_program_info.clone(),
|
||||
],
|
||||
&[associated_token_account_signer_seeds],
|
||||
&[&associated_token_account_signer_seeds],
|
||||
)?;
|
||||
|
||||
msg!("Initialize the associated token account");
|
||||
invoke(
|
||||
&spl_token::instruction::initialize_account(
|
||||
spl_token_program_id,
|
||||
&spl_token_program_id,
|
||||
associated_token_account_info.key,
|
||||
spl_token_mint_info.key,
|
||||
wallet_account_info.key,
|
||||
|
|
|
@ -13,16 +13,16 @@ test-bpf = []
|
|||
[dependencies]
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
solana-program = "1.7.3"
|
||||
spl-token = { version = "3.1", path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||
solana-program = "1.6.2"
|
||||
spl-token = { version = "3.0", path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||
thiserror = "1.0"
|
||||
uint = "0.8"
|
||||
arbitrary = { version = "0.4", features = ["derive"], optional = true }
|
||||
borsh = "0.8.2"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
solana-program-test = "1.6.2"
|
||||
solana-sdk = "1.6.2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
|
|
@ -421,7 +421,7 @@ impl Processor {
|
|||
authority_account_info.clone(),
|
||||
user_transfer_authority_info.clone(),
|
||||
amount,
|
||||
pool_account_info.key,
|
||||
&pool_account_info.key,
|
||||
pool.bump_seed,
|
||||
)?;
|
||||
|
||||
|
@ -446,7 +446,7 @@ impl Processor {
|
|||
authority_account_info.clone(),
|
||||
user_transfer_authority_info.clone(),
|
||||
amount,
|
||||
pool_account_info.key,
|
||||
&pool_account_info.key,
|
||||
pool.bump_seed,
|
||||
)?;
|
||||
|
||||
|
@ -477,7 +477,7 @@ impl Processor {
|
|||
authority_account_info.clone(),
|
||||
user_transfer_authority_info.clone(),
|
||||
possible_withdraw_amount,
|
||||
pool_account_info.key,
|
||||
&pool_account_info.key,
|
||||
pool.bump_seed,
|
||||
)?;
|
||||
|
||||
|
@ -489,7 +489,7 @@ impl Processor {
|
|||
authority_account_info.clone(),
|
||||
user_transfer_authority_info.clone(),
|
||||
amount,
|
||||
pool_account_info.key,
|
||||
&pool_account_info.key,
|
||||
pool.bump_seed,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -14,6 +14,10 @@ set -x
|
|||
# Build all C examples
|
||||
make -C examples/c
|
||||
|
||||
# Build/test all BPF programs
|
||||
cargo +"$rust_stable" test-bpf -- --nocapture
|
||||
rm -rf target/debug # Prevents running out of space on github action runners
|
||||
|
||||
# Build/test all host crates
|
||||
cargo +"$rust_stable" build
|
||||
cargo +"$rust_stable" test -- --nocapture
|
||||
|
@ -25,6 +29,13 @@ cargo +"$rust_stable" run --manifest-path=utils/test-client/Cargo.toml
|
|||
# client_ristretto disabled because it requires RpcBanksService, which is no longer supported.
|
||||
#cargo +"$rust_stable" test --manifest-path=themis/client_ristretto/Cargo.toml -- --nocapture
|
||||
|
||||
SWAP_PROGRAM_OWNER_FEE_ADDRESS="HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN" \
|
||||
cargo +"$rust_stable" build-bpf \
|
||||
--manifest-path=token-swap/program/Cargo.toml \
|
||||
--features production \
|
||||
--bpf-out-dir target/deploy-production
|
||||
mv target/deploy-production/spl_token_swap.so target/deploy/spl_token_swap_production.so
|
||||
|
||||
# # Check generated C headers
|
||||
# cargo run --manifest-path=utils/cgen/Cargo.toml
|
||||
#
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
source ./ci/rust-version.sh stable
|
||||
source ./ci/solana-version.sh
|
||||
|
||||
export RUSTFLAGS="-D warnings"
|
||||
export RUSTBACKTRACE=1
|
||||
|
||||
usage() {
|
||||
exitcode=0
|
||||
if [[ -n "$1" ]]; then
|
||||
exitcode=1
|
||||
echo "Error: $*"
|
||||
fi
|
||||
echo "Usage: $0 [program-directory]"
|
||||
exit $exitcode
|
||||
}
|
||||
|
||||
program_directory=$1
|
||||
if [[ -z $program_directory ]]; then
|
||||
usage "No program directory provided"
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
cd $program_directory
|
||||
run_dir=$(pwd)
|
||||
|
||||
if [[ -d $run_dir/program ]]; then
|
||||
# Build/test just one BPF program
|
||||
cd $run_dir/program
|
||||
cargo +"$rust_stable" test-bpf -- --nocapture
|
||||
else
|
||||
# Build/test all BPF programs
|
||||
for directory in $(ls -d $run_dir/*/); do
|
||||
cd $directory
|
||||
cargo +"$rust_stable" test-bpf -- --nocapture
|
||||
done
|
||||
fi
|
|
@ -4,10 +4,13 @@ set -ex
|
|||
cd "$(dirname "$0")/.."
|
||||
source ./ci/solana-version.sh install
|
||||
|
||||
(cd token/js && npm install)
|
||||
|
||||
cd token-swap/js
|
||||
npm install
|
||||
npm run lint
|
||||
npm run build
|
||||
npm run flow
|
||||
npx tsc module.d.ts
|
||||
npm run start-with-test-validator
|
||||
(cd ../../target/deploy && mv spl_token_swap_production.so spl_token_swap.so)
|
||||
SWAP_PROGRAM_OWNER_FEE_ADDRESS="HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN" npm run start-with-test-validator
|
||||
|
|
|
@ -18,13 +18,13 @@
|
|||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.53.0
|
||||
stable_version=1.50.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2021-06-09
|
||||
nightly_version=2021-02-18
|
||||
fi
|
||||
|
||||
|
||||
|
|
|
@ -14,10 +14,11 @@
|
|||
if [[ -n $SOLANA_VERSION ]]; then
|
||||
solana_version="$SOLANA_VERSION"
|
||||
else
|
||||
solana_version=v1.7.3
|
||||
solana_version=v1.5.15
|
||||
fi
|
||||
|
||||
export solana_version="$solana_version"
|
||||
export solana_docker_image=solanalabs/solana:"$solana_version"
|
||||
export PATH="$HOME"/.local/share/solana/install/active_release/bin:"$PATH"
|
||||
|
||||
if [[ -n $1 ]]; then
|
||||
|
|
|
@ -8,7 +8,6 @@ module.exports = {
|
|||
"token-lending",
|
||||
"associated-token-account",
|
||||
"memo",
|
||||
"name-service",
|
||||
"shared-memory",
|
||||
"stake-pool",
|
||||
"feature-proposal",
|
||||
|
|
|
@ -55,7 +55,7 @@ The [get_associated_token_address](https://docs.rs/spl-associated-token-account/
|
|||
Rust function may be used by clients to derive the wallet's associated token address.
|
||||
|
||||
|
||||
The associated account address can be derived in TypeScript with:
|
||||
The associated account address can be derived in Javascript with:
|
||||
```ts
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
||||
|
|
|
@ -62,7 +62,7 @@ Logging ends with the status of the instruction, one of:
|
|||
|
||||
For more information about exposing program logs on a node, head to the
|
||||
[developer
|
||||
docs](https://docs.solana.com/developing/on-chain-programs/debugging#logging)
|
||||
docs](https://docs.solana.com/developing/deployed-programs/debugging#logging)
|
||||
|
||||
### Compute Limits
|
||||
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
---
|
||||
title: Name Service
|
||||
---
|
||||
|
||||
A SPL program for issuing and managing ownership of: domain names, Solana Pubkeys, URLs, Twitter handles, ipfs cid's etc..
|
||||
|
||||
This program could be used for dns, pubkey etc lookups via a browser extension
|
||||
for example, the goal is to create an easy way to identify Solana public keys
|
||||
with various links.
|
||||
|
||||
Broader use cases are also imaginable.
|
||||
|
||||
Key points:
|
||||
- A Name is a string that maps to a record (program derived account) which can hold data.
|
||||
- Each name is of a certain class and has a certain owner, both are identified
|
||||
by their pubkeys. The class of a name needs to sign the issuance of it.
|
||||
- A name can have a parent name that is identified by the address of its record.
|
||||
The owner of the parent name (when it exists) needs to sign the issuance of
|
||||
the child name.
|
||||
- The data of a name registry is controlled by the class keypair or, when it is
|
||||
set to `Pubkey::default()`, by the name owner keypair.
|
||||
- Only the owner can delete a name registry.
|
||||
|
||||
|
||||
Remarks and use cases:
|
||||
- Domain name declarations: One could arbitrarily set-up a class that we can call
|
||||
Top-Level-Domain names. Names in this class can only be issued with the
|
||||
permission of the class keypair, ie the administrator, who can enforce that
|
||||
TLD names are of the type `".something"`. From then on one could create and
|
||||
own the TLD `".sol"` and create a class of ".sol" sub-domains, administrating
|
||||
the issuance of the `"something.sol"` sub-domains that way (by setting the
|
||||
parent name to the address of the `".sol"` registry).
|
||||
|
||||
An off-chain browser extension could then, similarly to DNS, parse the user SPL
|
||||
name service URL input and descend the chain of names, verifying that the names
|
||||
exist with the correct parenthood, and finally use the data of the last child
|
||||
name (or also a combination of the parents data) in order to resolve this call
|
||||
towards a real DNS URL or any kind of data.
|
||||
|
||||
Although the ownership and class system makes the administration a given class
|
||||
centralized, the creation of new classes is permissionless and as a class owner
|
||||
any kind of decentralized governance signing program could be used.
|
||||
|
||||
- Twitter handles can be added as names of one specific name class. The class
|
||||
authority of will therefore hold the right to add a Twitter handle name. This
|
||||
enables the verification of Twitter accounts for example by asking the user to
|
||||
tweet his pubkey or a signed message. A bot that holds the private issuing
|
||||
authority key can then sign the Create instruction (with a metadata_authority
|
||||
that is the tweeted pubkey) and send it back to the user who will then submit
|
||||
it to the program.
|
||||
In this case the class will still be able to control the data of the name registry, and not the user for example.
|
||||
|
||||
Therefore, another way of using this program would be to create a name
|
||||
(`"verified-twitter-handles"` for example) with the `Pubkey::default()` class
|
||||
and with the owner being the authority. That way verified Twitter names could be
|
||||
issued as child names of this parent by the owner, leaving the user as being
|
||||
able to modify the data of his Twitter name registry.
|
|
@ -3,7 +3,7 @@ title: Stake Pool Program
|
|||
---
|
||||
|
||||
A program for pooling together SOL to be staked by an off-chain agent running
|
||||
a Delegation Bot which redistributes the stakes across the network and tries
|
||||
a Delegation bot which redistributes the stakes across the network and tries
|
||||
to maximize censorship resistance and rewards.
|
||||
|
||||
## Overview
|
||||
|
@ -14,7 +14,7 @@ inflation rate, total number of SOL staked on the network, and an individual
|
|||
validator’s uptime and commission (fee).
|
||||
|
||||
Stake pools are an alternative method of earning staking rewards. This on-chain
|
||||
program pools together SOL to be staked by a staker, allowing SOL holders to
|
||||
program pools together SOL to be staked by a manager, allowing SOL holders to
|
||||
stake and earn rewards without managing stakes.
|
||||
|
||||
Additional information regarding staking and stake programming is available at:
|
||||
|
@ -24,18 +24,16 @@ Additional information regarding staking and stake programming is available at:
|
|||
|
||||
## Motivation
|
||||
|
||||
This document is intended for the main actors of the stake pool system:
|
||||
|
||||
* manager: creates and manages the stake pool, earns fees, can update the fee, staker, and manager
|
||||
* staker: adds and removes validators to the pool, rebalances stake among validators
|
||||
* user: provides staked SOL into an existing stake pool
|
||||
This document is intended for stake pool managers who want to create or manage
|
||||
stake pools, and users who want to provide staked SOL into an existing stake
|
||||
pool.
|
||||
|
||||
In its current iteration, the stake pool only processes totally active stakes.
|
||||
Deposits must come from fully active stakes, and withdrawals return a fully
|
||||
active stake account.
|
||||
|
||||
This means that stake pool managers, stakers, and users must be comfortable with
|
||||
creating and delegating stakes, which are more advanced operations than sending and
|
||||
This means that stake pool managers and users must be comfortable with creating
|
||||
and delegating stakes, which are more advanced operations than sending and
|
||||
receiving SPL tokens and SOL. Additional information on stake operations are
|
||||
available at:
|
||||
|
||||
|
@ -48,28 +46,27 @@ like [Token Swap](token-swap.md).
|
|||
|
||||
## Operation
|
||||
|
||||
A stake pool manager creates a stake pool, and the staker includes validators that will
|
||||
A stake pool manager creates a stake pool and includes validators that will
|
||||
receive delegations from the pool by creating "validator stake accounts" and
|
||||
activating a delegation on them. Once a validator stake account's delegation is
|
||||
active, the staker adds it to the stake pool.
|
||||
active, the stake pool manager adds it to the stake pool.
|
||||
|
||||
At this point, users can participate with deposits. They must delegate a stake
|
||||
account to the one of the validators in the stake pool. Once it's active, the
|
||||
user can deposit their stake into the pool in exchange for SPL staking derivatives
|
||||
representing their fractional ownership in pool. A percentage of the rewards
|
||||
earned by the pool goes to the pool manager as a fee.
|
||||
representing their fractional ownership in pool. A percentage of the user's
|
||||
deposit goes to the pool manager as a fee.
|
||||
|
||||
Over time, as the stakes in the stake pool accrue staking rewards, the user's fractional
|
||||
Over time, as the stake pool accrues staking rewards, the user's fractional
|
||||
ownership will be worth more than their initial deposit. Whenever the user chooses,
|
||||
they can withdraw their SPL staking derivatives in exchange for an activated stake.
|
||||
|
||||
The stake pool staker can add and remove validators, or rebalance the pool by
|
||||
decreasing the stake on a validator, waiting an epoch to move it into the stake
|
||||
pool's reserve account, then increasing the stake on another validator.
|
||||
The stake pool manager can add and remove validators, or rebalance the pool by
|
||||
withdrawing stakes from the pool, deactivating them, reactivating them on another
|
||||
validator, then depositing back into the pool.
|
||||
|
||||
The staker operation to add a new validator requires roughly 1.003 SOL to create
|
||||
the stake account on a validator, so the stake pool staker will need liquidity
|
||||
on hand to fully manage the pool stakes.
|
||||
These manager operations require SPL staking derivatives and staked SOL, so the
|
||||
stake pool manager will need liquidity on hand to properly manage the pool.
|
||||
|
||||
## Background
|
||||
|
||||
|
@ -115,7 +112,7 @@ Keypair Path: ${HOME}/.config/solana/id.json
|
|||
|
||||
See [Solana clusters](https://docs.solana.com/clusters) for cluster-specific RPC URLs
|
||||
```sh
|
||||
solana config set --url https://api.devnet.solana.com
|
||||
solana config set --url https://devnet.solana.com
|
||||
```
|
||||
|
||||
#### Default Keypair
|
||||
|
@ -133,111 +130,32 @@ Hardware Wallet URL (See [URL spec](https://docs.solana.com/wallet-guide/hardwar
|
|||
solana config set --keypair usb://ledger/
|
||||
```
|
||||
|
||||
#### Run Locally
|
||||
|
||||
If you would like to test a stake pool locally without having to wait for stakes
|
||||
to activate and deactivate, you can run the stake pool locally using the
|
||||
`solana-test-validator` tool with shorter epochs, and pulling the current program
|
||||
from devnet, testnet, or mainnet.
|
||||
|
||||
```sh
|
||||
$ solana-test-validator -c poo1B9L9nR3CrcaziKVYVpRX6A9Y1LAXYasjjfCbApj -c 5TfMPP2zwrXWTUvkg5AG54QWpEkwjeBUhpP7x99kkvEj --url devnet --slots-per-epoch 32
|
||||
$ solana config set --url http://127.0.0.1:8899
|
||||
```
|
||||
|
||||
### Stake Pool Manager Examples
|
||||
### Stake Pool Administrator Examples
|
||||
|
||||
#### Create a stake pool
|
||||
|
||||
The stake pool manager controls the stake pool from a high level, and in exchange
|
||||
receives a fee in the form of SPL token staking derivatives. The manager
|
||||
sets the fee on creation. Let's create a pool with a 3% fee and a maximum of 1000
|
||||
validator stake accounts:
|
||||
The pool administrator manages the stake accounts in a stake pool, and in exchange
|
||||
receives a fee in the form of SPL token staking derivatives. The administrator
|
||||
sets the fee on creation. Let's create a pool with a 3% fee:
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool create-pool --fee-numerator 3 --fee-denominator 100 --max-validators 1000
|
||||
Creating reserve stake 33Hg3bvYrAwfqCzTMjAWZNAWC6H96qJNEdzGamfFjG4J
|
||||
Creating mint D5yiK1tE1yAXBnrV9ZrSUJCw8WiQctZ8ekbv1U6ATVZ
|
||||
Creating pool fee collection account 5gpuSdutGY98KKbgmR5CfLK7toFcQD69JzKDwseegzXE
|
||||
Signature: 2dvCtHMcqxibckhvVgFQeFCRb7VcHbuFLRf71Aqd9PtzFzdbG3gAkNpxYznfpKDx2vTRrVtwW81sZAx5U3Frb5Uu
|
||||
Creating stake pool EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||
Signature: 2kYDVyJp8FVrLmEZyW9ivMYcXEsgWm4hFyhp5omxVtonjhYG6WS1S85sPTCdsQWe3idof6ZqsY8F3oaMXwrEkAYK
|
||||
$ spl-stake-pool create-pool --fee-numerator 3 --fee-denominator 100
|
||||
Creating mint Gmk71cM7j2RMorRsQrsyysM4HsByQx5PuDGtDdqGLWCS
|
||||
Creating pool fee collection account 3xvXPfQi2SaTkqPV9A7BQwh4GyTe2ZPasfoaCBCnTAJ5
|
||||
Creating stake pool 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||
Signature: 5HdDoPssqwyLjt2QvhRbnSATZqFLGKha92zMuJiBUpKeKYKGURRV41N5ydCQxqnFjCud3xv85Z6ghErppNJzaYM8
|
||||
```
|
||||
|
||||
The unique stake pool identifier is `EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1`.
|
||||
The unique stake pool identifier is `3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC`.
|
||||
|
||||
The identifier for the SPL token for staking derivatives is
|
||||
`D5yiK1tE1yAXBnrV9ZrSUJCw8WiQctZ8ekbv1U6ATVZ`. The stake pool has full control
|
||||
`Gmk71cM7j2RMorRsQrsyysM4HsByQx5PuDGtDdqGLWCS`. The stake pool has full control
|
||||
over the mint.
|
||||
|
||||
The pool creator's fee account identifier is
|
||||
`5gpuSdutGY98KKbgmR5CfLK7toFcQD69JzKDwseegzXE`. Every epoch, as stake accounts
|
||||
in the stake pool earn rewards, the program will mint SPL token staking derivatives
|
||||
equal to 3% of the gains on that epoch into this account. If no gains were observed,
|
||||
nothing will be deposited.
|
||||
|
||||
The reserve stake account identifier is `33Hg3bvYrAwfqCzTMjAWZNAWC6H96qJNEdzGamfFjG4J`.
|
||||
This account holds onto additional stake used when rebalancing between validators.
|
||||
|
||||
For a stake pool with 1000 validators, the cost to create a stake pool is less
|
||||
than 0.5 SOL.
|
||||
|
||||
#### Set manager
|
||||
|
||||
The stake pool manager may pass their administrator privileges to another account.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool set-manager EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --new-manager 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
|
||||
```
|
||||
|
||||
At the same time, they may also change the SPL token account that receives fees
|
||||
every epoch. The mint for the provided token account must be the SPL token mint,
|
||||
`D5yiK1tE1yAXBnrV9ZrSUJCw8WiQctZ8ekbv1U6ATVZ` in our example.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool set-manager EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --new-fee-receiver HoCsh97wRxRXVjtG7dyfsXSwH9VxdDzC7GvAsBE1eqJz
|
||||
Signature: 4aK8yzYvPBkP4PyuXTcCm529kjEH6tTt4ixc5D5ZyCrHwc4pvxAHj6wcr4cpAE1e3LddE87J1GLD466aiifcXoAY
|
||||
```
|
||||
|
||||
#### Set fee
|
||||
|
||||
The stake pool manager may update the fee assessed every epoch, passing the
|
||||
numerator and denominator for the fraction that make up the fee. For a fee of
|
||||
10%, they could run:
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool set-fee EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 10 100
|
||||
Signature: 5yPXfVj5cbKBfZiEVi2UR5bXzVDuc2c3ruBwSjkAqpvxPHigwGHiS1mXQVE4qwok5moMWT5RNYAMvkE9bnfQ1i93
|
||||
```
|
||||
|
||||
In order to protect stake pool depositors from malicious managers, the program
|
||||
applies the new fee for the following epoch. For example, if the fee is 1% at
|
||||
epoch 100, and the manager sets it to 10%, the manager will still gain 1% for
|
||||
the rewards earned during epoch 100. Starting with epoch 101, the manager will
|
||||
earn 10%.
|
||||
|
||||
#### Set staker
|
||||
|
||||
In order to manage the stake accounts, the stake pool manager or
|
||||
staker can set the staker authority of the stake pool's managed accounts.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool set-staker EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
|
||||
```
|
||||
|
||||
Now, the new staker can perform any normal stake pool operations, including
|
||||
adding and removing validators and rebalancing stake.
|
||||
|
||||
Important security note: the stake pool program only gives staking authority to
|
||||
the pool staker and always retains withdraw authority. Therefore, a malicious
|
||||
stake pool staker cannot steal funds from the stake pool.
|
||||
|
||||
Note: to avoid "disturbing the manager", the staker can also reassign their stake
|
||||
authority.
|
||||
|
||||
### Stake Pool Staker Examples
|
||||
`3xvXPfQi2SaTkqPV9A7BQwh4GyTe2ZPasfoaCBCnTAJ5`. When users deposit warmed up
|
||||
stake accounts into the stake pool, the program will transfer 3% of their
|
||||
contribution into this account in the form of SPL token staking derivatives.
|
||||
|
||||
#### Create a validator stake account
|
||||
|
||||
|
@ -252,7 +170,7 @@ lists, we choose some validators at random and start with identity
|
|||
delegated to that vote account.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||
Creating stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||
Signature: 4pA2WKT6d2wkXEtSpiQswv22WyoFad2KX6FdPEzwBiEquvaUBEtzenys5Jh1ABPCh7yc4w8kzqMRRCwDj6ZSUV1K
|
||||
```
|
||||
|
@ -261,13 +179,13 @@ In order to maximize censorship resistance, we want to distribute our SOL to as
|
|||
many validators as possible, so let's add a few more.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz
|
||||
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz
|
||||
Creating stake account E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie
|
||||
Signature: 4pyRZzjsWG7jP3GRZeZCo2Eb2TPjHM4kAYRFMivimme6HAee1nhzoNJBe3VSt2sv7acp5fwT7J8omBM8o3niY8gu
|
||||
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
||||
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
||||
Creating stake account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
||||
Signature: 4ZUdZzUARgUCPuY8nVsJbN6vRDbVX8sYAQGYYXj2YVvjoJ2oevq2H8uzrhYApe419uoP7QYukqNstiti5p5DDukN
|
||||
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm
|
||||
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm
|
||||
Creating stake account FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13
|
||||
Signature: yQqXCbuA66wQsHtkziNg3XadfZF5aCmvjfentwbZJnSPeEjJwPka3M1QY5GmR1efprptqaePn71BTMSLscX8DLr
|
||||
```
|
||||
|
@ -317,21 +235,22 @@ We created new validator stake accounts in the last step and staked them. Once
|
|||
the stake activates, we can add them to the stake pool.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool add-validator EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||
$ spl-stake-pool add-validator 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||
Creating account to receive tokens Gu8xqzYFg2sPHWHhUivKNBeF9uikiauihLs9hLzziKu7
|
||||
Signature: 3N1K89rGV9gWueTTrPGTDBwKAp8BikQhKHMFoREw98Q1piXFeZSSxqfnRQexrfAZQfrpYH9qwsaPWRruwkVeBivV
|
||||
```
|
||||
|
||||
Users can start depositing their activated stakes into the stake pool, as
|
||||
long as they are delegated to the same vote account, which was
|
||||
`FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN` in this example. You can also
|
||||
`FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13` in this example. You can also
|
||||
double-check that at any time using the Solana command-line utility.
|
||||
|
||||
```sh
|
||||
$ solana stake-account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||
Balance: 0.002282881 SOL
|
||||
Rent Exempt Reserve: 0.00228288 SOL
|
||||
Delegated Stake: 1.000000000 SOL
|
||||
Active Stake: 1.000000000 SOL
|
||||
Delegated Stake: 0.000000001 SOL
|
||||
Active Stake: 0.000000001 SOL
|
||||
Activating Stake: 0 SOL
|
||||
Stake activates starting from epoch: 161
|
||||
Delegated Vote Account Address: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||
|
@ -341,31 +260,26 @@ Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
|||
|
||||
#### Remove validator stake account
|
||||
|
||||
If the stake pool staker wants to stop delegating to a vote account, they can
|
||||
totally remove the validator stake account from the stake pool.
|
||||
If the stake pool manager wants to stop delegating to a vote account, they can
|
||||
totally remove the validator stake account from the stake pool by providing
|
||||
staking derivatives, just like `withdraw`.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool remove-validator EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
||||
$ spl-stake-pool remove-validator 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||
Signature: 5rrQ3xhDWyiPkUTAQkNAeq31n6sMf1xsg2x9hVY8Vj1NonwBnhxuTv87nADLkwC8Xzc4CGTNCTX2Vph9esWnXk2d
|
||||
```
|
||||
|
||||
The difference with `withdraw` is that the validator stake account is totally
|
||||
removed from the stake pool and now belongs to the administrator. The authority
|
||||
for the withdrawn stake account can also be specified using the `--new-authority` flag:
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool remove-validator EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G --new-authority 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||
Signature: 5rrQ3xhDWyiPkUTAQkNAeq31n6sMf1xsg2x9hVY8Vj1NonwBnhxuTv87nADLkwC8Xzc4CGTNCTX2Vph9esWnXk2d
|
||||
```
|
||||
removed from the stake pool and now belongs to the administrator.
|
||||
|
||||
We can check the removed stake account:
|
||||
|
||||
```sh
|
||||
$ solana stake-account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
||||
Balance: 1.002282880 SOL
|
||||
Balance: 1.002282881 SOL
|
||||
Rent Exempt Reserve: 0.00228288 SOL
|
||||
Delegated Stake: 1.000000000 SOL
|
||||
Active Stake: 1.000000000 SOL
|
||||
Delegated Stake: 1.000000001 SOL
|
||||
Active Stake: 1.000000001 SOL
|
||||
Delegated Vote Account Address: AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
||||
Stake Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||
Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||
|
@ -377,7 +291,7 @@ removal of staked SOL from the pool.
|
|||
We can also double-check that the stake pool no longer shows the stake account:
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
||||
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
||||
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
||||
|
@ -386,14 +300,14 @@ Total: ◎15.849959206
|
|||
|
||||
#### Rebalance the stake pool
|
||||
|
||||
As time goes on, users will deposit to and withdraw from all of the stake accounts
|
||||
managed by the pool, and the stake pool staker may want to rebalance the stakes.
|
||||
As time goes on, deposits and withdrawals will happen to all of the stake accounts
|
||||
managed by the pool, and the stake pool manager may want to rebalance the stakes.
|
||||
|
||||
For example, let's say the staker wants the same delegation to every validator
|
||||
For example, let's say the manager wants the same delegation to every validator
|
||||
in the pool. When they look at the state of the pool, they see:
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
||||
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
||||
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
||||
|
@ -401,63 +315,75 @@ Total: ◎15.849959206
|
|||
```
|
||||
|
||||
This isn't great! The last stake account, `E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`
|
||||
has too much allocated. For their strategy, the staker wants the `15.849959206`
|
||||
has too much allocated. For their strategy, the manager wants the `15.849959206`
|
||||
SOL to be distributed evenly, meaning around `5.283319735` in each account. They need
|
||||
to move `4.281036854` to `FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13` and
|
||||
`1.872447062` to `FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN`.
|
||||
|
||||
##### Decrease validator stake
|
||||
|
||||
First, they need to decrease the amount on stake account
|
||||
`E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`, delegated to
|
||||
`HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz`, by total of `6.153483916` SOL.
|
||||
|
||||
They decrease that amount of SOL:
|
||||
First, they need to withdraw a total of `6.153483916` from
|
||||
`E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`. Using the `spl-token` utility,
|
||||
let's check the total supply of pool tokens:
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool decrease-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz 6.153483916
|
||||
Signature: ZpQGwT85rJ8Y9afdkXhKo3TVv4xgTz741mmZj2vW7mihYseAkFsazWxza2y8eNGY4HDJm15c1cStwyiQzaM3RpH
|
||||
$ spl-token supply Gmk71cM7j2RMorRsQrsyysM4HsByQx5PuDGtDdqGLWCS
|
||||
0.034692168
|
||||
```
|
||||
|
||||
Internally, this instruction splits and deactivates 6.153483916 SOL from the
|
||||
validator stake account `E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie` into a
|
||||
transient stake account, owned and managed entirely by the stake pool.
|
||||
Given a total pool token supply of `0.034692168` and total staked SOL amount of
|
||||
`15.849959206`, let's calculate how many pool tokens to withdraw from the pool:
|
||||
|
||||
Once the stake is deactivated during the next epoch, the `update` command will
|
||||
automatically merge the transient stake account into a reserve stake account,
|
||||
also entirely owned and managed by the stake pool.
|
||||
|
||||
##### Increase validator stake
|
||||
|
||||
Now that the reserve stake account has enough to perform the rebalance, the staker
|
||||
can increase the stake on the two other validators,
|
||||
`8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm` and
|
||||
`2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3`.
|
||||
|
||||
They add 4.281036854 SOL to `8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm`:
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool increase-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm 4.281036854
|
||||
Signature: 3GJACzjUGLPjcd9RLUW86AfBLWKapZRkxnEMc2yHT6erYtcKBgCapzyrVH6VN8Utxj7e2mtvzcigwLm6ZafXyTMw
|
||||
```
|
||||
sol_to_withdraw * total_pool_tokens / total_sol_staked = pool_tokens_to_withdraw
|
||||
6.153483916 * 0.034692168 / 15.849959206 ~ 0.013468659
|
||||
```
|
||||
|
||||
And they add 1.872447062 SOL to `2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3`:
|
||||
They withdraw that amount of pool tokens:
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool increase-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 1.872447062
|
||||
Signature: 4zaKYu3MQ3as8reLbuHKaXN8FNaHvpHuiZtsJeARo67UKMo6wUUoWE88Fy8N4EYQYicuwULTNffcUD3a9jY88PoU
|
||||
$ spl-stake-pool withdraw 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --amount 0.013468659 --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||
Withdrawing from account E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie, amount ◎6.153483855, 0.013468659 pool tokens
|
||||
Creating account to receive stake 8ykyY7maA9HUfUphZHBkhsnydY5gFfyHFSfxCA7imqrk
|
||||
Signature: z8a5ZRfWdj8Fcsr3ttCJ731wFKyhZNcqoKEdV1RBCkzr3tHGQNCC56qvRVJ6oxyCVDqWZ3KL1Bkyn3sDpjYPDku
|
||||
```
|
||||
|
||||
Internally, this instruction also uses transient stake accounts. This time, the
|
||||
stake pool splits from the reserve stake, into the transient stake account,
|
||||
then activates it to the appropriate validator.
|
||||
Because of rounding in the calculation a few lines above, it looks like we receive
|
||||
less than we should. If we play that back the other way, we'll see that all is well:
|
||||
|
||||
One to two epochs later, once the transient stakes activate, the `update` command
|
||||
automatically merges the transient stakes into the validator stake account, leaving
|
||||
a fully rebalanced stake pool:
|
||||
```
|
||||
pool_tokens_to_withdraw * total_sol_staked / total_pool_tokens = sol_to_withdraw
|
||||
0.013468659 * 15.849959206 / 0.034692168 ~ 6.153483855
|
||||
```
|
||||
|
||||
Next, they deactivate the new received stake:
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||
$ solana deactivate-stake 8ykyY7maA9HUfUphZHBkhsnydY5gFfyHFSfxCA7imqrk
|
||||
Signature: 4SuwZK5JvYkYVkM5yfu2x8x6iou6558teMwzphGECLmstMVoWbSvngUH48Ra24PrxtgUDyVDA8SXYS1qMyx3fjMj
|
||||
```
|
||||
|
||||
Once the stake is deactivated during the next epoch, they split the stake
|
||||
and activate it on the other two validator vote accounts. For brevity, those
|
||||
commands are omitted.
|
||||
|
||||
Eventually, we are left with stake account `4zppED2kFodUS2hBf8Fzeepu6yZ6QuyeNPBXCT9VU6fK`
|
||||
with `4.281036854` delegated to `8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm`
|
||||
and stake account `GCJnuFGCDzaToPwJtG5GiK4g3DJBfuhQy6388NyGcfwf` with `1.872447062`
|
||||
delegated to `2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3`.
|
||||
|
||||
Once the new stakes are ready, the manager deposits them back into the stake pool:
|
||||
```sh
|
||||
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC GCJnuFGCDzaToPwJtG5GiK4g3DJBfuhQy6388NyGcfwf --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||
Signature: jKsdEr3zxF2zZs78rmrP3PmQiTwE7v15ieEuxp4db1VQe9owXVGM8nM3dJqVRHXPsS4frQW4gJ6xBfTTk2HvKDX
|
||||
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4zppED2kFodUS2hBf8Fzeepu6yZ6QuyeNPBXCT9VU6fK --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||
Depositing into stake account FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13
|
||||
Signature: 3JXvTvea6F4Epd2krSxnTRZPB4gLZ8GqisFE58Z4ocV92fDN1HRMVPoPhJtYcfuF12vyQZUueKwVmkvL6Wgf2evc
|
||||
```
|
||||
|
||||
Leaving them with a rebalanced stake pool!
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎5.283340235
|
||||
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎5.283612231
|
||||
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎5.284317422
|
||||
|
@ -465,7 +391,33 @@ Total: ◎15.851269888
|
|||
```
|
||||
|
||||
Due to staking rewards that accrued during the rebalancing process, the pool is
|
||||
not perfectly balanced. This is completely normal.
|
||||
not prefectly balanced. This is completely normal.
|
||||
|
||||
#### Set staking authority
|
||||
|
||||
In order to manage the stake accounts more directly, the stake pool owner can
|
||||
set the stake authority of the stake pool's managed accounts.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool set-staking-auth 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --stake-account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN --new-staker 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
|
||||
```
|
||||
|
||||
Now, the new staking authority can perform any normal staking operations,
|
||||
including deactivating or re-staking.
|
||||
|
||||
Important security note: the stake pool program only gives staking authority to
|
||||
the pool owner and always retains withdraw authority. Therefore, a malicious
|
||||
stake pool manager cannot steal funds from the stake pool.
|
||||
|
||||
#### Set owner
|
||||
|
||||
The stake pool owner may pass their administrator privileges to another account.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --new-owner 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
|
||||
```
|
||||
|
||||
### User Examples
|
||||
|
||||
|
@ -477,7 +429,7 @@ command-line utility has a special instruction for finding out which vote
|
|||
accounts are already associated with the stake pool.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||
CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E 1.002282880 SOL
|
||||
E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie 1.002282880 SOL
|
||||
FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN 1.002282880 SOL
|
||||
|
@ -489,13 +441,13 @@ If the manager has recently created the stake pool, and there are no stake
|
|||
accounts present yet, the command-line utility will inform us.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||
No accounts found.
|
||||
```
|
||||
|
||||
#### Deposit stake
|
||||
|
||||
Stake pools only accept deposits from active accounts, so we must first
|
||||
Stake pools only accept deposits from fully staked accounts, so we must first
|
||||
create stake accounts and delegate them to one of the validators managed by the
|
||||
stake pool. Using the `list` command from the previous section, we see that
|
||||
`2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3` is a valid vote account, so let's
|
||||
|
@ -521,19 +473,17 @@ Two epochs later, when the stake is fully active and has received one epoch of
|
|||
rewards, we can deposit the stake into the stake pool.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool deposit EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa
|
||||
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa
|
||||
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||
Creating account to receive tokens 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||
Signature: 4AESGZzqBVfj5xQnMiPWAwzJnAtQDRFK1Ha6jqKKTs46Zm5fw3LqgU1mRAT6CKTywVfFMHZCLm1hcQNScSMwVvjQ
|
||||
```
|
||||
|
||||
The CLI will default to using the fee payer's
|
||||
[Associated Token Account](associated-token-account.md) for stake pool tokens.
|
||||
Alternatively, you can create an SPL token account yourself and pass it as the
|
||||
`token-receiver` for the command.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool deposit EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||
Signature: 4AESGZzqBVfj5xQnMiPWAwzJnAtQDRFK1Ha6jqKKTs46Zm5fw3LqgU1mRAT6CKTywVfFMHZCLm1hcQNScSMwVvjQ
|
||||
```
|
||||
|
@ -555,8 +505,7 @@ In order to calculate the proper value of these stake pool tokens, we must updat
|
|||
the total value managed by the stake pool every epoch.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||
Updating stake pool...
|
||||
$ spl-stake-pool update 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||
Signature: 3Yx1RH3Afqj5ckX8YvPCRt1DudVP4HuRPkh1dBPvTM9GqGxcB9ZXHGZPADVSZiaqKi166fevMG232EWxrRWswPtt
|
||||
```
|
||||
|
||||
|
@ -564,33 +513,13 @@ If another user already updated the stake pool balance for the current epoch, we
|
|||
see a different output.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||
Update not required
|
||||
$ spl-stake-pool update 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||
Stake pool balances are up to date, no update required.
|
||||
```
|
||||
|
||||
If no one updates the stake pool in the current epoch, the deposit and withdraw
|
||||
instructions will fail. The update instruction is permissionless, so any user
|
||||
can run it before depositing or withdrawing. As a convenience, the CLI attempts
|
||||
to update before running any instruction on the stake pool.
|
||||
|
||||
If the stake pool transient stakes are in an unexpected state, and merges are
|
||||
not possible, there is the option to only update the stake pool balances without
|
||||
performing merges using the `--no-merge` flag.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --no-merge
|
||||
Updating stake pool...
|
||||
Signature: 3Yx1RH3Afqj5ckX8YvPCRt1DudVP4HuRPkh1dBPvTM9GqGxcB9ZXHGZPADVSZiaqKi166fevMG232EWxrRWswPtt
|
||||
```
|
||||
|
||||
Later on, whenever the transient stakes are ready to be merged, it is possible to
|
||||
force another update in the same epoch using the `--force` flag.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --force
|
||||
Updating stake pool...
|
||||
Signature: 3Yx1RH3Afqj5ckX8YvPCRt1DudVP4HuRPkh1dBPvTM9GqGxcB9ZXHGZPADVSZiaqKi166fevMG232EWxrRWswPtt
|
||||
```
|
||||
can run it before depositing or withdrawing.
|
||||
|
||||
#### Withdraw stake
|
||||
|
||||
|
@ -600,7 +529,7 @@ staking derivative SPL tokens in exchange for an activated stake account.
|
|||
Let's withdraw 0.02 staking derivative tokens from the stake pool.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 0.02
|
||||
$ spl-stake-pool withdraw 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --amount 0.02 --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||
Creating account to receive stake CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
||||
|
@ -621,58 +550,15 @@ Stake Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
|||
Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||
```
|
||||
|
||||
Alternatively, the user can specify an existing uninitialized stake account to
|
||||
receive their stake using the `--stake-receiver` parameter.
|
||||
Alternatively, the user can specify an existing stake account to receive their
|
||||
stake using the `stake-receiver` parameter.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --amount 0.02 --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF --stake-receiver CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||
$ spl-stake-pool withdraw 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --amount 0.02 --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF --stake-receiver CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
||||
```
|
||||
|
||||
By default, the withdraw command uses the fee payer's associated token account to
|
||||
source the derivative tokens. It's possible to specify the SPL token account using
|
||||
the `--pool-account` flag.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 0.02 --pool-account 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||
Creating account to receive stake CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
||||
```
|
||||
|
||||
By default, the withdraw command will withdraw from the largest validator stake
|
||||
accounts in the pool. It's also possible to specify a specific vote account for
|
||||
the withdraw using the `--vote-account` flag.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 0.02 --vote-account 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||
Creating account to receive stake CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
||||
```
|
||||
|
||||
Note that the associated validator stake account must have enough lamports to
|
||||
satisfy the pool token amount requested.
|
||||
|
||||
##### Special case: exiting pool with a delinquent staker
|
||||
|
||||
With the reserve stake, it's possible for a delinquent or malicious staker to
|
||||
move all stake into the reserve through `decrease-validator-stake`, so the
|
||||
staking derivatives will not gain rewards, and the stake pool users will not
|
||||
be able to withdraw their funds.
|
||||
|
||||
To get around this case, it is also possible to withdraw from the stake pool's
|
||||
reserve, but only if all of the validator stake accounts are at the minimum amount of
|
||||
`1 SOL + stake account rent exemption`.
|
||||
|
||||
```sh
|
||||
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 0.02 --use-reserve
|
||||
Withdrawing from account 33Hg3bvYrAwfqCzTMjAWZNAWC6H96qJNEdzGamfFjG4J, amount 8.867176377 SOL, 0.02 pool tokens
|
||||
Creating account to receive stake 9E5YzXXu9NDhtMxWJKCwe2M8Sdz6vL6bcBS92U76PVtE
|
||||
Signature: 4aZaeT9Azcq23PdKcjbQLseNveZVAQ4xMabBGQspfX316cE62Q2hoES373ExbT9y2JUhug7SgdybNaCjuZ6uqNYf
|
||||
```
|
||||
|
||||
## Appendix
|
||||
|
||||
### Activated stakes
|
||||
|
@ -683,22 +569,6 @@ are not equivalent to inactive, activating, or deactivating stakes due to the
|
|||
time cost of staking. Otherwise, malicious actors can deposit stake in one state
|
||||
and withdraw it in another state without waiting.
|
||||
|
||||
### Transient stake accounts
|
||||
|
||||
Each validator gets one transient stake account, so the staker can only
|
||||
perform one action at a time on a validator. It's impossible to increase
|
||||
and decrease the stake on a validator at the same time. The staker must wait for
|
||||
the existing transient stake account to get merged during an `update` instruction
|
||||
before performing a new action.
|
||||
|
||||
### Reserve stake account
|
||||
|
||||
Every stake pool is initialized with an undelegated reserve stake account, used
|
||||
to hold undelegated stake in process of rebalancing. After the staker decreases
|
||||
the stake on a validator, one epoch later, the update operation will merge the
|
||||
decreased stake into the reserve. Conversely, whenever the staker increases the
|
||||
stake on a validator, the lamports are drawn from the reserve stake account.
|
||||
|
||||
### Staking Credits Observed on Deposit
|
||||
|
||||
A deposited stake account's "credits observed" must match the destination
|
||||
|
|
|
@ -65,7 +65,7 @@ Keypair Path: ${HOME}/.config/solana/id.json
|
|||
|
||||
See [Solana clusters](https://docs.solana.com/clusters) for cluster-specific RPC URLs
|
||||
```
|
||||
solana config set --url https://api.devnet.solana.com
|
||||
solana config set --url https://devnet.solana.com
|
||||
```
|
||||
|
||||
#### Default Keypair
|
||||
|
@ -83,15 +83,6 @@ Hardware Wallet URL (See [URL spec](https://docs.solana.com/wallet-guide/hardwar
|
|||
solana config set --keypair usb://ledger/
|
||||
```
|
||||
|
||||
#### Airdrop SOL
|
||||
|
||||
Creating tokens and accounts requires SOL for account rent deposits and
|
||||
transaction fees. If the cluster you are targeting offers a faucet, you can get
|
||||
a little SOL for testing:
|
||||
```
|
||||
solana airdrop 1
|
||||
```
|
||||
|
||||
### Example: Creating your own fungible token
|
||||
|
||||
```sh
|
||||
|
@ -118,7 +109,7 @@ Signature: 42Sa5eK9dMEQyvD9GMHuKxXf55WLZ7tfjabUKDhNoZRAxj9MsnN7omriWMEHXLea3aYpj
|
|||
|
||||
`7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi` is now an empty account:
|
||||
```sh
|
||||
$ spl-token balance AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
||||
$ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||
0
|
||||
```
|
||||
|
||||
|
@ -135,7 +126,7 @@ The token `supply` and account `balance` now reflect the result of minting:
|
|||
```sh
|
||||
$ spl-token supply AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
||||
100
|
||||
$ spl-token balance AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
||||
$ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||
100
|
||||
```
|
||||
|
||||
|
@ -175,7 +166,7 @@ address by running `solana address` and provides it to the sender.
|
|||
|
||||
The sender then runs:
|
||||
```
|
||||
$ spl-token transfer AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||
$ spl-token transfer 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||
Transfer 50 tokens
|
||||
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||
|
@ -193,7 +184,7 @@ The receiver obtains their wallet address by running `solana address` and provid
|
|||
The sender then runs to fund the receiver's associated token account, at the
|
||||
sender's expense, and then transfers 50 tokens into it:
|
||||
```
|
||||
$ spl-token transfer --fund-recipient AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||
$ spl-token transfer --fund-recipient 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||
Transfer 50 tokens
|
||||
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||
|
@ -208,7 +199,7 @@ Tokens may be transferred to a specific recipient token account. The recipient
|
|||
token account must already exist and be of the same Token type.
|
||||
|
||||
```
|
||||
$ spl-token create-account AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM /path/to/auxiliary_keypair.json
|
||||
$ spl-token create-account AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
||||
Creating account CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
|
||||
Signature: 4yPWj22mbyLu5mhfZ5WATNfYzTt5EQ7LGzryxM7Ufu7QCVjTE7czZdEBqdKR7vjKsfAqsBdjU58NJvXrTqCXvfWW
|
||||
```
|
||||
|
@ -237,9 +228,9 @@ CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe
|
|||
|
||||
### Example: Create a non-fungible token
|
||||
|
||||
Create the token type with nine decimal places,
|
||||
Create the token type,
|
||||
```
|
||||
$ spl-token create-token --decimals 9
|
||||
$ spl-token create-token
|
||||
Creating token 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
||||
Signature: 4kz82JUey1B9ki1McPW7NYv1NqPKCod6WNptSkYqtuiEsQb9exHaktSAHJJsm4YxuGNW4NugPJMFX9ee6WA2dXts
|
||||
```
|
||||
|
@ -273,7 +264,7 @@ Now the `7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM` account holds the
|
|||
one and only `559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z` token:
|
||||
|
||||
```
|
||||
$ spl-token account-info 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
||||
$ spl-token account-info 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
||||
|
||||
Address: 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
||||
Balance: 1
|
||||
|
@ -529,7 +520,7 @@ There is a rich set of JSON RPC methods available for use with SPL Token:
|
|||
|
||||
See https://docs.solana.com/apps/jsonrpc-api for more details.
|
||||
|
||||
Additionally the versatile `getProgramAccounts` JSON RPC method can be employed in various ways to fetch SPL Token accounts of interest.
|
||||
Additionally the versatile `getProgramAcccounts` JSON RPC method can be employed in various ways to fetch SPL Token accounts of interest.
|
||||
|
||||
### Finding all token accounts for a specific mint
|
||||
|
||||
|
@ -562,7 +553,7 @@ curl http://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/js
|
|||
```
|
||||
|
||||
The `"dataSize": 165` filter selects all [Token
|
||||
Account](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
||||
Acccount](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
||||
and then the `"memcmp": ...` filter selects based on the
|
||||
[mint](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L88)
|
||||
address within each token account.
|
||||
|
@ -597,7 +588,7 @@ curl http://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/js
|
|||
```
|
||||
|
||||
The `"dataSize": 165` filter selects all [Token
|
||||
Account](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
||||
Acccount](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
||||
and then the `"memcmp": ...` filter selects based on the
|
||||
[owner](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L90)
|
||||
address within each token account.
|
||||
|
@ -834,13 +825,7 @@ The sender's wallet must not require that the recipient's main wallet address
|
|||
hold a balance before allowing the transfer.
|
||||
|
||||
### Registry for token details
|
||||
At the moment there exist two solutions for Token Mint registries:
|
||||
|
||||
* hard coded addresses in the wallet or dapp
|
||||
* [spl-token-registry](https://www.npmjs.com/package/@solana/spl-token-registry)
|
||||
package, maintained at [https://github.com/solana-labs/token-list](https://github.com/solana-labs/token-list)
|
||||
|
||||
**A decentralized solution is in progress.**
|
||||
At the moment Token Mint addresses need to be hard coded by each wallet. **Improving this situation is a work in progress.**
|
||||
|
||||
### Garbage Collecting Ancillary Token Accounts
|
||||
Wallets should empty ancillary token accounts as quickly as practical by
|
||||
|
@ -867,13 +852,3 @@ the maximum allowed transaction size, remove those extra clean up instructions.
|
|||
They can be cleaned up during the next send operation.
|
||||
|
||||
The `spl-token gc` command provides an example implementation of this cleanup process.
|
||||
|
||||
|
||||
### Token Vesting Contract:
|
||||
This program allows you to lock arbitrary SPL tokens and release the locked tokens with a determined unlock schedule. An `unlock schedule` is made of a `unix timestamp` and a token `amount`, when initializing a vesting contract, the creator can pass an array of `unlock schedule` with an arbitrary size giving the creator of the contract complete control of how the tokens unlock over time.
|
||||
|
||||
Unlocking works by pushing a permissionless crank on the contract that moves the tokens to the pre-specified address. The recipient address of a vesting contract can be modified by the owner of the current recipient key, meaning that vesting contract locked tokens can be traded.
|
||||
|
||||
- Code: [https://github.com/Bonfida/token-vesting](https://github.com/Bonfida/token-vesting)
|
||||
- UI: [https://vesting.bonfida.com/#/](https://vesting.bonfida.com/#/)
|
||||
- Audit: The audit was conducted by Kudelski, the report can be found [here](https://github.com/Bonfida/token-vesting/blob/master/audit/Bonfida_SecurityAssessment_Vesting_Final050521.pdf)
|
||||
|
|
|
@ -22,7 +22,7 @@ extern uint64_t do_invoke(SolParameters *params) {
|
|||
const SolSignerSeeds signers_seeds[] = {{seeds, SOL_ARRAY_SIZE(seeds)}};
|
||||
|
||||
SolPubkey expected_allocated_key;
|
||||
if (SUCCESS != sol_create_program_address(seeds, SOL_ARRAY_SIZE(seeds),
|
||||
if (SUCCESS == sol_create_program_address(seeds, SOL_ARRAY_SIZE(seeds),
|
||||
params->program_id,
|
||||
&expected_allocated_key)) {
|
||||
return ERROR_INVALID_INSTRUCTION_DATA;
|
||||
|
@ -31,7 +31,8 @@ extern uint64_t do_invoke(SolParameters *params) {
|
|||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
SolAccountMeta arguments[] = {{allocated_info->key, true, true}};
|
||||
SolAccountMeta arguments[] = {{system_program_info->key, false, false},
|
||||
{allocated_info->key, true, true}};
|
||||
uint8_t data[4 + 8]; // Enough room for the Allocate instruction
|
||||
*(uint16_t *)data = 8; // Allocate instruction enum value
|
||||
*(uint64_t *)(data + 4) = SIZE; // Size to allocate
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
# Program examples written in Rust
|
||||
|
||||
The examples in this directory demonstrate various Solana program mechanisms.
|
||||
|
|
|
@ -13,11 +13,11 @@ no-entrypoint = []
|
|||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
solana-program = "1.7.3"
|
||||
solana-program = "1.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
solana-program-test = "1.6.2"
|
||||
solana-sdk = "1.6.2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
|
|
@ -15,11 +15,11 @@ no-entrypoint = []
|
|||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
solana-program = "1.7.3"
|
||||
solana-program = "1.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
solana-program-test = "1.6.2"
|
||||
solana-sdk = "1.6.2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
|
|
@ -8,7 +8,7 @@ use {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_custom_heap() {
|
||||
let program_id = Pubkey::from_str("CustomHeap111111111111111111111111111111111").unwrap();
|
||||
let program_id = Pubkey::from_str(&"CustomHeap111111111111111111111111111111111").unwrap();
|
||||
let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
|
||||
"spl_example_custom_heap",
|
||||
program_id,
|
||||
|
|
|
@ -13,11 +13,11 @@ no-entrypoint = []
|
|||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
solana-program = "1.7.3"
|
||||
solana-program = "1.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
solana-program-test = "1.6.2"
|
||||
solana-sdk = "1.6.2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
|
|
@ -11,7 +11,7 @@ use {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_logging() {
|
||||
let program_id = Pubkey::from_str("Logging111111111111111111111111111111111111").unwrap();
|
||||
let program_id = Pubkey::from_str(&"Logging111111111111111111111111111111111111").unwrap();
|
||||
let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
|
||||
"spl_example_logging",
|
||||
program_id,
|
||||
|
|
|
@ -13,11 +13,11 @@ no-entrypoint = []
|
|||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
solana-program = "1.7.3"
|
||||
solana-program = "1.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
solana-program-test = "1.6.2"
|
||||
solana-sdk = "1.6.2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
|
|
@ -17,28 +17,24 @@ pub fn process_instruction(
|
|||
// Create in iterator to safety reference accounts in the slice
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
// Get the clock sysvar via syscall
|
||||
let clock_via_sysvar = Clock::get()?;
|
||||
// Or deserialize the account into a clock struct
|
||||
// The first account is the clock sysvar
|
||||
let clock_sysvar_info = next_account_info(account_info_iter)?;
|
||||
let clock_via_account = Clock::from_account_info(clock_sysvar_info)?;
|
||||
// Both produce the same sysvar
|
||||
assert_eq!(clock_via_sysvar, clock_via_account);
|
||||
// Note: `format!` can be very expensive, use cautiously
|
||||
msg!("{:?}", clock_via_sysvar);
|
||||
|
||||
// Get the rent sysvar via syscall
|
||||
let rent_via_sysvar = Rent::get()?;
|
||||
// Or deserialize the account into a rent struct
|
||||
// The second account is the rent sysvar
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?;
|
||||
let rent_via_account = Rent::from_account_info(rent_sysvar_info)?;
|
||||
// Both produce the same sysvar
|
||||
assert_eq!(rent_via_sysvar, rent_via_account);
|
||||
|
||||
// Deserialize the account into a clock struct
|
||||
let clock = Clock::from_account_info(&clock_sysvar_info)?;
|
||||
|
||||
// Deserialize the account into a rent struct
|
||||
let rent = Rent::from_account_info(&rent_sysvar_info)?;
|
||||
|
||||
// Note: `format!` can be very expensive, use cautiously
|
||||
msg!("{:?}", clock);
|
||||
// Can't print `exemption_threshold` because BPF does not support printing floats
|
||||
msg!(
|
||||
"Rent: lamports_per_byte_year: {:?}, burn_percent: {:?}",
|
||||
rent_via_sysvar.lamports_per_byte_year,
|
||||
rent_via_sysvar.burn_percent
|
||||
rent.lamports_per_byte_year,
|
||||
rent.burn_percent
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -12,7 +12,7 @@ use {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_sysvar() {
|
||||
let program_id = Pubkey::from_str("Sysvar1111111111111111111111111111111111111").unwrap();
|
||||
let program_id = Pubkey::from_str(&"Sysvar1111111111111111111111111111111111111").unwrap();
|
||||
let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
|
||||
"spl_example_sysvar",
|
||||
program_id,
|
||||
|
|
|
@ -12,11 +12,11 @@ no-entrypoint = []
|
|||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
solana-program = "1.7.3"
|
||||
solana-program = "1.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
solana-program-test = "1.6.2"
|
||||
solana-sdk = "1.6.2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
|
|
@ -11,7 +11,7 @@ use {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_lamport_transfer() {
|
||||
let program_id = Pubkey::from_str("TransferLamports111111111111111111111111111").unwrap();
|
||||
let program_id = Pubkey::from_str(&"TransferLamports111111111111111111111111111").unwrap();
|
||||
let source_pubkey = Pubkey::new_unique();
|
||||
let destination_pubkey = Pubkey::new_unique();
|
||||
let mut program_test = ProgramTest::new(
|
||||
|
|
|
@ -10,11 +10,11 @@ edition = "2018"
|
|||
[dependencies]
|
||||
chrono = "0.4.19"
|
||||
clap = "2.33.3"
|
||||
solana-clap-utils = "1.7.3"
|
||||
solana-cli-config = "1.7.3"
|
||||
solana-client = "1.7.3"
|
||||
solana-logger = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
solana-clap-utils = "1.6.2"
|
||||
solana-cli-config = "1.6.2"
|
||||
solana-client = "1.6.2"
|
||||
solana-logger = "1.6.2"
|
||||
solana-sdk = "1.6.2"
|
||||
spl-feature-proposal = { version = "1.0", path = "../program", features = ["no-entrypoint"] }
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -46,7 +46,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.global(true)
|
||||
.help("Configuration file to use");
|
||||
if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
|
||||
arg.default_value(config_file)
|
||||
arg.default_value(&config_file)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ fn process_propose(
|
|||
Some(&config.keypair.pubkey()),
|
||||
);
|
||||
let blockhash = rpc_client.get_recent_blockhash()?.0;
|
||||
transaction.try_sign(&[&config.keypair, feature_proposal_keypair], blockhash)?;
|
||||
transaction.try_sign(&[&config.keypair, &feature_proposal_keypair], blockhash)?;
|
||||
|
||||
println!("JSON RPC URL: {}", config.json_rpc_url);
|
||||
|
||||
|
@ -399,9 +399,10 @@ fn process_tally(
|
|||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let feature_proposal = get_feature_proposal(rpc_client, feature_proposal_address)?;
|
||||
|
||||
let feature_id_address = spl_feature_proposal::get_feature_id_address(feature_proposal_address);
|
||||
let feature_id_address =
|
||||
spl_feature_proposal::get_feature_id_address(&feature_proposal_address);
|
||||
let acceptance_token_address =
|
||||
spl_feature_proposal::get_acceptance_token_address(feature_proposal_address);
|
||||
spl_feature_proposal::get_acceptance_token_address(&feature_proposal_address);
|
||||
|
||||
println!("Feature Id: {}", feature_id_address);
|
||||
println!("Acceptance Token Address: {}", acceptance_token_address);
|
||||
|
|
|
@ -12,14 +12,15 @@ no-entrypoint = []
|
|||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
borsh = "0.8"
|
||||
borsh = "0.7.1"
|
||||
borsh-derive = "0.8.1"
|
||||
solana-program = "1.7.3"
|
||||
solana-program = "1.6.2"
|
||||
spl-token = { version = "3.1", path = "../../token/program", features = ["no-entrypoint"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-program-test = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
futures = "0.3"
|
||||
solana-program-test = "1.6.2"
|
||||
solana-sdk = "1.6.2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
|
|
@ -155,12 +155,13 @@ pub fn tally(feature_proposal_address: &Pubkey) -> Instruction {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::borsh_utils;
|
||||
|
||||
#[test]
|
||||
fn test_get_packed_len() {
|
||||
assert_eq!(
|
||||
FeatureProposalInstruction::get_packed_len(),
|
||||
solana_program::borsh::get_packed_len::<FeatureProposalInstruction>()
|
||||
borsh_utils::get_packed_len::<FeatureProposalInstruction>()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod borsh_utils;
|
||||
mod entrypoint;
|
||||
pub mod instruction;
|
||||
pub mod processor;
|
||||
|
|
|
@ -126,7 +126,7 @@ pub fn process_instruction(
|
|||
mint_info.clone(),
|
||||
system_program_info.clone(),
|
||||
],
|
||||
&[mint_signer_seeds],
|
||||
&[&mint_signer_seeds],
|
||||
)?;
|
||||
|
||||
msg!("Initializing mint");
|
||||
|
@ -159,7 +159,7 @@ pub fn process_instruction(
|
|||
distributor_token_info.clone(),
|
||||
system_program_info.clone(),
|
||||
],
|
||||
&[distributor_token_signer_seeds],
|
||||
&[&distributor_token_signer_seeds],
|
||||
)?;
|
||||
|
||||
msg!("Initializing distributor token account");
|
||||
|
@ -193,7 +193,7 @@ pub fn process_instruction(
|
|||
acceptance_token_info.clone(),
|
||||
system_program_info.clone(),
|
||||
],
|
||||
&[acceptance_token_signer_seeds],
|
||||
&[&acceptance_token_signer_seeds],
|
||||
)?;
|
||||
|
||||
msg!("Initializing acceptance token account");
|
||||
|
@ -216,7 +216,7 @@ pub fn process_instruction(
|
|||
&spl_token::instruction::set_authority(
|
||||
&spl_token::id(),
|
||||
acceptance_token_info.key,
|
||||
Some(feature_proposal_info.key),
|
||||
Some(&feature_proposal_info.key),
|
||||
spl_token::instruction::AuthorityType::CloseAccount,
|
||||
feature_proposal_info.key,
|
||||
&[],
|
||||
|
@ -231,7 +231,7 @@ pub fn process_instruction(
|
|||
&spl_token::instruction::set_authority(
|
||||
&spl_token::id(),
|
||||
acceptance_token_info.key,
|
||||
Some(program_id),
|
||||
Some(&program_id),
|
||||
spl_token::instruction::AuthorityType::AccountOwner,
|
||||
feature_proposal_info.key,
|
||||
&[],
|
||||
|
@ -260,7 +260,7 @@ pub fn process_instruction(
|
|||
distributor_token_info.clone(),
|
||||
spl_token_program_info.clone(),
|
||||
],
|
||||
&[mint_signer_seeds],
|
||||
&[&mint_signer_seeds],
|
||||
)?;
|
||||
|
||||
// Fully fund the feature id account so the `Tally` instruction will not require any
|
||||
|
@ -283,7 +283,7 @@ pub fn process_instruction(
|
|||
invoke_signed(
|
||||
&system_instruction::allocate(feature_id_info.key, Feature::size_of() as u64),
|
||||
&[feature_id_info.clone(), system_program_info.clone()],
|
||||
&[feature_id_signer_seeds],
|
||||
&[&feature_id_signer_seeds],
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -348,7 +348,7 @@ pub fn process_instruction(
|
|||
invoke_signed(
|
||||
&system_instruction::assign(feature_id_info.key, &feature::id()),
|
||||
&[feature_id_info.clone(), system_program_info.clone()],
|
||||
&[feature_id_signer_seeds],
|
||||
&[&feature_id_signer_seeds],
|
||||
)?;
|
||||
|
||||
msg!("Feature proposal accepted");
|
||||
|
|
|
@ -59,12 +59,13 @@ impl Pack for FeatureProposal {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::borsh_utils;
|
||||
|
||||
#[test]
|
||||
fn test_get_packed_len() {
|
||||
assert_eq!(
|
||||
FeatureProposal::get_packed_len(),
|
||||
solana_program::borsh::get_packed_len::<FeatureProposal>()
|
||||
borsh_utils::get_packed_len::<FeatureProposal>()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
// Mark this test as BPF-only due to current `ProgramTest` limitations when CPIing into the system program
|
||||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use {
|
||||
solana_program::{
|
||||
feature::{self, Feature},
|
||||
program_option::COption,
|
||||
pubkey::Pubkey,
|
||||
system_program,
|
||||
},
|
||||
solana_program_test::*,
|
||||
solana_sdk::{
|
||||
signature::{Keypair, Signer},
|
||||
transaction::Transaction,
|
||||
},
|
||||
spl_feature_proposal::{instruction::*, state::*, *},
|
||||
use futures::{Future, FutureExt};
|
||||
use solana_program::{
|
||||
feature::{self, Feature},
|
||||
program_option::COption,
|
||||
program_pack::Pack,
|
||||
pubkey::Pubkey,
|
||||
system_program,
|
||||
};
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::{
|
||||
signature::{Keypair, Signer},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use spl_feature_proposal::{instruction::*, state::*, *};
|
||||
use std::io;
|
||||
|
||||
fn program_test() -> ProgramTest {
|
||||
ProgramTest::new(
|
||||
|
@ -24,6 +25,21 @@ fn program_test() -> ProgramTest {
|
|||
)
|
||||
}
|
||||
|
||||
/// Fetch and unpack account data
|
||||
fn get_account_data<T: Pack>(
|
||||
banks_client: &mut BanksClient,
|
||||
address: Pubkey,
|
||||
) -> impl Future<Output = std::io::Result<T>> + '_ {
|
||||
banks_client.get_account(address).map(|result| {
|
||||
let account =
|
||||
result?.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "account not found"))?;
|
||||
|
||||
T::unpack_from_slice(&account.data)
|
||||
.ok()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to deserialize account"))
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_basic() {
|
||||
let feature_proposal = Keypair::new();
|
||||
|
@ -52,17 +68,16 @@ async fn test_basic() {
|
|||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
// Confirm feature id account is now funded and allocated, but not assigned
|
||||
let feature_id_account = banks_client
|
||||
let feature_id_acccount = banks_client
|
||||
.get_account(feature_id_address)
|
||||
.await
|
||||
.expect("success")
|
||||
.expect("some account");
|
||||
assert_eq!(feature_id_account.owner, system_program::id());
|
||||
assert_eq!(feature_id_account.data.len(), Feature::size_of());
|
||||
assert_eq!(feature_id_acccount.owner, system_program::id());
|
||||
assert_eq!(feature_id_acccount.data.len(), Feature::size_of());
|
||||
|
||||
// Confirm mint account state
|
||||
let mint = banks_client
|
||||
.get_packed_account_data::<spl_token::state::Mint>(mint_address)
|
||||
let mint = get_account_data::<spl_token::state::Mint>(&mut banks_client, mint_address)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(mint.supply, 42);
|
||||
|
@ -71,20 +86,20 @@ async fn test_basic() {
|
|||
assert_eq!(mint.mint_authority, COption::Some(mint_address));
|
||||
|
||||
// Confirm distributor token account state
|
||||
let distributor_token = banks_client
|
||||
.get_packed_account_data::<spl_token::state::Account>(distributor_token_address)
|
||||
.await
|
||||
.unwrap();
|
||||
let distributor_token =
|
||||
get_account_data::<spl_token::state::Account>(&mut banks_client, distributor_token_address)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(distributor_token.amount, 42);
|
||||
assert_eq!(distributor_token.mint, mint_address);
|
||||
assert_eq!(distributor_token.owner, feature_proposal.pubkey());
|
||||
assert!(distributor_token.close_authority.is_none());
|
||||
|
||||
// Confirm acceptance token account state
|
||||
let acceptance_token = banks_client
|
||||
.get_packed_account_data::<spl_token::state::Account>(acceptance_token_address)
|
||||
.await
|
||||
.unwrap();
|
||||
let acceptance_token =
|
||||
get_account_data::<spl_token::state::Account>(&mut banks_client, acceptance_token_address)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(acceptance_token.amount, 0);
|
||||
assert_eq!(acceptance_token.mint, mint_address);
|
||||
assert_eq!(acceptance_token.owner, id());
|
||||
|
@ -100,17 +115,15 @@ async fn test_basic() {
|
|||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
// Confirm feature id account is not yet assigned
|
||||
let feature_id_account = banks_client
|
||||
let feature_id_acccount = banks_client
|
||||
.get_account(feature_id_address)
|
||||
.await
|
||||
.expect("success")
|
||||
.expect("some account");
|
||||
assert_eq!(feature_id_account.owner, system_program::id());
|
||||
assert_eq!(feature_id_acccount.owner, system_program::id());
|
||||
|
||||
assert!(matches!(
|
||||
banks_client
|
||||
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
||||
.await,
|
||||
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
||||
Ok(FeatureProposal::Pending(_))
|
||||
));
|
||||
|
||||
|
@ -145,18 +158,16 @@ async fn test_basic() {
|
|||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
// Confirm feature id account is now assigned
|
||||
let feature_id_account = banks_client
|
||||
let feature_id_acccount = banks_client
|
||||
.get_account(feature_id_address)
|
||||
.await
|
||||
.expect("success")
|
||||
.expect("some account");
|
||||
assert_eq!(feature_id_account.owner, feature::id());
|
||||
assert_eq!(feature_id_acccount.owner, feature::id());
|
||||
|
||||
// Confirm feature proposal account state
|
||||
assert!(matches!(
|
||||
banks_client
|
||||
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
||||
.await,
|
||||
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
||||
Ok(FeatureProposal::Accepted {
|
||||
tokens_upon_acceptance: 42
|
||||
})
|
||||
|
@ -186,9 +197,7 @@ async fn test_expired() {
|
|||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
banks_client
|
||||
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
||||
.await,
|
||||
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
||||
Ok(FeatureProposal::Pending(_))
|
||||
));
|
||||
|
||||
|
@ -199,9 +208,7 @@ async fn test_expired() {
|
|||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
banks_client
|
||||
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
||||
.await,
|
||||
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
||||
Ok(FeatureProposal::Expired)
|
||||
));
|
||||
}
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
# Governance
|
||||
|
||||
Governance is a program the chief purpose of which is to control the upgrade of other programs through democratic means.
|
||||
It can also be used as an authority provider for mints and other forms of access control as well where we may want
|
||||
a voting population to vote on disbursement of access or funds collectively.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Accounts diagram
|
||||
|
||||

|
||||
|
||||
### Realm account
|
||||
|
||||
Realm account ties Community Token Mint and optional Council Token mint to create a realm
|
||||
for any governance pertaining to the community of the token holders.
|
||||
For example a trading protocol can issue a governance token and use it to create its governance realm.
|
||||
|
||||
Once a realm is created voters can deposit Governing tokens (Community or Council) to the realm and
|
||||
use the deposited amount as their voting weight to vote on Proposals within that realm.
|
||||
|
||||
### Program Governance account
|
||||
|
||||
The basic building block of governance to update programs is the ProgramGovernance account.
|
||||
It ties a governed Program ID and holds configuration options defining governance rules.
|
||||
The governed Program ID is used as the seed for a [Program Derived Address](https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses),
|
||||
and this program derived address is what is used as the address of the Governance account for your Program ID.
|
||||
|
||||
What this means is that there can only ever be ONE Governance account for a given Program.
|
||||
The governance program validates at creation time of the Governance account that the current upgrade authority of the program
|
||||
taken under governance signed the transaction. Optionally `CreateProgramGovernance` instruction can also transfer `upgrade_authority`
|
||||
of the governed program to the Governance PDA at the creation time of the Governance account.
|
||||
|
||||
### How does the authority work?
|
||||
|
||||
Governance can handle arbitrary executions of code, but it's real power lies in the power to upgrade programs.
|
||||
It does this through executing instructions to the bpf-upgradable-loader program.
|
||||
Bpf-upgradable-loader allows any signer who has Upgrade authority over a Buffer account and the Program account itself
|
||||
to upgrade it using its Upgrade command.
|
||||
Normally, this is the developer who created and deployed the program, and this creation of the Buffer account containing
|
||||
the new program data and overwriting of the existing Program account's data with it is handled in the background for you
|
||||
by the Solana program deploy cli command.
|
||||
However, in order for Governance to be useful, Governance now needs this authority.
|
||||
|
||||
### Proposal accounts
|
||||
|
||||
A Proposal is an instance of a Governance created to vote on and execute given set of changes.
|
||||
It is created by someone (Proposal Owner) and tied to a given Governance account
|
||||
and has a set of executable instructions to it, a name and a description.
|
||||
It goes through various states (draft, voting, executing, ...) and users can vote on it
|
||||
if they have relevant Community or Council tokens.
|
||||
Its rules are determined by the Governance account that it is tied to, and when it executes,
|
||||
it is only eligible to use the [Program Derived Address](https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses)
|
||||
authority given by the Governance account.
|
||||
So a Proposal for Sushi cannot for instance upgrade the Program for Uniswap.
|
||||
|
||||
When a Proposal is created by a user then the user becomes Proposal Owner and receives permission to edit the Proposal.
|
||||
With this power the Owner can edit the Proposal, add/remove Signatories to the Proposal and also cancel it.
|
||||
These Signatories can then show their approval of the Proposal by signing it off.
|
||||
Once all Signatories have signed off the Proposal the Proposal leaves Draft state and enters Voting state.
|
||||
Voting state lasts as long as the Governance has it configured to last, and during this time
|
||||
people holding Community (or Council) tokens may vote on the Proposal.
|
||||
Once the Proposal is "tipped" it either enters the Defeated or Succeeded state. If the vote can't be tipped automatically
|
||||
during the voting period but still reaches the required Yes vote threshold it can be manually transitioned to Succeeded state
|
||||
using FinalizeVote instruction.
|
||||
Once all Proposal instructions are executed the Proposal enters Completed state.
|
||||
|
||||
In the Executing state an instruction can be run by any one at any time after the `instruction_hold_up_time` period has
|
||||
transpired.
|
||||
|
||||
### ProposalInstruction
|
||||
|
||||
A Proposal can have multiple Proposal Instructions, and they run independently of each other.
|
||||
These contain the actual data for an instruction, and how long after the voting phase a user must wait before they can
|
||||
be executed.
|
||||
|
||||
### Voting Dynamics
|
||||
|
||||
When a Proposal is created and signed by its Signatories voters can start voting on it using their voting weight,
|
||||
equal to deposited governing tokens into the realm. A vote is tipped once it passes the defined `vote_threshold` of votes
|
||||
and enters Succeeded or Defeated state. If Succeeded then Proposal instructions can be executed after they hold_up_time passes.
|
||||
|
||||
Users can relinquish their vote any time during Proposal lifetime, but once Proposal it decided their vote can't be changed.
|
||||
|
||||
### Community and Councils governing tokens
|
||||
|
||||
Each Governance Realm that gets created has the option to also have a Council mint.
|
||||
A council mint is simply a separate mint from the Community mint.
|
||||
What this means is that users can submit Proposals that have a different voting population from a different mint
|
||||
that can affect the same program. A practical application of this policy may be to have a very large population control
|
||||
major version bumps of Solana via normal SOL, for instance, but hot fixes be controlled via Council tokens,
|
||||
of which there may be only 30, and which may be themselves minted and distributed via proposals by the governing population.
|
||||
|
||||
### Proposal Workflow
|
||||
|
||||

|
|
@ -1,33 +0,0 @@
|
|||
[package]
|
||||
name = "spl-governance"
|
||||
version = "0.1.0"
|
||||
description = "Solana Program Library Governance"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana-program-library"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
arrayref = "0.3.6"
|
||||
bincode = "1.3.2"
|
||||
borsh = "0.8.1"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
serde = "1.0.121"
|
||||
serde_derive = "1.0.103"
|
||||
solana-program = "1.7.3"
|
||||
spl-token = { path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||
thiserror = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5.0"
|
||||
proptest = "0.10"
|
||||
solana-program-test = "1.7.3"
|
||||
solana-sdk = "1.7.3"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
|
@ -1,2 +0,0 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -1,22 +0,0 @@
|
|||
//! Program entrypoint definitions
|
||||
#![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
|
||||
|
||||
use crate::{error::GovernanceError, processor};
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
|
||||
program_error::PrintProgramError, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
entrypoint!(process_instruction);
|
||||
fn process_instruction(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
instruction_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
if let Err(error) = processor::process_instruction(program_id, accounts, instruction_data) {
|
||||
// catch the error so we can print it
|
||||
error.print::<GovernanceError>();
|
||||
return Err(error);
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -1,272 +0,0 @@
|
|||
//! Error types
|
||||
|
||||
use num_derive::FromPrimitive;
|
||||
use solana_program::{
|
||||
decode_error::DecodeError,
|
||||
msg,
|
||||
program_error::{PrintProgramError, ProgramError},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that may be returned by the Governance program
|
||||
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
|
||||
pub enum GovernanceError {
|
||||
/// Invalid instruction passed to program
|
||||
#[error("Invalid instruction passed to program")]
|
||||
InvalidInstruction,
|
||||
|
||||
/// Realm with the given name and governing mints already exists
|
||||
#[error("Realm with the given name and governing mints already exists")]
|
||||
RealmAlreadyExists,
|
||||
|
||||
/// Invalid Realm
|
||||
#[error("Invalid realm")]
|
||||
InvalidRealm,
|
||||
|
||||
/// Invalid Governing Token Mint
|
||||
#[error("Invalid Governing Token Mint")]
|
||||
InvalidGoverningTokenMint,
|
||||
|
||||
/// Governing Token Owner must sign transaction
|
||||
#[error("Governing Token Owner must sign transaction")]
|
||||
GoverningTokenOwnerMustSign,
|
||||
|
||||
/// Governing Token Owner or Delegate must sign transaction
|
||||
#[error("Governing Token Owner or Delegate must sign transaction")]
|
||||
GoverningTokenOwnerOrDelegateMustSign,
|
||||
|
||||
/// All votes must be relinquished to withdraw governing tokens
|
||||
#[error("All votes must be relinquished to withdraw governing tokens")]
|
||||
AllVotesMustBeRelinquishedToWithdrawGoverningTokens,
|
||||
|
||||
/// Invalid Token Owner Record account address
|
||||
#[error("Invalid Token Owner Record account address")]
|
||||
InvalidTokenOwnerRecordAccountAddress,
|
||||
|
||||
/// Invalid GoverningMint for TokenOwnerRecord
|
||||
#[error("Invalid GoverningMint for TokenOwnerRecord")]
|
||||
InvalidGoverningMintForTokenOwnerRecord,
|
||||
|
||||
/// Invalid Realm for TokenOwnerRecord
|
||||
#[error("Invalid Realm for TokenOwnerRecord")]
|
||||
InvalidRealmForTokenOwnerRecord,
|
||||
|
||||
/// Invalid Proposal for ProposalInstruction
|
||||
#[error("Invalid Proposal for ProposalInstruction")]
|
||||
InvalidProposalForProposalInstruction,
|
||||
|
||||
/// Invalid Signatory account address
|
||||
#[error("Invalid Signatory account address")]
|
||||
InvalidSignatoryAddress,
|
||||
|
||||
/// Signatory already signed off
|
||||
#[error("Signatory already signed off")]
|
||||
SignatoryAlreadySignedOff,
|
||||
|
||||
/// Signatory must sign
|
||||
#[error("Signatory must sign")]
|
||||
SignatoryMustSign,
|
||||
|
||||
/// Invalid Proposal Owner
|
||||
#[error("Invalid Proposal Owner")]
|
||||
InvalidProposalOwnerAccount,
|
||||
|
||||
/// Invalid Proposal for VoterRecord
|
||||
#[error("Invalid Proposal for VoterRecord")]
|
||||
InvalidProposalForVoterRecord,
|
||||
|
||||
/// Invalid GoverningTokenOwner for VoteRecord
|
||||
#[error("Invalid GoverningTokenOwner for VoteRecord")]
|
||||
InvalidGoverningTokenOwnerForVoteRecord,
|
||||
|
||||
/// Invalid Governance config
|
||||
#[error("Invalid Governance config")]
|
||||
InvalidGovernanceConfig,
|
||||
|
||||
/// Proposal for the given Governance, Governing Token Mint and index already exists
|
||||
#[error("Proposal for the given Governance, Governing Token Mint and index already exists")]
|
||||
ProposalAlreadyExists,
|
||||
|
||||
/// Token Owner already voted on the Proposal
|
||||
#[error("Token Owner already voted on the Proposal")]
|
||||
VoteAlreadyExists,
|
||||
|
||||
/// Owner doesn't have enough governing tokens to create Proposal
|
||||
#[error("Owner doesn't have enough governing tokens to create Proposal")]
|
||||
NotEnoughTokensToCreateProposal,
|
||||
|
||||
/// Invalid State: Can't edit Signatories
|
||||
#[error("Invalid State: Can't edit Signatories")]
|
||||
InvalidStateCannotEditSignatories,
|
||||
|
||||
/// Invalid Proposal state
|
||||
#[error("Invalid Proposal state")]
|
||||
InvalidProposalState,
|
||||
/// Invalid State: Can't edit instructions
|
||||
#[error("Invalid State: Can't edit instructions")]
|
||||
InvalidStateCannotEditInstructions,
|
||||
|
||||
/// Invalid State: Can't execute instruction
|
||||
#[error("Invalid State: Can't execute instruction")]
|
||||
InvalidStateCannotExecuteInstruction,
|
||||
|
||||
/// Can't execute instruction within its hold up time
|
||||
#[error("Can't execute instruction within its hold up time")]
|
||||
CannotExecuteInstructionWithinHoldUpTime,
|
||||
|
||||
/// Instruction already executed
|
||||
#[error("Instruction already executed")]
|
||||
InstructionAlreadyExecuted,
|
||||
|
||||
/// Invalid Instruction index
|
||||
#[error("Invalid Instruction index")]
|
||||
InvalidInstructionIndex,
|
||||
|
||||
/// Instruction hold up time is below the min specified by Governance
|
||||
#[error("Instruction hold up time is below the min specified by Governance")]
|
||||
InstructionHoldUpTimeBelowRequiredMin,
|
||||
|
||||
/// Instruction at the given index for the Proposal already exists
|
||||
#[error("Instruction at the given index for the Proposal already exists")]
|
||||
InstructionAlreadyExists,
|
||||
|
||||
/// Invalid State: Can't sign off
|
||||
#[error("Invalid State: Can't sign off")]
|
||||
InvalidStateCannotSignOff,
|
||||
|
||||
/// Invalid State: Can't vote
|
||||
#[error("Invalid State: Can't vote")]
|
||||
InvalidStateCannotVote,
|
||||
|
||||
/// Invalid State: Can't finalize vote
|
||||
#[error("Invalid State: Can't finalize vote")]
|
||||
InvalidStateCannotFinalize,
|
||||
|
||||
/// Invalid State: Can't cancel Proposal
|
||||
#[error("Invalid State: Can't cancel Proposal")]
|
||||
InvalidStateCannotCancelProposal,
|
||||
|
||||
/// Vote already relinquished
|
||||
#[error("Vote already relinquished")]
|
||||
VoteAlreadyRelinquished,
|
||||
|
||||
/// Can't finalize vote. Voting still in progress
|
||||
#[error("Can't finalize vote. Voting still in progress")]
|
||||
CannotFinalizeVotingInProgress,
|
||||
|
||||
/// Proposal voting time expired
|
||||
#[error("Proposal voting time expired")]
|
||||
ProposalVotingTimeExpired,
|
||||
|
||||
/// Invalid Signatory Mint
|
||||
#[error("Invalid Signatory Mint")]
|
||||
InvalidSignatoryMint,
|
||||
|
||||
/// ---- Account Tools Errors ----
|
||||
|
||||
/// Invalid account owner
|
||||
#[error("Invalid account owner")]
|
||||
InvalidAccountOwner,
|
||||
|
||||
/// Account doesn't exist
|
||||
#[error("Account doesn't exist")]
|
||||
AccountDoesNotExist,
|
||||
|
||||
/// Invalid Account type
|
||||
#[error("Invalid Account type")]
|
||||
InvalidAccountType,
|
||||
|
||||
/// Proposal does not belong to the given Governance
|
||||
#[error("Proposal does not belong to the given Governance")]
|
||||
InvalidGovernanceForProposal,
|
||||
|
||||
/// Proposal does not belong to given Governing Mint"
|
||||
#[error("Proposal does not belong to given Governing Mint")]
|
||||
InvalidGoverningMintForProposal,
|
||||
|
||||
/// Current mint authority must sign transaction
|
||||
#[error("Current mint authority must sign transaction")]
|
||||
MintAuthorityMustSign,
|
||||
|
||||
/// Invalid mint authority
|
||||
#[error("Invalid mint authority")]
|
||||
InvalidMintAuthority,
|
||||
|
||||
/// Mint has no authority
|
||||
#[error("Mint has no authority")]
|
||||
MintHasNoAuthority,
|
||||
|
||||
/// ---- SPL Token Tools Errors ----
|
||||
|
||||
/// Invalid Token account owner
|
||||
#[error("Invalid Token account owner")]
|
||||
SplTokenAccountWithInvalidOwner,
|
||||
|
||||
/// Invalid Mint account owner
|
||||
#[error("Invalid Mint account owner")]
|
||||
SplTokenMintWithInvalidOwner,
|
||||
|
||||
/// Token Account is not initialized
|
||||
#[error("Token Account is not initialized")]
|
||||
SplTokenAccountNotInitialized,
|
||||
|
||||
/// Token Account doesn't exist
|
||||
#[error("Token Account doesn't exist")]
|
||||
SplTokenAccountDoesNotExist,
|
||||
|
||||
/// Token account data is invalid
|
||||
#[error("Token account data is invalid")]
|
||||
SplTokenInvalidTokenAccountData,
|
||||
|
||||
/// Token mint account data is invalid
|
||||
#[error("Token mint account data is invalid")]
|
||||
SplTokenInvalidMintAccountData,
|
||||
|
||||
/// Token Mint is not initialized
|
||||
#[error("Token Mint account is not initialized")]
|
||||
SplTokenMintNotInitialized,
|
||||
|
||||
/// Token Mint account doesn't exist
|
||||
#[error("Token Mint account doesn't exist")]
|
||||
SplTokenMintDoesNotExist,
|
||||
|
||||
/// ---- Bpf Upgradable Loader Tools Errors ----
|
||||
|
||||
/// Invalid ProgramData account Address
|
||||
#[error("Invalid ProgramData account address")]
|
||||
InvalidProgramDataAccountAddress,
|
||||
|
||||
/// Invalid ProgramData account data
|
||||
#[error("Invalid ProgramData account Data")]
|
||||
InvalidProgramDataAccountData,
|
||||
|
||||
/// Provided upgrade authority doesn't match current program upgrade authority
|
||||
#[error("Provided upgrade authority doesn't match current program upgrade authority")]
|
||||
InvalidUpgradeAuthority,
|
||||
|
||||
/// Current program upgrade authority must sign transaction
|
||||
#[error("Current program upgrade authority must sign transaction")]
|
||||
UpgradeAuthorityMustSign,
|
||||
|
||||
/// Given program is not upgradable
|
||||
#[error("Given program is not upgradable")]
|
||||
ProgramNotUpgradable,
|
||||
}
|
||||
|
||||
impl PrintProgramError for GovernanceError {
|
||||
fn print<E>(&self) {
|
||||
msg!("GOVERNANCE-ERROR: {}", &self.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GovernanceError> for ProgramError {
|
||||
fn from(e: GovernanceError) -> Self {
|
||||
ProgramError::Custom(e as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DecodeError<T> for GovernanceError {
|
||||
fn type_of() -> &'static str {
|
||||
"Governance Error"
|
||||
}
|
||||
}
|
|
@ -1,936 +0,0 @@
|
|||
//! Program instructions
|
||||
|
||||
use crate::{
|
||||
state::{
|
||||
governance::{
|
||||
get_account_governance_address, get_mint_governance_address,
|
||||
get_program_governance_address, GovernanceConfig,
|
||||
},
|
||||
proposal::get_proposal_address,
|
||||
proposal_instruction::{get_proposal_instruction_address, InstructionData},
|
||||
realm::{get_governing_token_holding_address, get_realm_address},
|
||||
signatory_record::get_signatory_record_address,
|
||||
token_owner_record::get_token_owner_record_address,
|
||||
vote_record::get_vote_record_address,
|
||||
},
|
||||
tools::bpf_loader_upgradeable::get_program_data_address,
|
||||
};
|
||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||
use solana_program::{
|
||||
bpf_loader_upgradeable,
|
||||
instruction::{AccountMeta, Instruction},
|
||||
pubkey::Pubkey,
|
||||
system_program, sysvar,
|
||||
};
|
||||
|
||||
/// Yes/No Vote
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub enum Vote {
|
||||
/// Yes vote
|
||||
Yes,
|
||||
/// No vote
|
||||
No,
|
||||
}
|
||||
|
||||
/// Instructions supported by the Governance program
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
#[repr(C)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum GovernanceInstruction {
|
||||
/// Creates Governance Realm account which aggregates governances for given Community Mint and optional Council Mint
|
||||
///
|
||||
/// 0. `[writable]` Governance Realm account. PDA seeds:['governance',name]
|
||||
/// 1. `[]` Community Token Mint
|
||||
/// 2. `[writable]` Community Token Holding account. PDA seeds: ['governance',realm,community_mint]
|
||||
/// The account will be created with the Realm PDA as its owner
|
||||
/// 3. `[signer]` Payer
|
||||
/// 4. `[]` System
|
||||
/// 5. `[]` SPL Token
|
||||
/// 6. `[]` Sysvar Rent
|
||||
/// 7. `[]` Council Token Mint - optional
|
||||
/// 8. `[writable]` Council Token Holding account - optional. . PDA seeds: ['governance',realm,council_mint]
|
||||
/// The account will be created with the Realm PDA as its owner
|
||||
CreateRealm {
|
||||
#[allow(dead_code)]
|
||||
/// UTF-8 encoded Governance Realm name
|
||||
name: String,
|
||||
},
|
||||
|
||||
/// Deposits governing tokens (Community or Council) to Governance Realm and establishes your voter weight to be used for voting within the Realm
|
||||
/// Note: If subsequent (top up) deposit is made and there are active votes for the Voter then the vote weights won't be updated automatically
|
||||
/// It can be done by relinquishing votes on active Proposals and voting again with the new weight
|
||||
///
|
||||
/// 0. `[]` Governance Realm account
|
||||
/// 1. `[writable]` Governing Token Holding account. PDA seeds: ['governance',realm, governing_token_mint]
|
||||
/// 2. `[writable]` Governing Token Source account. All tokens from the account will be transferred to the Holding account
|
||||
/// 3. `[signer]` Governing Token Owner account
|
||||
/// 4. `[signer]` Governing Token Transfer authority
|
||||
/// 5. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||
/// 6. `[signer]` Payer
|
||||
/// 7. `[]` System
|
||||
/// 8. `[]` SPL Token
|
||||
/// 9. `[]` Sysvar Rent
|
||||
DepositGoverningTokens {},
|
||||
|
||||
/// Withdraws governing tokens (Community or Council) from Governance Realm and downgrades your voter weight within the Realm
|
||||
/// Note: It's only possible to withdraw tokens if the Voter doesn't have any outstanding active votes
|
||||
/// If there are any outstanding votes then they must be relinquished before tokens could be withdrawn
|
||||
///
|
||||
/// 0. `[]` Governance Realm account
|
||||
/// 1. `[writable]` Governing Token Holding account. PDA seeds: ['governance',realm, governing_token_mint]
|
||||
/// 2. `[writable]` Governing Token Destination account. All tokens will be transferred to this account
|
||||
/// 3. `[signer]` Governing Token Owner account
|
||||
/// 4. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||
/// 5. `[]` SPL Token
|
||||
WithdrawGoverningTokens {},
|
||||
|
||||
/// Sets Governance Delegate for the given Realm and Governing Token Mint (Community or Council)
|
||||
/// The Delegate would have voting rights and could vote on behalf of the Governing Token Owner
|
||||
/// The Delegate would also be able to create Proposals on behalf of the Governing Token Owner
|
||||
/// Note: This doesn't take voting rights from the Token Owner who still can vote and change governance_delegate
|
||||
///
|
||||
/// 0. `[signer]` Current Governance Delegate or Governing Token owner
|
||||
/// 1. `[writable]` Token Owner Record
|
||||
SetGovernanceDelegate {
|
||||
#[allow(dead_code)]
|
||||
/// New Governance Delegate
|
||||
new_governance_delegate: Option<Pubkey>,
|
||||
},
|
||||
|
||||
/// Creates Account Governance account which can be used to govern an arbitrary account
|
||||
///
|
||||
/// 0. `[]` Realm account the created Governance belongs to
|
||||
/// 1. `[writable]` Account Governance account. PDA seeds: ['account-governance', realm, governed_account]
|
||||
/// 2. `[signer]` Payer
|
||||
/// 3. `[]` System program
|
||||
/// 4. `[]` Sysvar Rent
|
||||
CreateAccountGovernance {
|
||||
/// Governance config
|
||||
#[allow(dead_code)]
|
||||
config: GovernanceConfig,
|
||||
},
|
||||
|
||||
/// Creates Program Governance account which governs an upgradable program
|
||||
///
|
||||
/// 0. `[]` Realm account the created Governance belongs to
|
||||
/// 1. `[writable]` Program Governance account. PDA seeds: ['program-governance', realm, governed_program]
|
||||
/// 2. `[writable]` Program Data account of the Program governed by this Governance account
|
||||
/// 3. `[signer]` Current Upgrade Authority account of the Program governed by this Governance account
|
||||
/// 4. `[signer]` Payer
|
||||
/// 5. `[]` bpf_upgradeable_loader program
|
||||
/// 6. `[]` System program
|
||||
/// 7. `[]` Sysvar Rent
|
||||
CreateProgramGovernance {
|
||||
/// Governance config
|
||||
#[allow(dead_code)]
|
||||
config: GovernanceConfig,
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Indicates whether Program's upgrade_authority should be transferred to the Governance PDA
|
||||
/// If it's set to false then it can be done at a later time
|
||||
/// However the instruction would validate the current upgrade_authority signed the transaction nonetheless
|
||||
transfer_upgrade_authority: bool,
|
||||
},
|
||||
|
||||
/// Creates Proposal account for Instructions that will be executed at various slots in the future
|
||||
///
|
||||
/// 0. `[writable]` Proposal account. PDA seeds ['governance',governance, governing_token_mint, proposal_index]
|
||||
/// 1. `[writable]` Governance account
|
||||
/// 2. `[]` TokenOwnerRecord account for Proposal owner
|
||||
/// 3. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||
/// 4. `[signer]` Payer
|
||||
/// 5. `[]` System program
|
||||
/// 6. `[]` Rent sysvar
|
||||
/// 7. `[]` Clock sysvar
|
||||
CreateProposal {
|
||||
#[allow(dead_code)]
|
||||
/// UTF-8 encoded name of the proposal
|
||||
name: String,
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Link to gist explaining proposal
|
||||
description_link: String,
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Governing Token Mint the Proposal is created for
|
||||
governing_token_mint: Pubkey,
|
||||
},
|
||||
|
||||
/// Adds a signatory to the Proposal which means this Proposal can't leave Draft state until yet another Signatory signs
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[]` TokenOwnerRecord account for Proposal owner
|
||||
/// 2. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||
/// 3. `[writable]` Signatory Record Account
|
||||
/// 4. `[signer]` Payer
|
||||
/// 5. `[]` System program
|
||||
/// 6. `[]` Rent sysvar
|
||||
AddSignatory {
|
||||
#[allow(dead_code)]
|
||||
/// Signatory to add to the Proposal
|
||||
signatory: Pubkey,
|
||||
},
|
||||
|
||||
/// Removes a Signatory from the Proposal
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[]` TokenOwnerRecord account for Proposal owner
|
||||
/// 2. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||
/// 3. `[writable]` Signatory Record Account
|
||||
/// 4. `[writable]` Beneficiary Account which would receive lamports from the disposed Signatory Record Account
|
||||
RemoveSignatory {
|
||||
#[allow(dead_code)]
|
||||
/// Signatory to remove from the Proposal
|
||||
signatory: Pubkey,
|
||||
},
|
||||
|
||||
/// Inserts an instruction for the Proposal at the given index position
|
||||
/// New Instructions must be inserted at the end of the range indicated by Proposal instructions_next_index
|
||||
/// If an Instruction replaces an existing Instruction at a given index then the old one must be removed using RemoveInstruction first
|
||||
|
||||
/// 0. `[]` Governance account
|
||||
/// 1. `[writable]` Proposal account
|
||||
/// 2. `[]` TokenOwnerRecord account for Proposal owner
|
||||
/// 3. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||
/// 4. `[writable]` ProposalInstruction account. PDA seeds: ['governance',proposal,index]
|
||||
/// 5. `[signer]` Payer
|
||||
/// 6. `[]` System program
|
||||
/// 7. `[]` Clock sysvar
|
||||
InsertInstruction {
|
||||
#[allow(dead_code)]
|
||||
/// Instruction index to be inserted at.
|
||||
index: u16,
|
||||
#[allow(dead_code)]
|
||||
/// Slot waiting time between vote period ending and this being eligible for execution
|
||||
hold_up_time: u64,
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Instruction Data
|
||||
instruction: InstructionData,
|
||||
},
|
||||
|
||||
/// Removes instruction from the Proposal
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[]` TokenOwnerRecord account for Proposal owner
|
||||
/// 2. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||
/// 3. `[writable]` ProposalInstruction account
|
||||
/// 4. `[writable]` Beneficiary Account which would receive lamports from the disposed ProposalInstruction account
|
||||
RemoveInstruction,
|
||||
|
||||
/// Cancels Proposal by changing its state to Canceled
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[]` TokenOwnerRecord account for Proposal owner
|
||||
/// 2 `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||
/// 3. `[]` Clock sysvar
|
||||
CancelProposal,
|
||||
|
||||
/// Signs off Proposal indicating the Signatory approves the Proposal
|
||||
/// When the last Signatory signs the Proposal state moves to Voting state
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Signatory Record account
|
||||
/// 2. `[signer]` Signatory account
|
||||
/// 3. `[]` Clock sysvar
|
||||
SignOffProposal,
|
||||
|
||||
/// Uses your voter weight (deposited Community or Council tokens) to cast a vote on a Proposal
|
||||
/// By doing so you indicate you approve or disapprove of running the Proposal set of instructions
|
||||
/// If you tip the consensus then the instructions can begin to be run after their hold up time
|
||||
///
|
||||
/// 0. `[]` Governance account
|
||||
/// 1. `[writable]` Proposal account
|
||||
/// 2. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||
/// 3. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||
/// 4. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record]
|
||||
/// 5. `[]` Governing Token Mint
|
||||
/// 6. `[signer]` Payer
|
||||
/// 7. `[]` System program
|
||||
/// 8. `[]` Rent sysvar
|
||||
/// 9. `[]` Clock sysvar
|
||||
CastVote {
|
||||
#[allow(dead_code)]
|
||||
/// Yes/No vote
|
||||
vote: Vote,
|
||||
},
|
||||
|
||||
/// Finalizes vote in case the Vote was not automatically tipped within max_voting_time period
|
||||
///
|
||||
/// 0. `[]` Governance account
|
||||
/// 1. `[writable]` Proposal account
|
||||
/// 2. `[]` Governing Token Mint
|
||||
/// 3. `[]` Clock sysvar
|
||||
FinalizeVote {},
|
||||
|
||||
/// Relinquish Vote removes voter weight from a Proposal and removes it from voter's active votes
|
||||
/// If the Proposal is still being voted on then the voter's weight won't count towards the vote outcome
|
||||
/// If the Proposal is already in decided state then the instruction has no impact on the Proposal
|
||||
/// and only allows voters to prune their outstanding votes in case they wanted to withdraw Governing tokens from the Realm
|
||||
///
|
||||
/// 0. `[]` Governance account
|
||||
/// 1. `[writable]` Proposal account
|
||||
/// 2. `[writable]` TokenOwnerRecord account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||
/// 3. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record]
|
||||
/// 4. `[]` Governing Token Mint
|
||||
|
||||
/// 5. `[signer]` Optional Governance Authority (Token Owner or Governance Delegate)
|
||||
/// It's required only when Proposal is still being voted on
|
||||
/// 6. `[writable]` Optional Beneficiary account which would receive lamports when VoteRecord Account is disposed
|
||||
/// It's required only when Proposal is still being voted on
|
||||
RelinquishVote,
|
||||
|
||||
/// Executes an instruction in the Proposal
|
||||
/// Anybody can execute transaction once Proposal has been voted Yes and transaction_hold_up time has passed
|
||||
/// The actual instruction being executed will be signed by Governance PDA the Proposal belongs to
|
||||
/// For example to execute Program upgrade the ProgramGovernance PDA would be used as the singer
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` ProposalInstruction account you wish to execute
|
||||
/// 2. `[]` Clock sysvar
|
||||
/// 3+ Any extra accounts that are part of the instruction, in order
|
||||
ExecuteInstruction,
|
||||
|
||||
/// Creates Mint Governance account which governs a mint
|
||||
///
|
||||
/// 0. `[]` Realm account the created Governance belongs to
|
||||
/// 1. `[writable]` Mint Governance account. PDA seeds: ['mint-governance', realm, governed_mint]
|
||||
/// 2. `[writable]` Mint governed by this Governance account
|
||||
/// 3. `[signer]` Current Mint Authority
|
||||
/// 4. `[signer]` Payer
|
||||
/// 5. `[]` SPL Token program
|
||||
/// 6. `[]` System program
|
||||
/// 7. `[]` Sysvar Rent
|
||||
CreateMintGovernance {
|
||||
#[allow(dead_code)]
|
||||
/// Governance config
|
||||
config: GovernanceConfig,
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Indicates whether Mint's authority should be transferred to the Governance PDA
|
||||
/// If it's set to false then it can be done at a later time
|
||||
/// However the instruction would validate the current mint authority signed the transaction nonetheless
|
||||
transfer_mint_authority: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Creates CreateRealm instruction
|
||||
pub fn create_realm(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
community_token_mint: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
council_token_mint: Option<Pubkey>,
|
||||
// Args
|
||||
name: String,
|
||||
) -> Instruction {
|
||||
let realm_address = get_realm_address(program_id, &name);
|
||||
let community_token_holding_address =
|
||||
get_governing_token_holding_address(program_id, &realm_address, community_token_mint);
|
||||
|
||||
let mut accounts = vec![
|
||||
AccountMeta::new(realm_address, false),
|
||||
AccountMeta::new_readonly(*community_token_mint, false),
|
||||
AccountMeta::new(community_token_holding_address, false),
|
||||
AccountMeta::new_readonly(*payer, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
];
|
||||
|
||||
if let Some(council_token_mint) = council_token_mint {
|
||||
let council_token_holding_address =
|
||||
get_governing_token_holding_address(program_id, &realm_address, &council_token_mint);
|
||||
|
||||
accounts.push(AccountMeta::new_readonly(council_token_mint, false));
|
||||
accounts.push(AccountMeta::new(council_token_holding_address, false));
|
||||
}
|
||||
|
||||
let instruction = GovernanceInstruction::CreateRealm { name };
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates DepositGoverningTokens instruction
|
||||
pub fn deposit_governing_tokens(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
realm: &Pubkey,
|
||||
governing_token_source: &Pubkey,
|
||||
governing_token_owner: &Pubkey,
|
||||
governing_token_transfer_authority: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
// Args
|
||||
governing_token_mint: &Pubkey,
|
||||
) -> Instruction {
|
||||
let token_owner_record_address = get_token_owner_record_address(
|
||||
program_id,
|
||||
realm,
|
||||
governing_token_mint,
|
||||
governing_token_owner,
|
||||
);
|
||||
|
||||
let governing_token_holding_address =
|
||||
get_governing_token_holding_address(program_id, realm, governing_token_mint);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*realm, false),
|
||||
AccountMeta::new(governing_token_holding_address, false),
|
||||
AccountMeta::new(*governing_token_source, false),
|
||||
AccountMeta::new_readonly(*governing_token_owner, true),
|
||||
AccountMeta::new_readonly(*governing_token_transfer_authority, true),
|
||||
AccountMeta::new(token_owner_record_address, false),
|
||||
AccountMeta::new_readonly(*payer, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::DepositGoverningTokens {};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates WithdrawGoverningTokens instruction
|
||||
pub fn withdraw_governing_tokens(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
realm: &Pubkey,
|
||||
governing_token_destination: &Pubkey,
|
||||
governing_token_owner: &Pubkey,
|
||||
// Args
|
||||
governing_token_mint: &Pubkey,
|
||||
) -> Instruction {
|
||||
let token_owner_record_address = get_token_owner_record_address(
|
||||
program_id,
|
||||
realm,
|
||||
governing_token_mint,
|
||||
governing_token_owner,
|
||||
);
|
||||
|
||||
let governing_token_holding_address =
|
||||
get_governing_token_holding_address(program_id, realm, governing_token_mint);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*realm, false),
|
||||
AccountMeta::new(governing_token_holding_address, false),
|
||||
AccountMeta::new(*governing_token_destination, false),
|
||||
AccountMeta::new_readonly(*governing_token_owner, true),
|
||||
AccountMeta::new(token_owner_record_address, false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::WithdrawGoverningTokens {};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates SetGovernanceDelegate instruction
|
||||
pub fn set_governance_delegate(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governance_authority: &Pubkey,
|
||||
// Args
|
||||
realm: &Pubkey,
|
||||
governing_token_mint: &Pubkey,
|
||||
governing_token_owner: &Pubkey,
|
||||
new_governance_delegate: &Option<Pubkey>,
|
||||
) -> Instruction {
|
||||
let vote_record_address = get_token_owner_record_address(
|
||||
program_id,
|
||||
realm,
|
||||
governing_token_mint,
|
||||
governing_token_owner,
|
||||
);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*governance_authority, true),
|
||||
AccountMeta::new(vote_record_address, false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::SetGovernanceDelegate {
|
||||
new_governance_delegate: *new_governance_delegate,
|
||||
};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates CreateAccountGovernance instruction
|
||||
pub fn create_account_governance(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
payer: &Pubkey,
|
||||
// Args
|
||||
config: GovernanceConfig,
|
||||
) -> Instruction {
|
||||
let account_governance_address =
|
||||
get_account_governance_address(program_id, &config.realm, &config.governed_account);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(config.realm, false),
|
||||
AccountMeta::new(account_governance_address, false),
|
||||
AccountMeta::new_readonly(*payer, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::CreateAccountGovernance { config };
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates CreateProgramGovernance instruction
|
||||
pub fn create_program_governance(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governed_program_upgrade_authority: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
// Args
|
||||
config: GovernanceConfig,
|
||||
transfer_upgrade_authority: bool,
|
||||
) -> Instruction {
|
||||
let program_governance_address =
|
||||
get_program_governance_address(program_id, &config.realm, &config.governed_account);
|
||||
let governed_program_data_address = get_program_data_address(&config.governed_account);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(config.realm, false),
|
||||
AccountMeta::new(program_governance_address, false),
|
||||
AccountMeta::new(governed_program_data_address, false),
|
||||
AccountMeta::new_readonly(*governed_program_upgrade_authority, true),
|
||||
AccountMeta::new_readonly(*payer, true),
|
||||
AccountMeta::new_readonly(bpf_loader_upgradeable::id(), false),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::CreateProgramGovernance {
|
||||
config,
|
||||
transfer_upgrade_authority,
|
||||
};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates CreateMintGovernance instruction
|
||||
pub fn create_mint_governance(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governed_mint_authority: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
// Args
|
||||
config: GovernanceConfig,
|
||||
transfer_mint_authority: bool,
|
||||
) -> Instruction {
|
||||
let mint_governance_address =
|
||||
get_mint_governance_address(program_id, &config.realm, &config.governed_account);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(config.realm, false),
|
||||
AccountMeta::new(mint_governance_address, false),
|
||||
AccountMeta::new(config.governed_account, false),
|
||||
AccountMeta::new_readonly(*governed_mint_authority, true),
|
||||
AccountMeta::new_readonly(*payer, true),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::CreateMintGovernance {
|
||||
config,
|
||||
transfer_mint_authority,
|
||||
};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates CreateProposal instruction
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_proposal(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governance: &Pubkey,
|
||||
governing_token_owner: &Pubkey,
|
||||
governance_authority: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
// Args
|
||||
realm: &Pubkey,
|
||||
name: String,
|
||||
description_link: String,
|
||||
governing_token_mint: &Pubkey,
|
||||
proposal_index: u32,
|
||||
) -> Instruction {
|
||||
let proposal_address = get_proposal_address(
|
||||
program_id,
|
||||
governance,
|
||||
governing_token_mint,
|
||||
&proposal_index.to_le_bytes(),
|
||||
);
|
||||
let token_owner_record_address = get_token_owner_record_address(
|
||||
program_id,
|
||||
realm,
|
||||
governing_token_mint,
|
||||
governing_token_owner,
|
||||
);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new(proposal_address, false),
|
||||
AccountMeta::new(*governance, false),
|
||||
AccountMeta::new_readonly(token_owner_record_address, false),
|
||||
AccountMeta::new_readonly(*governance_authority, true),
|
||||
AccountMeta::new_readonly(*payer, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::CreateProposal {
|
||||
name,
|
||||
description_link,
|
||||
governing_token_mint: *governing_token_mint,
|
||||
};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates AddSignatory instruction
|
||||
pub fn add_signatory(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
proposal: &Pubkey,
|
||||
token_owner_record: &Pubkey,
|
||||
governance_authority: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
// Args
|
||||
signatory: &Pubkey,
|
||||
) -> Instruction {
|
||||
let signatory_record_address = get_signatory_record_address(program_id, proposal, signatory);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new_readonly(*token_owner_record, false),
|
||||
AccountMeta::new_readonly(*governance_authority, true),
|
||||
AccountMeta::new(signatory_record_address, false),
|
||||
AccountMeta::new_readonly(*payer, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::AddSignatory {
|
||||
signatory: *signatory,
|
||||
};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates RemoveSignatory instruction
|
||||
pub fn remove_signatory(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
proposal: &Pubkey,
|
||||
token_owner_record: &Pubkey,
|
||||
governance_authority: &Pubkey,
|
||||
signatory: &Pubkey,
|
||||
beneficiary: &Pubkey,
|
||||
) -> Instruction {
|
||||
let signatory_record_address = get_signatory_record_address(program_id, proposal, signatory);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new_readonly(*token_owner_record, false),
|
||||
AccountMeta::new_readonly(*governance_authority, true),
|
||||
AccountMeta::new(signatory_record_address, false),
|
||||
AccountMeta::new(*beneficiary, false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::RemoveSignatory {
|
||||
signatory: *signatory,
|
||||
};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates SignOffProposal instruction
|
||||
pub fn sign_off_proposal(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
proposal: &Pubkey,
|
||||
signatory: &Pubkey,
|
||||
) -> Instruction {
|
||||
let signatory_record_address = get_signatory_record_address(program_id, proposal, signatory);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new(signatory_record_address, false),
|
||||
AccountMeta::new_readonly(*signatory, true),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::SignOffProposal;
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates CastVote instruction
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn cast_vote(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governance: &Pubkey,
|
||||
proposal: &Pubkey,
|
||||
token_owner_record: &Pubkey,
|
||||
governance_authority: &Pubkey,
|
||||
governing_token_mint: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
// Args
|
||||
vote: Vote,
|
||||
) -> Instruction {
|
||||
let vote_record_address = get_vote_record_address(program_id, proposal, token_owner_record);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*governance, false),
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new(*token_owner_record, false),
|
||||
AccountMeta::new_readonly(*governance_authority, true),
|
||||
AccountMeta::new(vote_record_address, false),
|
||||
AccountMeta::new_readonly(*governing_token_mint, false),
|
||||
AccountMeta::new_readonly(*payer, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::CastVote { vote };
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates FinalizeVote instruction
|
||||
pub fn finalize_vote(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governance: &Pubkey,
|
||||
proposal: &Pubkey,
|
||||
governing_token_mint: &Pubkey,
|
||||
) -> Instruction {
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*governance, false),
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new_readonly(*governing_token_mint, false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::FinalizeVote {};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates RelinquishVote instruction
|
||||
pub fn relinquish_vote(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governance: &Pubkey,
|
||||
proposal: &Pubkey,
|
||||
token_owner_record: &Pubkey,
|
||||
governing_token_mint: &Pubkey,
|
||||
governance_authority: Option<Pubkey>,
|
||||
beneficiary: Option<Pubkey>,
|
||||
) -> Instruction {
|
||||
let vote_record_address = get_vote_record_address(program_id, proposal, token_owner_record);
|
||||
|
||||
let mut accounts = vec![
|
||||
AccountMeta::new_readonly(*governance, false),
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new(*token_owner_record, false),
|
||||
AccountMeta::new(vote_record_address, false),
|
||||
AccountMeta::new_readonly(*governing_token_mint, false),
|
||||
];
|
||||
|
||||
if let Some(governance_authority) = governance_authority {
|
||||
accounts.push(AccountMeta::new_readonly(governance_authority, true));
|
||||
accounts.push(AccountMeta::new(beneficiary.unwrap(), false));
|
||||
}
|
||||
|
||||
let instruction = GovernanceInstruction::RelinquishVote {};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates CancelProposal instruction
|
||||
pub fn cancel_proposal(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
proposal: &Pubkey,
|
||||
token_owner_record: &Pubkey,
|
||||
governance_authority: &Pubkey,
|
||||
) -> Instruction {
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new_readonly(*token_owner_record, false),
|
||||
AccountMeta::new_readonly(*governance_authority, true),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::CancelProposal {};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates InsertInstruction instruction
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn insert_instruction(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governance: &Pubkey,
|
||||
proposal: &Pubkey,
|
||||
token_owner_record: &Pubkey,
|
||||
governance_authority: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
// Args
|
||||
index: u16,
|
||||
hold_up_time: u64,
|
||||
instruction: InstructionData,
|
||||
) -> Instruction {
|
||||
let proposal_instruction_address =
|
||||
get_proposal_instruction_address(program_id, proposal, &index.to_le_bytes());
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*governance, false),
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new_readonly(*token_owner_record, false),
|
||||
AccountMeta::new_readonly(*governance_authority, true),
|
||||
AccountMeta::new(proposal_instruction_address, false),
|
||||
AccountMeta::new_readonly(*payer, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::InsertInstruction {
|
||||
index,
|
||||
hold_up_time,
|
||||
instruction,
|
||||
};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates RemoveInstruction instruction
|
||||
pub fn remove_instruction(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
proposal: &Pubkey,
|
||||
token_owner_record: &Pubkey,
|
||||
governance_authority: &Pubkey,
|
||||
proposal_instruction: &Pubkey,
|
||||
beneficiary: &Pubkey,
|
||||
) -> Instruction {
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new_readonly(*token_owner_record, false),
|
||||
AccountMeta::new_readonly(*governance_authority, true),
|
||||
AccountMeta::new(*proposal_instruction, false),
|
||||
AccountMeta::new(*beneficiary, false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::RemoveInstruction {};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates ExecuteInstruction instruction
|
||||
pub fn execute_instruction(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governance: &Pubkey,
|
||||
proposal: &Pubkey,
|
||||
proposal_instruction: &Pubkey,
|
||||
instruction_program_id: &Pubkey,
|
||||
instruction_accounts: &[AccountMeta],
|
||||
) -> Instruction {
|
||||
let mut accounts = vec![
|
||||
AccountMeta::new_readonly(*governance, false),
|
||||
AccountMeta::new(*proposal, false),
|
||||
AccountMeta::new(*proposal_instruction, false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(*instruction_program_id, false),
|
||||
];
|
||||
|
||||
accounts.extend_from_slice(instruction_accounts);
|
||||
|
||||
let instruction = GovernanceInstruction::ExecuteInstruction {};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
#![deny(missing_docs)]
|
||||
//! A Governance program for the Solana blockchain.
|
||||
|
||||
pub mod entrypoint;
|
||||
pub mod error;
|
||||
pub mod instruction;
|
||||
pub mod processor;
|
||||
pub mod state;
|
||||
pub mod tools;
|
||||
|
||||
// Export current sdk types for downstream users building with a different sdk version
|
||||
pub use solana_program;
|
||||
|
||||
/// Seed prefix for Governance PDAs
|
||||
pub const PROGRAM_AUTHORITY_SEED: &[u8] = b"governance";
|
|
@ -1,151 +0,0 @@
|
|||
//! Program processor
|
||||
|
||||
mod process_add_signatory;
|
||||
mod process_cancel_proposal;
|
||||
mod process_cast_vote;
|
||||
mod process_create_account_governance;
|
||||
mod process_create_mint_governance;
|
||||
mod process_create_program_governance;
|
||||
mod process_create_proposal;
|
||||
mod process_create_realm;
|
||||
mod process_deposit_governing_tokens;
|
||||
mod process_execute_instruction;
|
||||
mod process_finalize_vote;
|
||||
mod process_insert_instruction;
|
||||
mod process_relinquish_vote;
|
||||
mod process_remove_instruction;
|
||||
mod process_remove_signatory;
|
||||
mod process_set_governance_delegate;
|
||||
mod process_sign_off_proposal;
|
||||
mod process_withdraw_governing_tokens;
|
||||
|
||||
use crate::instruction::GovernanceInstruction;
|
||||
use borsh::BorshDeserialize;
|
||||
|
||||
use process_add_signatory::*;
|
||||
use process_cancel_proposal::*;
|
||||
use process_cast_vote::*;
|
||||
use process_create_account_governance::*;
|
||||
use process_create_mint_governance::*;
|
||||
use process_create_program_governance::*;
|
||||
use process_create_proposal::*;
|
||||
use process_create_realm::*;
|
||||
use process_deposit_governing_tokens::*;
|
||||
use process_execute_instruction::*;
|
||||
use process_finalize_vote::*;
|
||||
use process_insert_instruction::*;
|
||||
use process_relinquish_vote::*;
|
||||
use process_remove_instruction::*;
|
||||
use process_remove_signatory::*;
|
||||
use process_set_governance_delegate::*;
|
||||
use process_sign_off_proposal::*;
|
||||
use process_withdraw_governing_tokens::*;
|
||||
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
/// Processes an instruction
|
||||
pub fn process_instruction(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
input: &[u8],
|
||||
) -> ProgramResult {
|
||||
let instruction = GovernanceInstruction::try_from_slice(input)
|
||||
.map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
|
||||
if let GovernanceInstruction::InsertInstruction {
|
||||
index,
|
||||
hold_up_time,
|
||||
instruction: _,
|
||||
} = instruction
|
||||
{
|
||||
// Do not dump instruction data into logs
|
||||
msg!(
|
||||
"GOVERNANCE-INSTRUCTION: InsertInstruction {{ index: {:?}, hold_up_time: {:?} }}",
|
||||
index,
|
||||
hold_up_time
|
||||
);
|
||||
} else {
|
||||
msg!("GOVERNANCE-INSTRUCTION: {:?}", instruction);
|
||||
}
|
||||
|
||||
match instruction {
|
||||
GovernanceInstruction::CreateRealm { name } => {
|
||||
process_create_realm(program_id, accounts, name)
|
||||
}
|
||||
|
||||
GovernanceInstruction::DepositGoverningTokens {} => {
|
||||
process_deposit_governing_tokens(program_id, accounts)
|
||||
}
|
||||
|
||||
GovernanceInstruction::WithdrawGoverningTokens {} => {
|
||||
process_withdraw_governing_tokens(program_id, accounts)
|
||||
}
|
||||
|
||||
GovernanceInstruction::SetGovernanceDelegate {
|
||||
new_governance_delegate,
|
||||
} => process_set_governance_delegate(program_id, accounts, &new_governance_delegate),
|
||||
|
||||
GovernanceInstruction::CreateProgramGovernance {
|
||||
config,
|
||||
transfer_upgrade_authority,
|
||||
} => process_create_program_governance(
|
||||
program_id,
|
||||
accounts,
|
||||
config,
|
||||
transfer_upgrade_authority,
|
||||
),
|
||||
|
||||
GovernanceInstruction::CreateMintGovernance {
|
||||
config,
|
||||
transfer_mint_authority,
|
||||
} => process_create_mint_governance(program_id, accounts, config, transfer_mint_authority),
|
||||
|
||||
GovernanceInstruction::CreateAccountGovernance { config } => {
|
||||
process_create_account_governance(program_id, accounts, config)
|
||||
}
|
||||
|
||||
GovernanceInstruction::CreateProposal {
|
||||
name,
|
||||
description_link,
|
||||
governing_token_mint,
|
||||
} => process_create_proposal(
|
||||
program_id,
|
||||
accounts,
|
||||
name,
|
||||
description_link,
|
||||
governing_token_mint,
|
||||
),
|
||||
GovernanceInstruction::AddSignatory { signatory } => {
|
||||
process_add_signatory(program_id, accounts, signatory)
|
||||
}
|
||||
GovernanceInstruction::RemoveSignatory { signatory } => {
|
||||
process_remove_signatory(program_id, accounts, signatory)
|
||||
}
|
||||
GovernanceInstruction::SignOffProposal {} => {
|
||||
process_sign_off_proposal(program_id, accounts)
|
||||
}
|
||||
GovernanceInstruction::CastVote { vote } => process_cast_vote(program_id, accounts, vote),
|
||||
|
||||
GovernanceInstruction::FinalizeVote {} => process_finalize_vote(program_id, accounts),
|
||||
|
||||
GovernanceInstruction::RelinquishVote {} => process_relinquish_vote(program_id, accounts),
|
||||
|
||||
GovernanceInstruction::CancelProposal {} => process_cancel_proposal(program_id, accounts),
|
||||
|
||||
GovernanceInstruction::InsertInstruction {
|
||||
index,
|
||||
hold_up_time,
|
||||
instruction,
|
||||
} => process_insert_instruction(program_id, accounts, index, hold_up_time, instruction),
|
||||
|
||||
GovernanceInstruction::RemoveInstruction {} => {
|
||||
process_remove_instruction(program_id, accounts)
|
||||
}
|
||||
GovernanceInstruction::ExecuteInstruction {} => {
|
||||
process_execute_instruction(program_id, accounts)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
state::{
|
||||
enums::GovernanceAccountType,
|
||||
proposal::get_proposal_data,
|
||||
signatory_record::{get_signatory_record_address_seeds, SignatoryRecord},
|
||||
token_owner_record::get_token_owner_record_data_for_proposal_owner,
|
||||
},
|
||||
tools::account::create_and_serialize_account_signed,
|
||||
};
|
||||
|
||||
/// Processes AddSignatory instruction
|
||||
pub fn process_add_signatory(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
signatory: Pubkey,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 1
|
||||
let governance_authority_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let signatory_record_info = next_account_info(account_info_iter)?; // 3
|
||||
|
||||
let payer_info = next_account_info(account_info_iter)?; // 4
|
||||
let system_info = next_account_info(account_info_iter)?; // 5
|
||||
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 6
|
||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||
|
||||
let mut proposal_data = get_proposal_data(program_id, proposal_info)?;
|
||||
proposal_data.assert_can_edit_signatories()?;
|
||||
|
||||
let token_owner_record_data = get_token_owner_record_data_for_proposal_owner(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&proposal_data.token_owner_record,
|
||||
)?;
|
||||
|
||||
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
|
||||
|
||||
let signatory_record_data = SignatoryRecord {
|
||||
account_type: GovernanceAccountType::SignatoryRecord,
|
||||
proposal: *proposal_info.key,
|
||||
signatory,
|
||||
signed_off: false,
|
||||
};
|
||||
|
||||
create_and_serialize_account_signed::<SignatoryRecord>(
|
||||
payer_info,
|
||||
signatory_record_info,
|
||||
&signatory_record_data,
|
||||
&get_signatory_record_address_seeds(proposal_info.key, &signatory),
|
||||
program_id,
|
||||
system_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
proposal_data.signatories_count = proposal_data.signatories_count.checked_add(1).unwrap();
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
clock::Clock,
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::state::{
|
||||
enums::ProposalState, proposal::get_proposal_data,
|
||||
token_owner_record::get_token_owner_record_data_for_proposal_owner,
|
||||
};
|
||||
|
||||
/// Processes CancelProposal instruction
|
||||
pub fn process_cancel_proposal(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 1
|
||||
let governance_authority_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let clock_info = next_account_info(account_info_iter)?; // 3
|
||||
let clock = Clock::from_account_info(clock_info)?;
|
||||
|
||||
let mut proposal_data = get_proposal_data(program_id, proposal_info)?;
|
||||
proposal_data.assert_can_cancel()?;
|
||||
|
||||
let token_owner_record_data = get_token_owner_record_data_for_proposal_owner(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&proposal_data.token_owner_record,
|
||||
)?;
|
||||
|
||||
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
|
||||
|
||||
proposal_data.state = ProposalState::Cancelled;
|
||||
proposal_data.closed_at = Some(clock.slot);
|
||||
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
clock::Clock,
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
instruction::Vote,
|
||||
state::{
|
||||
enums::{GovernanceAccountType, VoteWeight},
|
||||
governance::get_governance_data,
|
||||
proposal::get_proposal_data_for_governance_and_governing_mint,
|
||||
token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint,
|
||||
vote_record::{get_vote_record_address_seeds, VoteRecord},
|
||||
},
|
||||
tools::{account::create_and_serialize_account_signed, spl_token::get_spl_token_mint_supply},
|
||||
};
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
|
||||
/// Processes CastVote instruction
|
||||
pub fn process_cast_vote(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
vote: Vote,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let governance_info = next_account_info(account_info_iter)?; // 0
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 1
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 2
|
||||
let governance_authority_info = next_account_info(account_info_iter)?; // 3
|
||||
|
||||
let vote_record_info = next_account_info(account_info_iter)?; // 4
|
||||
let governing_token_mint_info = next_account_info(account_info_iter)?; // 5
|
||||
|
||||
let payer_info = next_account_info(account_info_iter)?; // 6
|
||||
let system_info = next_account_info(account_info_iter)?; // 7
|
||||
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 8
|
||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||
|
||||
let clock_info = next_account_info(account_info_iter)?; // 9
|
||||
let clock = Clock::from_account_info(clock_info)?;
|
||||
|
||||
if !vote_record_info.data_is_empty() {
|
||||
return Err(GovernanceError::VoteAlreadyExists.into());
|
||||
}
|
||||
|
||||
let governance_data = get_governance_data(program_id, governance_info)?;
|
||||
|
||||
let mut proposal_data = get_proposal_data_for_governance_and_governing_mint(
|
||||
program_id,
|
||||
proposal_info,
|
||||
governance_info.key,
|
||||
governing_token_mint_info.key,
|
||||
)?;
|
||||
proposal_data.assert_can_cast_vote(&governance_data.config, clock.slot)?;
|
||||
|
||||
let mut token_owner_record_data = get_token_owner_record_data_for_realm_and_governing_mint(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&governance_data.config.realm,
|
||||
governing_token_mint_info.key,
|
||||
)?;
|
||||
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
|
||||
|
||||
// Update TokenOwnerRecord vote counts
|
||||
token_owner_record_data.unrelinquished_votes_count = token_owner_record_data
|
||||
.unrelinquished_votes_count
|
||||
.checked_add(1)
|
||||
.unwrap();
|
||||
|
||||
token_owner_record_data.total_votes_count = token_owner_record_data
|
||||
.total_votes_count
|
||||
.checked_add(1)
|
||||
.unwrap();
|
||||
|
||||
token_owner_record_data.serialize(&mut *token_owner_record_info.data.borrow_mut())?;
|
||||
|
||||
let vote_amount = token_owner_record_data.governing_token_deposit_amount;
|
||||
|
||||
// Calculate Proposal voting weights
|
||||
let vote_weight = match vote {
|
||||
Vote::Yes => {
|
||||
proposal_data.yes_votes_count = proposal_data
|
||||
.yes_votes_count
|
||||
.checked_add(vote_amount)
|
||||
.unwrap();
|
||||
VoteWeight::Yes(vote_amount)
|
||||
}
|
||||
Vote::No => {
|
||||
proposal_data.no_votes_count = proposal_data
|
||||
.no_votes_count
|
||||
.checked_add(vote_amount)
|
||||
.unwrap();
|
||||
VoteWeight::No(vote_amount)
|
||||
}
|
||||
};
|
||||
|
||||
let governing_token_supply = get_spl_token_mint_supply(governing_token_mint_info)?;
|
||||
proposal_data.try_tip_vote(governing_token_supply, &governance_data.config, clock.slot);
|
||||
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
// Create and serialize VoteRecord
|
||||
let vote_record_data = VoteRecord {
|
||||
account_type: GovernanceAccountType::VoteRecord,
|
||||
proposal: *proposal_info.key,
|
||||
governing_token_owner: token_owner_record_data.governing_token_owner,
|
||||
vote_weight,
|
||||
is_relinquished: false,
|
||||
};
|
||||
|
||||
create_and_serialize_account_signed::<VoteRecord>(
|
||||
payer_info,
|
||||
vote_record_info,
|
||||
&vote_record_data,
|
||||
&get_vote_record_address_seeds(proposal_info.key, token_owner_record_info.key),
|
||||
program_id,
|
||||
system_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use crate::{
|
||||
state::{
|
||||
enums::GovernanceAccountType,
|
||||
governance::{
|
||||
assert_is_valid_governance_config, get_account_governance_address_seeds, Governance,
|
||||
GovernanceConfig,
|
||||
},
|
||||
},
|
||||
tools::account::create_and_serialize_account_signed,
|
||||
};
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
/// Processes CreateAccountGovernance instruction
|
||||
pub fn process_create_account_governance(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
config: GovernanceConfig,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let realm_info = next_account_info(account_info_iter)?; // 0
|
||||
let account_governance_info = next_account_info(account_info_iter)?; // 0
|
||||
let payer_info = next_account_info(account_info_iter)?; // 1
|
||||
let system_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 3
|
||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||
|
||||
assert_is_valid_governance_config(program_id, &config, realm_info)?;
|
||||
|
||||
let account_governance_data = Governance {
|
||||
account_type: GovernanceAccountType::AccountGovernance,
|
||||
config: config.clone(),
|
||||
proposals_count: 0,
|
||||
};
|
||||
|
||||
create_and_serialize_account_signed::<Governance>(
|
||||
payer_info,
|
||||
account_governance_info,
|
||||
&account_governance_data,
|
||||
&get_account_governance_address_seeds(&config.realm, &config.governed_account),
|
||||
program_id,
|
||||
system_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use crate::{
|
||||
state::{
|
||||
enums::GovernanceAccountType,
|
||||
governance::{
|
||||
assert_is_valid_governance_config, get_mint_governance_address_seeds, Governance,
|
||||
GovernanceConfig,
|
||||
},
|
||||
},
|
||||
tools::{
|
||||
account::create_and_serialize_account_signed,
|
||||
spl_token::{assert_spl_token_mint_authority_is_signer, set_spl_token_mint_authority},
|
||||
},
|
||||
};
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
/// Processes CreateMintGovernance instruction
|
||||
pub fn process_create_mint_governance(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
config: GovernanceConfig,
|
||||
transfer_mint_authority: bool,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let realm_info = next_account_info(account_info_iter)?; // 0
|
||||
let mint_governance_info = next_account_info(account_info_iter)?; // 1
|
||||
|
||||
let governed_mint_info = next_account_info(account_info_iter)?; // 2
|
||||
let governed_mint_authority_info = next_account_info(account_info_iter)?; // 3
|
||||
|
||||
let payer_info = next_account_info(account_info_iter)?; // 4
|
||||
let spl_token_info = next_account_info(account_info_iter)?; // 5
|
||||
|
||||
let system_info = next_account_info(account_info_iter)?; // 6
|
||||
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 7
|
||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||
|
||||
assert_is_valid_governance_config(program_id, &config, realm_info)?;
|
||||
|
||||
let mint_governance_data = Governance {
|
||||
account_type: GovernanceAccountType::MintGovernance,
|
||||
config: config.clone(),
|
||||
proposals_count: 0,
|
||||
};
|
||||
|
||||
create_and_serialize_account_signed::<Governance>(
|
||||
payer_info,
|
||||
mint_governance_info,
|
||||
&mint_governance_data,
|
||||
&get_mint_governance_address_seeds(&config.realm, &config.governed_account),
|
||||
program_id,
|
||||
system_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
if transfer_mint_authority {
|
||||
set_spl_token_mint_authority(
|
||||
governed_mint_info,
|
||||
governed_mint_authority_info,
|
||||
mint_governance_info.key,
|
||||
spl_token_info,
|
||||
)?;
|
||||
} else {
|
||||
assert_spl_token_mint_authority_is_signer(
|
||||
governed_mint_info,
|
||||
governed_mint_authority_info,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use crate::{
|
||||
state::governance::Governance,
|
||||
state::{
|
||||
enums::GovernanceAccountType,
|
||||
governance::{
|
||||
assert_is_valid_governance_config, get_program_governance_address_seeds,
|
||||
GovernanceConfig,
|
||||
},
|
||||
},
|
||||
tools::{
|
||||
account::create_and_serialize_account_signed,
|
||||
bpf_loader_upgradeable::{
|
||||
assert_program_upgrade_authority_is_signer, set_program_upgrade_authority,
|
||||
},
|
||||
},
|
||||
};
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
/// Processes CreateProgramGovernance instruction
|
||||
pub fn process_create_program_governance(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
config: GovernanceConfig,
|
||||
transfer_upgrade_authority: bool,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let realm_info = next_account_info(account_info_iter)?; // 0
|
||||
let program_governance_info = next_account_info(account_info_iter)?; // 0
|
||||
|
||||
let governed_program_data_info = next_account_info(account_info_iter)?; // 1
|
||||
let governed_program_upgrade_authority_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let payer_info = next_account_info(account_info_iter)?; // 3
|
||||
let bpf_upgrade_loader_info = next_account_info(account_info_iter)?; // 4
|
||||
|
||||
let system_info = next_account_info(account_info_iter)?; // 5
|
||||
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 6
|
||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||
|
||||
assert_is_valid_governance_config(program_id, &config, realm_info)?;
|
||||
|
||||
let program_governance_data = Governance {
|
||||
account_type: GovernanceAccountType::ProgramGovernance,
|
||||
config: config.clone(),
|
||||
proposals_count: 0,
|
||||
};
|
||||
|
||||
create_and_serialize_account_signed::<Governance>(
|
||||
payer_info,
|
||||
program_governance_info,
|
||||
&program_governance_data,
|
||||
&get_program_governance_address_seeds(&config.realm, &config.governed_account),
|
||||
program_id,
|
||||
system_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
if transfer_upgrade_authority {
|
||||
set_program_upgrade_authority(
|
||||
&config.governed_account,
|
||||
governed_program_data_info,
|
||||
governed_program_upgrade_authority_info,
|
||||
program_governance_info,
|
||||
bpf_upgrade_loader_info,
|
||||
)?;
|
||||
} else {
|
||||
assert_program_upgrade_authority_is_signer(
|
||||
&config.governed_account,
|
||||
governed_program_data_info,
|
||||
governed_program_upgrade_authority_info,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
clock::Clock,
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
state::{
|
||||
enums::{GovernanceAccountType, ProposalState},
|
||||
governance::get_governance_data,
|
||||
proposal::{get_proposal_address_seeds, Proposal},
|
||||
token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint,
|
||||
},
|
||||
tools::account::create_and_serialize_account_signed,
|
||||
};
|
||||
|
||||
/// Processes CreateProposal instruction
|
||||
pub fn process_create_proposal(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
name: String,
|
||||
description_link: String,
|
||||
governing_token_mint: Pubkey,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||
let governance_info = next_account_info(account_info_iter)?; // 1
|
||||
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 2
|
||||
let governance_authority_info = next_account_info(account_info_iter)?; // 3
|
||||
|
||||
let payer_info = next_account_info(account_info_iter)?; // 4
|
||||
let system_info = next_account_info(account_info_iter)?; // 5
|
||||
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 6
|
||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||
|
||||
let clock_info = next_account_info(account_info_iter)?; // 7
|
||||
let clock = Clock::from_account_info(clock_info)?;
|
||||
|
||||
if !proposal_info.data_is_empty() {
|
||||
return Err(GovernanceError::ProposalAlreadyExists.into());
|
||||
}
|
||||
|
||||
let mut governance_data = get_governance_data(program_id, governance_info)?;
|
||||
|
||||
let token_owner_record_data = get_token_owner_record_data_for_realm_and_governing_mint(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&governance_data.config.realm,
|
||||
&governing_token_mint,
|
||||
)?;
|
||||
|
||||
// proposal_owner must be either governing token owner or governance_delegate and must sign this transaction
|
||||
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
|
||||
|
||||
if token_owner_record_data.governing_token_deposit_amount
|
||||
< governance_data.config.min_tokens_to_create_proposal as u64
|
||||
{
|
||||
return Err(GovernanceError::NotEnoughTokensToCreateProposal.into());
|
||||
}
|
||||
|
||||
let proposal_data = Proposal {
|
||||
account_type: GovernanceAccountType::Proposal,
|
||||
governance: *governance_info.key,
|
||||
governing_token_mint,
|
||||
state: ProposalState::Draft,
|
||||
token_owner_record: *token_owner_record_info.key,
|
||||
|
||||
signatories_count: 0,
|
||||
signatories_signed_off_count: 0,
|
||||
|
||||
name,
|
||||
description_link,
|
||||
|
||||
draft_at: clock.slot,
|
||||
signing_off_at: None,
|
||||
voting_at: None,
|
||||
voting_completed_at: None,
|
||||
executing_at: None,
|
||||
closed_at: None,
|
||||
|
||||
instructions_executed_count: 0,
|
||||
instructions_count: 0,
|
||||
instructions_next_index: 0,
|
||||
|
||||
yes_votes_count: 0,
|
||||
no_votes_count: 0,
|
||||
};
|
||||
|
||||
create_and_serialize_account_signed::<Proposal>(
|
||||
payer_info,
|
||||
proposal_info,
|
||||
&proposal_data,
|
||||
&get_proposal_address_seeds(
|
||||
governance_info.key,
|
||||
&governing_token_mint,
|
||||
&governance_data.proposals_count.to_le_bytes(),
|
||||
),
|
||||
program_id,
|
||||
system_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
governance_data.proposals_count = governance_data.proposals_count.checked_add(1).unwrap();
|
||||
governance_data.serialize(&mut *governance_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
state::{
|
||||
enums::GovernanceAccountType,
|
||||
realm::{get_governing_token_holding_address_seeds, get_realm_address_seeds, Realm},
|
||||
},
|
||||
tools::{
|
||||
account::create_and_serialize_account_signed, spl_token::create_spl_token_account_signed,
|
||||
},
|
||||
};
|
||||
|
||||
/// Processes CreateRealm instruction
|
||||
pub fn process_create_realm(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
name: String,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let realm_info = next_account_info(account_info_iter)?; // 0
|
||||
let governance_token_mint_info = next_account_info(account_info_iter)?; // 1
|
||||
let governance_token_holding_info = next_account_info(account_info_iter)?; // 2
|
||||
let payer_info = next_account_info(account_info_iter)?; // 3
|
||||
let system_info = next_account_info(account_info_iter)?; // 4
|
||||
let spl_token_info = next_account_info(account_info_iter)?; // 5
|
||||
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 6
|
||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||
|
||||
if !realm_info.data_is_empty() {
|
||||
return Err(GovernanceError::RealmAlreadyExists.into());
|
||||
}
|
||||
|
||||
create_spl_token_account_signed(
|
||||
payer_info,
|
||||
governance_token_holding_info,
|
||||
&get_governing_token_holding_address_seeds(realm_info.key, governance_token_mint_info.key),
|
||||
governance_token_mint_info,
|
||||
realm_info,
|
||||
program_id,
|
||||
system_info,
|
||||
spl_token_info,
|
||||
rent_sysvar_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
let council_token_mint_address = if let Ok(council_token_mint_info) =
|
||||
next_account_info(account_info_iter)
|
||||
// 7
|
||||
{
|
||||
let council_token_holding_info = next_account_info(account_info_iter)?; //8
|
||||
|
||||
create_spl_token_account_signed(
|
||||
payer_info,
|
||||
council_token_holding_info,
|
||||
&get_governing_token_holding_address_seeds(realm_info.key, council_token_mint_info.key),
|
||||
council_token_mint_info,
|
||||
realm_info,
|
||||
program_id,
|
||||
system_info,
|
||||
spl_token_info,
|
||||
rent_sysvar_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
Some(*council_token_mint_info.key)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let realm_data = Realm {
|
||||
account_type: GovernanceAccountType::Realm,
|
||||
community_mint: *governance_token_mint_info.key,
|
||||
council_mint: council_token_mint_address,
|
||||
name: name.clone(),
|
||||
};
|
||||
|
||||
create_and_serialize_account_signed::<Realm>(
|
||||
payer_info,
|
||||
realm_info,
|
||||
&realm_data,
|
||||
&get_realm_address_seeds(&name),
|
||||
program_id,
|
||||
system_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
state::{
|
||||
enums::GovernanceAccountType,
|
||||
realm::get_realm_data,
|
||||
token_owner_record::{
|
||||
get_token_owner_record_address_seeds, get_token_owner_record_data_for_seeds,
|
||||
TokenOwnerRecord,
|
||||
},
|
||||
},
|
||||
tools::{
|
||||
account::create_and_serialize_account_signed,
|
||||
spl_token::{
|
||||
get_spl_token_amount, get_spl_token_mint, get_spl_token_owner, transfer_spl_tokens,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// Processes DepositGoverningTokens instruction
|
||||
pub fn process_deposit_governing_tokens(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let realm_info = next_account_info(account_info_iter)?; // 0
|
||||
let governing_token_holding_info = next_account_info(account_info_iter)?; // 1
|
||||
let governing_token_source_info = next_account_info(account_info_iter)?; // 2
|
||||
let governing_token_owner_info = next_account_info(account_info_iter)?; // 3
|
||||
let governing_token_transfer_authority_info = next_account_info(account_info_iter)?; // 4
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 5
|
||||
let payer_info = next_account_info(account_info_iter)?; // 6
|
||||
let system_info = next_account_info(account_info_iter)?; // 7
|
||||
let spl_token_info = next_account_info(account_info_iter)?; // 8
|
||||
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 9
|
||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||
|
||||
let realm_data = get_realm_data(program_id, realm_info)?;
|
||||
let governing_token_mint = get_spl_token_mint(governing_token_holding_info)?;
|
||||
|
||||
realm_data.assert_is_valid_governing_token_mint(&governing_token_mint)?;
|
||||
|
||||
let amount = get_spl_token_amount(governing_token_source_info)?;
|
||||
|
||||
transfer_spl_tokens(
|
||||
governing_token_source_info,
|
||||
governing_token_holding_info,
|
||||
governing_token_transfer_authority_info,
|
||||
amount,
|
||||
spl_token_info,
|
||||
)?;
|
||||
|
||||
let token_owner_record_address_seeds = get_token_owner_record_address_seeds(
|
||||
realm_info.key,
|
||||
&governing_token_mint,
|
||||
governing_token_owner_info.key,
|
||||
);
|
||||
|
||||
if token_owner_record_info.data_is_empty() {
|
||||
// Deposited tokens can only be withdrawn by the owner so let's make sure the owner signed the transaction
|
||||
let governing_token_owner = get_spl_token_owner(governing_token_source_info)?;
|
||||
|
||||
if !(governing_token_owner == *governing_token_owner_info.key
|
||||
&& governing_token_owner_info.is_signer)
|
||||
{
|
||||
return Err(GovernanceError::GoverningTokenOwnerMustSign.into());
|
||||
}
|
||||
|
||||
let token_owner_record_data = TokenOwnerRecord {
|
||||
account_type: GovernanceAccountType::TokenOwnerRecord,
|
||||
realm: *realm_info.key,
|
||||
governing_token_owner: *governing_token_owner_info.key,
|
||||
governing_token_deposit_amount: amount,
|
||||
governing_token_mint,
|
||||
governance_delegate: None,
|
||||
unrelinquished_votes_count: 0,
|
||||
total_votes_count: 0,
|
||||
};
|
||||
|
||||
create_and_serialize_account_signed(
|
||||
payer_info,
|
||||
token_owner_record_info,
|
||||
&token_owner_record_data,
|
||||
&token_owner_record_address_seeds,
|
||||
program_id,
|
||||
system_info,
|
||||
rent,
|
||||
)?;
|
||||
} else {
|
||||
let mut token_owner_record_data = get_token_owner_record_data_for_seeds(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&token_owner_record_address_seeds,
|
||||
)?;
|
||||
|
||||
token_owner_record_data.governing_token_deposit_amount = token_owner_record_data
|
||||
.governing_token_deposit_amount
|
||||
.checked_add(amount)
|
||||
.unwrap();
|
||||
|
||||
token_owner_record_data.serialize(&mut *token_owner_record_info.data.borrow_mut())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
clock::Clock,
|
||||
entrypoint::ProgramResult,
|
||||
instruction::Instruction,
|
||||
program::invoke_signed,
|
||||
pubkey::Pubkey,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::state::{
|
||||
enums::ProposalState, governance::get_governance_data,
|
||||
proposal::get_proposal_data_for_governance,
|
||||
proposal_instruction::get_proposal_instruction_data_for_proposal,
|
||||
};
|
||||
|
||||
/// Processes ExecuteInstruction instruction
|
||||
pub fn process_execute_instruction(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let governance_info = next_account_info(account_info_iter)?; // 0
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 1
|
||||
let proposal_instruction_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let clock_info = next_account_info(account_info_iter)?; // 3
|
||||
let clock = Clock::from_account_info(clock_info)?;
|
||||
|
||||
let governance_data = get_governance_data(program_id, governance_info)?;
|
||||
|
||||
let mut proposal_data =
|
||||
get_proposal_data_for_governance(program_id, proposal_info, governance_info.key)?;
|
||||
|
||||
let mut proposal_instruction_data = get_proposal_instruction_data_for_proposal(
|
||||
program_id,
|
||||
proposal_instruction_info,
|
||||
proposal_info.key,
|
||||
)?;
|
||||
|
||||
proposal_data.assert_can_execute_instruction(&proposal_instruction_data, clock.slot)?;
|
||||
|
||||
// Execute instruction with Governance PDA as signer
|
||||
let instruction = Instruction::from(&proposal_instruction_data.instruction);
|
||||
|
||||
let instruction_account_infos = account_info_iter.as_slice();
|
||||
|
||||
let mut governance_seeds = governance_data.get_governance_address_seeds()?.to_vec();
|
||||
let (_, bump_seed) = Pubkey::find_program_address(&governance_seeds, program_id);
|
||||
let bump = &[bump_seed];
|
||||
governance_seeds.push(bump);
|
||||
|
||||
invoke_signed(
|
||||
&instruction,
|
||||
instruction_account_infos,
|
||||
&[&governance_seeds[..]],
|
||||
)?;
|
||||
|
||||
// Update proposal and instruction accounts
|
||||
if proposal_data.state == ProposalState::Succeeded {
|
||||
proposal_data.executing_at = Some(clock.slot);
|
||||
proposal_data.state = ProposalState::Executing;
|
||||
}
|
||||
|
||||
proposal_data.instructions_executed_count = proposal_data
|
||||
.instructions_executed_count
|
||||
.checked_add(1)
|
||||
.unwrap();
|
||||
|
||||
if proposal_data.state == ProposalState::Executing
|
||||
&& proposal_data.instructions_executed_count == proposal_data.instructions_count
|
||||
{
|
||||
proposal_data.closed_at = Some(clock.slot);
|
||||
proposal_data.state = ProposalState::Completed;
|
||||
}
|
||||
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
proposal_instruction_data.executed_at = Some(clock.slot);
|
||||
proposal_instruction_data.serialize(&mut *proposal_instruction_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
clock::Clock,
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
state::{
|
||||
governance::get_governance_data,
|
||||
proposal::get_proposal_data_for_governance_and_governing_mint,
|
||||
},
|
||||
tools::spl_token::get_spl_token_mint_supply,
|
||||
};
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
|
||||
/// Processes FinalizeVote instruction
|
||||
pub fn process_finalize_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let governance_info = next_account_info(account_info_iter)?; // 0
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 1
|
||||
|
||||
let governing_token_mint_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let clock_info = next_account_info(account_info_iter)?; // 3
|
||||
let clock = Clock::from_account_info(clock_info)?;
|
||||
|
||||
let governance_data = get_governance_data(program_id, governance_info)?;
|
||||
|
||||
let mut proposal_data = get_proposal_data_for_governance_and_governing_mint(
|
||||
program_id,
|
||||
proposal_info,
|
||||
governance_info.key,
|
||||
governing_token_mint_info.key,
|
||||
)?;
|
||||
|
||||
let governing_token_supply = get_spl_token_mint_supply(governing_token_mint_info)?;
|
||||
|
||||
proposal_data.finalize_vote(governing_token_supply, &governance_data.config, clock.slot)?;
|
||||
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
state::{
|
||||
enums::GovernanceAccountType,
|
||||
governance::get_governance_data,
|
||||
proposal::get_proposal_data_for_governance,
|
||||
proposal_instruction::{
|
||||
get_proposal_instruction_address_seeds, InstructionData, ProposalInstruction,
|
||||
},
|
||||
token_owner_record::get_token_owner_record_data_for_proposal_owner,
|
||||
},
|
||||
tools::account::create_and_serialize_account_signed,
|
||||
};
|
||||
|
||||
/// Processes InsertInstruction instruction
|
||||
pub fn process_insert_instruction(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
index: u16,
|
||||
hold_up_time: u64,
|
||||
instruction: InstructionData,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let governance_info = next_account_info(account_info_iter)?; // 0
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 1
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 2
|
||||
let governance_authority_info = next_account_info(account_info_iter)?; // 3
|
||||
|
||||
let proposal_instruction_info = next_account_info(account_info_iter)?; // 4
|
||||
|
||||
let payer_info = next_account_info(account_info_iter)?; // 5
|
||||
let system_info = next_account_info(account_info_iter)?; // 6
|
||||
|
||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 7
|
||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||
|
||||
if !proposal_instruction_info.data_is_empty() {
|
||||
return Err(GovernanceError::InstructionAlreadyExists.into());
|
||||
}
|
||||
|
||||
let governance_data = get_governance_data(program_id, governance_info)?;
|
||||
|
||||
if hold_up_time < governance_data.config.min_instruction_hold_up_time {
|
||||
return Err(GovernanceError::InstructionHoldUpTimeBelowRequiredMin.into());
|
||||
}
|
||||
|
||||
let mut proposal_data =
|
||||
get_proposal_data_for_governance(program_id, proposal_info, governance_info.key)?;
|
||||
proposal_data.assert_can_edit_instructions()?;
|
||||
|
||||
let token_owner_record_data = get_token_owner_record_data_for_proposal_owner(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&proposal_data.token_owner_record,
|
||||
)?;
|
||||
|
||||
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
|
||||
|
||||
match index.cmp(&proposal_data.instructions_next_index) {
|
||||
Ordering::Greater => return Err(GovernanceError::InvalidInstructionIndex.into()),
|
||||
// If the index is the same as instructions_next_index then we are adding a new instruction
|
||||
// If the index is below instructions_next_index then we are inserting into an existing empty slot
|
||||
Ordering::Equal => {
|
||||
proposal_data.instructions_next_index = proposal_data
|
||||
.instructions_next_index
|
||||
.checked_add(1)
|
||||
.unwrap();
|
||||
}
|
||||
Ordering::Less => {}
|
||||
}
|
||||
|
||||
proposal_data.instructions_count = proposal_data.instructions_count.checked_add(1).unwrap();
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
let proposal_instruction_data = ProposalInstruction {
|
||||
account_type: GovernanceAccountType::ProposalInstruction,
|
||||
hold_up_time,
|
||||
instruction,
|
||||
executed_at: None,
|
||||
proposal: *proposal_info.key,
|
||||
};
|
||||
|
||||
create_and_serialize_account_signed::<ProposalInstruction>(
|
||||
payer_info,
|
||||
proposal_instruction_info,
|
||||
&proposal_instruction_data,
|
||||
&get_proposal_instruction_address_seeds(proposal_info.key, &index.to_le_bytes()),
|
||||
program_id,
|
||||
system_info,
|
||||
rent,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
state::{
|
||||
enums::{ProposalState, VoteWeight},
|
||||
governance::get_governance_data,
|
||||
proposal::get_proposal_data_for_governance_and_governing_mint,
|
||||
token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint,
|
||||
vote_record::get_vote_record_data_for_proposal_and_token_owner,
|
||||
},
|
||||
tools::account::dispose_account,
|
||||
};
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
|
||||
/// Processes RelinquishVote instruction
|
||||
pub fn process_relinquish_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let governance_info = next_account_info(account_info_iter)?; // 0
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 1
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let vote_record_info = next_account_info(account_info_iter)?; // 3
|
||||
let governing_token_mint_info = next_account_info(account_info_iter)?; // 4
|
||||
|
||||
let governance_data = get_governance_data(program_id, governance_info)?;
|
||||
|
||||
let mut proposal_data = get_proposal_data_for_governance_and_governing_mint(
|
||||
program_id,
|
||||
proposal_info,
|
||||
governance_info.key,
|
||||
governing_token_mint_info.key,
|
||||
)?;
|
||||
|
||||
let mut token_owner_record_data = get_token_owner_record_data_for_realm_and_governing_mint(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&governance_data.config.realm,
|
||||
governing_token_mint_info.key,
|
||||
)?;
|
||||
|
||||
let mut vote_record_data = get_vote_record_data_for_proposal_and_token_owner(
|
||||
program_id,
|
||||
vote_record_info,
|
||||
proposal_info.key,
|
||||
&token_owner_record_data.governing_token_owner,
|
||||
)?;
|
||||
vote_record_data.assert_can_relinquish_vote()?;
|
||||
|
||||
// If the Proposal is still being voted on then the token owner vote won't count towards the outcome
|
||||
if proposal_data.state == ProposalState::Voting {
|
||||
let governance_authority_info = next_account_info(account_info_iter)?; // 5
|
||||
let beneficiary_info = next_account_info(account_info_iter)?; // 6
|
||||
|
||||
// Note: It's only required to sign by governing_authority if relinquishing the vote results in vote change
|
||||
// If the Proposal is already decided then anybody can prune active votes for token owner
|
||||
token_owner_record_data
|
||||
.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
|
||||
|
||||
match vote_record_data.vote_weight {
|
||||
VoteWeight::Yes(vote_amount) => {
|
||||
proposal_data.yes_votes_count = proposal_data
|
||||
.yes_votes_count
|
||||
.checked_sub(vote_amount)
|
||||
.unwrap();
|
||||
}
|
||||
VoteWeight::No(vote_amount) => {
|
||||
proposal_data.no_votes_count = proposal_data
|
||||
.no_votes_count
|
||||
.checked_sub(vote_amount)
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
dispose_account(vote_record_info, beneficiary_info);
|
||||
|
||||
token_owner_record_data.total_votes_count = token_owner_record_data
|
||||
.total_votes_count
|
||||
.checked_sub(1)
|
||||
.unwrap();
|
||||
} else {
|
||||
vote_record_data.is_relinquished = true;
|
||||
vote_record_data.serialize(&mut *vote_record_info.data.borrow_mut())?;
|
||||
}
|
||||
|
||||
// If the Proposal has been already voted on then we only have to decrease unrelinquished_votes_count
|
||||
token_owner_record_data.unrelinquished_votes_count = token_owner_record_data
|
||||
.unrelinquished_votes_count
|
||||
.checked_sub(1)
|
||||
.unwrap();
|
||||
|
||||
token_owner_record_data.serialize(&mut *token_owner_record_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
state::{
|
||||
proposal::get_proposal_data,
|
||||
proposal_instruction::assert_proposal_instruction_for_proposal,
|
||||
token_owner_record::get_token_owner_record_data_for_proposal_owner,
|
||||
},
|
||||
tools::account::dispose_account,
|
||||
};
|
||||
|
||||
/// Processes RemoveInstruction instruction
|
||||
pub fn process_remove_instruction(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 1
|
||||
let governance_authority_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let proposal_instruction_info = next_account_info(account_info_iter)?; // 3
|
||||
|
||||
let beneficiary_info = next_account_info(account_info_iter)?; // 4
|
||||
|
||||
let mut proposal_data = get_proposal_data(program_id, proposal_info)?;
|
||||
proposal_data.assert_can_edit_instructions()?;
|
||||
|
||||
let token_owner_record_data = get_token_owner_record_data_for_proposal_owner(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&proposal_data.token_owner_record,
|
||||
)?;
|
||||
|
||||
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
|
||||
|
||||
assert_proposal_instruction_for_proposal(
|
||||
program_id,
|
||||
proposal_instruction_info,
|
||||
proposal_info.key,
|
||||
)?;
|
||||
|
||||
dispose_account(proposal_instruction_info, beneficiary_info);
|
||||
|
||||
proposal_data.instructions_count = proposal_data.instructions_count.checked_sub(1).unwrap();
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
state::{
|
||||
proposal::get_proposal_data, signatory_record::get_signatory_record_data_for_seeds,
|
||||
token_owner_record::get_token_owner_record_data_for_proposal_owner,
|
||||
},
|
||||
tools::account::dispose_account,
|
||||
};
|
||||
|
||||
/// Processes RemoveSignatory instruction
|
||||
pub fn process_remove_signatory(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
signatory: Pubkey,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 1
|
||||
let governance_authority_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let signatory_record_info = next_account_info(account_info_iter)?; // 3
|
||||
let beneficiary_info = next_account_info(account_info_iter)?; // 4
|
||||
|
||||
let mut proposal_data = get_proposal_data(program_id, proposal_info)?;
|
||||
proposal_data.assert_can_edit_signatories()?;
|
||||
|
||||
let token_owner_record_data = get_token_owner_record_data_for_proposal_owner(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&proposal_data.token_owner_record,
|
||||
)?;
|
||||
|
||||
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
|
||||
|
||||
let signatory_record_data = get_signatory_record_data_for_seeds(
|
||||
program_id,
|
||||
signatory_record_info,
|
||||
proposal_info.key,
|
||||
&signatory,
|
||||
)?;
|
||||
signatory_record_data.assert_can_remove_signatory()?;
|
||||
|
||||
proposal_data.signatories_count = proposal_data.signatories_count.checked_sub(1).unwrap();
|
||||
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
dispose_account(signatory_record_info, beneficiary_info);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::state::token_owner_record::get_token_owner_record_data;
|
||||
|
||||
/// Processes SetGovernanceDelegate instruction
|
||||
pub fn process_set_governance_delegate(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
new_governance_delegate: &Option<Pubkey>,
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let governance_authority_info = next_account_info(account_info_iter)?; // 0
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 1
|
||||
|
||||
let mut token_owner_record_data =
|
||||
get_token_owner_record_data(program_id, token_owner_record_info)?;
|
||||
|
||||
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
|
||||
|
||||
token_owner_record_data.governance_delegate = *new_governance_delegate;
|
||||
token_owner_record_data.serialize(&mut *token_owner_record_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
clock::Clock,
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
|
||||
use crate::state::{
|
||||
enums::ProposalState, proposal::get_proposal_data,
|
||||
signatory_record::get_signatory_record_data_for_seeds,
|
||||
};
|
||||
|
||||
/// Processes SignOffProposal instruction
|
||||
pub fn process_sign_off_proposal(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||
|
||||
let signatory_record_info = next_account_info(account_info_iter)?; // 1
|
||||
let signatory_info = next_account_info(account_info_iter)?; // 2
|
||||
|
||||
let clock_info = next_account_info(account_info_iter)?; // 3
|
||||
let clock = Clock::from_account_info(clock_info)?;
|
||||
|
||||
let mut proposal_data = get_proposal_data(program_id, proposal_info)?;
|
||||
proposal_data.assert_can_sign_off()?;
|
||||
|
||||
let mut signatory_record_data = get_signatory_record_data_for_seeds(
|
||||
program_id,
|
||||
signatory_record_info,
|
||||
proposal_info.key,
|
||||
signatory_info.key,
|
||||
)?;
|
||||
signatory_record_data.assert_can_sign_off(signatory_info)?;
|
||||
|
||||
signatory_record_data.signed_off = true;
|
||||
signatory_record_data.serialize(&mut *signatory_record_info.data.borrow_mut())?;
|
||||
|
||||
if proposal_data.signatories_signed_off_count == 0 {
|
||||
proposal_data.signing_off_at = Some(clock.slot);
|
||||
proposal_data.state = ProposalState::SigningOff;
|
||||
}
|
||||
|
||||
proposal_data.signatories_signed_off_count = proposal_data
|
||||
.signatories_signed_off_count
|
||||
.checked_add(1)
|
||||
.unwrap();
|
||||
|
||||
// If all Signatories signed off we can start voting
|
||||
if proposal_data.signatories_signed_off_count == proposal_data.signatories_count {
|
||||
proposal_data.voting_at = Some(clock.slot);
|
||||
proposal_data.state = ProposalState::Voting;
|
||||
}
|
||||
|
||||
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
//! Program state processor
|
||||
|
||||
use borsh::BorshSerialize;
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
state::{
|
||||
realm::{get_realm_address_seeds, get_realm_data},
|
||||
token_owner_record::{
|
||||
get_token_owner_record_address_seeds, get_token_owner_record_data_for_seeds,
|
||||
},
|
||||
},
|
||||
tools::spl_token::{get_spl_token_mint, transfer_spl_tokens_signed},
|
||||
};
|
||||
|
||||
/// Processes WithdrawGoverningTokens instruction
|
||||
pub fn process_withdraw_governing_tokens(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let realm_info = next_account_info(account_info_iter)?; // 0
|
||||
let governing_token_holding_info = next_account_info(account_info_iter)?; // 1
|
||||
let governing_token_destination_info = next_account_info(account_info_iter)?; // 2
|
||||
let governing_token_owner_info = next_account_info(account_info_iter)?; // 3
|
||||
let token_owner_record_info = next_account_info(account_info_iter)?; // 4
|
||||
let spl_token_info = next_account_info(account_info_iter)?; // 5
|
||||
|
||||
if !governing_token_owner_info.is_signer {
|
||||
return Err(GovernanceError::GoverningTokenOwnerMustSign.into());
|
||||
}
|
||||
|
||||
let realm_data = get_realm_data(program_id, realm_info)?;
|
||||
let governing_token_mint = get_spl_token_mint(governing_token_holding_info)?;
|
||||
|
||||
let token_owner_record_address_seeds = get_token_owner_record_address_seeds(
|
||||
realm_info.key,
|
||||
&governing_token_mint,
|
||||
governing_token_owner_info.key,
|
||||
);
|
||||
|
||||
let mut token_owner_record_data = get_token_owner_record_data_for_seeds(
|
||||
program_id,
|
||||
token_owner_record_info,
|
||||
&token_owner_record_address_seeds,
|
||||
)?;
|
||||
|
||||
if token_owner_record_data.unrelinquished_votes_count > 0 {
|
||||
return Err(GovernanceError::AllVotesMustBeRelinquishedToWithdrawGoverningTokens.into());
|
||||
}
|
||||
|
||||
transfer_spl_tokens_signed(
|
||||
governing_token_holding_info,
|
||||
governing_token_destination_info,
|
||||
realm_info,
|
||||
&get_realm_address_seeds(&realm_data.name),
|
||||
program_id,
|
||||
token_owner_record_data.governing_token_deposit_amount,
|
||||
spl_token_info,
|
||||
)?;
|
||||
|
||||
token_owner_record_data.governing_token_deposit_amount = 0;
|
||||
token_owner_record_data.serialize(&mut *token_owner_record_info.data.borrow_mut())?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
//! State enumerations
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||
|
||||
/// Defines all Governance accounts types
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub enum GovernanceAccountType {
|
||||
/// Default uninitialized account state
|
||||
Uninitialized,
|
||||
|
||||
/// Top level aggregation for governances with Community Token (and optional Council Token)
|
||||
Realm,
|
||||
|
||||
/// Token Owner Record for given governing token owner within a Realm
|
||||
TokenOwnerRecord,
|
||||
|
||||
/// Generic Account Governance account
|
||||
AccountGovernance,
|
||||
|
||||
/// Program Governance account
|
||||
ProgramGovernance,
|
||||
|
||||
/// Proposal account for Governance account. A single Governance account can have multiple Proposal accounts
|
||||
Proposal,
|
||||
|
||||
/// Proposal Signatory account
|
||||
SignatoryRecord,
|
||||
|
||||
/// Vote record account for a given Proposal. Proposal can have 0..n voting records
|
||||
VoteRecord,
|
||||
|
||||
/// ProposalInstruction account which holds an instruction to execute for Proposal
|
||||
ProposalInstruction,
|
||||
|
||||
/// Mint Governance account
|
||||
MintGovernance,
|
||||
}
|
||||
|
||||
impl Default for GovernanceAccountType {
|
||||
fn default() -> Self {
|
||||
GovernanceAccountType::Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
/// Vote with number of votes
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub enum VoteWeight {
|
||||
/// Yes vote
|
||||
Yes(u64),
|
||||
|
||||
/// No vote
|
||||
No(u64),
|
||||
}
|
||||
|
||||
/// What state a Proposal is in
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub enum ProposalState {
|
||||
/// Draft - Proposal enters Draft state when it's created
|
||||
Draft,
|
||||
|
||||
/// SigningOff - The Proposal is being signed off by Signatories
|
||||
/// Proposal enters the state when first Signatory Sings and leaves it when last Signatory signs
|
||||
SigningOff,
|
||||
|
||||
/// Taking votes
|
||||
Voting,
|
||||
|
||||
/// Voting ended with success
|
||||
Succeeded,
|
||||
|
||||
/// Voting completed and now instructions are being execute. Proposal enter this state when first instruction is executed and leaves when the last instruction is executed
|
||||
Executing,
|
||||
|
||||
/// Completed
|
||||
Completed,
|
||||
|
||||
/// Cancelled
|
||||
Cancelled,
|
||||
|
||||
/// Defeated
|
||||
Defeated,
|
||||
}
|
||||
|
||||
impl Default for ProposalState {
|
||||
fn default() -> Self {
|
||||
ProposalState::Draft
|
||||
}
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
//! Governance Account
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError, state::enums::GovernanceAccountType, tools::account::get_account_data,
|
||||
tools::account::AccountMaxSize,
|
||||
};
|
||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::state::realm::assert_is_valid_realm;
|
||||
|
||||
/// Governance config
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub struct GovernanceConfig {
|
||||
/// Governance Realm
|
||||
pub realm: Pubkey,
|
||||
|
||||
/// Account governed by this Governance. It can be for example Program account, Mint account or Token Account
|
||||
pub governed_account: Pubkey,
|
||||
|
||||
/// Voting threshold of Yes votes in % required to tip the vote
|
||||
/// It's the percentage of tokens out of the entire pool of governance tokens eligible to vote
|
||||
// Note: If the threshold is below or equal to 50% then an even split of votes ex: 50:50 or 40:40 is always resolved as Defeated
|
||||
// In other words +1 vote tie breaker is required to have successful vote
|
||||
pub yes_vote_threshold_percentage: u8,
|
||||
|
||||
/// Minimum number of tokens a governance token owner must possess to be able to create a proposal
|
||||
pub min_tokens_to_create_proposal: u16,
|
||||
|
||||
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
|
||||
pub min_instruction_hold_up_time: u64,
|
||||
|
||||
/// Time limit in slots for proposal to be open for voting
|
||||
pub max_voting_time: u64,
|
||||
}
|
||||
|
||||
/// Governance Account
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub struct Governance {
|
||||
/// Account type. It can be Uninitialized, AccountGovernance or ProgramGovernance
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// Governance config
|
||||
pub config: GovernanceConfig,
|
||||
|
||||
/// Running count of proposals
|
||||
pub proposals_count: u32,
|
||||
}
|
||||
|
||||
impl AccountMaxSize for Governance {}
|
||||
|
||||
impl IsInitialized for Governance {
|
||||
fn is_initialized(&self) -> bool {
|
||||
self.account_type == GovernanceAccountType::AccountGovernance
|
||||
|| self.account_type == GovernanceAccountType::ProgramGovernance
|
||||
|| self.account_type == GovernanceAccountType::MintGovernance
|
||||
}
|
||||
}
|
||||
|
||||
impl Governance {
|
||||
/// Returns Governance PDA seeds
|
||||
pub fn get_governance_address_seeds(&self) -> Result<[&[u8]; 3], ProgramError> {
|
||||
let seeds = match self.account_type {
|
||||
GovernanceAccountType::AccountGovernance => get_account_governance_address_seeds(
|
||||
&self.config.realm,
|
||||
&self.config.governed_account,
|
||||
),
|
||||
GovernanceAccountType::ProgramGovernance => get_program_governance_address_seeds(
|
||||
&self.config.realm,
|
||||
&self.config.governed_account,
|
||||
),
|
||||
GovernanceAccountType::MintGovernance => {
|
||||
get_mint_governance_address_seeds(&self.config.realm, &self.config.governed_account)
|
||||
}
|
||||
_ => return Err(GovernanceError::InvalidAccountType.into()),
|
||||
};
|
||||
|
||||
Ok(seeds)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes account and checks owner program
|
||||
pub fn get_governance_data(
|
||||
program_id: &Pubkey,
|
||||
governance_info: &AccountInfo,
|
||||
) -> Result<Governance, ProgramError> {
|
||||
get_account_data::<Governance>(governance_info, program_id)
|
||||
}
|
||||
|
||||
/// Returns ProgramGovernance PDA seeds
|
||||
pub fn get_program_governance_address_seeds<'a>(
|
||||
realm: &'a Pubkey,
|
||||
governed_program: &'a Pubkey,
|
||||
) -> [&'a [u8]; 3] {
|
||||
// 'program-governance' prefix ensures uniqueness of the PDA
|
||||
// Note: Only the current program upgrade authority can create an account with this PDA using CreateProgramGovernance instruction
|
||||
[
|
||||
b"program-governance",
|
||||
realm.as_ref(),
|
||||
governed_program.as_ref(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns ProgramGovernance PDA address
|
||||
pub fn get_program_governance_address<'a>(
|
||||
program_id: &Pubkey,
|
||||
realm: &'a Pubkey,
|
||||
governed_program: &'a Pubkey,
|
||||
) -> Pubkey {
|
||||
Pubkey::find_program_address(
|
||||
&get_program_governance_address_seeds(realm, governed_program),
|
||||
program_id,
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
/// Returns MintGovernance PDA seeds
|
||||
pub fn get_mint_governance_address_seeds<'a>(
|
||||
realm: &'a Pubkey,
|
||||
governed_mint: &'a Pubkey,
|
||||
) -> [&'a [u8]; 3] {
|
||||
// 'mint-governance' prefix ensures uniqueness of the PDA
|
||||
// Note: Only the current mint authority can create an account with this PDA using CreateMintGovernance instruction
|
||||
[b"mint-governance", realm.as_ref(), governed_mint.as_ref()]
|
||||
}
|
||||
|
||||
/// Returns MintGovernance PDA address
|
||||
pub fn get_mint_governance_address<'a>(
|
||||
program_id: &Pubkey,
|
||||
realm: &'a Pubkey,
|
||||
governed_mint: &'a Pubkey,
|
||||
) -> Pubkey {
|
||||
Pubkey::find_program_address(
|
||||
&get_mint_governance_address_seeds(realm, governed_mint),
|
||||
program_id,
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
/// Returns AccountGovernance PDA seeds
|
||||
pub fn get_account_governance_address_seeds<'a>(
|
||||
realm: &'a Pubkey,
|
||||
governed_account: &'a Pubkey,
|
||||
) -> [&'a [u8]; 3] {
|
||||
[
|
||||
b"account-governance",
|
||||
realm.as_ref(),
|
||||
governed_account.as_ref(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns AccountGovernance PDA address
|
||||
pub fn get_account_governance_address<'a>(
|
||||
program_id: &Pubkey,
|
||||
realm: &'a Pubkey,
|
||||
governed_account: &'a Pubkey,
|
||||
) -> Pubkey {
|
||||
Pubkey::find_program_address(
|
||||
&get_account_governance_address_seeds(realm, governed_account),
|
||||
program_id,
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
/// Validates governance config
|
||||
pub fn assert_is_valid_governance_config(
|
||||
program_id: &Pubkey,
|
||||
governance_config: &GovernanceConfig,
|
||||
realm_info: &AccountInfo,
|
||||
) -> Result<(), ProgramError> {
|
||||
if realm_info.key != &governance_config.realm {
|
||||
return Err(GovernanceError::InvalidGovernanceConfig.into());
|
||||
}
|
||||
|
||||
assert_is_valid_realm(program_id, realm_info)?;
|
||||
|
||||
if governance_config.yes_vote_threshold_percentage < 1
|
||||
|| governance_config.yes_vote_threshold_percentage > 100
|
||||
{
|
||||
return Err(GovernanceError::InvalidGovernanceConfig.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
//! Program accounts
|
||||
|
||||
pub mod enums;
|
||||
pub mod governance;
|
||||
pub mod proposal;
|
||||
pub mod proposal_instruction;
|
||||
pub mod realm;
|
||||
pub mod signatory_record;
|
||||
pub mod token_owner_record;
|
||||
pub mod vote_record;
|
File diff suppressed because it is too large
Load Diff
|
@ -1,236 +0,0 @@
|
|||
//! ProposalInstruction Account
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
state::enums::GovernanceAccountType,
|
||||
tools::account::{get_account_data, AccountMaxSize},
|
||||
PROGRAM_AUTHORITY_SEED,
|
||||
};
|
||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||
use solana_program::{
|
||||
account_info::AccountInfo,
|
||||
epoch_schedule::Slot,
|
||||
instruction::{AccountMeta, Instruction},
|
||||
program_error::ProgramError,
|
||||
program_pack::IsInitialized,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
/// InstructionData wrapper. It can be removed once Borsh serialization for Instruction is supported in the SDK
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
#[repr(C)]
|
||||
pub struct InstructionData {
|
||||
/// Pubkey of the instruction processor that executes this instruction
|
||||
pub program_id: Pubkey,
|
||||
/// Metadata for what accounts should be passed to the instruction processor
|
||||
pub accounts: Vec<AccountMetaData>,
|
||||
/// Opaque data passed to the instruction processor
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Account metadata used to define Instructions
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
#[repr(C)]
|
||||
pub struct AccountMetaData {
|
||||
/// An account's public key
|
||||
pub pubkey: Pubkey,
|
||||
/// True if an Instruction requires a Transaction signature matching `pubkey`.
|
||||
pub is_signer: bool,
|
||||
/// True if the `pubkey` can be loaded as a read-write account.
|
||||
pub is_writable: bool,
|
||||
}
|
||||
|
||||
impl From<Instruction> for InstructionData {
|
||||
fn from(instruction: Instruction) -> Self {
|
||||
InstructionData {
|
||||
program_id: instruction.program_id,
|
||||
accounts: instruction
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|a| AccountMetaData {
|
||||
pubkey: a.pubkey,
|
||||
is_signer: a.is_signer,
|
||||
is_writable: a.is_writable,
|
||||
})
|
||||
.collect(),
|
||||
data: instruction.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&InstructionData> for Instruction {
|
||||
fn from(instruction: &InstructionData) -> Self {
|
||||
Instruction {
|
||||
program_id: instruction.program_id,
|
||||
accounts: instruction
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|a| AccountMeta {
|
||||
pubkey: a.pubkey,
|
||||
is_signer: a.is_signer,
|
||||
is_writable: a.is_writable,
|
||||
})
|
||||
.collect(),
|
||||
data: instruction.data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Account for an instruction to be executed for Proposal
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub struct ProposalInstruction {
|
||||
/// Governance Account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// The Proposal the instruction belongs to
|
||||
pub proposal: Pubkey,
|
||||
|
||||
/// Minimum waiting time in slots for the instruction to be executed once proposal is voted on
|
||||
pub hold_up_time: u64,
|
||||
|
||||
/// Instruction to execute
|
||||
/// The instruction will be signed by Governance PDA the Proposal belongs to
|
||||
// For example for ProgramGovernance the instruction to upgrade program will be signed by ProgramGovernance PDA
|
||||
pub instruction: InstructionData,
|
||||
|
||||
/// Executed at flag
|
||||
pub executed_at: Option<Slot>,
|
||||
}
|
||||
|
||||
impl AccountMaxSize for ProposalInstruction {
|
||||
fn get_max_size(&self) -> Option<usize> {
|
||||
Some(self.instruction.accounts.len() * 34 + self.instruction.data.len() + 90)
|
||||
}
|
||||
}
|
||||
|
||||
impl IsInitialized for ProposalInstruction {
|
||||
fn is_initialized(&self) -> bool {
|
||||
self.account_type == GovernanceAccountType::ProposalInstruction
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns ProposalInstruction PDA seeds
|
||||
pub fn get_proposal_instruction_address_seeds<'a>(
|
||||
proposal: &'a Pubkey,
|
||||
instruction_index_le_bytes: &'a [u8],
|
||||
) -> [&'a [u8]; 3] {
|
||||
[
|
||||
PROGRAM_AUTHORITY_SEED,
|
||||
proposal.as_ref(),
|
||||
instruction_index_le_bytes,
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns ProposalInstruction PDA address
|
||||
pub fn get_proposal_instruction_address<'a>(
|
||||
program_id: &Pubkey,
|
||||
proposal: &'a Pubkey,
|
||||
instruction_index_le_bytes: &'a [u8],
|
||||
) -> Pubkey {
|
||||
Pubkey::find_program_address(
|
||||
&get_proposal_instruction_address_seeds(proposal, instruction_index_le_bytes),
|
||||
program_id,
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
/// Deserializes ProposalInstruction account and checks owner program
|
||||
pub fn get_proposal_instruction_data(
|
||||
program_id: &Pubkey,
|
||||
proposal_instruction_info: &AccountInfo,
|
||||
) -> Result<ProposalInstruction, ProgramError> {
|
||||
get_account_data::<ProposalInstruction>(proposal_instruction_info, program_id)
|
||||
}
|
||||
|
||||
/// Deserializes and returns ProposalInstruction account and checks it belongs to the given Proposal
|
||||
pub fn get_proposal_instruction_data_for_proposal(
|
||||
program_id: &Pubkey,
|
||||
proposal_instruction_info: &AccountInfo,
|
||||
proposal: &Pubkey,
|
||||
) -> Result<ProposalInstruction, ProgramError> {
|
||||
let proposal_instruction_data =
|
||||
get_proposal_instruction_data(program_id, proposal_instruction_info)?;
|
||||
|
||||
if proposal_instruction_data.proposal != *proposal {
|
||||
return Err(GovernanceError::InvalidProposalForProposalInstruction.into());
|
||||
}
|
||||
|
||||
Ok(proposal_instruction_data)
|
||||
}
|
||||
|
||||
/// Deserializes ProposalInstruction account and checks it belongs to the given Proposal
|
||||
pub fn assert_proposal_instruction_for_proposal(
|
||||
program_id: &Pubkey,
|
||||
proposal_instruction_info: &AccountInfo,
|
||||
proposal: &Pubkey,
|
||||
) -> Result<(), ProgramError> {
|
||||
get_proposal_instruction_data_for_proposal(program_id, proposal_instruction_info, proposal)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
|
||||
fn create_test_account_meta_data() -> AccountMetaData {
|
||||
AccountMetaData {
|
||||
pubkey: Pubkey::new_unique(),
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_test_instruction_data() -> InstructionData {
|
||||
InstructionData {
|
||||
program_id: Pubkey::new_unique(),
|
||||
accounts: vec![
|
||||
create_test_account_meta_data(),
|
||||
create_test_account_meta_data(),
|
||||
],
|
||||
data: vec![1, 2, 3],
|
||||
}
|
||||
}
|
||||
|
||||
fn create_test_proposal_instruction() -> ProposalInstruction {
|
||||
ProposalInstruction {
|
||||
account_type: GovernanceAccountType::ProposalInstruction,
|
||||
proposal: Pubkey::new_unique(),
|
||||
hold_up_time: 10,
|
||||
instruction: create_test_instruction_data(),
|
||||
executed_at: Some(100),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_account_meta_data_size() {
|
||||
let account_meta_data = create_test_account_meta_data();
|
||||
let size = account_meta_data.try_to_vec().unwrap().len();
|
||||
|
||||
assert_eq!(34, size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proposal_instruction_max_size() {
|
||||
// Arrange
|
||||
let proposal_instruction = create_test_proposal_instruction();
|
||||
let size = proposal_instruction.try_to_vec().unwrap().len();
|
||||
|
||||
// Act, Assert
|
||||
assert_eq!(proposal_instruction.get_max_size(), Some(size));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_proposal_instruction_max_size() {
|
||||
// Arrange
|
||||
let mut proposal_instruction = create_test_proposal_instruction();
|
||||
proposal_instruction.instruction.data = vec![];
|
||||
proposal_instruction.instruction.accounts = vec![];
|
||||
|
||||
let size = proposal_instruction.try_to_vec().unwrap().len();
|
||||
|
||||
// Act, Assert
|
||||
assert_eq!(proposal_instruction.get_max_size(), Some(size));
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
//! Realm Account
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize},
|
||||
PROGRAM_AUTHORITY_SEED,
|
||||
};
|
||||
|
||||
use crate::state::enums::GovernanceAccountType;
|
||||
|
||||
/// Governance Realm Account
|
||||
/// Account PDA seeds" ['governance', name]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub struct Realm {
|
||||
/// Governance account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// Community mint
|
||||
pub community_mint: Pubkey,
|
||||
|
||||
/// Council mint
|
||||
pub council_mint: Option<Pubkey>,
|
||||
|
||||
/// Governance Realm name
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl AccountMaxSize for Realm {}
|
||||
|
||||
impl IsInitialized for Realm {
|
||||
fn is_initialized(&self) -> bool {
|
||||
self.account_type == GovernanceAccountType::Realm
|
||||
}
|
||||
}
|
||||
|
||||
impl Realm {
|
||||
/// Asserts the given mint is either Community or Council mint of the Realm
|
||||
pub fn assert_is_valid_governing_token_mint(
|
||||
&self,
|
||||
governing_token_mint: &Pubkey,
|
||||
) -> Result<(), ProgramError> {
|
||||
if self.community_mint == *governing_token_mint {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.council_mint == Some(*governing_token_mint) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(GovernanceError::InvalidGoverningTokenMint.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether realm account exists, is initialized and owned by Governance program
|
||||
pub fn assert_is_valid_realm(
|
||||
program_id: &Pubkey,
|
||||
realm_info: &AccountInfo,
|
||||
) -> Result<(), ProgramError> {
|
||||
assert_is_valid_account(realm_info, GovernanceAccountType::Realm, program_id)
|
||||
}
|
||||
|
||||
/// Deserializes account and checks owner program
|
||||
pub fn get_realm_data(
|
||||
program_id: &Pubkey,
|
||||
realm_info: &AccountInfo,
|
||||
) -> Result<Realm, ProgramError> {
|
||||
get_account_data::<Realm>(realm_info, program_id)
|
||||
}
|
||||
|
||||
/// Returns Realm PDA seeds
|
||||
pub fn get_realm_address_seeds(name: &str) -> [&[u8]; 2] {
|
||||
[PROGRAM_AUTHORITY_SEED, name.as_bytes()]
|
||||
}
|
||||
|
||||
/// Returns Realm PDA address
|
||||
pub fn get_realm_address(program_id: &Pubkey, name: &str) -> Pubkey {
|
||||
Pubkey::find_program_address(&get_realm_address_seeds(name), program_id).0
|
||||
}
|
||||
|
||||
/// Returns Realm Token Holding PDA seeds
|
||||
pub fn get_governing_token_holding_address_seeds<'a>(
|
||||
realm: &'a Pubkey,
|
||||
governing_token_mint: &'a Pubkey,
|
||||
) -> [&'a [u8]; 3] {
|
||||
[
|
||||
PROGRAM_AUTHORITY_SEED,
|
||||
realm.as_ref(),
|
||||
governing_token_mint.as_ref(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns Realm Token Holding PDA address
|
||||
pub fn get_governing_token_holding_address(
|
||||
program_id: &Pubkey,
|
||||
realm: &Pubkey,
|
||||
governing_token_mint: &Pubkey,
|
||||
) -> Pubkey {
|
||||
Pubkey::find_program_address(
|
||||
&get_governing_token_holding_address_seeds(realm, governing_token_mint),
|
||||
program_id,
|
||||
)
|
||||
.0
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
//! Signatory Record
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
tools::account::{get_account_data, AccountMaxSize},
|
||||
PROGRAM_AUTHORITY_SEED,
|
||||
};
|
||||
|
||||
use crate::state::enums::GovernanceAccountType;
|
||||
|
||||
/// Account PDA seeds: ['governance', proposal, signatory]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub struct SignatoryRecord {
|
||||
/// Governance account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
/// Proposal the signatory is assigned for
|
||||
pub proposal: Pubkey,
|
||||
/// The account of the signatory who can sign off the proposal
|
||||
pub signatory: Pubkey,
|
||||
/// Indicates whether the signatory signed off the proposal
|
||||
pub signed_off: bool,
|
||||
}
|
||||
|
||||
impl AccountMaxSize for SignatoryRecord {}
|
||||
|
||||
impl IsInitialized for SignatoryRecord {
|
||||
fn is_initialized(&self) -> bool {
|
||||
self.account_type == GovernanceAccountType::SignatoryRecord
|
||||
}
|
||||
}
|
||||
|
||||
impl SignatoryRecord {
|
||||
/// Checks signatory hasn't signed off yet and is transaction signer
|
||||
pub fn assert_can_sign_off(&self, signatory_info: &AccountInfo) -> Result<(), ProgramError> {
|
||||
if self.signed_off {
|
||||
return Err(GovernanceError::SignatoryAlreadySignedOff.into());
|
||||
}
|
||||
|
||||
if !signatory_info.is_signer {
|
||||
return Err(GovernanceError::SignatoryMustSign.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks signatory can be removed from Proposal
|
||||
pub fn assert_can_remove_signatory(&self) -> Result<(), ProgramError> {
|
||||
if self.signed_off {
|
||||
return Err(GovernanceError::SignatoryAlreadySignedOff.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns SignatoryRecord PDA seeds
|
||||
pub fn get_signatory_record_address_seeds<'a>(
|
||||
proposal: &'a Pubkey,
|
||||
signatory: &'a Pubkey,
|
||||
) -> [&'a [u8]; 3] {
|
||||
[
|
||||
PROGRAM_AUTHORITY_SEED,
|
||||
proposal.as_ref(),
|
||||
signatory.as_ref(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns SignatoryRecord PDA address
|
||||
pub fn get_signatory_record_address<'a>(
|
||||
program_id: &Pubkey,
|
||||
proposal: &'a Pubkey,
|
||||
signatory: &'a Pubkey,
|
||||
) -> Pubkey {
|
||||
Pubkey::find_program_address(
|
||||
&get_signatory_record_address_seeds(proposal, signatory),
|
||||
program_id,
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
/// Deserializes SignatoryRecord account and checks owner program
|
||||
pub fn get_signatory_record_data(
|
||||
program_id: &Pubkey,
|
||||
signatory_record_info: &AccountInfo,
|
||||
) -> Result<SignatoryRecord, ProgramError> {
|
||||
get_account_data::<SignatoryRecord>(signatory_record_info, program_id)
|
||||
}
|
||||
|
||||
/// Deserializes SignatoryRecord and validates its PDA
|
||||
pub fn get_signatory_record_data_for_seeds(
|
||||
program_id: &Pubkey,
|
||||
signatory_record_info: &AccountInfo,
|
||||
proposal: &Pubkey,
|
||||
signatory: &Pubkey,
|
||||
) -> Result<SignatoryRecord, ProgramError> {
|
||||
let (signatory_record_address, _) = Pubkey::find_program_address(
|
||||
&get_signatory_record_address_seeds(proposal, signatory),
|
||||
program_id,
|
||||
);
|
||||
|
||||
if signatory_record_address != *signatory_record_info.key {
|
||||
return Err(GovernanceError::InvalidSignatoryAddress.into());
|
||||
}
|
||||
|
||||
get_signatory_record_data(program_id, signatory_record_info)
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
//! Token Owner Record Account
|
||||
|
||||
use crate::{
|
||||
error::GovernanceError,
|
||||
tools::account::{get_account_data, AccountMaxSize},
|
||||
PROGRAM_AUTHORITY_SEED,
|
||||
};
|
||||
|
||||
use crate::state::enums::GovernanceAccountType;
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
/// Governance Token Owner Record
|
||||
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub struct TokenOwnerRecord {
|
||||
/// Governance account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// The Realm the TokenOwnerRecord belongs to
|
||||
pub realm: Pubkey,
|
||||
|
||||
/// Governing Token Mint the TokenOwnerRecord holds deposit for
|
||||
pub governing_token_mint: Pubkey,
|
||||
|
||||
/// The owner (either single or multisig) of the deposited governing SPL Tokens
|
||||
/// This is who can authorize a withdrawal of the tokens
|
||||
pub governing_token_owner: Pubkey,
|
||||
|
||||
/// The amount of governing tokens deposited into the Realm
|
||||
/// This amount is the voter weight used when voting on proposals
|
||||
pub governing_token_deposit_amount: u64,
|
||||
|
||||
/// A single account that is allowed to operate governance with the deposited governing tokens
|
||||
/// It can be delegated to by the governing_token_owner or current governance_delegate
|
||||
pub governance_delegate: Option<Pubkey>,
|
||||
|
||||
/// The number of votes cast by TokenOwner but not relinquished yet
|
||||
/// Every time a vote is cast this number is increased and it's always decreased when relinquishing a vote regardless of the vote state
|
||||
pub unrelinquished_votes_count: u32,
|
||||
|
||||
/// The total number of votes cast by the TokenOwner
|
||||
/// If TokenOwner withdraws vote while voting is still in progress total_votes_count is decreased and the vote doesn't count towards the total
|
||||
pub total_votes_count: u32,
|
||||
}
|
||||
|
||||
impl AccountMaxSize for TokenOwnerRecord {
|
||||
fn get_max_size(&self) -> Option<usize> {
|
||||
Some(146)
|
||||
}
|
||||
}
|
||||
|
||||
impl IsInitialized for TokenOwnerRecord {
|
||||
fn is_initialized(&self) -> bool {
|
||||
self.account_type == GovernanceAccountType::TokenOwnerRecord
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenOwnerRecord {
|
||||
/// Checks whether the provided Governance Authority signed transaction
|
||||
pub fn assert_token_owner_or_delegate_is_signer(
|
||||
&self,
|
||||
governance_authority_info: &AccountInfo,
|
||||
) -> Result<(), ProgramError> {
|
||||
if governance_authority_info.is_signer {
|
||||
if &self.governing_token_owner == governance_authority_info.key {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(governance_delegate) = self.governance_delegate {
|
||||
if &governance_delegate == governance_authority_info.key {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Err(GovernanceError::GoverningTokenOwnerOrDelegateMustSign.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns TokenOwnerRecord PDA address
|
||||
pub fn get_token_owner_record_address(
|
||||
program_id: &Pubkey,
|
||||
realm: &Pubkey,
|
||||
governing_token_mint: &Pubkey,
|
||||
governing_token_owner: &Pubkey,
|
||||
) -> Pubkey {
|
||||
Pubkey::find_program_address(
|
||||
&get_token_owner_record_address_seeds(realm, governing_token_mint, governing_token_owner),
|
||||
program_id,
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
/// Returns TokenOwnerRecord PDA seeds
|
||||
pub fn get_token_owner_record_address_seeds<'a>(
|
||||
realm: &'a Pubkey,
|
||||
governing_token_mint: &'a Pubkey,
|
||||
governing_token_owner: &'a Pubkey,
|
||||
) -> [&'a [u8]; 4] {
|
||||
[
|
||||
PROGRAM_AUTHORITY_SEED,
|
||||
realm.as_ref(),
|
||||
governing_token_mint.as_ref(),
|
||||
governing_token_owner.as_ref(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Deserializes TokenOwnerRecord account and checks owner program
|
||||
pub fn get_token_owner_record_data(
|
||||
program_id: &Pubkey,
|
||||
token_owner_record_info: &AccountInfo,
|
||||
) -> Result<TokenOwnerRecord, ProgramError> {
|
||||
get_account_data::<TokenOwnerRecord>(token_owner_record_info, program_id)
|
||||
}
|
||||
|
||||
/// Deserializes TokenOwnerRecord account and checks its PDA against the provided seeds
|
||||
pub fn get_token_owner_record_data_for_seeds(
|
||||
program_id: &Pubkey,
|
||||
token_owner_record_info: &AccountInfo,
|
||||
token_owner_record_seeds: &[&[u8]],
|
||||
) -> Result<TokenOwnerRecord, ProgramError> {
|
||||
let (token_owner_record_address, _) =
|
||||
Pubkey::find_program_address(token_owner_record_seeds, program_id);
|
||||
|
||||
if token_owner_record_address != *token_owner_record_info.key {
|
||||
return Err(GovernanceError::InvalidTokenOwnerRecordAccountAddress.into());
|
||||
}
|
||||
|
||||
get_token_owner_record_data(program_id, token_owner_record_info)
|
||||
}
|
||||
|
||||
/// Deserializes TokenOwnerRecord account and checks that its PDA matches the given realm and governing mint
|
||||
pub fn get_token_owner_record_data_for_realm_and_governing_mint(
|
||||
program_id: &Pubkey,
|
||||
token_owner_record_info: &AccountInfo,
|
||||
realm: &Pubkey,
|
||||
governing_token_mint: &Pubkey,
|
||||
) -> Result<TokenOwnerRecord, ProgramError> {
|
||||
let token_owner_record_data = get_token_owner_record_data(program_id, token_owner_record_info)?;
|
||||
|
||||
if token_owner_record_data.governing_token_mint != *governing_token_mint {
|
||||
return Err(GovernanceError::InvalidGoverningMintForTokenOwnerRecord.into());
|
||||
}
|
||||
|
||||
if token_owner_record_data.realm != *realm {
|
||||
return Err(GovernanceError::InvalidRealmForTokenOwnerRecord.into());
|
||||
}
|
||||
|
||||
Ok(token_owner_record_data)
|
||||
}
|
||||
|
||||
/// Deserializes TokenOwnerRecord account and checks its address is the give proposal_owner
|
||||
pub fn get_token_owner_record_data_for_proposal_owner(
|
||||
program_id: &Pubkey,
|
||||
token_owner_record_info: &AccountInfo,
|
||||
proposal_owner: &Pubkey,
|
||||
) -> Result<TokenOwnerRecord, ProgramError> {
|
||||
if token_owner_record_info.key != proposal_owner {
|
||||
return Err(GovernanceError::InvalidProposalOwnerAccount.into());
|
||||
}
|
||||
|
||||
get_token_owner_record_data(program_id, token_owner_record_info)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use solana_program::borsh::get_packed_len;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_max_size() {
|
||||
let token_owner_record = TokenOwnerRecord {
|
||||
account_type: GovernanceAccountType::TokenOwnerRecord,
|
||||
realm: Pubkey::new_unique(),
|
||||
governing_token_mint: Pubkey::new_unique(),
|
||||
governing_token_owner: Pubkey::new_unique(),
|
||||
governing_token_deposit_amount: 10,
|
||||
governance_delegate: Some(Pubkey::new_unique()),
|
||||
unrelinquished_votes_count: 1,
|
||||
total_votes_count: 1,
|
||||
};
|
||||
|
||||
let size = get_packed_len::<TokenOwnerRecord>();
|
||||
|
||||
assert_eq!(token_owner_record.get_max_size(), Some(size));
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
//! Proposal Vote Record Account
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::program_error::ProgramError;
|
||||
use solana_program::{program_pack::IsInitialized, pubkey::Pubkey};
|
||||
|
||||
use crate::error::GovernanceError;
|
||||
use crate::tools::account::get_account_data;
|
||||
use crate::{tools::account::AccountMaxSize, PROGRAM_AUTHORITY_SEED};
|
||||
|
||||
use crate::state::enums::{GovernanceAccountType, VoteWeight};
|
||||
|
||||
/// Proposal VoteRecord
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub struct VoteRecord {
|
||||
/// Governance account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// Proposal account
|
||||
pub proposal: Pubkey,
|
||||
|
||||
/// The user who casted this vote
|
||||
/// This is the Governing Token Owner who deposited governing tokens into the Realm
|
||||
pub governing_token_owner: Pubkey,
|
||||
|
||||
/// Indicates whether the vote was relinquished by voter
|
||||
pub is_relinquished: bool,
|
||||
|
||||
/// Voter's vote: Yes/No and amount
|
||||
pub vote_weight: VoteWeight,
|
||||
}
|
||||
|
||||
impl AccountMaxSize for VoteRecord {}
|
||||
|
||||
impl IsInitialized for VoteRecord {
|
||||
fn is_initialized(&self) -> bool {
|
||||
self.account_type == GovernanceAccountType::VoteRecord
|
||||
}
|
||||
}
|
||||
impl VoteRecord {
|
||||
/// Checks the vote can be relinquished
|
||||
pub fn assert_can_relinquish_vote(&self) -> Result<(), ProgramError> {
|
||||
if self.is_relinquished {
|
||||
return Err(GovernanceError::VoteAlreadyRelinquished.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes VoteRecord account and checks owner program
|
||||
pub fn get_vote_record_data(
|
||||
program_id: &Pubkey,
|
||||
vote_record_info: &AccountInfo,
|
||||
) -> Result<VoteRecord, ProgramError> {
|
||||
get_account_data::<VoteRecord>(vote_record_info, program_id)
|
||||
}
|
||||
|
||||
/// Deserializes VoteRecord and checks it belongs to the provided Proposal and Governing Token Owner
|
||||
pub fn get_vote_record_data_for_proposal_and_token_owner(
|
||||
program_id: &Pubkey,
|
||||
vote_record_info: &AccountInfo,
|
||||
proposal: &Pubkey,
|
||||
governing_token_owner: &Pubkey,
|
||||
) -> Result<VoteRecord, ProgramError> {
|
||||
let vote_record_data = get_vote_record_data(program_id, vote_record_info)?;
|
||||
|
||||
if vote_record_data.proposal != *proposal {
|
||||
return Err(GovernanceError::InvalidProposalForVoterRecord.into());
|
||||
}
|
||||
|
||||
if vote_record_data.governing_token_owner != *governing_token_owner {
|
||||
return Err(GovernanceError::InvalidGoverningTokenOwnerForVoteRecord.into());
|
||||
}
|
||||
|
||||
Ok(vote_record_data)
|
||||
}
|
||||
|
||||
/// Returns VoteRecord PDA seeds
|
||||
pub fn get_vote_record_address_seeds<'a>(
|
||||
proposal: &'a Pubkey,
|
||||
token_owner_record: &'a Pubkey,
|
||||
) -> [&'a [u8]; 3] {
|
||||
[
|
||||
PROGRAM_AUTHORITY_SEED,
|
||||
proposal.as_ref(),
|
||||
token_owner_record.as_ref(),
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns VoteRecord PDA address
|
||||
pub fn get_vote_record_address<'a>(
|
||||
program_id: &Pubkey,
|
||||
proposal: &'a Pubkey,
|
||||
token_owner_record: &'a Pubkey,
|
||||
) -> Pubkey {
|
||||
Pubkey::find_program_address(
|
||||
&get_vote_record_address_seeds(proposal, token_owner_record),
|
||||
program_id,
|
||||
)
|
||||
.0
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
//! General purpose account utility functions
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, borsh::try_from_slice_unchecked, msg, program::invoke_signed,
|
||||
program_error::ProgramError, program_pack::IsInitialized, pubkey::Pubkey, rent::Rent,
|
||||
system_instruction::create_account,
|
||||
};
|
||||
|
||||
use crate::error::GovernanceError;
|
||||
|
||||
/// Trait for accounts to return their max size
|
||||
pub trait AccountMaxSize {
|
||||
/// Returns max account size or None if max size is not known and actual instance size should be used
|
||||
fn get_max_size(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new account and serializes data into it using the provided seeds to invoke signed CPI call
|
||||
/// Note: This functions also checks the provided account PDA matches the supplied seeds
|
||||
pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSize>(
|
||||
payer_info: &AccountInfo<'a>,
|
||||
account_info: &AccountInfo<'a>,
|
||||
account_data: &T,
|
||||
account_address_seeds: &[&[u8]],
|
||||
program_id: &Pubkey,
|
||||
system_info: &AccountInfo<'a>,
|
||||
rent: &Rent,
|
||||
) -> Result<(), ProgramError> {
|
||||
// Get PDA and assert it's the same as the requested account address
|
||||
let (account_address, bump_seed) =
|
||||
Pubkey::find_program_address(account_address_seeds, program_id);
|
||||
|
||||
if account_address != *account_info.key {
|
||||
msg!(
|
||||
"Create account with PDA: {:?} was requested while PDA: {:?} was expected",
|
||||
account_info.key,
|
||||
account_address
|
||||
);
|
||||
return Err(ProgramError::InvalidSeeds);
|
||||
}
|
||||
|
||||
let (serialized_data, account_size) = if let Some(max_size) = account_data.get_max_size() {
|
||||
(None, max_size)
|
||||
} else {
|
||||
let serialized_data = account_data.try_to_vec()?;
|
||||
let account_size = serialized_data.len();
|
||||
(Some(serialized_data), account_size)
|
||||
};
|
||||
|
||||
let create_account_instruction = create_account(
|
||||
payer_info.key,
|
||||
account_info.key,
|
||||
rent.minimum_balance(account_size),
|
||||
account_size as u64,
|
||||
program_id,
|
||||
);
|
||||
|
||||
let mut signers_seeds = account_address_seeds.to_vec();
|
||||
let bump = &[bump_seed];
|
||||
signers_seeds.push(bump);
|
||||
|
||||
invoke_signed(
|
||||
&create_account_instruction,
|
||||
&[
|
||||
payer_info.clone(),
|
||||
account_info.clone(),
|
||||
system_info.clone(),
|
||||
],
|
||||
&[&signers_seeds[..]],
|
||||
)?;
|
||||
|
||||
if let Some(serialized_data) = serialized_data {
|
||||
account_info
|
||||
.data
|
||||
.borrow_mut()
|
||||
.copy_from_slice(&serialized_data);
|
||||
} else {
|
||||
account_data.serialize(&mut *account_info.data.borrow_mut())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deserializes account and checks it's initialized and owned by the specified program
|
||||
pub fn get_account_data<T: BorshDeserialize + IsInitialized>(
|
||||
account_info: &AccountInfo,
|
||||
owner_program_id: &Pubkey,
|
||||
) -> Result<T, ProgramError> {
|
||||
if account_info.data_is_empty() {
|
||||
return Err(GovernanceError::AccountDoesNotExist.into());
|
||||
}
|
||||
if account_info.owner != owner_program_id {
|
||||
return Err(GovernanceError::InvalidAccountOwner.into());
|
||||
}
|
||||
|
||||
let account: T = try_from_slice_unchecked(&account_info.data.borrow())?;
|
||||
if !account.is_initialized() {
|
||||
Err(ProgramError::UninitializedAccount)
|
||||
} else {
|
||||
Ok(account)
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts the given account is not empty, owned by the given program and of the expected type
|
||||
/// Note: The function assumes the account type T is stored as the first element in the account data
|
||||
pub fn assert_is_valid_account<T: BorshDeserialize + PartialEq>(
|
||||
account_info: &AccountInfo,
|
||||
expected_account_type: T,
|
||||
owner_program_id: &Pubkey,
|
||||
) -> Result<(), ProgramError> {
|
||||
if account_info.owner != owner_program_id {
|
||||
return Err(GovernanceError::InvalidAccountOwner.into());
|
||||
}
|
||||
|
||||
if account_info.data_is_empty() {
|
||||
return Err(GovernanceError::AccountDoesNotExist.into());
|
||||
}
|
||||
|
||||
let account_type: T = try_from_slice_unchecked(&account_info.data.borrow())?;
|
||||
|
||||
if account_type != expected_account_type {
|
||||
return Err(GovernanceError::InvalidAccountType.into());
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Disposes account by transferring its lamports to the beneficiary account and zeros its data
|
||||
// After transaction completes the runtime would remove the account with no lamports
|
||||
pub fn dispose_account(account_info: &AccountInfo, beneficiary_info: &AccountInfo) {
|
||||
let account_lamports = account_info.lamports();
|
||||
**account_info.lamports.borrow_mut() = 0;
|
||||
|
||||
**beneficiary_info.lamports.borrow_mut() = beneficiary_info
|
||||
.lamports()
|
||||
.checked_add(account_lamports)
|
||||
.unwrap();
|
||||
|
||||
let mut account_data = account_info.data.borrow_mut();
|
||||
|
||||
account_data.fill(0);
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
//! General purpose bpf_loader_upgradeable utility functions
|
||||
|
||||
use solana_program::{
|
||||
account_info::AccountInfo,
|
||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
program::invoke,
|
||||
program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use bincode::deserialize;
|
||||
|
||||
use crate::error::GovernanceError;
|
||||
|
||||
/// Returns ProgramData account address for the given Program
|
||||
pub fn get_program_data_address(program: &Pubkey) -> Pubkey {
|
||||
Pubkey::find_program_address(&[program.as_ref()], &bpf_loader_upgradeable::id()).0
|
||||
}
|
||||
|
||||
/// Returns upgrade_authority from the given Upgradable Loader Account
|
||||
pub fn get_program_upgrade_authority(
|
||||
upgradable_loader_state: &UpgradeableLoaderState,
|
||||
) -> Result<Option<Pubkey>, ProgramError> {
|
||||
let upgrade_authority = match upgradable_loader_state {
|
||||
UpgradeableLoaderState::ProgramData {
|
||||
slot: _,
|
||||
upgrade_authority_address,
|
||||
} => *upgrade_authority_address,
|
||||
_ => return Err(ProgramError::InvalidAccountData),
|
||||
};
|
||||
|
||||
Ok(upgrade_authority)
|
||||
}
|
||||
|
||||
/// Sets new upgrade authority for the given upgradable program
|
||||
pub fn set_program_upgrade_authority<'a>(
|
||||
program_address: &Pubkey,
|
||||
program_data_info: &AccountInfo<'a>,
|
||||
program_upgrade_authority_info: &AccountInfo<'a>,
|
||||
new_authority_info: &AccountInfo<'a>,
|
||||
bpf_upgrade_loader_info: &AccountInfo<'a>,
|
||||
) -> Result<(), ProgramError> {
|
||||
let set_upgrade_authority_instruction = bpf_loader_upgradeable::set_upgrade_authority(
|
||||
program_address,
|
||||
program_upgrade_authority_info.key,
|
||||
Some(new_authority_info.key),
|
||||
);
|
||||
|
||||
invoke(
|
||||
&set_upgrade_authority_instruction,
|
||||
&[
|
||||
program_data_info.clone(),
|
||||
program_upgrade_authority_info.clone(),
|
||||
bpf_upgrade_loader_info.clone(),
|
||||
new_authority_info.clone(),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Asserts the program is upgradable and its upgrade authority is a signer of the transaction
|
||||
pub fn assert_program_upgrade_authority_is_signer(
|
||||
program_address: &Pubkey,
|
||||
program_data_info: &AccountInfo,
|
||||
program_upgrade_authority_info: &AccountInfo,
|
||||
) -> Result<(), ProgramError> {
|
||||
if program_data_info.owner != &bpf_loader_upgradeable::id() {
|
||||
return Err(ProgramError::IncorrectProgramId);
|
||||
}
|
||||
let program_data_address = get_program_data_address(program_address);
|
||||
|
||||
if program_data_address != *program_data_info.key {
|
||||
return Err(GovernanceError::InvalidProgramDataAccountAddress.into());
|
||||
}
|
||||
|
||||
let upgrade_authority = if let UpgradeableLoaderState::ProgramData {
|
||||
slot: _,
|
||||
upgrade_authority_address,
|
||||
} = deserialize(&program_data_info.data.borrow())
|
||||
.map_err(|_| GovernanceError::InvalidProgramDataAccountData)?
|
||||
{
|
||||
upgrade_authority_address
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let upgrade_authority = upgrade_authority.ok_or(GovernanceError::ProgramNotUpgradable)?;
|
||||
|
||||
if upgrade_authority != *program_upgrade_authority_info.key {
|
||||
return Err(GovernanceError::InvalidUpgradeAuthority.into());
|
||||
}
|
||||
if !program_upgrade_authority_info.is_signer {
|
||||
return Err(GovernanceError::UpgradeAuthorityMustSign.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
//! Utility functions
|
||||
|
||||
pub mod account;
|
||||
|
||||
pub mod spl_token;
|
||||
|
||||
pub mod bpf_loader_upgradeable;
|
||||
|
||||
pub mod pack;
|
|
@ -1,14 +0,0 @@
|
|||
//! General purpose packing utility functions
|
||||
|
||||
use arrayref::array_refs;
|
||||
use solana_program::{program_error::ProgramError, program_option::COption, pubkey::Pubkey};
|
||||
|
||||
/// Unpacks COption from a slice
|
||||
pub fn unpack_coption_pubkey(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
|
||||
let (tag, body) = array_refs![src, 4, 32];
|
||||
match *tag {
|
||||
[0, 0, 0, 0] => Ok(COption::None),
|
||||
[1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
|
||||
_ => Err(ProgramError::InvalidAccountData),
|
||||
}
|
||||
}
|
|
@ -1,327 +0,0 @@
|
|||
//! General purpose SPL token utility functions
|
||||
|
||||
use arrayref::array_ref;
|
||||
use solana_program::{
|
||||
account_info::AccountInfo,
|
||||
entrypoint::ProgramResult,
|
||||
msg,
|
||||
program::{invoke, invoke_signed},
|
||||
program_error::ProgramError,
|
||||
program_option::COption,
|
||||
program_pack::Pack,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
system_instruction,
|
||||
};
|
||||
use spl_token::{
|
||||
instruction::set_authority,
|
||||
state::{Account, Mint},
|
||||
};
|
||||
|
||||
use crate::{error::GovernanceError, tools::pack::unpack_coption_pubkey};
|
||||
|
||||
/// Creates and initializes SPL token account with PDA using the provided PDA seeds
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_spl_token_account_signed<'a>(
|
||||
payer_info: &AccountInfo<'a>,
|
||||
token_account_info: &AccountInfo<'a>,
|
||||
token_account_address_seeds: &[&[u8]],
|
||||
token_mint_info: &AccountInfo<'a>,
|
||||
token_account_owner_info: &AccountInfo<'a>,
|
||||
program_id: &Pubkey,
|
||||
system_info: &AccountInfo<'a>,
|
||||
spl_token_info: &AccountInfo<'a>,
|
||||
rent_sysvar_info: &AccountInfo<'a>,
|
||||
rent: &Rent,
|
||||
) -> Result<(), ProgramError> {
|
||||
let create_account_instruction = system_instruction::create_account(
|
||||
payer_info.key,
|
||||
token_account_info.key,
|
||||
1.max(rent.minimum_balance(spl_token::state::Account::get_packed_len())),
|
||||
spl_token::state::Account::get_packed_len() as u64,
|
||||
&spl_token::id(),
|
||||
);
|
||||
|
||||
let (account_address, bump_seed) =
|
||||
Pubkey::find_program_address(token_account_address_seeds, program_id);
|
||||
|
||||
if account_address != *token_account_info.key {
|
||||
msg!(
|
||||
"Create SPL Token Account with PDA: {:?} was requested while PDA: {:?} was expected",
|
||||
token_account_info.key,
|
||||
account_address
|
||||
);
|
||||
return Err(ProgramError::InvalidSeeds);
|
||||
}
|
||||
|
||||
let mut signers_seeds = token_account_address_seeds.to_vec();
|
||||
let bump = &[bump_seed];
|
||||
signers_seeds.push(bump);
|
||||
|
||||
invoke_signed(
|
||||
&create_account_instruction,
|
||||
&[
|
||||
payer_info.clone(),
|
||||
token_account_info.clone(),
|
||||
system_info.clone(),
|
||||
],
|
||||
&[&signers_seeds[..]],
|
||||
)?;
|
||||
|
||||
let initialize_account_instruction = spl_token::instruction::initialize_account(
|
||||
&spl_token::id(),
|
||||
token_account_info.key,
|
||||
token_mint_info.key,
|
||||
token_account_owner_info.key,
|
||||
)?;
|
||||
|
||||
invoke(
|
||||
&initialize_account_instruction,
|
||||
&[
|
||||
payer_info.clone(),
|
||||
token_account_info.clone(),
|
||||
token_account_owner_info.clone(),
|
||||
token_mint_info.clone(),
|
||||
spl_token_info.clone(),
|
||||
rent_sysvar_info.clone(),
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transfers SPL Tokens
|
||||
pub fn transfer_spl_tokens<'a>(
|
||||
source_info: &AccountInfo<'a>,
|
||||
destination_info: &AccountInfo<'a>,
|
||||
authority_info: &AccountInfo<'a>,
|
||||
amount: u64,
|
||||
spl_token_info: &AccountInfo<'a>,
|
||||
) -> ProgramResult {
|
||||
let transfer_instruction = spl_token::instruction::transfer(
|
||||
&spl_token::id(),
|
||||
source_info.key,
|
||||
destination_info.key,
|
||||
authority_info.key,
|
||||
&[],
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
invoke(
|
||||
&transfer_instruction,
|
||||
&[
|
||||
spl_token_info.clone(),
|
||||
authority_info.clone(),
|
||||
source_info.clone(),
|
||||
destination_info.clone(),
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transfers SPL Tokens from a token account owned by the provided PDA authority with seeds
|
||||
pub fn transfer_spl_tokens_signed<'a>(
|
||||
source_info: &AccountInfo<'a>,
|
||||
destination_info: &AccountInfo<'a>,
|
||||
authority_info: &AccountInfo<'a>,
|
||||
authority_seeds: &[&[u8]],
|
||||
program_id: &Pubkey,
|
||||
amount: u64,
|
||||
spl_token_info: &AccountInfo<'a>,
|
||||
) -> ProgramResult {
|
||||
let (authority_address, bump_seed) = Pubkey::find_program_address(authority_seeds, program_id);
|
||||
|
||||
if authority_address != *authority_info.key {
|
||||
msg!(
|
||||
"Transfer SPL Token with Authority PDA: {:?} was requested while PDA: {:?} was expected",
|
||||
authority_info.key,
|
||||
authority_address
|
||||
);
|
||||
return Err(ProgramError::InvalidSeeds);
|
||||
}
|
||||
|
||||
let transfer_instruction = spl_token::instruction::transfer(
|
||||
&spl_token::id(),
|
||||
source_info.key,
|
||||
destination_info.key,
|
||||
authority_info.key,
|
||||
&[],
|
||||
amount,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut signers_seeds = authority_seeds.to_vec();
|
||||
let bump = &[bump_seed];
|
||||
signers_seeds.push(bump);
|
||||
|
||||
invoke_signed(
|
||||
&transfer_instruction,
|
||||
&[
|
||||
spl_token_info.clone(),
|
||||
authority_info.clone(),
|
||||
source_info.clone(),
|
||||
destination_info.clone(),
|
||||
],
|
||||
&[&signers_seeds[..]],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Asserts the given account_info represents a valid SPL Token account which is initialized and belongs to spl_token program
|
||||
pub fn assert_is_valid_spl_token_account(account_info: &AccountInfo) -> Result<(), ProgramError> {
|
||||
if account_info.data_is_empty() {
|
||||
return Err(GovernanceError::SplTokenAccountDoesNotExist.into());
|
||||
}
|
||||
|
||||
if account_info.owner != &spl_token::id() {
|
||||
return Err(GovernanceError::SplTokenAccountWithInvalidOwner.into());
|
||||
}
|
||||
|
||||
if account_info.data_len() != Account::LEN {
|
||||
return Err(GovernanceError::SplTokenInvalidTokenAccountData.into());
|
||||
}
|
||||
|
||||
// TokeAccount layout: mint(32), owner(32), amount(8), delegate(36), state(1), ...
|
||||
let data = account_info.try_borrow_data()?;
|
||||
let state = array_ref![data, 108, 1];
|
||||
|
||||
if state == &[0] {
|
||||
return Err(GovernanceError::SplTokenAccountNotInitialized.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Asserts the given mint_info represents a valid SPL Token Mint account which is initialized and belongs to spl_token program
|
||||
pub fn assert_is_valid_spl_token_mint(mint_info: &AccountInfo) -> Result<(), ProgramError> {
|
||||
if mint_info.data_is_empty() {
|
||||
return Err(GovernanceError::SplTokenMintDoesNotExist.into());
|
||||
}
|
||||
|
||||
if mint_info.owner != &spl_token::id() {
|
||||
return Err(GovernanceError::SplTokenMintWithInvalidOwner.into());
|
||||
}
|
||||
|
||||
if mint_info.data_len() != Mint::LEN {
|
||||
return Err(GovernanceError::SplTokenInvalidMintAccountData.into());
|
||||
}
|
||||
|
||||
// In token program [36, 8, 1, is_initialized(1), 36] is the layout
|
||||
let data = mint_info.try_borrow_data().unwrap();
|
||||
let is_initialized = array_ref![data, 45, 1];
|
||||
|
||||
if is_initialized == &[0] {
|
||||
return Err(GovernanceError::SplTokenMintNotInitialized.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Computationally cheap method to get amount from a token account
|
||||
/// It reads amount without deserializing full account data
|
||||
pub fn get_spl_token_amount(token_account_info: &AccountInfo) -> Result<u64, ProgramError> {
|
||||
assert_is_valid_spl_token_account(token_account_info)?;
|
||||
|
||||
// TokeAccount layout: mint(32), owner(32), amount(8), ...
|
||||
let data = token_account_info.try_borrow_data()?;
|
||||
let amount = array_ref![data, 64, 8];
|
||||
Ok(u64::from_le_bytes(*amount))
|
||||
}
|
||||
|
||||
/// Computationally cheap method to get mint from a token account
|
||||
/// It reads mint without deserializing full account data
|
||||
pub fn get_spl_token_mint(token_account_info: &AccountInfo) -> Result<Pubkey, ProgramError> {
|
||||
assert_is_valid_spl_token_account(token_account_info)?;
|
||||
|
||||
// TokeAccount layout: mint(32), owner(32), amount(8), ...
|
||||
let data = token_account_info.try_borrow_data()?;
|
||||
let mint_data = array_ref![data, 0, 32];
|
||||
Ok(Pubkey::new_from_array(*mint_data))
|
||||
}
|
||||
|
||||
/// Computationally cheap method to get owner from a token account
|
||||
/// It reads owner without deserializing full account data
|
||||
pub fn get_spl_token_owner(token_account_info: &AccountInfo) -> Result<Pubkey, ProgramError> {
|
||||
assert_is_valid_spl_token_account(token_account_info)?;
|
||||
|
||||
// TokeAccount layout: mint(32), owner(32), amount(8)
|
||||
let data = token_account_info.try_borrow_data()?;
|
||||
let owner_data = array_ref![data, 32, 32];
|
||||
Ok(Pubkey::new_from_array(*owner_data))
|
||||
}
|
||||
|
||||
/// Computationally cheap method to just get supply from a mint without unpacking the whole object
|
||||
pub fn get_spl_token_mint_supply(mint_info: &AccountInfo) -> Result<u64, ProgramError> {
|
||||
assert_is_valid_spl_token_mint(mint_info)?;
|
||||
// In token program, 36, 8, 1, 1 is the layout, where the first 8 is supply u64.
|
||||
// so we start at 36.
|
||||
let data = mint_info.try_borrow_data().unwrap();
|
||||
let bytes = array_ref![data, 36, 8];
|
||||
|
||||
Ok(u64::from_le_bytes(*bytes))
|
||||
}
|
||||
|
||||
/// Computationally cheap method to just get authority from a mint without unpacking the whole object
|
||||
pub fn get_spl_token_mint_authority(
|
||||
mint_info: &AccountInfo,
|
||||
) -> Result<COption<Pubkey>, ProgramError> {
|
||||
assert_is_valid_spl_token_mint(mint_info)?;
|
||||
// In token program, 36, 8, 1, 1 is the layout, where the first 36 is authority.
|
||||
let data = mint_info.try_borrow_data().unwrap();
|
||||
let bytes = array_ref![data, 0, 36];
|
||||
|
||||
unpack_coption_pubkey(bytes)
|
||||
}
|
||||
|
||||
/// Asserts current mint authority matches the given authority and it's signer of the transaction
|
||||
pub fn assert_spl_token_mint_authority_is_signer(
|
||||
mint_info: &AccountInfo,
|
||||
mint_authority_info: &AccountInfo,
|
||||
) -> Result<(), ProgramError> {
|
||||
let mint_authority = get_spl_token_mint_authority(mint_info)?;
|
||||
|
||||
if mint_authority.is_none() {
|
||||
return Err(GovernanceError::MintHasNoAuthority.into());
|
||||
}
|
||||
|
||||
if !mint_authority.contains(mint_authority_info.key) {
|
||||
return Err(GovernanceError::InvalidMintAuthority.into());
|
||||
}
|
||||
|
||||
if !mint_authority_info.is_signer {
|
||||
return Err(GovernanceError::MintAuthorityMustSign.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets new mint authority
|
||||
pub fn set_spl_token_mint_authority<'a>(
|
||||
mint_info: &AccountInfo<'a>,
|
||||
mint_authority: &AccountInfo<'a>,
|
||||
new_mint_authority: &Pubkey,
|
||||
spl_token_info: &AccountInfo<'a>,
|
||||
) -> Result<(), ProgramError> {
|
||||
let set_authority_ix = set_authority(
|
||||
&spl_token::id(),
|
||||
mint_info.key,
|
||||
Some(new_mint_authority),
|
||||
spl_token::instruction::AuthorityType::MintTokens,
|
||||
mint_authority.key,
|
||||
&[],
|
||||
)?;
|
||||
|
||||
invoke(
|
||||
&set_authority_ix,
|
||||
&[
|
||||
mint_info.clone(),
|
||||
mint_authority.clone(),
|
||||
spl_token_info.clone(),
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
!*.so
|
Binary file not shown.
Binary file not shown.
|
@ -1,132 +0,0 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
mod program_test;
|
||||
|
||||
use solana_program_test::tokio;
|
||||
|
||||
use program_test::*;
|
||||
|
||||
use spl_governance::error::GovernanceError;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_add_signatory() {
|
||||
// Arrange
|
||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||
|
||||
let realm_cookie = governance_test.with_realm().await;
|
||||
let governed_account_cookie = governance_test.with_governed_account().await;
|
||||
|
||||
let mut account_governance_cookie = governance_test
|
||||
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let token_owner_record_cookie = governance_test
|
||||
.with_community_token_deposit(&realm_cookie)
|
||||
.await;
|
||||
|
||||
let proposal_cookie = governance_test
|
||||
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Act
|
||||
let signatory_record_cookie = governance_test
|
||||
.with_signatory(&proposal_cookie, &token_owner_record_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Assert
|
||||
let signatory_record_account = governance_test
|
||||
.get_signatory_record_account(&signatory_record_cookie.address)
|
||||
.await;
|
||||
|
||||
assert_eq!(signatory_record_cookie.account, signatory_record_account);
|
||||
|
||||
let proposal_account = governance_test
|
||||
.get_proposal_account(&proposal_cookie.address)
|
||||
.await;
|
||||
|
||||
assert_eq!(1, proposal_account.signatories_count);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_add_signatory_with_owner_or_delegate_must_sign_error() {
|
||||
// Arrange
|
||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||
|
||||
let realm_cookie = governance_test.with_realm().await;
|
||||
let governed_account_cookie = governance_test.with_governed_account().await;
|
||||
|
||||
let mut account_governance_cookie = governance_test
|
||||
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut token_owner_record_cookie = governance_test
|
||||
.with_community_token_deposit(&realm_cookie)
|
||||
.await;
|
||||
|
||||
let proposal_cookie = governance_test
|
||||
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let other_token_owner_record_cookie = governance_test
|
||||
.with_council_token_deposit(&realm_cookie)
|
||||
.await;
|
||||
|
||||
token_owner_record_cookie.token_owner = other_token_owner_record_cookie.token_owner;
|
||||
|
||||
// Act
|
||||
let err = governance_test
|
||||
.with_signatory(&proposal_cookie, &token_owner_record_cookie)
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
|
||||
// Assert
|
||||
assert_eq!(
|
||||
err,
|
||||
GovernanceError::GoverningTokenOwnerOrDelegateMustSign.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_add_signatory_with_invalid_proposal_owner_error() {
|
||||
// Arrange
|
||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||
|
||||
let realm_cookie = governance_test.with_realm().await;
|
||||
let governed_account_cookie = governance_test.with_governed_account().await;
|
||||
|
||||
let mut account_governance_cookie = governance_test
|
||||
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut token_owner_record_cookie = governance_test
|
||||
.with_community_token_deposit(&realm_cookie)
|
||||
.await;
|
||||
|
||||
let proposal_cookie = governance_test
|
||||
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let other_token_owner_record_cookie = governance_test
|
||||
.with_council_token_deposit(&realm_cookie)
|
||||
.await;
|
||||
|
||||
token_owner_record_cookie.address = other_token_owner_record_cookie.address;
|
||||
|
||||
// Act
|
||||
let err = governance_test
|
||||
.with_signatory(&proposal_cookie, &token_owner_record_cookie)
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
|
||||
// Assert
|
||||
assert_eq!(err, GovernanceError::InvalidProposalOwnerAccount.into());
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
mod program_test;
|
||||
|
||||
use solana_program_test::tokio;
|
||||
|
||||
use program_test::*;
|
||||
use spl_governance::{error::GovernanceError, instruction::Vote, state::enums::ProposalState};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_cancel_proposal() {
|
||||
// Arrange
|
||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||
|
||||
let realm_cookie = governance_test.with_realm().await;
|
||||
let governed_account_cookie = governance_test.with_governed_account().await;
|
||||
|
||||
let mut account_governance_cookie = governance_test
|
||||
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let token_owner_record_cookie = governance_test
|
||||
.with_community_token_deposit(&realm_cookie)
|
||||
.await;
|
||||
|
||||
let proposal_cookie = governance_test
|
||||
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Act
|
||||
governance_test
|
||||
.cancel_proposal(&proposal_cookie, &token_owner_record_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Assert
|
||||
let proposal_account = governance_test
|
||||
.get_proposal_account(&proposal_cookie.address)
|
||||
.await;
|
||||
|
||||
assert_eq!(ProposalState::Cancelled, proposal_account.state);
|
||||
assert_eq!(Some(1), proposal_account.closed_at);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_cancel_proposal_with_already_completed_error() {
|
||||
// Arrange
|
||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||
|
||||
let realm_cookie = governance_test.with_realm().await;
|
||||
let governed_account_cookie = governance_test.with_governed_account().await;
|
||||
|
||||
let mut account_governance_cookie = governance_test
|
||||
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let token_owner_record_cookie = governance_test
|
||||
.with_community_token_deposit(&realm_cookie)
|
||||
.await;
|
||||
|
||||
let proposal_cookie = governance_test
|
||||
.with_signed_off_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
governance_test
|
||||
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Yes)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Act
|
||||
let err = governance_test
|
||||
.cancel_proposal(&proposal_cookie, &token_owner_record_cookie)
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
|
||||
// Assert
|
||||
|
||||
assert_eq!(
|
||||
err,
|
||||
GovernanceError::InvalidStateCannotCancelProposal.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_cancel_proposal_with_owner_or_delegate_must_sign_error() {
|
||||
// Arrange
|
||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||
|
||||
let realm_cookie = governance_test.with_realm().await;
|
||||
let governed_account_cookie = governance_test.with_governed_account().await;
|
||||
|
||||
let mut account_governance_cookie = governance_test
|
||||
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut token_owner_record_cookie = governance_test
|
||||
.with_community_token_deposit(&realm_cookie)
|
||||
.await;
|
||||
|
||||
let proposal_cookie = governance_test
|
||||
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let token_owner_record_cookie2 = governance_test
|
||||
.with_council_token_deposit(&realm_cookie)
|
||||
.await;
|
||||
|
||||
token_owner_record_cookie.token_owner = token_owner_record_cookie2.token_owner;
|
||||
|
||||
// Act
|
||||
let err = governance_test
|
||||
.cancel_proposal(&proposal_cookie, &token_owner_record_cookie)
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
|
||||
// Assert
|
||||
|
||||
assert_eq!(
|
||||
err,
|
||||
GovernanceError::GoverningTokenOwnerOrDelegateMustSign.into()
|
||||
);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue