-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(batch): implement ixs, sdk and tests
- Loading branch information
Showing
63 changed files
with
3,228 additions
and
535 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
programs/multisig/src/instructions/batch_add_transaction.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
Oops, something went wrong.