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/**'
|
- 'docs/**'
|
||||||
|
|
||||||
jobs:
|
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:
|
all_github_action_checks:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: check_non_docs
|
|
||||||
if: needs.check_non_docs.outputs.run_all_github_action_checks == 'true'
|
|
||||||
steps:
|
steps:
|
||||||
- run: echo "Done"
|
- 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
|
- rustfmt
|
||||||
- clippy
|
- clippy
|
||||||
- cargo-build-test
|
- cargo-build-test
|
||||||
|
- js-test-token
|
||||||
|
- js-test-token-swap
|
||||||
|
- js-test-token-lending
|
||||||
|
- fuzz
|
||||||
steps:
|
steps:
|
||||||
- run: echo "Done"
|
- run: echo "Done"
|
||||||
|
|
||||||
|
@ -124,3 +128,135 @@ jobs:
|
||||||
|
|
||||||
- name: Build and test
|
- name: Build and test
|
||||||
run: ./ci/cargo-build-test.sh
|
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_target
|
||||||
hfuzz_workspace
|
hfuzz_workspace
|
||||||
**/*.so
|
**/*.so
|
||||||
**/.DS_Store
|
|
||||||
test-ledger
|
|
||||||
|
|
13
.mergify.yml
13
.mergify.yml
|
@ -4,21 +4,10 @@
|
||||||
#
|
#
|
||||||
# https://doc.mergify.io/
|
# https://doc.mergify.io/
|
||||||
pull_request_rules:
|
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
|
- name: automatic merge (squash) on CI success
|
||||||
conditions:
|
conditions:
|
||||||
- check-success=Travis CI - Pull Request
|
- check-success=Travis CI - Pull Request
|
||||||
- check-success=all_github_action_checks
|
- check-success=all_github_action_checks
|
||||||
- "#status-failure=0"
|
|
||||||
- "#status-neutral=0"
|
|
||||||
- label=automerge
|
- label=automerge
|
||||||
- author≠@dont-squash-my-commits
|
- author≠@dont-squash-my-commits
|
||||||
actions:
|
actions:
|
||||||
|
@ -29,8 +18,6 @@ pull_request_rules:
|
||||||
conditions:
|
conditions:
|
||||||
- check-success=Travis CI - Pull Request
|
- check-success=Travis CI - Pull Request
|
||||||
- check-success=all_github_action_checks
|
- check-success=all_github_action_checks
|
||||||
- "#status-failure=0"
|
|
||||||
- "#status-neutral=0"
|
|
||||||
- label=automerge
|
- label=automerge
|
||||||
- author=@dont-squash-my-commits
|
- author=@dont-squash-my-commits
|
||||||
actions:
|
actions:
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,16 +9,14 @@ members = [
|
||||||
"examples/rust/transfer-lamports",
|
"examples/rust/transfer-lamports",
|
||||||
"feature-proposal/program",
|
"feature-proposal/program",
|
||||||
"feature-proposal/cli",
|
"feature-proposal/cli",
|
||||||
"governance/program",
|
|
||||||
"libraries/math",
|
"libraries/math",
|
||||||
"memo/program",
|
"memo/program",
|
||||||
"name-service/program",
|
|
||||||
"record/program",
|
"record/program",
|
||||||
"shared-memory/program",
|
"shared-memory/program",
|
||||||
"stake-pool/cli",
|
"stake-pool/cli",
|
||||||
"stake-pool/program",
|
"stake-pool/program",
|
||||||
"token-lending/cli",
|
|
||||||
"token-lending/program",
|
"token-lending/program",
|
||||||
|
"token-lending/client",
|
||||||
"token-swap/program",
|
"token-swap/program",
|
||||||
"token-swap/program/fuzz",
|
"token-swap/program/fuzz",
|
||||||
"token/cli",
|
"token/cli",
|
||||||
|
@ -31,6 +29,3 @@ exclude = [
|
||||||
"themis/program_ristretto",
|
"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
|
"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 = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.7.3"
|
solana-program = "1.6.2"
|
||||||
spl-token = { version = "3.1", path = "../../token/program", features = ["no-entrypoint"] }
|
spl-token = { version = "3.1", path = "../../token/program", features = ["no-entrypoint"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.7.3"
|
solana-program-test = "1.6.2"
|
||||||
solana-sdk = "1.7.3"
|
solana-sdk = "1.6.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub fn get_associated_token_address(
|
||||||
wallet_address: &Pubkey,
|
wallet_address: &Pubkey,
|
||||||
spl_token_mint_address: &Pubkey,
|
spl_token_mint_address: &Pubkey,
|
||||||
) -> 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(
|
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 rent_sysvar_info = next_account_info(account_info_iter)?;
|
||||||
|
|
||||||
let (associated_token_address, bump_seed) = get_associated_token_address_and_bump_seed_internal(
|
let (associated_token_address, bump_seed) = get_associated_token_address_and_bump_seed_internal(
|
||||||
wallet_account_info.key,
|
&wallet_account_info.key,
|
||||||
spl_token_mint_info.key,
|
&spl_token_mint_info.key,
|
||||||
program_id,
|
program_id,
|
||||||
spl_token_program_id,
|
&spl_token_program_id,
|
||||||
);
|
);
|
||||||
if associated_token_address != *associated_token_account_info.key {
|
if associated_token_address != *associated_token_account_info.key {
|
||||||
msg!("Error: Associated address does not match seed derivation");
|
msg!("Error: Associated address does not match seed derivation");
|
||||||
|
@ -62,7 +62,7 @@ pub fn process_instruction(
|
||||||
);
|
);
|
||||||
invoke(
|
invoke(
|
||||||
&system_instruction::transfer(
|
&system_instruction::transfer(
|
||||||
funder_info.key,
|
&funder_info.key,
|
||||||
associated_token_account_info.key,
|
associated_token_account_info.key,
|
||||||
required_lamports,
|
required_lamports,
|
||||||
),
|
),
|
||||||
|
@ -84,23 +84,23 @@ pub fn process_instruction(
|
||||||
associated_token_account_info.clone(),
|
associated_token_account_info.clone(),
|
||||||
system_program_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");
|
msg!("Assign the associated token account to the SPL Token program");
|
||||||
invoke_signed(
|
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(),
|
associated_token_account_info.clone(),
|
||||||
system_program_info.clone(),
|
system_program_info.clone(),
|
||||||
],
|
],
|
||||||
&[associated_token_account_signer_seeds],
|
&[&associated_token_account_signer_seeds],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
msg!("Initialize the associated token account");
|
msg!("Initialize the associated token account");
|
||||||
invoke(
|
invoke(
|
||||||
&spl_token::instruction::initialize_account(
|
&spl_token::instruction::initialize_account(
|
||||||
spl_token_program_id,
|
&spl_token_program_id,
|
||||||
associated_token_account_info.key,
|
associated_token_account_info.key,
|
||||||
spl_token_mint_info.key,
|
spl_token_mint_info.key,
|
||||||
wallet_account_info.key,
|
wallet_account_info.key,
|
||||||
|
|
|
@ -13,16 +13,16 @@ test-bpf = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
num-derive = "0.3"
|
num-derive = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
solana-program = "1.7.3"
|
solana-program = "1.6.2"
|
||||||
spl-token = { version = "3.1", path = "../../token/program", features = [ "no-entrypoint" ] }
|
spl-token = { version = "3.0", path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
uint = "0.8"
|
uint = "0.8"
|
||||||
arbitrary = { version = "0.4", features = ["derive"], optional = true }
|
arbitrary = { version = "0.4", features = ["derive"], optional = true }
|
||||||
borsh = "0.8.2"
|
borsh = "0.8.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.7.3"
|
solana-program-test = "1.6.2"
|
||||||
solana-sdk = "1.7.3"
|
solana-sdk = "1.6.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -421,7 +421,7 @@ impl Processor {
|
||||||
authority_account_info.clone(),
|
authority_account_info.clone(),
|
||||||
user_transfer_authority_info.clone(),
|
user_transfer_authority_info.clone(),
|
||||||
amount,
|
amount,
|
||||||
pool_account_info.key,
|
&pool_account_info.key,
|
||||||
pool.bump_seed,
|
pool.bump_seed,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -446,7 +446,7 @@ impl Processor {
|
||||||
authority_account_info.clone(),
|
authority_account_info.clone(),
|
||||||
user_transfer_authority_info.clone(),
|
user_transfer_authority_info.clone(),
|
||||||
amount,
|
amount,
|
||||||
pool_account_info.key,
|
&pool_account_info.key,
|
||||||
pool.bump_seed,
|
pool.bump_seed,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -477,7 +477,7 @@ impl Processor {
|
||||||
authority_account_info.clone(),
|
authority_account_info.clone(),
|
||||||
user_transfer_authority_info.clone(),
|
user_transfer_authority_info.clone(),
|
||||||
possible_withdraw_amount,
|
possible_withdraw_amount,
|
||||||
pool_account_info.key,
|
&pool_account_info.key,
|
||||||
pool.bump_seed,
|
pool.bump_seed,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -489,7 +489,7 @@ impl Processor {
|
||||||
authority_account_info.clone(),
|
authority_account_info.clone(),
|
||||||
user_transfer_authority_info.clone(),
|
user_transfer_authority_info.clone(),
|
||||||
amount,
|
amount,
|
||||||
pool_account_info.key,
|
&pool_account_info.key,
|
||||||
pool.bump_seed,
|
pool.bump_seed,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,10 @@ set -x
|
||||||
# Build all C examples
|
# Build all C examples
|
||||||
make -C examples/c
|
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
|
# Build/test all host crates
|
||||||
cargo +"$rust_stable" build
|
cargo +"$rust_stable" build
|
||||||
cargo +"$rust_stable" test -- --nocapture
|
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.
|
# client_ristretto disabled because it requires RpcBanksService, which is no longer supported.
|
||||||
#cargo +"$rust_stable" test --manifest-path=themis/client_ristretto/Cargo.toml -- --nocapture
|
#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
|
# # Check generated C headers
|
||||||
# cargo run --manifest-path=utils/cgen/Cargo.toml
|
# 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")/.."
|
cd "$(dirname "$0")/.."
|
||||||
source ./ci/solana-version.sh install
|
source ./ci/solana-version.sh install
|
||||||
|
|
||||||
|
(cd token/js && npm install)
|
||||||
|
|
||||||
cd token-swap/js
|
cd token-swap/js
|
||||||
npm install
|
npm install
|
||||||
npm run lint
|
npm run lint
|
||||||
npm run build
|
npm run flow
|
||||||
|
npx tsc module.d.ts
|
||||||
npm run start-with-test-validator
|
npm run start-with-test-validator
|
||||||
(cd ../../target/deploy && mv spl_token_swap_production.so spl_token_swap.so)
|
(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
|
SWAP_PROGRAM_OWNER_FEE_ADDRESS="HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN" npm run start-with-test-validator
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||||
stable_version="$RUST_STABLE_VERSION"
|
stable_version="$RUST_STABLE_VERSION"
|
||||||
else
|
else
|
||||||
stable_version=1.53.0
|
stable_version=1.50.0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||||
else
|
else
|
||||||
nightly_version=2021-06-09
|
nightly_version=2021-02-18
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,11 @@
|
||||||
if [[ -n $SOLANA_VERSION ]]; then
|
if [[ -n $SOLANA_VERSION ]]; then
|
||||||
solana_version="$SOLANA_VERSION"
|
solana_version="$SOLANA_VERSION"
|
||||||
else
|
else
|
||||||
solana_version=v1.7.3
|
solana_version=v1.5.15
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export solana_version="$solana_version"
|
export solana_version="$solana_version"
|
||||||
|
export solana_docker_image=solanalabs/solana:"$solana_version"
|
||||||
export PATH="$HOME"/.local/share/solana/install/active_release/bin:"$PATH"
|
export PATH="$HOME"/.local/share/solana/install/active_release/bin:"$PATH"
|
||||||
|
|
||||||
if [[ -n $1 ]]; then
|
if [[ -n $1 ]]; then
|
||||||
|
|
|
@ -8,7 +8,6 @@ module.exports = {
|
||||||
"token-lending",
|
"token-lending",
|
||||||
"associated-token-account",
|
"associated-token-account",
|
||||||
"memo",
|
"memo",
|
||||||
"name-service",
|
|
||||||
"shared-memory",
|
"shared-memory",
|
||||||
"stake-pool",
|
"stake-pool",
|
||||||
"feature-proposal",
|
"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.
|
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
|
```ts
|
||||||
import { PublicKey } from '@solana/web3.js';
|
import { PublicKey } from '@solana/web3.js';
|
||||||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
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
|
For more information about exposing program logs on a node, head to the
|
||||||
[developer
|
[developer
|
||||||
docs](https://docs.solana.com/developing/on-chain-programs/debugging#logging)
|
docs](https://docs.solana.com/developing/deployed-programs/debugging#logging)
|
||||||
|
|
||||||
### Compute Limits
|
### 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 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.
|
to maximize censorship resistance and rewards.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
@ -14,7 +14,7 @@ inflation rate, total number of SOL staked on the network, and an individual
|
||||||
validator’s uptime and commission (fee).
|
validator’s uptime and commission (fee).
|
||||||
|
|
||||||
Stake pools are an alternative method of earning staking rewards. This on-chain
|
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.
|
stake and earn rewards without managing stakes.
|
||||||
|
|
||||||
Additional information regarding staking and stake programming is available at:
|
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
|
## Motivation
|
||||||
|
|
||||||
This document is intended for the main actors of the stake pool system:
|
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
|
||||||
* manager: creates and manages the stake pool, earns fees, can update the fee, staker, and manager
|
pool.
|
||||||
* staker: adds and removes validators to the pool, rebalances stake among validators
|
|
||||||
* user: provides staked SOL into an existing stake pool
|
|
||||||
|
|
||||||
In its current iteration, the stake pool only processes totally active stakes.
|
In its current iteration, the stake pool only processes totally active stakes.
|
||||||
Deposits must come from fully active stakes, and withdrawals return a fully
|
Deposits must come from fully active stakes, and withdrawals return a fully
|
||||||
active stake account.
|
active stake account.
|
||||||
|
|
||||||
This means that stake pool managers, stakers, and users must be comfortable with
|
This means that stake pool managers and users must be comfortable with creating
|
||||||
creating and delegating stakes, which are more advanced operations than sending and
|
and delegating stakes, which are more advanced operations than sending and
|
||||||
receiving SPL tokens and SOL. Additional information on stake operations are
|
receiving SPL tokens and SOL. Additional information on stake operations are
|
||||||
available at:
|
available at:
|
||||||
|
|
||||||
|
@ -48,28 +46,27 @@ like [Token Swap](token-swap.md).
|
||||||
|
|
||||||
## Operation
|
## 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
|
receive delegations from the pool by creating "validator stake accounts" and
|
||||||
activating a delegation on them. Once a validator stake account's delegation is
|
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
|
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
|
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
|
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
|
representing their fractional ownership in pool. A percentage of the user's
|
||||||
earned by the pool goes to the pool manager as a fee.
|
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,
|
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.
|
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
|
The stake pool manager 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
|
withdrawing stakes from the pool, deactivating them, reactivating them on another
|
||||||
pool's reserve account, then increasing the stake on another validator.
|
validator, then depositing back into the pool.
|
||||||
|
|
||||||
The staker operation to add a new validator requires roughly 1.003 SOL to create
|
These manager operations require SPL staking derivatives and staked SOL, so the
|
||||||
the stake account on a validator, so the stake pool staker will need liquidity
|
stake pool manager will need liquidity on hand to properly manage the pool.
|
||||||
on hand to fully manage the pool stakes.
|
|
||||||
|
|
||||||
## Background
|
## 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
|
See [Solana clusters](https://docs.solana.com/clusters) for cluster-specific RPC URLs
|
||||||
```sh
|
```sh
|
||||||
solana config set --url https://api.devnet.solana.com
|
solana config set --url https://devnet.solana.com
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Default Keypair
|
#### 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/
|
solana config set --keypair usb://ledger/
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Run Locally
|
### Stake Pool Administrator Examples
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
#### Create a stake pool
|
#### Create a stake pool
|
||||||
|
|
||||||
The stake pool manager controls the stake pool from a high level, and in exchange
|
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 manager
|
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 and a maximum of 1000
|
sets the fee on creation. Let's create a pool with a 3% fee:
|
||||||
validator stake accounts:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool create-pool --fee-numerator 3 --fee-denominator 100 --max-validators 1000
|
$ spl-stake-pool create-pool --fee-numerator 3 --fee-denominator 100
|
||||||
Creating reserve stake 33Hg3bvYrAwfqCzTMjAWZNAWC6H96qJNEdzGamfFjG4J
|
Creating mint Gmk71cM7j2RMorRsQrsyysM4HsByQx5PuDGtDdqGLWCS
|
||||||
Creating mint D5yiK1tE1yAXBnrV9ZrSUJCw8WiQctZ8ekbv1U6ATVZ
|
Creating pool fee collection account 3xvXPfQi2SaTkqPV9A7BQwh4GyTe2ZPasfoaCBCnTAJ5
|
||||||
Creating pool fee collection account 5gpuSdutGY98KKbgmR5CfLK7toFcQD69JzKDwseegzXE
|
Creating stake pool 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||||
Signature: 2dvCtHMcqxibckhvVgFQeFCRb7VcHbuFLRf71Aqd9PtzFzdbG3gAkNpxYznfpKDx2vTRrVtwW81sZAx5U3Frb5Uu
|
Signature: 5HdDoPssqwyLjt2QvhRbnSATZqFLGKha92zMuJiBUpKeKYKGURRV41N5ydCQxqnFjCud3xv85Z6ghErppNJzaYM8
|
||||||
Creating stake pool EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
|
||||||
Signature: 2kYDVyJp8FVrLmEZyW9ivMYcXEsgWm4hFyhp5omxVtonjhYG6WS1S85sPTCdsQWe3idof6ZqsY8F3oaMXwrEkAYK
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The unique stake pool identifier is `EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1`.
|
The unique stake pool identifier is `3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC`.
|
||||||
|
|
||||||
The identifier for the SPL token for staking derivatives is
|
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.
|
over the mint.
|
||||||
|
|
||||||
The pool creator's fee account identifier is
|
The pool creator's fee account identifier is
|
||||||
`5gpuSdutGY98KKbgmR5CfLK7toFcQD69JzKDwseegzXE`. Every epoch, as stake accounts
|
`3xvXPfQi2SaTkqPV9A7BQwh4GyTe2ZPasfoaCBCnTAJ5`. When users deposit warmed up
|
||||||
in the stake pool earn rewards, the program will mint SPL token staking derivatives
|
stake accounts into the stake pool, the program will transfer 3% of their
|
||||||
equal to 3% of the gains on that epoch into this account. If no gains were observed,
|
contribution into this account in the form of SPL token staking derivatives.
|
||||||
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
|
|
||||||
|
|
||||||
#### Create a validator stake account
|
#### 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.
|
delegated to that vote account.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||||
Creating stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
Creating stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||||
Signature: 4pA2WKT6d2wkXEtSpiQswv22WyoFad2KX6FdPEzwBiEquvaUBEtzenys5Jh1ABPCh7yc4w8kzqMRRCwDj6ZSUV1K
|
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.
|
many validators as possible, so let's add a few more.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz
|
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz
|
||||||
Creating stake account E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie
|
Creating stake account E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie
|
||||||
Signature: 4pyRZzjsWG7jP3GRZeZCo2Eb2TPjHM4kAYRFMivimme6HAee1nhzoNJBe3VSt2sv7acp5fwT7J8omBM8o3niY8gu
|
Signature: 4pyRZzjsWG7jP3GRZeZCo2Eb2TPjHM4kAYRFMivimme6HAee1nhzoNJBe3VSt2sv7acp5fwT7J8omBM8o3niY8gu
|
||||||
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
||||||
Creating stake account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
Creating stake account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
||||||
Signature: 4ZUdZzUARgUCPuY8nVsJbN6vRDbVX8sYAQGYYXj2YVvjoJ2oevq2H8uzrhYApe419uoP7QYukqNstiti5p5DDukN
|
Signature: 4ZUdZzUARgUCPuY8nVsJbN6vRDbVX8sYAQGYYXj2YVvjoJ2oevq2H8uzrhYApe419uoP7QYukqNstiti5p5DDukN
|
||||||
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm
|
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm
|
||||||
Creating stake account FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13
|
Creating stake account FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13
|
||||||
Signature: yQqXCbuA66wQsHtkziNg3XadfZF5aCmvjfentwbZJnSPeEjJwPka3M1QY5GmR1efprptqaePn71BTMSLscX8DLr
|
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.
|
the stake activates, we can add them to the stake pool.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool add-validator EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
$ spl-stake-pool add-validator 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||||
|
Creating account to receive tokens Gu8xqzYFg2sPHWHhUivKNBeF9uikiauihLs9hLzziKu7
|
||||||
Signature: 3N1K89rGV9gWueTTrPGTDBwKAp8BikQhKHMFoREw98Q1piXFeZSSxqfnRQexrfAZQfrpYH9qwsaPWRruwkVeBivV
|
Signature: 3N1K89rGV9gWueTTrPGTDBwKAp8BikQhKHMFoREw98Q1piXFeZSSxqfnRQexrfAZQfrpYH9qwsaPWRruwkVeBivV
|
||||||
```
|
```
|
||||||
|
|
||||||
Users can start depositing their activated stakes into the stake pool, as
|
Users can start depositing their activated stakes into the stake pool, as
|
||||||
long as they are delegated to the same vote account, which was
|
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.
|
double-check that at any time using the Solana command-line utility.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ solana stake-account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
$ solana stake-account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||||
Balance: 0.002282881 SOL
|
Balance: 0.002282881 SOL
|
||||||
Rent Exempt Reserve: 0.00228288 SOL
|
Rent Exempt Reserve: 0.00228288 SOL
|
||||||
Delegated Stake: 1.000000000 SOL
|
Delegated Stake: 0.000000001 SOL
|
||||||
Active Stake: 1.000000000 SOL
|
Active Stake: 0.000000001 SOL
|
||||||
Activating Stake: 0 SOL
|
Activating Stake: 0 SOL
|
||||||
Stake activates starting from epoch: 161
|
Stake activates starting from epoch: 161
|
||||||
Delegated Vote Account Address: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
Delegated Vote Account Address: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||||
|
@ -341,31 +260,26 @@ Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
|
|
||||||
#### Remove validator stake account
|
#### Remove validator stake account
|
||||||
|
|
||||||
If the stake pool staker wants to stop delegating to a vote account, they can
|
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.
|
totally remove the validator stake account from the stake pool by providing
|
||||||
|
staking derivatives, just like `withdraw`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool remove-validator EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
$ spl-stake-pool remove-validator 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||||
Signature: 5rrQ3xhDWyiPkUTAQkNAeq31n6sMf1xsg2x9hVY8Vj1NonwBnhxuTv87nADLkwC8Xzc4CGTNCTX2Vph9esWnXk2d
|
Signature: 5rrQ3xhDWyiPkUTAQkNAeq31n6sMf1xsg2x9hVY8Vj1NonwBnhxuTv87nADLkwC8Xzc4CGTNCTX2Vph9esWnXk2d
|
||||||
```
|
```
|
||||||
|
|
||||||
The difference with `withdraw` is that the validator stake account is totally
|
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
|
removed from the stake pool and now belongs to the administrator.
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
We can check the removed stake account:
|
We can check the removed stake account:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ solana stake-account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
$ solana stake-account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
||||||
Balance: 1.002282880 SOL
|
Balance: 1.002282881 SOL
|
||||||
Rent Exempt Reserve: 0.00228288 SOL
|
Rent Exempt Reserve: 0.00228288 SOL
|
||||||
Delegated Stake: 1.000000000 SOL
|
Delegated Stake: 1.000000001 SOL
|
||||||
Active Stake: 1.000000000 SOL
|
Active Stake: 1.000000001 SOL
|
||||||
Delegated Vote Account Address: AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
Delegated Vote Account Address: AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
||||||
Stake Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
Stake Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
Withdraw 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:
|
We can also double-check that the stake pool no longer shows the stake account:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||||
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
||||||
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
||||||
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
||||||
|
@ -386,14 +300,14 @@ Total: ◎15.849959206
|
||||||
|
|
||||||
#### Rebalance the stake pool
|
#### Rebalance the stake pool
|
||||||
|
|
||||||
As time goes on, users will deposit to and withdraw from all of the stake accounts
|
As time goes on, deposits and withdrawals will happen to all of the stake accounts
|
||||||
managed by the pool, and the stake pool staker may want to rebalance the stakes.
|
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:
|
in the pool. When they look at the state of the pool, they see:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||||
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
||||||
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
||||||
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
||||||
|
@ -401,63 +315,75 @@ Total: ◎15.849959206
|
||||||
```
|
```
|
||||||
|
|
||||||
This isn't great! The last stake account, `E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`
|
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
|
SOL to be distributed evenly, meaning around `5.283319735` in each account. They need
|
||||||
to move `4.281036854` to `FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13` and
|
to move `4.281036854` to `FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13` and
|
||||||
`1.872447062` to `FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN`.
|
`1.872447062` to `FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN`.
|
||||||
|
|
||||||
##### Decrease validator stake
|
First, they need to withdraw a total of `6.153483916` from
|
||||||
|
`E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`. Using the `spl-token` utility,
|
||||||
First, they need to decrease the amount on stake account
|
let's check the total supply of pool tokens:
|
||||||
`E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`, delegated to
|
|
||||||
`HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz`, by total of `6.153483916` SOL.
|
|
||||||
|
|
||||||
They decrease that amount of SOL:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool decrease-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz 6.153483916
|
$ spl-token supply Gmk71cM7j2RMorRsQrsyysM4HsByQx5PuDGtDdqGLWCS
|
||||||
Signature: ZpQGwT85rJ8Y9afdkXhKo3TVv4xgTz741mmZj2vW7mihYseAkFsazWxza2y8eNGY4HDJm15c1cStwyiQzaM3RpH
|
0.034692168
|
||||||
```
|
```
|
||||||
|
|
||||||
Internally, this instruction splits and deactivates 6.153483916 SOL from the
|
Given a total pool token supply of `0.034692168` and total staked SOL amount of
|
||||||
validator stake account `E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie` into a
|
`15.849959206`, let's calculate how many pool tokens to withdraw from the pool:
|
||||||
transient stake account, owned and managed entirely by the stake 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,
|
sol_to_withdraw * total_pool_tokens / total_sol_staked = pool_tokens_to_withdraw
|
||||||
also entirely owned and managed by the stake pool.
|
6.153483916 * 0.034692168 / 15.849959206 ~ 0.013468659
|
||||||
|
|
||||||
##### 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And they add 1.872447062 SOL to `2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3`:
|
They withdraw that amount of pool tokens:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool increase-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 1.872447062
|
$ spl-stake-pool withdraw 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --amount 0.013468659 --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||||
Signature: 4zaKYu3MQ3as8reLbuHKaXN8FNaHvpHuiZtsJeARo67UKMo6wUUoWE88Fy8N4EYQYicuwULTNffcUD3a9jY88PoU
|
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
|
Because of rounding in the calculation a few lines above, it looks like we receive
|
||||||
stake pool splits from the reserve stake, into the transient stake account,
|
less than we should. If we play that back the other way, we'll see that all is well:
|
||||||
then activates it to the appropriate validator.
|
|
||||||
|
|
||||||
One to two epochs later, once the transient stakes activate, the `update` command
|
```
|
||||||
automatically merges the transient stakes into the validator stake account, leaving
|
pool_tokens_to_withdraw * total_sol_staked / total_pool_tokens = sol_to_withdraw
|
||||||
a fully rebalanced stake pool:
|
0.013468659 * 15.849959206 / 0.034692168 ~ 6.153483855
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, they deactivate the new received stake:
|
||||||
|
|
||||||
```sh
|
```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: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎5.283340235
|
||||||
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎5.283612231
|
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎5.283612231
|
||||||
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎5.284317422
|
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
|
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
|
### 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.
|
accounts are already associated with the stake pool.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||||
CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E 1.002282880 SOL
|
CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E 1.002282880 SOL
|
||||||
E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie 1.002282880 SOL
|
E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie 1.002282880 SOL
|
||||||
FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN 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.
|
accounts present yet, the command-line utility will inform us.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||||
No accounts found.
|
No accounts found.
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Deposit stake
|
#### 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
|
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
|
stake pool. Using the `list` command from the previous section, we see that
|
||||||
`2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3` is a valid vote account, so let's
|
`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.
|
rewards, we can deposit the stake into the stake pool.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool deposit EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa
|
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa
|
||||||
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||||
Creating account to receive tokens 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
Creating account to receive tokens 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||||
Signature: 4AESGZzqBVfj5xQnMiPWAwzJnAtQDRFK1Ha6jqKKTs46Zm5fw3LqgU1mRAT6CKTywVfFMHZCLm1hcQNScSMwVvjQ
|
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
|
Alternatively, you can create an SPL token account yourself and pass it as the
|
||||||
`token-receiver` for the command.
|
`token-receiver` for the command.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool deposit EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||||
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||||
Signature: 4AESGZzqBVfj5xQnMiPWAwzJnAtQDRFK1Ha6jqKKTs46Zm5fw3LqgU1mRAT6CKTywVfFMHZCLm1hcQNScSMwVvjQ
|
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.
|
the total value managed by the stake pool every epoch.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
$ spl-stake-pool update 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||||
Updating stake pool...
|
|
||||||
Signature: 3Yx1RH3Afqj5ckX8YvPCRt1DudVP4HuRPkh1dBPvTM9GqGxcB9ZXHGZPADVSZiaqKi166fevMG232EWxrRWswPtt
|
Signature: 3Yx1RH3Afqj5ckX8YvPCRt1DudVP4HuRPkh1dBPvTM9GqGxcB9ZXHGZPADVSZiaqKi166fevMG232EWxrRWswPtt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -564,33 +513,13 @@ If another user already updated the stake pool balance for the current epoch, we
|
||||||
see a different output.
|
see a different output.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
$ spl-stake-pool update 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
||||||
Update not required
|
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
|
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
|
instructions will fail. The update instruction is permissionless, so any user
|
||||||
can run it before depositing or withdrawing. As a convenience, the CLI attempts
|
can run it before depositing or withdrawing.
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Withdraw stake
|
#### 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.
|
Let's withdraw 0.02 staking derivative tokens from the stake pool.
|
||||||
|
|
||||||
```sh
|
```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
|
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||||
Creating account to receive stake CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
Creating account to receive stake CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||||
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
||||||
|
@ -621,58 +550,15 @@ Stake Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, the user can specify an existing uninitialized stake account to
|
Alternatively, the user can specify an existing stake account to receive their
|
||||||
receive their stake using the `--stake-receiver` parameter.
|
stake using the `stake-receiver` parameter.
|
||||||
|
|
||||||
```sh
|
```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
|
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||||
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
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
|
## Appendix
|
||||||
|
|
||||||
### Activated stakes
|
### 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
|
time cost of staking. Otherwise, malicious actors can deposit stake in one state
|
||||||
and withdraw it in another state without waiting.
|
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
|
### Staking Credits Observed on Deposit
|
||||||
|
|
||||||
A deposited stake account's "credits observed" must match the destination
|
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
|
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
|
#### 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/
|
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
|
### Example: Creating your own fungible token
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -118,7 +109,7 @@ Signature: 42Sa5eK9dMEQyvD9GMHuKxXf55WLZ7tfjabUKDhNoZRAxj9MsnN7omriWMEHXLea3aYpj
|
||||||
|
|
||||||
`7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi` is now an empty account:
|
`7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi` is now an empty account:
|
||||||
```sh
|
```sh
|
||||||
$ spl-token balance AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
$ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||||
0
|
0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -135,7 +126,7 @@ The token `supply` and account `balance` now reflect the result of minting:
|
||||||
```sh
|
```sh
|
||||||
$ spl-token supply AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
$ spl-token supply AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
||||||
100
|
100
|
||||||
$ spl-token balance AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
$ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||||
100
|
100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -175,7 +166,7 @@ address by running `solana address` and provides it to the sender.
|
||||||
|
|
||||||
The sender then runs:
|
The sender then runs:
|
||||||
```
|
```
|
||||||
$ spl-token transfer AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
$ spl-token transfer 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||||
Transfer 50 tokens
|
Transfer 50 tokens
|
||||||
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||||
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
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
|
The sender then runs to fund the receiver's associated token account, at the
|
||||||
sender's expense, and then transfers 50 tokens into it:
|
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
|
Transfer 50 tokens
|
||||||
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||||
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
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.
|
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
|
Creating account CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
|
||||||
Signature: 4yPWj22mbyLu5mhfZ5WATNfYzTt5EQ7LGzryxM7Ufu7QCVjTE7czZdEBqdKR7vjKsfAqsBdjU58NJvXrTqCXvfWW
|
Signature: 4yPWj22mbyLu5mhfZ5WATNfYzTt5EQ7LGzryxM7Ufu7QCVjTE7czZdEBqdKR7vjKsfAqsBdjU58NJvXrTqCXvfWW
|
||||||
```
|
```
|
||||||
|
@ -237,9 +228,9 @@ CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe
|
||||||
|
|
||||||
### Example: Create a non-fungible token
|
### 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
|
Creating token 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
||||||
Signature: 4kz82JUey1B9ki1McPW7NYv1NqPKCod6WNptSkYqtuiEsQb9exHaktSAHJJsm4YxuGNW4NugPJMFX9ee6WA2dXts
|
Signature: 4kz82JUey1B9ki1McPW7NYv1NqPKCod6WNptSkYqtuiEsQb9exHaktSAHJJsm4YxuGNW4NugPJMFX9ee6WA2dXts
|
||||||
```
|
```
|
||||||
|
@ -273,7 +264,7 @@ Now the `7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM` account holds the
|
||||||
one and only `559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z` token:
|
one and only `559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z` token:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ spl-token account-info 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
$ spl-token account-info 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
||||||
|
|
||||||
Address: 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
Address: 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
||||||
Balance: 1
|
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.
|
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
|
### 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
|
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
|
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)
|
[mint](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L88)
|
||||||
address within each token account.
|
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
|
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
|
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)
|
[owner](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L90)
|
||||||
address within each token account.
|
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.
|
hold a balance before allowing the transfer.
|
||||||
|
|
||||||
### Registry for token details
|
### Registry for token details
|
||||||
At the moment there exist two solutions for Token Mint registries:
|
At the moment Token Mint addresses need to be hard coded by each wallet. **Improving this situation is a work in progress.**
|
||||||
|
|
||||||
* 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.**
|
|
||||||
|
|
||||||
### Garbage Collecting Ancillary Token Accounts
|
### Garbage Collecting Ancillary Token Accounts
|
||||||
Wallets should empty ancillary token accounts as quickly as practical by
|
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.
|
They can be cleaned up during the next send operation.
|
||||||
|
|
||||||
The `spl-token gc` command provides an example implementation of this cleanup process.
|
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)}};
|
const SolSignerSeeds signers_seeds[] = {{seeds, SOL_ARRAY_SIZE(seeds)}};
|
||||||
|
|
||||||
SolPubkey expected_allocated_key;
|
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,
|
params->program_id,
|
||||||
&expected_allocated_key)) {
|
&expected_allocated_key)) {
|
||||||
return ERROR_INVALID_INSTRUCTION_DATA;
|
return ERROR_INVALID_INSTRUCTION_DATA;
|
||||||
|
@ -31,7 +31,8 @@ extern uint64_t do_invoke(SolParameters *params) {
|
||||||
return ERROR_INVALID_ARGUMENT;
|
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
|
uint8_t data[4 + 8]; // Enough room for the Allocate instruction
|
||||||
*(uint16_t *)data = 8; // Allocate instruction enum value
|
*(uint16_t *)data = 8; // Allocate instruction enum value
|
||||||
*(uint64_t *)(data + 4) = SIZE; // Size to allocate
|
*(uint64_t *)(data + 4) = SIZE; // Size to allocate
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
# Program examples written in Rust
|
# Program examples written in Rust
|
||||||
|
|
||||||
The examples in this directory demonstrate various Solana program mechanisms.
|
The examples in this directory demonstrate various Solana program mechanisms.
|
||||||
|
|
|
@ -13,11 +13,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.7.3"
|
solana-program = "1.6.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.7.3"
|
solana-program-test = "1.6.2"
|
||||||
solana-sdk = "1.7.3"
|
solana-sdk = "1.6.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -15,11 +15,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.7.3"
|
solana-program = "1.6.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.7.3"
|
solana-program-test = "1.6.2"
|
||||||
solana-sdk = "1.7.3"
|
solana-sdk = "1.6.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -8,7 +8,7 @@ use {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_custom_heap() {
|
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(
|
let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
|
||||||
"spl_example_custom_heap",
|
"spl_example_custom_heap",
|
||||||
program_id,
|
program_id,
|
||||||
|
|
|
@ -13,11 +13,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.7.3"
|
solana-program = "1.6.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.7.3"
|
solana-program-test = "1.6.2"
|
||||||
solana-sdk = "1.7.3"
|
solana-sdk = "1.6.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -11,7 +11,7 @@ use {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_logging() {
|
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(
|
let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
|
||||||
"spl_example_logging",
|
"spl_example_logging",
|
||||||
program_id,
|
program_id,
|
||||||
|
|
|
@ -13,11 +13,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.7.3"
|
solana-program = "1.6.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.7.3"
|
solana-program-test = "1.6.2"
|
||||||
solana-sdk = "1.7.3"
|
solana-sdk = "1.6.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -17,28 +17,24 @@ pub fn process_instruction(
|
||||||
// Create in iterator to safety reference accounts in the slice
|
// Create in iterator to safety reference accounts in the slice
|
||||||
let account_info_iter = &mut accounts.iter();
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
|
||||||
// Get the clock sysvar via syscall
|
// The first account is the clock sysvar
|
||||||
let clock_via_sysvar = Clock::get()?;
|
|
||||||
// Or deserialize the account into a clock struct
|
|
||||||
let clock_sysvar_info = next_account_info(account_info_iter)?;
|
let clock_sysvar_info = next_account_info(account_info_iter)?;
|
||||||
let clock_via_account = Clock::from_account_info(clock_sysvar_info)?;
|
// The second account is the rent sysvar
|
||||||
// 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
|
|
||||||
let rent_sysvar_info = next_account_info(account_info_iter)?;
|
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
|
// Deserialize the account into a clock struct
|
||||||
assert_eq!(rent_via_sysvar, rent_via_account);
|
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
|
// Can't print `exemption_threshold` because BPF does not support printing floats
|
||||||
msg!(
|
msg!(
|
||||||
"Rent: lamports_per_byte_year: {:?}, burn_percent: {:?}",
|
"Rent: lamports_per_byte_year: {:?}, burn_percent: {:?}",
|
||||||
rent_via_sysvar.lamports_per_byte_year,
|
rent.lamports_per_byte_year,
|
||||||
rent_via_sysvar.burn_percent
|
rent.burn_percent
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -12,7 +12,7 @@ use {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_sysvar() {
|
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(
|
let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
|
||||||
"spl_example_sysvar",
|
"spl_example_sysvar",
|
||||||
program_id,
|
program_id,
|
||||||
|
|
|
@ -12,11 +12,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.7.3"
|
solana-program = "1.6.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.7.3"
|
solana-program-test = "1.6.2"
|
||||||
solana-sdk = "1.7.3"
|
solana-sdk = "1.6.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -11,7 +11,7 @@ use {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_lamport_transfer() {
|
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 source_pubkey = Pubkey::new_unique();
|
||||||
let destination_pubkey = Pubkey::new_unique();
|
let destination_pubkey = Pubkey::new_unique();
|
||||||
let mut program_test = ProgramTest::new(
|
let mut program_test = ProgramTest::new(
|
||||||
|
|
|
@ -10,11 +10,11 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
solana-clap-utils = "1.7.3"
|
solana-clap-utils = "1.6.2"
|
||||||
solana-cli-config = "1.7.3"
|
solana-cli-config = "1.6.2"
|
||||||
solana-client = "1.7.3"
|
solana-client = "1.6.2"
|
||||||
solana-logger = "1.7.3"
|
solana-logger = "1.6.2"
|
||||||
solana-sdk = "1.7.3"
|
solana-sdk = "1.6.2"
|
||||||
spl-feature-proposal = { version = "1.0", path = "../program", features = ["no-entrypoint"] }
|
spl-feature-proposal = { version = "1.0", path = "../program", features = ["no-entrypoint"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -46,7 +46,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.global(true)
|
.global(true)
|
||||||
.help("Configuration file to use");
|
.help("Configuration file to use");
|
||||||
if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
|
if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
|
||||||
arg.default_value(config_file)
|
arg.default_value(&config_file)
|
||||||
} else {
|
} else {
|
||||||
arg
|
arg
|
||||||
}
|
}
|
||||||
|
@ -329,7 +329,7 @@ fn process_propose(
|
||||||
Some(&config.keypair.pubkey()),
|
Some(&config.keypair.pubkey()),
|
||||||
);
|
);
|
||||||
let blockhash = rpc_client.get_recent_blockhash()?.0;
|
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);
|
println!("JSON RPC URL: {}", config.json_rpc_url);
|
||||||
|
|
||||||
|
@ -399,9 +399,10 @@ fn process_tally(
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let feature_proposal = get_feature_proposal(rpc_client, feature_proposal_address)?;
|
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 =
|
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!("Feature Id: {}", feature_id_address);
|
||||||
println!("Acceptance Token Address: {}", acceptance_token_address);
|
println!("Acceptance Token Address: {}", acceptance_token_address);
|
||||||
|
|
|
@ -12,14 +12,15 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
borsh = "0.8"
|
borsh = "0.7.1"
|
||||||
borsh-derive = "0.8.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"] }
|
spl-token = { version = "3.1", path = "../../token/program", features = ["no-entrypoint"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.7.3"
|
futures = "0.3"
|
||||||
solana-sdk = "1.7.3"
|
solana-program-test = "1.6.2"
|
||||||
|
solana-sdk = "1.6.2"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -155,12 +155,13 @@ pub fn tally(feature_proposal_address: &Pubkey) -> Instruction {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::borsh_utils;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_packed_len() {
|
fn test_get_packed_len() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FeatureProposalInstruction::get_packed_len(),
|
FeatureProposalInstruction::get_packed_len(),
|
||||||
solana_program::borsh::get_packed_len::<FeatureProposalInstruction>()
|
borsh_utils::get_packed_len::<FeatureProposalInstruction>()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
pub mod borsh_utils;
|
||||||
mod entrypoint;
|
mod entrypoint;
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
pub mod processor;
|
pub mod processor;
|
||||||
|
|
|
@ -126,7 +126,7 @@ pub fn process_instruction(
|
||||||
mint_info.clone(),
|
mint_info.clone(),
|
||||||
system_program_info.clone(),
|
system_program_info.clone(),
|
||||||
],
|
],
|
||||||
&[mint_signer_seeds],
|
&[&mint_signer_seeds],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
msg!("Initializing mint");
|
msg!("Initializing mint");
|
||||||
|
@ -159,7 +159,7 @@ pub fn process_instruction(
|
||||||
distributor_token_info.clone(),
|
distributor_token_info.clone(),
|
||||||
system_program_info.clone(),
|
system_program_info.clone(),
|
||||||
],
|
],
|
||||||
&[distributor_token_signer_seeds],
|
&[&distributor_token_signer_seeds],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
msg!("Initializing distributor token account");
|
msg!("Initializing distributor token account");
|
||||||
|
@ -193,7 +193,7 @@ pub fn process_instruction(
|
||||||
acceptance_token_info.clone(),
|
acceptance_token_info.clone(),
|
||||||
system_program_info.clone(),
|
system_program_info.clone(),
|
||||||
],
|
],
|
||||||
&[acceptance_token_signer_seeds],
|
&[&acceptance_token_signer_seeds],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
msg!("Initializing acceptance token account");
|
msg!("Initializing acceptance token account");
|
||||||
|
@ -216,7 +216,7 @@ pub fn process_instruction(
|
||||||
&spl_token::instruction::set_authority(
|
&spl_token::instruction::set_authority(
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
acceptance_token_info.key,
|
acceptance_token_info.key,
|
||||||
Some(feature_proposal_info.key),
|
Some(&feature_proposal_info.key),
|
||||||
spl_token::instruction::AuthorityType::CloseAccount,
|
spl_token::instruction::AuthorityType::CloseAccount,
|
||||||
feature_proposal_info.key,
|
feature_proposal_info.key,
|
||||||
&[],
|
&[],
|
||||||
|
@ -231,7 +231,7 @@ pub fn process_instruction(
|
||||||
&spl_token::instruction::set_authority(
|
&spl_token::instruction::set_authority(
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
acceptance_token_info.key,
|
acceptance_token_info.key,
|
||||||
Some(program_id),
|
Some(&program_id),
|
||||||
spl_token::instruction::AuthorityType::AccountOwner,
|
spl_token::instruction::AuthorityType::AccountOwner,
|
||||||
feature_proposal_info.key,
|
feature_proposal_info.key,
|
||||||
&[],
|
&[],
|
||||||
|
@ -260,7 +260,7 @@ pub fn process_instruction(
|
||||||
distributor_token_info.clone(),
|
distributor_token_info.clone(),
|
||||||
spl_token_program_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
|
// Fully fund the feature id account so the `Tally` instruction will not require any
|
||||||
|
@ -283,7 +283,7 @@ pub fn process_instruction(
|
||||||
invoke_signed(
|
invoke_signed(
|
||||||
&system_instruction::allocate(feature_id_info.key, Feature::size_of() as u64),
|
&system_instruction::allocate(feature_id_info.key, Feature::size_of() as u64),
|
||||||
&[feature_id_info.clone(), system_program_info.clone()],
|
&[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(
|
invoke_signed(
|
||||||
&system_instruction::assign(feature_id_info.key, &feature::id()),
|
&system_instruction::assign(feature_id_info.key, &feature::id()),
|
||||||
&[feature_id_info.clone(), system_program_info.clone()],
|
&[feature_id_info.clone(), system_program_info.clone()],
|
||||||
&[feature_id_signer_seeds],
|
&[&feature_id_signer_seeds],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
msg!("Feature proposal accepted");
|
msg!("Feature proposal accepted");
|
||||||
|
|
|
@ -59,12 +59,13 @@ impl Pack for FeatureProposal {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::borsh_utils;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_packed_len() {
|
fn test_get_packed_len() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FeatureProposal::get_packed_len(),
|
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
|
// Mark this test as BPF-only due to current `ProgramTest` limitations when CPIing into the system program
|
||||||
#![cfg(feature = "test-bpf")]
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
use {
|
use futures::{Future, FutureExt};
|
||||||
solana_program::{
|
use solana_program::{
|
||||||
feature::{self, Feature},
|
feature::{self, Feature},
|
||||||
program_option::COption,
|
program_option::COption,
|
||||||
pubkey::Pubkey,
|
program_pack::Pack,
|
||||||
system_program,
|
pubkey::Pubkey,
|
||||||
},
|
system_program,
|
||||||
solana_program_test::*,
|
|
||||||
solana_sdk::{
|
|
||||||
signature::{Keypair, Signer},
|
|
||||||
transaction::Transaction,
|
|
||||||
},
|
|
||||||
spl_feature_proposal::{instruction::*, state::*, *},
|
|
||||||
};
|
};
|
||||||
|
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 {
|
fn program_test() -> ProgramTest {
|
||||||
ProgramTest::new(
|
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]
|
#[tokio::test]
|
||||||
async fn test_basic() {
|
async fn test_basic() {
|
||||||
let feature_proposal = Keypair::new();
|
let feature_proposal = Keypair::new();
|
||||||
|
@ -52,17 +68,16 @@ async fn test_basic() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
// Confirm feature id account is now funded and allocated, but not assigned
|
// 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)
|
.get_account(feature_id_address)
|
||||||
.await
|
.await
|
||||||
.expect("success")
|
.expect("success")
|
||||||
.expect("some account");
|
.expect("some account");
|
||||||
assert_eq!(feature_id_account.owner, system_program::id());
|
assert_eq!(feature_id_acccount.owner, system_program::id());
|
||||||
assert_eq!(feature_id_account.data.len(), Feature::size_of());
|
assert_eq!(feature_id_acccount.data.len(), Feature::size_of());
|
||||||
|
|
||||||
// Confirm mint account state
|
// Confirm mint account state
|
||||||
let mint = banks_client
|
let mint = get_account_data::<spl_token::state::Mint>(&mut banks_client, mint_address)
|
||||||
.get_packed_account_data::<spl_token::state::Mint>(mint_address)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(mint.supply, 42);
|
assert_eq!(mint.supply, 42);
|
||||||
|
@ -71,20 +86,20 @@ async fn test_basic() {
|
||||||
assert_eq!(mint.mint_authority, COption::Some(mint_address));
|
assert_eq!(mint.mint_authority, COption::Some(mint_address));
|
||||||
|
|
||||||
// Confirm distributor token account state
|
// Confirm distributor token account state
|
||||||
let distributor_token = banks_client
|
let distributor_token =
|
||||||
.get_packed_account_data::<spl_token::state::Account>(distributor_token_address)
|
get_account_data::<spl_token::state::Account>(&mut banks_client, distributor_token_address)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(distributor_token.amount, 42);
|
assert_eq!(distributor_token.amount, 42);
|
||||||
assert_eq!(distributor_token.mint, mint_address);
|
assert_eq!(distributor_token.mint, mint_address);
|
||||||
assert_eq!(distributor_token.owner, feature_proposal.pubkey());
|
assert_eq!(distributor_token.owner, feature_proposal.pubkey());
|
||||||
assert!(distributor_token.close_authority.is_none());
|
assert!(distributor_token.close_authority.is_none());
|
||||||
|
|
||||||
// Confirm acceptance token account state
|
// Confirm acceptance token account state
|
||||||
let acceptance_token = banks_client
|
let acceptance_token =
|
||||||
.get_packed_account_data::<spl_token::state::Account>(acceptance_token_address)
|
get_account_data::<spl_token::state::Account>(&mut banks_client, acceptance_token_address)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(acceptance_token.amount, 0);
|
assert_eq!(acceptance_token.amount, 0);
|
||||||
assert_eq!(acceptance_token.mint, mint_address);
|
assert_eq!(acceptance_token.mint, mint_address);
|
||||||
assert_eq!(acceptance_token.owner, id());
|
assert_eq!(acceptance_token.owner, id());
|
||||||
|
@ -100,17 +115,15 @@ async fn test_basic() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
// Confirm feature id account is not yet assigned
|
// 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)
|
.get_account(feature_id_address)
|
||||||
.await
|
.await
|
||||||
.expect("success")
|
.expect("success")
|
||||||
.expect("some account");
|
.expect("some account");
|
||||||
assert_eq!(feature_id_account.owner, system_program::id());
|
assert_eq!(feature_id_acccount.owner, system_program::id());
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
banks_client
|
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
||||||
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
|
||||||
.await,
|
|
||||||
Ok(FeatureProposal::Pending(_))
|
Ok(FeatureProposal::Pending(_))
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -145,18 +158,16 @@ async fn test_basic() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
// Confirm feature id account is now assigned
|
// Confirm feature id account is now assigned
|
||||||
let feature_id_account = banks_client
|
let feature_id_acccount = banks_client
|
||||||
.get_account(feature_id_address)
|
.get_account(feature_id_address)
|
||||||
.await
|
.await
|
||||||
.expect("success")
|
.expect("success")
|
||||||
.expect("some account");
|
.expect("some account");
|
||||||
assert_eq!(feature_id_account.owner, feature::id());
|
assert_eq!(feature_id_acccount.owner, feature::id());
|
||||||
|
|
||||||
// Confirm feature proposal account state
|
// Confirm feature proposal account state
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
banks_client
|
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
||||||
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
|
||||||
.await,
|
|
||||||
Ok(FeatureProposal::Accepted {
|
Ok(FeatureProposal::Accepted {
|
||||||
tokens_upon_acceptance: 42
|
tokens_upon_acceptance: 42
|
||||||
})
|
})
|
||||||
|
@ -186,9 +197,7 @@ async fn test_expired() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
banks_client
|
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
||||||
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
|
||||||
.await,
|
|
||||||
Ok(FeatureProposal::Pending(_))
|
Ok(FeatureProposal::Pending(_))
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -199,9 +208,7 @@ async fn test_expired() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
banks_client
|
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
||||||
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
|
||||||
.await,
|
|
||||||
Ok(FeatureProposal::Expired)
|
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