Skip to content

Commit

Permalink
Add 2fa cosigners for distribution claim (#6)
Browse files Browse the repository at this point in the history
* Add 2fa cosigners for distribution claim

* Update

* Update distribution seeds
  • Loading branch information
nothing0012 authored May 31, 2024
1 parent 098b321 commit e21cf2c
Show file tree
Hide file tree
Showing 20 changed files with 1,952 additions and 71 deletions.
2 changes: 2 additions & 0 deletions programs/vetoken/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub enum CustomError {
#[msg("Invalid Token Amount")]
InvalidTokenAmount,
#[msg("Invalid Timestamp")]
InvalidTokenDelegate,
#[msg("Invalid Token Delegate")]
InvalidTimestamp,
#[msg("Invalid Lockup Amount")]
InvalidLockupAmount,
Expand Down
121 changes: 121 additions & 0 deletions programs/vetoken/src/ins_v1/claim_from_distribution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use crate::{
distribution_seeds,
errors::CustomError,
id,
states::{Distribution, DistributionClaim, Namespace},
};
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_interface::{Mint, TokenAccount, TokenInterface},
};

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct ClaimFromDistributionArgs {
amount: u64,
cosigned_msg: [u8; 32],
}

#[derive(Accounts)]
#[instruction(args:ClaimFromDistributionArgs)]
pub struct ClaimFromDistribution<'info> {
#[account(mut)]
payer: Signer<'info>,

/// CHECK: claimant is an input parameter, it will be the owner of the claimant_token_account
#[account()]
claimant: UncheckedAccount<'info>,

#[account()]
cosigner_1: Signer<'info>,

#[account()]
cosigner_2: Signer<'info>,

#[account(
init,
seeds=[b"distribution_claim", ns.key().as_ref(), distribution.key().as_ref(), args.cosigned_msg.as_ref()],
payer=payer,
space=8+DistributionClaim::INIT_SPACE,
bump,
)]
distribution_claim: Box<Account<'info, DistributionClaim>>,

#[account(
seeds=[b"distribution", ns.key().as_ref(), cosigner_1.key().as_ref(), cosigner_2.key().as_ref(), distribution.uuid.as_ref()],
has_one = distribution_token_mint,
has_one = cosigner_1,
has_one = cosigner_2,
constraint = distribution.start_ts <= ns.now() @ CustomError::InvalidTimestamp,
bump,
)]
distribution: Box<Account<'info, Distribution>>,

#[account()]
distribution_token_mint: Box<InterfaceAccount<'info, Mint>>,

#[account(
mut,
token::token_program = token_program,
token::mint = distribution_token_mint,
constraint = delegated_token_account.delegate == Some(distribution.key()).into() @ CustomError::InvalidTokenDelegate,
constraint = delegated_token_account.amount >= args.amount @ CustomError::InvalidTokenAmount,
constraint = delegated_token_account.delegated_amount >= args.amount @ CustomError::InvalidTokenDelegate,
)]
delegated_token_account: Box<InterfaceAccount<'info, TokenAccount>>,

#[account(
init_if_needed,
token::token_program = token_program,
associated_token::token_program = token_program,
associated_token::mint = distribution_token_mint,
associated_token::authority = claimant,
payer = payer,
)]
claimant_token_account: Box<InterfaceAccount<'info, TokenAccount>>,

#[account(
constraint = *ns.to_account_info().owner == id(),
)]
ns: Box<Account<'info, Namespace>>,

token_program: Interface<'info, TokenInterface>,
system_program: Program<'info, System>,
associated_token_program: Program<'info, AssociatedToken>,
}

pub fn handle<'info>(
ctx: Context<'_, '_, '_, 'info, ClaimFromDistribution<'info>>,
args: ClaimFromDistributionArgs,
) -> Result<()> {
let distribution_claim = &mut ctx.accounts.distribution_claim;
let ns = &ctx.accounts.ns;
let cosigner_1 = &ctx.accounts.cosigner_1;
let cosigner_2 = &ctx.accounts.cosigner_2;
let uuid = ctx.accounts.distribution.uuid;
let bump = ctx.bumps.distribution;

distribution_claim.ns = ctx.accounts.ns.key();
distribution_claim.claimant = ctx.accounts.claimant.key();
distribution_claim.distribution = ctx.accounts.distribution.key();
distribution_claim.amount = args.amount;
distribution_claim.distribution_token_mint = ctx.accounts.distribution_token_mint.key();
distribution_claim.cosigned_msg = args.cosigned_msg;

anchor_spl::token_interface::transfer_checked(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token_interface::TransferChecked {
from: ctx.accounts.delegated_token_account.to_account_info(),
mint: ctx.accounts.distribution_token_mint.to_account_info(),
to: ctx.accounts.claimant_token_account.to_account_info(),
authority: ctx.accounts.distribution.to_account_info(),
},
&[distribution_seeds!(ns, cosigner_1, cosigner_2, uuid, bump)],
),
args.amount,
ctx.accounts.distribution_token_mint.decimals,
)?;

