diff --git a/packages/client/core/src/lib/CryptidTransaction.ts b/packages/client/core/src/lib/CryptidTransaction.ts index 4066c82e..c1aae72c 100644 --- a/packages/client/core/src/lib/CryptidTransaction.ts +++ b/packages/client/core/src/lib/CryptidTransaction.ts @@ -278,6 +278,7 @@ export class CryptidTransaction { this.cryptidAccount.didAccountBump, TransactionState.toBorsh(state), allowUnauthorized, + this.cryptidAccount.middlewares.map((m) => m.programId), this.instructions, this.accountMetas.length ) diff --git a/packages/client/idl/src/cryptid.ts b/packages/client/idl/src/cryptid.ts index f4cab7d7..bcd61159 100644 --- a/packages/client/idl/src/cryptid.ts +++ b/packages/client/idl/src/cryptid.ts @@ -214,6 +214,12 @@ export type Cryptid = { "name": "allowUnauthorized", "type": "bool" }, + { + "name": "whitelistedMiddlewarePrograms", + "type": { + "vec": "publicKey" + } + }, { "name": "instructions", "type": { @@ -609,6 +615,17 @@ export type Cryptid = { "option": "publicKey" } }, + { + "name": "whitelistedMiddlewarePrograms", + "docs": [ + "This vector contains a list of middleware program ids that are allowed to", + "approve the execution. Important, is not used for passing transactions execution", + "checks. (approved_middleware: Option) is used for that." + ], + "type": { + "vec": "publicKey" + } + }, { "name": "authorized", "type": "bool" @@ -1066,6 +1083,12 @@ export const IDL: Cryptid = { "name": "allowUnauthorized", "type": "bool" }, + { + "name": "whitelistedMiddlewarePrograms", + "type": { + "vec": "publicKey" + } + }, { "name": "instructions", "type": { @@ -1461,6 +1484,17 @@ export const IDL: Cryptid = { "option": "publicKey" } }, + { + "name": "whitelistedMiddlewarePrograms", + "docs": [ + "This vector contains a list of middleware program ids that are allowed to", + "approve the execution. Important, is not used for passing transactions execution", + "checks. (approved_middleware: Option) is used for that." + ], + "type": { + "vec": "publicKey" + } + }, { "name": "authorized", "type": "bool" diff --git a/packages/tests/src/middleware/checkPass.ts b/packages/tests/src/middleware/checkPass.ts index 67814ebe..81163224 100644 --- a/packages/tests/src/middleware/checkPass.ts +++ b/packages/tests/src/middleware/checkPass.ts @@ -143,7 +143,8 @@ describe("Middleware: checkPass", () => { const propose = async ( transactionAccount: Keypair, - instruction: InstructionData = transferInstructionData + instruction: InstructionData = transferInstructionData, + whitelistedMiddleware: PublicKey[] = [checkPassMiddlewareProgram.programId] ) => program.methods .proposeTransaction( @@ -153,6 +154,7 @@ describe("Middleware: checkPass", () => { cryptid.details.didAccountBump, TransactionState.toBorsh(TransactionState.Ready), false, + whitelistedMiddleware, [instruction], 2 ) @@ -446,6 +448,22 @@ describe("Middleware: checkPass", () => { expect(previousBalance - currentBalance).to.equal(LAMPORTS_PER_SOL); // Now the tx has been executed }); + it("fails a transfer if the middleware program has not been whitelisted in the propose", async () => { + const transactionAccount = Keypair.generate(); + + // issue a gateway token to the authority + const gatewayToken = await createGatewayToken(authority.publicKey); + + // propose the Cryptid transaction without whitelisting the middleware + await propose(transactionAccount, transferInstructionData, []); + + // pass through the middleware + const shouldFail = checkPass(transactionAccount, gatewayToken.publicKey); + return expect(shouldFail).to.be.rejectedWith( + "Error Code: InvalidMiddlewareAccount" + ); + }); + it("allows a transfer if the DID account has a valid gateway token", async () => { // the difference between this one and the previous one is that it shows that // the gateway token can be associated with the DID account itself rather than the authority wallet diff --git a/packages/tests/src/proposeExecute.ts b/packages/tests/src/proposeExecute.ts index e5acb041..39e031f5 100644 --- a/packages/tests/src/proposeExecute.ts +++ b/packages/tests/src/proposeExecute.ts @@ -56,6 +56,7 @@ didTestCases.forEach(({ didType, getDidAccount }) => { cryptid.details.didAccountBump, TransactionState.toBorsh(TransactionState.Ready), false, + [], // no whitelisted middleware programs [instruction], 2 ) @@ -178,6 +179,7 @@ didTestCases.forEach(({ didType, getDidAccount }) => { cryptid.details.didAccountBump, TransactionState.toBorsh(TransactionState.Ready), false, + [], // no whitelisted middleware programs [transferInstructionData], 2 ) @@ -337,6 +339,7 @@ didTestCases.forEach(({ didType, getDidAccount }) => { cryptid.details.didAccountBump, TransactionState.toBorsh(TransactionState.Ready), false, + [], // no whitelisted middleware programs [transferInstructionData], 2 ) diff --git a/programs/cryptid/src/instructions/approve_execution.rs b/programs/cryptid/src/instructions/approve_execution.rs index 41126241..73376645 100644 --- a/programs/cryptid/src/instructions/approve_execution.rs +++ b/programs/cryptid/src/instructions/approve_execution.rs @@ -18,12 +18,11 @@ pub struct ApproveExecution<'info> { pub fn approve_execution<'info>( ctx: Context<'_, '_, '_, 'info, ApproveExecution<'info>>, ) -> Result<()> { - // Make sure owner is NOT the System Program - // TODO(ticket): Consider implementing an approval registry on middleware require!( - !anchor_lang::solana_program::system_program::check_id( - ctx.accounts.middleware_account.owner - ), + ctx.accounts + .transaction_account + .whitelisted_middleware_programs + .contains(ctx.accounts.middleware_account.owner), CryptidError::InvalidMiddlewareAccount ); diff --git a/programs/cryptid/src/instructions/extend_transaction.rs b/programs/cryptid/src/instructions/extend_transaction.rs index 54bd042f..b155d27b 100644 --- a/programs/cryptid/src/instructions/extend_transaction.rs +++ b/programs/cryptid/src/instructions/extend_transaction.rs @@ -54,7 +54,8 @@ pub struct ExtendTransaction<'info> { transaction_account.accounts.len() + num_accounts as usize, InstructionSize::from_iter_to_iter( instructions.iter().chain(transaction_account.instructions.iter()) - ) + ), + transaction_account.whitelisted_middleware_programs.len() ), realloc::payer = authority, realloc::zero = false, diff --git a/programs/cryptid/src/instructions/propose_transaction.rs b/programs/cryptid/src/instructions/propose_transaction.rs index fdc6ad17..066c0674 100644 --- a/programs/cryptid/src/instructions/propose_transaction.rs +++ b/programs/cryptid/src/instructions/propose_transaction.rs @@ -22,6 +22,8 @@ did_account_bump: u8, state: TransactionState, /// True if the transaction account is being proposed by a non-authority on the DID allow_unauthorized: bool, +/// A list of middleware programs that are allowed to approve this transaction account. +whitelisted_middleware_programs: Vec, /// The instructions to execute instructions: Vec, num_accounts: u8, @@ -50,7 +52,8 @@ pub struct ProposeTransaction<'info> { num_accounts.into(), InstructionSize::from_iter_to_iter( instructions.iter() - ) + ), + whitelisted_middleware_programs.len() )) ] transaction_account: Account<'info, TransactionAccount>, @@ -89,6 +92,7 @@ pub fn propose_transaction<'info>( did_account_bump: u8, state: TransactionState, allow_unauthorized: bool, + whitelisted_middleware_programs: Vec, instructions: Vec, ) -> Result<()> { let all_accounts = ctx.all_accounts(); @@ -132,6 +136,9 @@ pub fn propose_transaction<'info>( None }; ctx.accounts.transaction_account.authorized = !allow_unauthorized; + ctx.accounts + .transaction_account + .whitelisted_middleware_programs = whitelisted_middleware_programs; // if the transaction is being created by an unauthorized signer, // then the cryptid account must have superuser middleware registered diff --git a/programs/cryptid/src/instructions/superuser_approve_execution.rs b/programs/cryptid/src/instructions/superuser_approve_execution.rs index 2e56651e..2ecb2e42 100644 --- a/programs/cryptid/src/instructions/superuser_approve_execution.rs +++ b/programs/cryptid/src/instructions/superuser_approve_execution.rs @@ -25,12 +25,11 @@ pub struct SuperuserApproveExecution<'info> { pub fn superuser_approve_execution<'info>( ctx: Context<'_, '_, '_, 'info, SuperuserApproveExecution<'info>>, ) -> Result<()> { - // Make sure owner is NOT the System Program - // TODO(ticket): Consider implementing an approval registry on middleware require!( - !anchor_lang::solana_program::system_program::check_id( - ctx.accounts.middleware_account.owner - ), + ctx.accounts + .transaction_account + .whitelisted_middleware_programs + .contains(ctx.accounts.middleware_account.owner), CryptidError::InvalidMiddlewareAccount ); diff --git a/programs/cryptid/src/lib.rs b/programs/cryptid/src/lib.rs index 95bfdeb8..049cbe91 100644 --- a/programs/cryptid/src/lib.rs +++ b/programs/cryptid/src/lib.rs @@ -68,6 +68,7 @@ pub mod cryptid { did_account_bump: u8, state: TransactionState, allow_unauthorized: bool, + whitelisted_middleware_programs: Vec, instructions: Vec, _num_accounts: u8, ) -> Result<()> { @@ -79,6 +80,7 @@ pub mod cryptid { did_account_bump, state, allow_unauthorized, + whitelisted_middleware_programs, instructions, ) } diff --git a/programs/cryptid/src/state/transaction_account.rs b/programs/cryptid/src/state/transaction_account.rs index 4559e8ec..9679fe16 100644 --- a/programs/cryptid/src/state/transaction_account.rs +++ b/programs/cryptid/src/state/transaction_account.rs @@ -29,6 +29,10 @@ pub struct TransactionAccount { /// If the transaction account is proposed by an unauthorized cryptid client, then /// it is set to to that signer, and only a `superUser` middleware can approve it. pub unauthorized_signer: Option, + /// This vector contains a list of middleware program ids that are allowed to + /// approve the execution. Important, is not used for passing transactions execution + /// checks. (approved_middleware: Option) is used for that. + pub whitelisted_middleware_programs: Vec, pub authorized: bool, } impl TransactionAccount { @@ -36,6 +40,7 @@ impl TransactionAccount { pub fn calculate_size( num_accounts: usize, instruction_sizes: impl Iterator, + num_whitelisted_middleware_programs: usize, ) -> usize { DISCRIMINATOR_SIZE + 32 // cryptid_account @@ -45,6 +50,7 @@ impl TransactionAccount { + 1 + 32 // approved_middleware + 1 // state + 1 + 32 // unauthorized signer + + 4 + 32 * num_whitelisted_middleware_programs // whitelisted_middleware_programs + 1 // authorized } @@ -87,6 +93,7 @@ mod test { accounts: 1, data_len: 1, }), + 1, ); println!("Size: {size}"); @@ -102,6 +109,7 @@ mod test { approved_middleware: None, state: TransactionState::Ready, unauthorized_signer: None, + whitelisted_middleware_programs: vec![Default::default()], authorized: true, }; let ser_size = BorshSerialize::try_to_vec(&account).unwrap().len();