From e1e290a2f897c26e123032f08817ff7f0284b666 Mon Sep 17 00:00:00 2001 From: gigileungyingchi Date: Tue, 24 Oct 2023 17:16:09 +0200 Subject: [PATCH] Redefine `certificate_vault` to be account owning all ATAs of all certificates --- packages/fund-sender/client/index.ts | 7 +- packages/tests/fundSender.ts | 173 ++++++++++++++++++----- packages/types/fund_sender.ts | 20 +-- programs/fund-sender/src/lib.rs | 4 +- programs/fund-sender/src/utils/errors.rs | 6 +- programs/fund-sender/src/utils/state.rs | 6 +- 6 files changed, 158 insertions(+), 58 deletions(-) diff --git a/packages/fund-sender/client/index.ts b/packages/fund-sender/client/index.ts index 6c994d6..35ebefd 100644 --- a/packages/fund-sender/client/index.ts +++ b/packages/fund-sender/client/index.ts @@ -1,7 +1,7 @@ import { AnchorProvider, Program } from "@coral-xyz/anchor"; import * as anchor from "@coral-xyz/anchor"; import { PublicKey, SystemProgram, Connection } from "@solana/web3.js"; -import TOKEN_PROGRAM_ID from "@solana/spl-token"; +import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import BN from "bn.js"; import { FundSender, IDL } from "../../types/fund_sender"; @@ -389,7 +389,8 @@ export class FundSenderClient { * */ public async storeCertificates( - outputYieldTokenAccount: PublicKey + outputYieldTokenAccount: PublicKey, + certificateVaultAta: PublicKey ): Promise { if (!this.config) { throw new Error("Client not initialized"); @@ -405,7 +406,7 @@ export class FundSenderClient { state: this.stateAddress, outputYieldAccount, outputYieldTokenAccount, - certificateVault: this.config.certificateVault, + certificateVaultAta, tokenProgram: TOKEN_PROGRAM_ID, }) .rpc() diff --git a/packages/tests/fundSender.ts b/packages/tests/fundSender.ts index 3e1f475..03ab232 100644 --- a/packages/tests/fundSender.ts +++ b/packages/tests/fundSender.ts @@ -9,7 +9,6 @@ import { } from "@solana/web3.js"; import { FundSenderClient } from "../fund-sender/client"; import { - Account, createMint, getAccount, getOrCreateAssociatedTokenAccount, @@ -38,36 +37,14 @@ describe("fund-sender", () => { context("create and update", () => { it("can register a new fund sender state", async () => { const destinationAccount = Keypair.generate().publicKey; - const provider = AnchorProvider.local(); - const connection0 = provider.connection; - const payer = Keypair.generate(); - const tx = await connection0.requestAirdrop( - payer.publicKey, - LAMPORTS_PER_SOL - ); - const blockhash = await connection0.getLatestBlockhash(); - await connection0.confirmTransaction({ signature: tx, ...blockhash }); - const mint = await createMint( - connection0, - payer, - payer.publicKey, - null, - 0 - ); - const certificateVault = await getOrCreateAssociatedTokenAccount( - connection0, - payer, - mint, - authority.publicKey, - true - ); + const certificateVault = Keypair.generate(); client = await FundSenderClient.register( sunriseState, authority.publicKey, destinationSeed, destinationAccount, - certificateVault.address, + certificateVault.publicKey, spendThreshold ); }); @@ -168,7 +145,7 @@ describe("fund-sender", () => { context("transfer functions", () => { let destinationAccount: PublicKey; - let certificateVault: Account; + let certificateVault: Keypair; let authority: Keypair; let mint: PublicKey; // let connection0: Connection; @@ -192,19 +169,20 @@ describe("fund-sender", () => { null, 10 ); - certificateVault = await getOrCreateAssociatedTokenAccount( + /* certificateVault = await getOrCreateAssociatedTokenAccount( connection0, authority, mint, authority.publicKey, true - ); + ); */ + certificateVault = Keypair.generate(); client = await FundSenderClient.register( sunriseState, authority.publicKey, destinationSeed, destinationAccount, - certificateVault.address, + certificateVault.publicKey, spendThreshold ); }); @@ -257,10 +235,18 @@ describe("fund-sender", () => { const blockhash3 = await connection.getLatestBlockhash(); await connection.confirmTransaction({ signature: tx3, ...blockhash3 }); - await client.storeCertificates(ata.address); + const certificateVaultAta = await getOrCreateAssociatedTokenAccount( + connection, + authority, + mint, + certificateVault.publicKey, + true + ); + + await client.storeCertificates(ata.address, certificateVaultAta.address); const certificateVaultInfo = await getAccount( connection, - certificateVault.address + certificateVaultAta.address ); expect(Number(certificateVaultInfo.amount)).to.equal(mintAmount); @@ -297,12 +283,120 @@ describe("fund-sender", () => { const blockhash3 = await connection.getLatestBlockhash(); await connection.confirmTransaction({ signature: tx3, ...blockhash3 }); - const shouldFail = client.storeCertificates(ata.address); + const certificateVaultAta = await getOrCreateAssociatedTokenAccount( + connection, + authority, + mint, + certificateVault.publicKey, + true + ); + + const shouldFail = client.storeCertificates( + ata.address, + certificateVaultAta.address + ); return expect(shouldFail).to.be.rejectedWith( "IncorrectTokenAccountOwner." ); }); + it("should not be able to transfer to a token account not owned by certificate_vault", async () => { + const connection = client.program.provider.connection; + const tx1 = await connection.requestAirdrop( + authority.publicKey, + LAMPORTS_PER_SOL + ); + const blockhash1 = await connection.getLatestBlockhash(); + await connection.confirmTransaction({ signature: tx1, ...blockhash1 }); + const ata = await getOrCreateAssociatedTokenAccount( + connection, + authority, + mint, + client.getOutputYieldAccount(destinationSeed), + true + ); + + const mintAmount = 100; + const tx3 = await mintTo( + connection, + authority, + mint, + ata.address, + authority.publicKey, + mintAmount + ); + + const blockhash3 = await connection.getLatestBlockhash(); + await connection.confirmTransaction({ signature: tx3, ...blockhash3 }); + + const unauthorisedCertificateVault = Keypair.generate(); + const unauthorisedCertificateVaultAta = + await getOrCreateAssociatedTokenAccount( + connection, + authority, + mint, + unauthorisedCertificateVault.publicKey, + true + ); + + const shouldFail = client.storeCertificates( + ata.address, + unauthorisedCertificateVaultAta.address + ); + return expect(shouldFail).to.be.rejectedWith("ConstraintTokenOwner."); + }); + + it("should not be able to transfer to a token account of another mint", async () => { + const connection = client.program.provider.connection; + const tx1 = await connection.requestAirdrop( + authority.publicKey, + LAMPORTS_PER_SOL + ); + const blockhash1 = await connection.getLatestBlockhash(); + await connection.confirmTransaction({ signature: tx1, ...blockhash1 }); + const ata = await getOrCreateAssociatedTokenAccount( + connection, + authority, + mint, + client.getOutputYieldAccount(destinationSeed), + true + ); + + const mintAmount = 100; + const tx3 = await mintTo( + connection, + authority, + mint, + ata.address, + authority.publicKey, + mintAmount + ); + + const blockhash3 = await connection.getLatestBlockhash(); + await connection.confirmTransaction({ signature: tx3, ...blockhash3 }); + + const wrongMint = await createMint( + connection, + authority, + authority.publicKey, + null, + 10 + ); + const wrongCertificateVaultAta = await getOrCreateAssociatedTokenAccount( + connection, + authority, + wrongMint, + certificateVault.publicKey, + true + ); + + const shouldFail = client.storeCertificates( + ata.address, + wrongCertificateVaultAta.address + ); + return expect(shouldFail).to.be.rejectedWith("ConstraintAssociated."); + }); + it("should be able to update certificate vault and store certificates in new vault", async () => { const connection = client.program.provider.connection; const tx1 = await connection.requestAirdrop( @@ -332,12 +426,12 @@ describe("fund-sender", () => { const blockhash3 = await connection.getLatestBlockhash(); await connection.confirmTransaction({ signature: tx3, ...blockhash3 }); - const anotherUser = Keypair.generate(); - const newCertificateVault = await getOrCreateAssociatedTokenAccount( + const newCertificateVaultA = Keypair.generate(); + const newCertificateVaultAta = await getOrCreateAssociatedTokenAccount( connection, authority, mint, - anotherUser.publicKey, + newCertificateVaultA.publicKey, true ); const authorisedUserProvider = new AnchorProvider( @@ -350,15 +444,18 @@ describe("fund-sender", () => { authorisedUserProvider ); await authorisedClient.updateCertificateVault( - newCertificateVault.address + newCertificateVaultA.publicKey ); const updatedClient = await FundSenderClient.fetch(client.stateAddress); - await updatedClient.storeCertificates(ata.address); + await updatedClient.storeCertificates( + ata.address, + newCertificateVaultAta.address + ); const certificateVaultInfo = await getAccount( connection, - newCertificateVault.address + newCertificateVaultAta.address ); expect(Number(certificateVaultInfo.amount)).to.equal(mintAmount); diff --git a/packages/types/fund_sender.ts b/packages/types/fund_sender.ts index 4fa5ec3..175d129 100644 --- a/packages/types/fund_sender.ts +++ b/packages/types/fund_sender.ts @@ -125,7 +125,7 @@ export type FundSender = { ] }, { - "name": "certificateVault", + "name": "certificateVaultAta", "isMut": true, "isSigner": false }, @@ -228,13 +228,13 @@ export type FundSender = { }, { "code": 6003, - "name": "IncorrectDestinationAccount", - "msg": "Incorrect destination account" + "name": "MintMismatch", + "msg": "Source and destination token accounts do not have the same mint" }, { "code": 6004, - "name": "IncorrectHoldAccount", - "msg": "Incorrect hold account" + "name": "IncorrectDestinationAccount", + "msg": "Incorrect destination account" }, { "code": 6005, @@ -371,7 +371,7 @@ export const IDL: FundSender = { ] }, { - "name": "certificateVault", + "name": "certificateVaultAta", "isMut": true, "isSigner": false }, @@ -474,13 +474,13 @@ export const IDL: FundSender = { }, { "code": 6003, - "name": "IncorrectDestinationAccount", - "msg": "Incorrect destination account" + "name": "MintMismatch", + "msg": "Source and destination token accounts do not have the same mint" }, { "code": 6004, - "name": "IncorrectHoldAccount", - "msg": "Incorrect hold account" + "name": "IncorrectDestinationAccount", + "msg": "Incorrect destination account" }, { "code": 6005, diff --git a/programs/fund-sender/src/lib.rs b/programs/fund-sender/src/lib.rs index 6f4cece..f80a4bd 100644 --- a/programs/fund-sender/src/lib.rs +++ b/programs/fund-sender/src/lib.rs @@ -78,14 +78,14 @@ pub mod fund_sender { let state = &mut ctx.accounts.state; let output_yield_account = &mut ctx.accounts.output_yield_account; let output_yield_token_account = &mut ctx.accounts.output_yield_token_account; - let certificate_vault = &mut ctx.accounts.certificate_vault; + let certificate_vault_ata = &mut ctx.accounts.certificate_vault_ata; let amount: u64 = output_yield_token_account.amount; transfer_token( &state.key(), &AccountsTokenTransfer { source: output_yield_token_account.to_account_info(), - dest: certificate_vault.to_account_info(), + dest: certificate_vault_ata.to_account_info(), authority: output_yield_account.to_account_info(), }, amount, diff --git a/programs/fund-sender/src/utils/errors.rs b/programs/fund-sender/src/utils/errors.rs index 95f4378..37ab2ed 100644 --- a/programs/fund-sender/src/utils/errors.rs +++ b/programs/fund-sender/src/utils/errors.rs @@ -11,12 +11,12 @@ pub enum ErrorCode { #[msg("Token account not owned by output yield account")] IncorrectTokenAccountOwner, + #[msg("Source and destination token accounts do not have the same mint")] + MintMismatch, + #[msg("Incorrect destination account")] IncorrectDestinationAccount, - #[msg("Incorrect hold account")] - IncorrectHoldAccount, - #[msg("Incorrect update authority")] Unauthorized, } diff --git a/programs/fund-sender/src/utils/state.rs b/programs/fund-sender/src/utils/state.rs index 8e61d40..dfd3e99 100644 --- a/programs/fund-sender/src/utils/state.rs +++ b/programs/fund-sender/src/utils/state.rs @@ -115,9 +115,11 @@ pub struct StoreCertificates<'info> { pub output_yield_token_account: Account<'info, TokenAccount>, #[account( mut, - constraint = certificate_vault.key() == state.certificate_vault @ ErrorCode::IncorrectHoldAccount, + associated_token::mint = output_yield_token_account.mint, + associated_token::authority = state.certificate_vault, )] // the account where we store all the certificates - pub certificate_vault: Account<'info, TokenAccount>, + pub certificate_vault_ata: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, + // pub system_program: Program<'info, System>, }