Ok(())
}
57 changes: 57 additions & 0 deletions programs/vetoken/src/ins_v1/init_distribution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use anchor_lang::prelude::*;
use anchor_spl::token_interface::Mint;

use crate::{
id,
states::{Distribution, Namespace},
};

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct InitDistributionArgs {
uuid: Pubkey,
cosigner_1: Pubkey,
cosigner_2: Pubkey,
start_ts: i64,
}

#[derive(Accounts)]
#[instruction(args:InitDistributionArgs)]
pub struct InitDistribution<'info> {
#[account(mut)]
payer: Signer<'info>,

#[account(
init,
seeds=[b"distribution", ns.key().as_ref(), args.cosigner_1.as_ref(), args.cosigner_2.as_ref(), args.uuid.as_ref()],
payer=payer,
space=8+Distribution::INIT_SPACE,
bump,
)]
distribution: Account<'info, Distribution>,

/// CHECK: This is an input for the distribution token mint
#[account()]
distribution_token_mint: Box<InterfaceAccount<'info, Mint>>,

#[account(
constraint = *ns.to_account_info().owner == id(),
)]
ns: Box<Account<'info, Namespace>>,

system_program: Program<'info, System>,
}

pub fn handle<'info>(
ctx: Context<'_, '_, '_, 'info, InitDistribution<'info>>,
args: InitDistributionArgs,
) -> Result<()> {
let distribution = &mut ctx.accounts.distribution;
distribution.ns = ctx.accounts.ns.key();
distribution.cosigner_1 = args.cosigner_1;
distribution.cosigner_2 = args.cosigner_2;
distribution.uuid = args.uuid;
distribution.start_ts = args.start_ts;
distribution.distribution_token_mint = ctx.accounts.distribution_token_mint.key();

Ok(())
}
6 changes: 6 additions & 0 deletions programs/vetoken/src/ins_v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ pub use unstake::*;
pub mod init_proposal;
pub use init_proposal::*;

pub mod init_distribution;
pub use init_distribution::*;

pub mod update_proposal;
pub use update_proposal::*;

pub mod vote;
pub use vote::*;

pub mod claim_from_distribution;
pub use claim_from_distribution::*;
31 changes: 31 additions & 0 deletions programs/vetoken/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ pub mod vetoken {
pub fn vote<'info>(ctx: Context<'_, '_, '_, 'info, Vote<'info>>, args: VoteArgs) -> Result<()> {
ins_v1::vote::handle(ctx, args)
}

// Init a 2FA cosigner-based distribution
pub fn init_distribution<'info>(
ctx: Context<'_, '_, '_, 'info, InitDistribution<'info>>,
args: InitDistributionArgs,
) -> Result<()> {
ins_v1::init_distribution::handle(ctx, args)
}

// Claim from distribution using a sharded delegate_token_account
// The cosigned_msg should be independently signed by both 2FA cosigners
pub fn claim_from_distribution<'info>(
ctx: Context<'_, '_, '_, 'info, ClaimFromDistribution<'info>>,
args: ClaimFromDistributionArgs,
) -> Result<()> {
ins_v1::claim_from_distribution::handle(ctx, args)
}
}

#[macro_export]
Expand All @@ -89,3 +106,17 @@ macro_rules! lockup_seeds {
]
};
}

#[macro_export]
macro_rules! distribution_seeds {
( $ns:expr, $cosigner_1:expr, $cosigner_2:expr, $uuid:expr, $bump:expr ) => {
&[
b"distribution".as_ref(),
$ns.key().as_ref(),
$cosigner_1.key().as_ref(),
$cosigner_2.key().as_ref(),
$uuid.as_ref(),
&[$bump],
]
};
}
28 changes: 28 additions & 0 deletions programs/vetoken/src/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,31 @@ pub struct VoteRecord {

pub _padding: [u8; 240],
}

#[account]
#[derive(Copy, InitSpace)]
pub struct Distribution {
// Seeds: [b"distribution", ns.key().as_ref(), uuid.key().as_ref()]
pub ns: Pubkey,
pub uuid: Pubkey,
pub cosigner_1: Pubkey,
pub cosigner_2: Pubkey,
pub start_ts: i64,
pub distribution_token_mint: Pubkey,

pub _padding: [u8; 240],
}

#[account]
#[derive(Copy, InitSpace)]
pub struct DistributionClaim {
// Seeds: [b"distribution_claim", ns.key().as_ref(), distribution.key().as_ref(), args.cosigned_msg.as_ref()]
pub ns: Pubkey,
pub distribution: Pubkey,
pub claimant: Pubkey,
pub distribution_token_mint: Pubkey,
pub amount: u64,
pub cosigned_msg: [u8; 32], // sha256 hash of the cosigned message

pub _padding: [u8; 240],
}
Loading

0 comments on commit e21cf2c

Please sign in to comment.