Skip to content
This repository was archived by the owner on Feb 9, 2025. It is now read-only.

Commit b117178

Browse files
authored
[wip] Token haver plugin (#92)
* init * . * start tests * more test updates * check locked * typo * typo * pubkey * rn * answer comments
1 parent 513a77b commit b117178

31 files changed

+2605
-0
lines changed

.devcontainer/Dockerfile

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#
2+
# Docker image to generate deterministic, verifiable builds of Anchor programs.
3+
# This must be run *after* a given ANCHOR_CLI version is published and a git tag
4+
# is released on GitHub.
5+
#
6+
7+
FROM ubuntu:22.04
8+
9+
ARG DEBIAN_FRONTEND=noninteractive
10+
11+
ARG SOLANA_CLI="1.14.7"
12+
ARG ANCHOR_CLI="0.27.0"
13+
ARG NODE_VERSION="v18.16.0"
14+
15+
ENV HOME="/root"
16+
ENV PATH="${HOME}/.cargo/bin:${PATH}"
17+
ENV PATH="${HOME}/.local/share/solana/install/active_release/bin:${PATH}"
18+
ENV PATH="${HOME}/.nvm/versions/node/${NODE_VERSION}/bin:${PATH}"
19+
20+
# Install base utilities.
21+
RUN mkdir -p /workdir && mkdir -p /tmp && \
22+
apt-get update -qq && apt-get upgrade -qq && apt-get install -qq \
23+
build-essential git curl wget jq pkg-config python3-pip \
24+
libssl-dev libudev-dev
25+
26+
RUN wget http://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb
27+
RUN dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb
28+
29+
# Install rust.
30+
RUN curl "https://sh.rustup.rs" -sfo rustup.sh && \
31+
sh rustup.sh -y && \
32+
rustup component add rustfmt clippy
33+
34+
# Install node / npm / yarn.
35+
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
36+
ENV NVM_DIR="${HOME}/.nvm"
37+
RUN . $NVM_DIR/nvm.sh && \
38+
nvm install ${NODE_VERSION} && \
39+
nvm use ${NODE_VERSION} && \
40+
nvm alias default node && \
41+
npm install -g yarn && \
42+
yarn add ts-mocha
43+
44+
# Install Solana tools.
45+
RUN sh -c "$(curl -sSfL https://release.solana.com/v${SOLANA_CLI}/install)"
46+
47+
# Install anchor.
48+
RUN cargo install --git https://github.com/coral-xyz/anchor avm --locked --force
49+
RUN avm install ${ANCHOR_CLI} && avm use ${ANCHOR_CLI}
50+
51+
WORKDIR /workdir
52+
#be sure to add `/root/.avm/bin` to your PATH to be able to run the installed binaries

.devcontainer/devcontainer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"build": { "dockerfile": "Dockerfile" }
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"build": { "dockerfile": "Dockerfile" }
3+
}

Anchor.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ seeds = false
88
nft_voter = "GnftV5kLjd67tvHpNGyodwWveEKivz3ZWvvE3Z4xi2iw"
99
gateway = "GgathUhdrCWRHowoRKACjgWhYHfxCEdBi5ViqYN6HVxk"
1010
solana-gateway-program = "gatem74V238djXdzWnJf94Wo1DcnuGkfijbf3AuBhfs"
11+
token-haver = "vo65JQC6U8HCDPfoJxRrr17n7RZ5fKdfyjHm3erHJ2V"
1112

1213
[registry]
1314
url = "https://anchor.projectserum.com"

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

programs/token-haver/Cargo.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "gpl-token-haver"
3+
version = "0.0.1"
4+
description = "SPL Governance plugin granting governance power based on the nonzero presence of locked tokens"
5+
license = "Apache-2.0"
6+
edition = "2018"
7+
8+
[lib]
9+
crate-type = ["cdylib", "lib"]
10+
name = "gpl_token_haver"
11+
12+
[features]
13+
no-entrypoint = []
14+
no-idl = []
15+
no-log-ix-name = []
16+
cpi = ["no-entrypoint"]
17+
default = []
18+
19+
[dependencies]
20+
arrayref = "0.3.6"
21+
anchor-lang = { version = "0.26.0" }
22+
anchor-spl = "0.26.0"
23+
solana-program = "1.14.16"
24+
spl-governance = { version = "3.1.1", features = ["no-entrypoint"] }
25+
spl-governance-tools= "0.1.3"
26+
spl-token = { version = "3.3", features = [ "no-entrypoint" ] }
27+
28+
[dev-dependencies]
29+
borsh = "0.9.1"
30+
solana-sdk = "1.14.16"
31+
solana-program-test = "1.14.16"

