Skip to content

Commit

Permalink
feat(batch): implement ixs, sdk and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vovacodes committed Mar 18, 2023
1 parent d89ed9b commit 4b98ee9
Show file tree
Hide file tree
Showing 63 changed files with 3,228 additions and 535 deletions.
4 changes: 0 additions & 4 deletions programs/multisig/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ pub enum MultisigError {
InvalidProposalStatus,
#[msg("Invalid transaction index")]
InvalidTransactionIndex,
#[msg("Proposal does not belong to the multisig")]
ProposalNotForMultisig,
#[msg("Transaction does not belong to the multisig")]
TransactionNotForMultisig,
#[msg("Member already approved the transaction")]
AlreadyApproved,
#[msg("Member already rejected the transaction")]
Expand Down
140 changes: 140 additions & 0 deletions programs/multisig/src/instructions/batch_add_transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use anchor_lang::prelude::*;

use crate::errors::*;
use crate::state::*;
use crate::TransactionMessage;

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct BatchAddTransactionArgs {
/// Number of ephemeral signing PDAs required by the transaction.
pub ephemeral_signers: u8,
pub transaction_message: Vec<u8>,
}

#[derive(Accounts)]
#[instruction(args: BatchAddTransactionArgs)]
pub struct BatchAddTransaction<'info> {
/// Multisig account this batch belongs to.
#[account(
seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
bump = multisig.bump,
)]
pub multisig: Account<'info, Multisig>,

/// Member of the multisig.
#[account(mut)]
pub member: Signer<'info>,

/// The proposal account associated with the batch.
#[account(
seeds = [
SEED_PREFIX,
multisig.key().as_ref(),
SEED_TRANSACTION,
&batch.index.to_le_bytes(),
SEED_PROPOSAL,
],
bump = proposal.bump,
)]
pub proposal: Account<'info, Proposal>,

#[account(
mut,
seeds = [
SEED_PREFIX,
multisig.key().as_ref(),
SEED_TRANSACTION,
&batch.index.to_le_bytes(),
],
bump = batch.bump,
)]
pub batch: Account<'info, Batch>,

/// `VaultBatchTransaction` account to initialize and add to the `batch`.
#[account(
init,
payer = member,
space = VaultBatchTransaction::size(args.ephemeral_signers, &args.transaction_message)?,
seeds = [
SEED_PREFIX,
multisig.key().as_ref(),
SEED_TRANSACTION,
&batch.index.to_le_bytes(),
SEED_BATCH_TRANSACTION,
&batch.size.checked_add(1).unwrap().to_le_bytes(),
],
bump
)]
pub transaction: Account<'info, VaultBatchTransaction>,

pub system_program: Program<'info, System>,
}

impl BatchAddTransaction<'_> {
fn validate(&self) -> Result<()> {
let Self {
multisig,
member,
proposal,
..
} = self;

// `member`
require!(
multisig.is_member(member.key()).is_some(),
MultisigError::NotAMember
);
require!(
multisig.member_has_permission(member.key(), Permission::Initiate),
MultisigError::Unauthorized
);

// `proposal`
require!(
matches!(proposal.status, ProposalStatus::Draft { .. }),
MultisigError::InvalidProposalStatus
);

// `batch` is validated by its seeds.

Ok(())
}

/// Add a transaction to the batch.
#[access_control(ctx.accounts.validate())]
pub fn batch_add_transaction(ctx: Context<Self>, args: BatchAddTransactionArgs) -> Result<()> {
let batch = &mut ctx.accounts.batch;
let transaction = &mut ctx.accounts.transaction;

let batch_key = batch.key();

let transaction_message =
TransactionMessage::deserialize(&mut args.transaction_message.as_slice())?;

let ephemeral_signer_bumps: Vec<u8> = (0..args.ephemeral_signers)
.into_iter()
.map(|ephemeral_signer_index| {
let ephemeral_signer_seeds = &[
SEED_PREFIX,
batch_key.as_ref(),
SEED_EPHEMERAL_SIGNER,
&ephemeral_signer_index.to_le_bytes(),
];

let (_, bump) =
Pubkey::find_program_address(ephemeral_signer_seeds, ctx.program_id);

bump
})
.collect();

transaction.bump = *ctx.bumps.get("transaction").unwrap();
transaction.ephemeral_signer_bumps = ephemeral_signer_bumps;
transaction.message = transaction_message.try_into()?;

// Increment the batch size.
batch.size = batch.size.checked_add(1).expect("overflow");

Ok(())
}
}
97 changes: 97 additions & 0 deletions programs/multisig/src/instructions/batch_create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use anchor_lang::prelude::*;

use crate::errors::*;
use crate::state::*;

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct BatchCreateArgs {
/// Index of the vault this transaction belongs to.
pub vault_index: u8,
pub memo: Option<String>,
}

#[derive(Accounts)]
pub struct BatchCreate<'info> {
#[account(
mut,
seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
bump = multisig.bump,
)]
pub multisig: Account<'info, Multisig>,

#[account(mut)]
pub creator: Signer<'info>,

#[account(
init,
payer = creator,
space = 8 + Batch::INIT_SPACE,
seeds = [
SEED_PREFIX,
multisig.key().as_ref(),
SEED_TRANSACTION,
&multisig.transaction_index.checked_add(1).unwrap().to_le_bytes(),
],
bump
)]
pub batch: Account<'info, Batch>,

pub system_program: Program<'info, System>,
}

impl BatchCreate<'_> {
fn validate(&self) -> Result<()> {
let Self {
multisig, creator, ..
} = self;

// creator
require!(
multisig.is_member(creator.key()).is_some(),
MultisigError::NotAMember
);
require!(
multisig.member_has_permission(creator.key(), Permission::Initiate),
MultisigError::Unauthorized
);

Ok(())
}

/// Create a new batch.
#[access_control(ctx.accounts.validate())]
pub fn batch_create(ctx: Context<Self>, args: BatchCreateArgs) -> Result<()> {
let multisig = &mut ctx.accounts.multisig;
let creator = &mut ctx.accounts.creator;
let batch = &mut ctx.accounts.batch;

let multisig_key = multisig.key();

// Increment the transaction index.
let index = multisig.transaction_index.checked_add(1).expect("overflow");

let vault_seeds = &[
SEED_PREFIX,
multisig_key.as_ref(),
SEED_VAULT,
&args.vault_index.to_le_bytes(),
];
let (_, vault_bump) = Pubkey::find_program_address(vault_seeds, ctx.program_id);

batch.multisig = multisig_key;
batch.creator = creator.key();
batch.index = index;
batch.bump = *ctx.bumps.get("batch").unwrap();
batch.vault_index = args.vault_index;
batch.vault_bump = vault_bump;
batch.size = 0;
batch.executed_transaction_index = 0;

// Updated last transaction index in the multisig account.
multisig.transaction_index = index;

multisig.invariant()?;

Ok(())
}
}
Loading

0 comments on commit 4b98ee9

Please sign in to comment.