//! dareo: a coin that pays strangers to do dares in real life. //! //! The coin's creator fees land in the pot account. Holders post dares and back them with tokens, which sit in the //! program's vault until they take them back. Every day at 20:00 UTC anyone can send the most-backed dare live with //! the whole pot on it. A doer submits a link to their proof; holders then vote for 6 hours by locking tokens on yes //! or no (locked tokens can't vote twice). If yes has more than half of the votes cast, the program sends the whole pot //! to the doer. If not, the dare stays live for the next proof until 24 hours after it went live; then it fails and //! the pot rolls over to the next dare. The admin key can veto a dare before it goes live, and nothing else: it has no //! instruction that touches the pot, the vault or a vote. use anchor_lang::prelude::*; use anchor_lang::solana_program::{instruction::{AccountMeta, Instruction}, program::{invoke, invoke_signed}}; declare_id!("9m8hPhNeWNWVC1jrC1Z3SDDJEJp913TXroiWjyhA4bDd"); pub const DEPLOYER: Pubkey = pubkey!("CTNkYr8FmCqiPsyx3Xt59mL4sLryJmFDM36wvg5e6kPP"); pub const TOKEN: Pubkey = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); pub const T22: Pubkey = pubkey!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); pub const ATA: Pubkey = pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); /// Token-2022 mint extensions that cannot take, freeze, tax or block tokens: metadata and group pointers. pub const SAFE_EXTENSIONS: [u16; 6] = [18, 19, 20, 21, 22, 23]; pub const DAY: i64 = 86_400; pub const LIVE_AT: i64 = 72_000; // 20:00 UTC pub const VOTE_SECS: i64 = 6 * 3_600; pub const LIVE_SECS: i64 = DAY; pub const MIN_POST: u64 = 100_000_000_000; // 100k tokens (6 decimals) to post a dare pub const MAX_TEXT: usize = 160; pub const MAX_PROOF: usize = 140; pub const QUEUED: u8 = 0; pub const LIVE: u8 = 1; pub const VOTING: u8 = 2; pub const PAID: u8 = 3; pub const FAILED: u8 = 4; pub const VETOED: u8 = 5; pub const YES: u8 = 1; pub const NO: u8 = 2; #[program] pub mod dareo { use super::*; pub fn init(ctx: Context) -> Result<()> { let c = &mut ctx.accounts.config; c.admin = ctx.accounts.admin.key(); c.bump = ctx.bumps.config; ctx.accounts.pot.bump = ctx.bumps.pot; Ok(()) } /// Admin, once: name the coin and open the token vault. The mint must not be able to print, freeze or tax. pub fn set_mint(ctx: Context) -> Result<()> { let a = &ctx.accounts; require!(a.config.mint == Pubkey::default(), Err::AlreadySet); let tp = *a.mint.owner; require!(tp == TOKEN || tp == T22, Err::BadMint); require_keys_eq!(a.token_program.key(), tp, Err::BadMint); check_mint(&a.mint, tp)?; let (vault, _) = Pubkey::find_program_address(&[a.config.key().as_ref(), tp.as_ref(), a.mint.key().as_ref()], &ATA); require_keys_eq!(a.vault.key(), vault, Err::BadVault); invoke( &Instruction { program_id: ATA, accounts: vec![ AccountMeta::new(a.admin.key(), true), AccountMeta::new(vault, false), AccountMeta::new_readonly(a.config.key(), false), AccountMeta::new_readonly(a.mint.key(), false), AccountMeta::new_readonly(a.system_program.key(), false), AccountMeta::new_readonly(tp, false), ], data: vec![1], // CreateIdempotent }, &[a.admin.to_account_info(), a.vault.to_account_info(), a.config.to_account_info(), a.mint.to_account_info(), a.system_program.to_account_info(), a.token_program.to_account_info()], )?; let now = Clock::get()?.unix_timestamp; let c = &mut ctx.accounts.config; c.mint = ctx.accounts.mint.key(); c.token_program = tp; c.vault = vault; c.next_live = next_slot(now); Ok(()) } pub fn post_dare(ctx: Context, text: String, amount: u64) -> Result<()> { require!(!text.is_empty() && text.len() <= MAX_TEXT, Err::BadText); require!(amount >= MIN_POST, Err::TooFewTokens); lock(&ctx.accounts.tok, &ctx.accounts.config, amount)?; let id = ctx.accounts.config.next_id; let d = &mut ctx.accounts.dare; d.id = id; d.author = ctx.accounts.tok.user.key(); d.text = text; d.backing = amount; d.state = QUEUED; d.bump = ctx.bumps.dare; let b = &mut ctx.accounts.backing; b.user = d.author; b.dare = d.key(); b.amount = amount; b.bump = ctx.bumps.backing; let c = &mut ctx.accounts.config; c.next_id += 1; lead(c, d.key(), d.backing); emit!(DarePosted { dare: d.key(), id, author: d.author, backing: amount }); Ok(()) } pub fn back(ctx: Context, amount: u64) -> Result<()> { require!(amount > 0, Err::TooFewTokens); require!(ctx.accounts.dare.state == QUEUED, Err::NotQueued); lock(&ctx.accounts.tok, &ctx.accounts.config, amount)?; let b = &mut ctx.accounts.backing; if b.user == Pubkey::default() { b.user = ctx.accounts.tok.user.key(); b.dare = ctx.accounts.dare.key(); b.bump = ctx.bumps.backing; } b.amount = b.amount.checked_add(amount).ok_or(Err::Overflow)?; let d = &mut ctx.accounts.dare; d.backing = d.backing.checked_add(amount).ok_or(Err::Overflow)?; lead(&mut ctx.accounts.config, d.key(), d.backing); emit!(Backed { dare: d.key(), user: b.user, amount, backing: d.backing }); Ok(()) } /// Take backing tokens back at any time. While the dare is still queued it loses that backing. pub fn unback(ctx: Context, amount: u64) -> Result<()> { let b = &mut ctx.accounts.backing; require!(amount > 0 && amount <= b.amount, Err::TooFewTokens); b.amount -= amount; unlock(&ctx.accounts.tok, &ctx.accounts.config, amount)?; let d = &mut ctx.accounts.dare; if d.state == QUEUED { d.backing -= amount; let c = &mut ctx.accounts.config; if c.leader == d.key() { c.leader_backing = d.backing; } } Ok(()) } /// Anyone: a queued dare with more backing than the current leader becomes the leader. pub fn challenge(ctx: Context) -> Result<()> { let d = &ctx.accounts.dare; require!(d.state == QUEUED, Err::NotQueued); let c = &mut ctx.accounts.config; require!(d.backing > c.leader_backing || c.leader == Pubkey::default(), Err::NotAhead); c.leader = d.key(); c.leader_backing = d.backing; Ok(()) } /// Admin: a dare that hurts someone, breaks a law or drags in people who didn't agree never goes live. pub fn veto(ctx: Context) -> Result<()> { let d = &mut ctx.accounts.dare; require!(d.state == QUEUED, Err::NotQueued); d.state = VETOED; let c = &mut ctx.accounts.config; if c.leader == d.key() { c.leader = Pubkey::default(); c.leader_backing = 0; } emit!(Vetoed { dare: d.key() }); Ok(()) } /// Anyone, from 20:00 UTC: the leader goes live with the whole pot on it. pub fn go_live(ctx: Context) -> Result<()> { let now = Clock::get()?.unix_timestamp; let c = &mut ctx.accounts.config; require!(c.mint != Pubkey::default(), Err::NotStarted); require!(now >= c.next_live, Err::TooEarly); require!(c.live == Pubkey::default(), Err::AlreadyLive); let d = &mut ctx.accounts.dare; require_keys_eq!(d.key(), c.leader, Err::NotLeader); require!(d.state == QUEUED && d.backing > 0, Err::NotQueued); d.state = LIVE; d.live_at = now; c.live = d.key(); c.leader = Pubkey::default(); c.leader_backing = 0; c.next_live = next_slot(now); emit!(WentLive { dare: d.key(), pot: ctx.accounts.pot.to_account_info().lamports() }); Ok(()) } /// Anyone, from 20:00 UTC when no dare is queued: move the slot to tomorrow (the pot just waits). pub fn skip(ctx: Context) -> Result<()> { let now = Clock::get()?.unix_timestamp; let c = &mut ctx.accounts.config; require!(now >= c.next_live && c.leader == Pubkey::default(), Err::TooEarly); c.next_live = next_slot(now); Ok(()) } /// The doer: a link to the proof (the reply on X with the video and wallet). Opens a 6-hour vote. pub fn submit_proof(ctx: Context, proof: String) -> Result<()> { require!(!proof.is_empty() && proof.len() <= MAX_PROOF, Err::BadText); let now = Clock::get()?.unix_timestamp; let d = &mut ctx.accounts.dare; require!(d.state == LIVE, Err::NotLive); require!(now < d.live_at + LIVE_SECS, Err::TooLate); d.state = VOTING; d.doer = ctx.accounts.doer.key(); d.proof = proof; d.round = d.round.checked_add(1).ok_or(Err::Overflow)?; d.vote_end = now + VOTE_SECS; d.yes = 0; d.no = 0; emit!(ProofIn { dare: d.key(), doer: d.doer, round: d.round, vote_end: d.vote_end }); Ok(()) } /// Lock tokens on yes or no for this proof. One side per wallet per proof; the tokens come back after the vote. pub fn vote(ctx: Context, side: u8, amount: u64) -> Result<()> { require!(side == YES || side == NO, Err::BadSide); require!(amount > 0, Err::TooFewTokens); let now = Clock::get()?.unix_timestamp; let d = &ctx.accounts.dare; require!(d.state == VOTING && now < d.vote_end, Err::NotVoting); let round = d.round; lock(&ctx.accounts.tok, &ctx.accounts.config, amount)?; let v = &mut ctx.accounts.ballot; if v.user == Pubkey::default() { v.user = ctx.accounts.tok.user.key(); v.dare = ctx.accounts.dare.key(); v.round = round; v.side = side; v.bump = ctx.bumps.ballot; } require!(v.side == side, Err::OneSide); v.amount = v.amount.checked_add(amount).ok_or(Err::Overflow)?; let d = &mut ctx.accounts.dare; if side == YES { d.yes = d.yes.checked_add(amount).ok_or(Err::Overflow)?; } else { d.no = d.no.checked_add(amount).ok_or(Err::Overflow)?; } emit!(Voted { dare: d.key(), user: v.user, side, amount, yes: d.yes, no: d.no }); Ok(()) } /// Anyone, after the 6 hours: yes over half of the votes cast pays the whole pot to the doer. pub fn finalize(ctx: Context) -> Result<()> { let now = Clock::get()?.unix_timestamp; let d = &ctx.accounts.dare; require!(d.state == VOTING && now >= d.vote_end, Err::NotVoting); require_keys_eq!(ctx.accounts.doer.key(), d.doer, Err::NotDoer); let passed = d.yes > d.no; let d = &mut ctx.accounts.dare; if passed { let pot = ctx.accounts.pot.to_account_info(); let keep = Rent::get()?.minimum_balance(pot.data_len()); let prize = pot.lamports().saturating_sub(keep); **pot.try_borrow_mut_lamports()? -= prize; **ctx.accounts.doer.try_borrow_mut_lamports()? += prize; d.state = PAID; d.prize = prize; ctx.accounts.config.live = Pubkey::default(); emit!(Paid { dare: d.key(), doer: d.doer, prize }); } else if now < d.live_at + LIVE_SECS { d.state = LIVE; // rejected: the dare stays live for the next proof emit!(Rejected { dare: d.key(), round: d.round }); } else { d.state = FAILED; ctx.accounts.config.live = Pubkey::default(); emit!(Rejected { dare: d.key(), round: d.round }); } Ok(()) } /// Anyone: a live dare nobody proved within 24 hours fails, and the pot rolls over. pub fn expire(ctx: Context) -> Result<()> { let now = Clock::get()?.unix_timestamp; let d = &mut ctx.accounts.dare; require!(d.state == LIVE && now >= d.live_at + LIVE_SECS, Err::NotOver); d.state = FAILED; ctx.accounts.config.live = Pubkey::default(); Ok(()) } /// Take voted tokens back once that proof's vote is over. pub fn unvote(ctx: Context) -> Result<()> { let d = &ctx.accounts.dare; let v = &ctx.accounts.ballot; require!(!(d.state == VOTING && v.round == d.round), Err::StillVoting); unlock(&ctx.accounts.tok, &ctx.accounts.config, v.amount)?; Ok(()) // `close = user` returns the ballot's rent } pub fn set_admin(ctx: Context, admin: Pubkey) -> Result<()> { ctx.accounts.config.admin = admin; Ok(()) } } /// The next 20:00 UTC after `now`. fn next_slot(now: i64) -> i64 { let t = now - now.rem_euclid(DAY) + LIVE_AT; if t > now { t } else { t + DAY } } fn lead(c: &mut Config, dare: Pubkey, backing: u64) { if c.leader == dare || backing > c.leader_backing { c.leader = dare; c.leader_backing = backing; } } fn decimals(mint: &AccountInfo) -> Result { let d = mint.try_borrow_data()?; require!(d.len() >= 45, Err::BadMint); Ok(d[44]) } fn transfer<'info>(tok: &TokenAccounts<'info>, from: &AccountInfo<'info>, to: &AccountInfo<'info>, authority: &AccountInfo<'info>, amount: u64, seeds: Option<&[&[u8]]>) -> Result<()> { let mut data = vec![12u8]; // TransferChecked data.extend_from_slice(&amount.to_le_bytes()); data.push(decimals(&tok.mint)?); let ix = Instruction { program_id: tok.token_program.key(), accounts: vec![AccountMeta::new(from.key(), false), AccountMeta::new_readonly(tok.mint.key(), false), AccountMeta::new(to.key(), false), AccountMeta::new_readonly(authority.key(), true)], data, }; let infos = [from.clone(), tok.mint.to_account_info(), to.clone(), authority.clone(), tok.token_program.to_account_info()]; match seeds { Some(s) => invoke_signed(&ix, &infos, &[s])?, None => invoke(&ix, &infos)?, } Ok(()) } fn check_tok(tok: &TokenAccounts<'_>, c: &Config) -> Result<()> { require!(c.mint != Pubkey::default(), Err::NotStarted); require_keys_eq!(tok.mint.key(), c.mint, Err::BadMint); require_keys_eq!(tok.vault.key(), c.vault, Err::BadVault); require_keys_eq!(tok.token_program.key(), c.token_program, Err::BadMint); Ok(()) } fn lock<'info>(tok: &TokenAccounts<'info>, c: &Account<'info, Config>, amount: u64) -> Result<()> { check_tok(tok, c)?; transfer(tok, &tok.user_tokens.to_account_info(), &tok.vault.to_account_info(), &tok.user.to_account_info(), amount, None) } fn unlock<'info>(tok: &TokenAccounts<'info>, c: &Account<'info, Config>, amount: u64) -> Result<()> { check_tok(tok, c)?; let bump = [c.bump]; let seeds: &[&[u8]] = &[b"config", &bump]; transfer(tok, &tok.vault.to_account_info(), &tok.user_tokens.to_account_info(), &c.to_account_info(), amount, Some(seeds)) } /// Refused unless the mint can never create, freeze, tax, hook, pause or take tokens. fn check_mint(mint: &AccountInfo, tp: Pubkey) -> Result<()> { let d = mint.try_borrow_data()?; require!(d.len() >= 82 && d[45] == 1, Err::BadMint); require!(d[0..4] == [0, 0, 0, 0], Err::BadMint); require!(d[46..50] == [0, 0, 0, 0], Err::BadMint); if d.len() > 82 { require!(tp == T22 && d.len() >= 166 && d[165] == 1, Err::BadMint); let mut i = 166; while i + 4 <= d.len() { let ty = u16::from_le_bytes([d[i], d[i + 1]]); let len = u16::from_le_bytes([d[i + 2], d[i + 3]]) as usize; if ty == 0 { break; } require!(SAFE_EXTENSIONS.contains(&ty), Err::BadMint); i += 4 + len; } } Ok(()) } #[account] #[derive(InitSpace)] pub struct Config { pub admin: Pubkey, pub mint: Pubkey, pub token_program: Pubkey, pub vault: Pubkey, pub leader: Pubkey, pub live: Pubkey, pub leader_backing: u64, pub next_live: i64, pub next_id: u64, pub bump: u8, } #[account] #[derive(InitSpace)] pub struct Pot { pub bump: u8, } #[account] #[derive(InitSpace)] pub struct Dare { pub id: u64, pub author: Pubkey, pub doer: Pubkey, #[max_len(160)] pub text: String, #[max_len(140)] pub proof: String, pub backing: u64, pub live_at: i64, pub vote_end: i64, pub yes: u64, pub no: u64, pub prize: u64, pub round: u8, pub state: u8, pub bump: u8, } #[account] #[derive(InitSpace)] pub struct Backing { pub user: Pubkey, pub dare: Pubkey, pub amount: u64, pub bump: u8, } #[account] #[derive(InitSpace)] pub struct Ballot { pub user: Pubkey, pub dare: Pubkey, pub amount: u64, pub round: u8, pub side: u8, pub bump: u8, } /// The token accounts every lock/unlock needs. Checked against the config in `check_tok`. #[derive(Accounts)] pub struct TokenAccounts<'info> { #[account(mut)] pub user: Signer<'info>, /// CHECK: the user's token account for the coin; the token program checks owner and mint #[account(mut)] pub user_tokens: UncheckedAccount<'info>, /// CHECK: must equal config.vault #[account(mut)] pub vault: UncheckedAccount<'info>, /// CHECK: must equal config.mint pub mint: UncheckedAccount<'info>, /// CHECK: must equal config.token_program pub token_program: UncheckedAccount<'info>, } #[derive(Accounts)] pub struct Init<'info> { #[account(init, payer = admin, space = 8 + Config::INIT_SPACE, seeds = [b"config"], bump)] pub config: Account<'info, Config>, #[account(init, payer = admin, space = 8 + Pot::INIT_SPACE, seeds = [b"pot"], bump)] pub pot: Account<'info, Pot>, #[account(mut, address = DEPLOYER)] pub admin: Signer<'info>, pub system_program: Program<'info, System>, } #[derive(Accounts)] pub struct SetMint<'info> { #[account(mut, seeds = [b"config"], bump = config.bump, has_one = admin)] pub config: Account<'info, Config>, #[account(mut)] pub admin: Signer<'info>, /// CHECK: owner, authorities and extensions checked in the handler pub mint: UncheckedAccount<'info>, /// CHECK: must be the config's associated token account for the mint #[account(mut)] pub vault: UncheckedAccount<'info>, /// CHECK: must be the mint's owner pub token_program: UncheckedAccount<'info>, /// CHECK: the associated token account program #[account(address = ATA)] pub ata_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } #[derive(Accounts)] pub struct PostDare<'info> { #[account(mut, seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, #[account(init, payer = user, space = 8 + Dare::INIT_SPACE, seeds = [b"dare".as_ref(), config.next_id.to_le_bytes().as_ref()], bump)] pub dare: Account<'info, Dare>, #[account(init, payer = user, space = 8 + Backing::INIT_SPACE, seeds = [b"backing", dare.key().as_ref(), user.key().as_ref()], bump)] pub backing: Account<'info, Backing>, #[account(mut, constraint = user.key() == tok.user.key() @ Err::BadUser)] pub user: Signer<'info>, pub tok: TokenAccounts<'info>, pub system_program: Program<'info, System>, } #[derive(Accounts)] pub struct Back<'info> { #[account(mut, seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, #[account(mut)] pub dare: Account<'info, Dare>, #[account(init_if_needed, payer = user, space = 8 + Backing::INIT_SPACE, seeds = [b"backing", dare.key().as_ref(), user.key().as_ref()], bump)] pub backing: Account<'info, Backing>, #[account(mut, constraint = user.key() == tok.user.key() @ Err::BadUser)] pub user: Signer<'info>, pub tok: TokenAccounts<'info>, pub system_program: Program<'info, System>, } #[derive(Accounts)] pub struct Unback<'info> { #[account(mut, seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, #[account(mut)] pub dare: Account<'info, Dare>, #[account(mut, seeds = [b"backing", dare.key().as_ref(), tok.user.key().as_ref()], bump = backing.bump)] pub backing: Account<'info, Backing>, pub tok: TokenAccounts<'info>, } #[derive(Accounts)] pub struct Challenge<'info> { #[account(mut, seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, pub dare: Account<'info, Dare>, } #[derive(Accounts)] pub struct Veto<'info> { #[account(mut, seeds = [b"config"], bump = config.bump, has_one = admin)] pub config: Account<'info, Config>, pub admin: Signer<'info>, #[account(mut)] pub dare: Account<'info, Dare>, } #[derive(Accounts)] pub struct GoLive<'info> { #[account(mut, seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, #[account(mut)] pub dare: Account<'info, Dare>, #[account(seeds = [b"pot"], bump = pot.bump)] pub pot: Account<'info, Pot>, } #[derive(Accounts)] pub struct Skip<'info> { #[account(mut, seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, } #[derive(Accounts)] pub struct SubmitProof<'info> { #[account(mut)] pub dare: Account<'info, Dare>, pub doer: Signer<'info>, } #[derive(Accounts)] pub struct Vote<'info> { #[account(seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, #[account(mut)] pub dare: Account<'info, Dare>, #[account(init_if_needed, payer = user, space = 8 + Ballot::INIT_SPACE, seeds = [b"ballot", dare.key().as_ref(), &dare.round.to_le_bytes(), user.key().as_ref()], bump)] pub ballot: Account<'info, Ballot>, #[account(mut, constraint = user.key() == tok.user.key() @ Err::BadUser)] pub user: Signer<'info>, pub tok: TokenAccounts<'info>, pub system_program: Program<'info, System>, } #[derive(Accounts)] pub struct Finalize<'info> { #[account(mut, seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, #[account(mut)] pub dare: Account<'info, Dare>, #[account(mut, seeds = [b"pot"], bump = pot.bump)] pub pot: Account<'info, Pot>, /// CHECK: must equal dare.doer; receives the pot #[account(mut)] pub doer: UncheckedAccount<'info>, } #[derive(Accounts)] pub struct Expire<'info> { #[account(mut, seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, #[account(mut, address = config.live)] pub dare: Account<'info, Dare>, } #[derive(Accounts)] pub struct Unvote<'info> { #[account(seeds = [b"config"], bump = config.bump)] pub config: Account<'info, Config>, pub dare: Account<'info, Dare>, #[account(mut, close = user, has_one = user, has_one = dare)] pub ballot: Account<'info, Ballot>, #[account(mut, constraint = user.key() == tok.user.key() @ Err::BadUser)] pub user: Signer<'info>, pub tok: TokenAccounts<'info>, } #[derive(Accounts)] pub struct AdminOnly<'info> { #[account(mut, seeds = [b"config"], bump = config.bump, has_one = admin)] pub config: Account<'info, Config>, pub admin: Signer<'info>, } #[event] pub struct DarePosted { pub dare: Pubkey, pub id: u64, pub author: Pubkey, pub backing: u64 } #[event] pub struct Backed { pub dare: Pubkey, pub user: Pubkey, pub amount: u64, pub backing: u64 } #[event] pub struct Vetoed { pub dare: Pubkey } #[event] pub struct WentLive { pub dare: Pubkey, pub pot: u64 } #[event] pub struct ProofIn { pub dare: Pubkey, pub doer: Pubkey, pub round: u8, pub vote_end: i64 } #[event] pub struct Voted { pub dare: Pubkey, pub user: Pubkey, pub side: u8, pub amount: u64, pub yes: u64, pub no: u64 } #[event] pub struct Paid { pub dare: Pubkey, pub doer: Pubkey, pub prize: u64 } #[event] pub struct Rejected { pub dare: Pubkey, pub round: u8 } #[error_code] pub enum Err { #[msg("the coin is already set")] AlreadySet, #[msg("the coin is not set yet")] NotStarted, #[msg("this mint can print, freeze or tax tokens, or is not the coin")] BadMint, #[msg("not the vault")] BadVault, #[msg("text is empty or too long")] BadText, #[msg("not enough tokens")] TooFewTokens, #[msg("the dare is not queued")] NotQueued, #[msg("not more backing than the leader")] NotAhead, #[msg("only the most-backed dare can go live")] NotLeader, #[msg("a dare is already live")] AlreadyLive, #[msg("too early")] TooEarly, #[msg("too late")] TooLate, #[msg("the dare is not live")] NotLive, #[msg("no vote is open")] NotVoting, #[msg("the vote on this proof is still open")] StillVoting, #[msg("one side per wallet per proof")] OneSide, #[msg("side must be 1 (yes) or 2 (no)")] BadSide, #[msg("not the doer")] NotDoer, #[msg("not over yet")] NotOver, #[msg("the signer and the token owner differ")] BadUser, #[msg("overflow")] Overflow, }