programs/token-haver/Xargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.bpfel-unknown-unknown.dependencies.std]
2+
features = []

programs/token-haver/readme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
This plugin checks for the presence of nonzero tokens from certain mints in the user's wallet.
2+
3+
### You would use this if:
4+
5+
- You want voting power to be based on having an _indefinitely locked_ token, but not proportional to the amount of the token
6+
- You don't want to use a Membership (for UX reasons)

programs/token-haver/src/error.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use anchor_lang::prelude::*;
2+
3+
#[error_code]
4+
pub enum TokenHaverError {
5+
#[msg("Invalid Realm Authority")]
6+
InvalidRealmAuthority,
7+
8+
#[msg("Invalid Realm for Registrar")]
9+
InvalidRealmForRegistrar,
10+
11+
#[msg("Invalid VoterWeightRecord Realm")]
12+
InvalidVoterWeightRecordRealm,
13+
14+
#[msg("Invalid VoterWeightRecord Mint")]
15+
InvalidVoterWeightRecordMint,
16+
17+
#[msg("Governing TokenOwner must match")]
18+
GoverningTokenOwnerMustMatch,
19+
20+
#[msg("All token accounts must be owned by the governing token owner")]
21+
TokenAccountWrongOwner,
22+
23+
#[msg("All token accounts' mints must be included in the registrar")]
24+
TokenAccountWrongMint,
25+
26+
#[msg("All token accounts' mints must be included in the registrar")]
27+
TokenAccountNotLocked,
28+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use crate::error::TokenHaverError;
2+
use crate::state::*;
3+
use anchor_lang::system_program::Transfer;
4+
use anchor_lang::{prelude::*, system_program};
5+
use spl_governance::state::realm;
6+
7+
/// Configures mints for Registrar
8+
#[derive(Accounts)]
9+
#[instruction(mints: Vec<Pubkey>)]
10+
pub struct ConfigureMints<'info> {
11+
/// The Registrar for the given realm and governing_token_mint
12+
#[account(mut)]
13+
pub registrar: Account<'info, Registrar>,
14+
15+
#[account(
16+
address = registrar.realm @ TokenHaverError::InvalidRealmForRegistrar,
17+
owner = registrar.governance_program_id
18+
)]
19+
/// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id
20+
pub realm: UncheckedAccount<'info>,
21+
22+
// will pay in the event of a resize
23+
pub payer: Signer<'info>,
24+
25+
/// Authority of the Realm must sign and match realm.authority
26+
pub realm_authority: Signer<'info>,
27+
28+
pub system_program: Program<'info, System>,
29+
}
30+
31+
pub fn configure_mints(ctx: Context<ConfigureMints>, mints: Vec<Pubkey>) -> Result<()> {
32+
let new_size = Registrar::get_space(mints.len() as u8);
33+
34+
let rent = Rent::get()?;
35+
let new_minimum_balance = rent.minimum_balance(new_size);
36+
37+
let lamports_diff =
38+
new_minimum_balance.saturating_sub(ctx.accounts.registrar.to_account_info().lamports());
39+
40+
// if lamports_diff is positive, we need to fund the account
41+
if lamports_diff > 0 {
42+
// Create a CPI context for the transfer
43+
let cpi_accounts = Transfer {
44+
from: ctx.accounts.payer.to_account_info().clone(),
45+
to: ctx.accounts.registrar.to_account_info().clone(),
46+
};
47+
48+
let cpi_program = ctx.accounts.system_program.to_account_info();
49+
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
50+
51+
// Perform the transfer
52+
system_program::transfer(cpi_ctx, lamports_diff)?;
53+
}
54+
55+
let registrar = &mut ctx.accounts.registrar;
56+
registrar.to_account_info().realloc(new_size, false)?;
57+
58+
registrar.mints = mints;
59+
60+
let realm = realm::get_realm_data_for_governing_token_mint(
61+
&registrar.governance_program_id,
62+
&ctx.accounts.realm,
63+
&registrar.governing_token_mint,
64+
)?;
65+
66+
require_eq!(
67+
realm.authority.unwrap(),
68+
ctx.accounts.realm_authority.key(),
69+
TokenHaverError::InvalidRealmAuthority
70+
);
71+
72+
Ok(())
73+
}

0 commit comments

Comments
 (0)