From 67dad3068b7fd93175ec2a7575f0f66602388155 Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Fri, 4 Aug 2023 15:26:10 -0500 Subject: [PATCH] solana: refactor tbtc tests --- cross-chain/solana/package-lock.json | 100 ++ cross-chain/solana/package.json | 1 + cross-chain/solana/tests/01__tbtc.ts | 1408 +++++++++-------- .../solana/tests/02__wormholeGateway.ts | 113 +- cross-chain/solana/tests/helpers/tbtc.ts | 486 +++++- cross-chain/solana/tests/helpers/utils.ts | 83 +- .../solana/tests/helpers/wormholeGateway.ts | 8 +- 7 files changed, 1467 insertions(+), 732 deletions(-) diff --git a/cross-chain/solana/package-lock.json b/cross-chain/solana/package-lock.json index ddcd6cb2d..caf320a81 100644 --- a/cross-chain/solana/package-lock.json +++ b/cross-chain/solana/package-lock.json @@ -9,6 +9,7 @@ }, "devDependencies": { "@certusone/wormhole-sdk": "^0.9.22", + "@metaplex-foundation/mpl-token-metadata": "^2.13.0", "@solana/spl-token": "^0.3.8", "@solana/web3.js": "^1.77.3", "@types/bn.js": "^5.1.0", @@ -1602,6 +1603,99 @@ "rlp": "^2.2.3" } }, + "node_modules/@metaplex-foundation/beet": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@metaplex-foundation/beet/-/beet-0.7.1.tgz", + "integrity": "sha512-hNCEnS2WyCiYyko82rwuISsBY3KYpe828ubsd2ckeqZr7tl0WVLivGkoyA/qdiaaHEBGdGl71OpfWa2rqL3DiA==", + "dev": true, + "dependencies": { + "ansicolors": "^0.3.2", + "bn.js": "^5.2.0", + "debug": "^4.3.3" + } + }, + "node_modules/@metaplex-foundation/beet-solana": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@metaplex-foundation/beet-solana/-/beet-solana-0.4.0.tgz", + "integrity": "sha512-B1L94N3ZGMo53b0uOSoznbuM5GBNJ8LwSeznxBxJ+OThvfHQ4B5oMUqb+0zdLRfkKGS7Q6tpHK9P+QK0j3w2cQ==", + "dev": true, + "dependencies": { + "@metaplex-foundation/beet": ">=0.1.0", + "@solana/web3.js": "^1.56.2", + "bs58": "^5.0.0", + "debug": "^4.3.4" + } + }, + "node_modules/@metaplex-foundation/beet-solana/node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==", + "dev": true + }, + "node_modules/@metaplex-foundation/beet-solana/node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dev": true, + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/@metaplex-foundation/beet-solana/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@metaplex-foundation/cusper": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@metaplex-foundation/cusper/-/cusper-0.0.2.tgz", + "integrity": "sha512-S9RulC2fFCFOQraz61bij+5YCHhSO9llJegK8c8Y6731fSi6snUSQJdCUqYS8AIgR0TKbQvdvgSyIIdbDFZbBA==", + "dev": true + }, + "node_modules/@metaplex-foundation/mpl-token-metadata": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@metaplex-foundation/mpl-token-metadata/-/mpl-token-metadata-2.13.0.tgz", + "integrity": "sha512-Fl/8I0L9rv4bKTV/RAl5YIbJe9SnQPInKvLz+xR1fEc4/VQkuCn3RPgypfUMEKWmCznzaw4sApDxy6CFS4qmJw==", + "dev": true, + "dependencies": { + "@metaplex-foundation/beet": "^0.7.1", + "@metaplex-foundation/beet-solana": "^0.4.0", + "@metaplex-foundation/cusper": "^0.0.2", + "@solana/spl-token": "^0.3.6", + "@solana/web3.js": "^1.66.2", + "bn.js": "^5.2.0", + "debug": "^4.3.4" + } + }, + "node_modules/@metaplex-foundation/mpl-token-metadata/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@mysten/bcs": { "version": "0.7.1", "dev": true, @@ -2222,6 +2316,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "dev": true, diff --git a/cross-chain/solana/package.json b/cross-chain/solana/package.json index a18adfd59..ea15b6a98 100644 --- a/cross-chain/solana/package.json +++ b/cross-chain/solana/package.json @@ -8,6 +8,7 @@ }, "devDependencies": { "@certusone/wormhole-sdk": "^0.9.22", + "@metaplex-foundation/mpl-token-metadata": "^2.13.0", "@solana/spl-token": "^0.3.8", "@solana/web3.js": "^1.77.3", "@types/bn.js": "^5.1.0", diff --git a/cross-chain/solana/tests/01__tbtc.ts b/cross-chain/solana/tests/01__tbtc.ts index b9beb26b2..7d3591ec0 100644 --- a/cross-chain/solana/tests/01__tbtc.ts +++ b/cross-chain/solana/tests/01__tbtc.ts @@ -1,258 +1,17 @@ import * as anchor from "@coral-xyz/anchor"; -import { AnchorError, Program } from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; import * as spl from "@solana/spl-token"; -import * as web3 from "@solana/web3.js"; -import { expect } from "chai"; +import { assert, expect } from "chai"; import { Tbtc } from "../target/types/tbtc"; +import * as tbtc from "./helpers/tbtc"; import { - addMinter, - checkState, - getConfigPDA, - getGuardianPDA, - getGuardiansPDA, - getMinterInfoPDA, - getMintersPDA, - getTokenPDA, - maybeAuthorityAnd, -} from "./helpers/tbtc"; -import { transferLamports } from "./helpers/utils"; - -async function setup(program: Program, authority) { - const config = getConfigPDA(); - const guardians = getGuardiansPDA(); - const minters = getMintersPDA(); - const tbtcMintPDA = getTokenPDA(); - - await program.methods - .initialize() - .accounts({ - mint: tbtcMintPDA, - config, - guardians, - minters, - authority: authority.publicKey, - }) - .rpc(); -} - -async function changeAuthority( - program: Program, - authority, - newAuthority -) { - const config = getConfigPDA(); - await program.methods - .changeAuthority() - .accounts({ - config, - authority: authority.publicKey, - newAuthority: newAuthority.publicKey, - }) - .signers(maybeAuthorityAnd(authority, [])) - .rpc(); -} - -async function takeAuthority(program: Program, newAuthority) { - const config = getConfigPDA(); - await program.methods - .takeAuthority() - .accounts({ - config, - pendingAuthority: newAuthority.publicKey, - }) - .signers(maybeAuthorityAnd(newAuthority, [])) - .rpc(); -} - -async function cancelAuthorityChange(program: Program, authority) { - const config = getConfigPDA(); - await program.methods - .cancelAuthorityChange() - .accounts({ - config, - authority: authority.publicKey, - }) - .signers(maybeAuthorityAnd(authority, [])) - .rpc(); -} - -async function checkPendingAuthority(program: Program, pendingAuthority) { - const config = getConfigPDA(); - let configState = await program.account.config.fetch(config); - expect(configState.pendingAuthority).to.eql(pendingAuthority.publicKey); -} - -async function checkNoPendingAuthority(program: Program) { - const config = getConfigPDA(); - let configState = await program.account.config.fetch(config); - expect(configState.pendingAuthority).to.equal(null); -} - -async function checkPaused(program: Program, paused: boolean) { - const config = getConfigPDA(); - let configState = await program.account.config.fetch(config); - expect(configState.paused).to.equal(paused); -} - -async function checkMinter(program: Program, minter) { - const minterInfoPDA = getMinterInfoPDA(minter.publicKey); - let minterInfo = await program.account.minterInfo.fetch(minterInfoPDA); - - expect(minterInfo.minter).to.eql(minter.publicKey); -} - -async function removeMinter( - program: Program, - authority, - minter, - minterInfo -) { - const config = getConfigPDA(); - const minters = getMintersPDA(); - await program.methods - .removeMinter() - .accounts({ - config, - authority: authority.publicKey, - minters, - minterInfo: minterInfo, - minter: minter.publicKey, - }) - .signers(maybeAuthorityAnd(authority, [])) - .rpc(); -} - -async function addGuardian( - program: Program, - authority, - guardian, - payer -): Promise { - const config = getConfigPDA(); - const guardians = getGuardiansPDA(); - const guardianInfoPDA = getGuardianPDA(guardian); - await program.methods - .addGuardian() - .accounts({ - config, - authority: authority.publicKey, - guardians, - guardianInfo: guardianInfoPDA, - guardian: guardian.publicKey, - }) - .signers(maybeAuthorityAnd(authority, [])) - .rpc(); - return guardianInfoPDA; -} - -async function checkGuardian(program: Program, guardian) { - const guardianInfoPDA = getGuardianPDA(guardian); - let guardianInfo = await program.account.guardianInfo.fetch(guardianInfoPDA); - - expect(guardianInfo.guardian).to.eql(guardian.publicKey); -} - -async function removeGuardian( - program: Program, - authority, - guardian, - guardianInfo -) { - const config = getConfigPDA(); - const guardians = getGuardiansPDA(); - await program.methods - .removeGuardian() - .accounts({ - config, - authority: authority.publicKey, - guardians, - guardianInfo: guardianInfo, - guardian: guardian.publicKey, - }) - .signers(maybeAuthorityAnd(authority, [])) - .rpc(); -} - -async function pause(program: Program, guardian) { - const config = getConfigPDA(); - const guardianInfoPDA = getGuardianPDA(guardian); - await program.methods - .pause() - .accounts({ - config, - guardianInfo: guardianInfoPDA, - guardian: guardian.publicKey, - }) - .signers([guardian]) - .rpc(); -} - -async function unpause(program: Program, authority) { - const config = getConfigPDA(); - await program.methods - .unpause() - .accounts({ - config, - authority: authority.publicKey, - }) - .signers(maybeAuthorityAnd(authority, [])) - .rpc(); -} - -async function mint( - program: Program, - minter, - minterInfoPDA, - recipient, - amount, - payer -) { - const connection = program.provider.connection; - - const config = getConfigPDA(); - const tbtcMintPDA = getTokenPDA(); - const recipientToken = spl.getAssociatedTokenAddressSync( - tbtcMintPDA, - recipient.publicKey - ); - - const tokenData = await spl - .getAccount(connection, recipientToken) - .catch((err) => { - if (err instanceof spl.TokenAccountNotFoundError) { - return null; - } else { - throw err; - } - }); - - if (tokenData === null) { - const tx = await web3.sendAndConfirmTransaction( - connection, - new web3.Transaction().add( - spl.createAssociatedTokenAccountIdempotentInstruction( - payer.publicKey, - recipientToken, - recipient.publicKey, - tbtcMintPDA - ) - ), - [payer.payer] - ); - } - - await program.methods - .mint(new anchor.BN(amount)) - .accounts({ - mint: tbtcMintPDA, - config, - minterInfo: minterInfoPDA, - minter: minter.publicKey, - recipientToken, - }) - .signers(maybeAuthorityAnd(payer, [minter])) - .rpc(); -} + expectIxFail, + expectIxSuccess, + getOrCreateAta, + getTokenBalance, + sleep, + transferLamports, +} from "./helpers/utils"; describe("tbtc", () => { // Configure the client to use the local cluster. @@ -260,388 +19,805 @@ describe("tbtc", () => { const program = anchor.workspace.Tbtc as Program; - const authority = (program.provider as anchor.AnchorProvider) - .wallet as anchor.Wallet; + const authority = ( + (program.provider as anchor.AnchorProvider).wallet as anchor.Wallet + ).payer; const newAuthority = anchor.web3.Keypair.generate(); - const minterKeys = anchor.web3.Keypair.generate(); - const minter2Keys = anchor.web3.Keypair.generate(); - const impostorKeys = anchor.web3.Keypair.generate(); - const guardianKeys = anchor.web3.Keypair.generate(); - const guardian2Keys = anchor.web3.Keypair.generate(); - - const recipientKeys = anchor.web3.Keypair.generate(); - - it("setup", async () => { - await setup(program, authority); - await checkState(authority, 0, 0, 0); + const minter = anchor.web3.Keypair.generate(); + const anotherMinter = anchor.web3.Keypair.generate(); + const imposter = anchor.web3.Keypair.generate(); + const guardian = anchor.web3.Keypair.generate(); + const anotherGuardian = anchor.web3.Keypair.generate(); + + const recipient = anchor.web3.Keypair.generate(); + const txPayer = anchor.web3.Keypair.generate(); + + it("set up payers", async () => { + await transferLamports(authority, newAuthority.publicKey, 10000000000); + await transferLamports(authority, imposter.publicKey, 10000000000); + await transferLamports(authority, recipient.publicKey, 10000000000); + await transferLamports(authority, txPayer.publicKey, 10000000000); }); - it("change authority", async () => { - await checkState(authority, 0, 0, 0); - await checkNoPendingAuthority(program); - try { - await cancelAuthorityChange(program, authority); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("NoPendingAuthorityChange"); - expect(err.program.equals(program.programId)).is.true; - } - try { - await takeAuthority(program, newAuthority); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("NoPendingAuthorityChange"); - expect(err.program.equals(program.programId)).is.true; - } - - await changeAuthority(program, authority, newAuthority); - await checkPendingAuthority(program, newAuthority); - await takeAuthority(program, newAuthority); - await checkNoPendingAuthority(program); - await checkState(newAuthority, 0, 0, 0); - await changeAuthority(program, newAuthority, authority.payer); - try { - await takeAuthority(program, impostorKeys); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsNotPendingAuthority"); - expect(err.program.equals(program.programId)).is.true; - } - try { - await takeAuthority(program, newAuthority); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsNotPendingAuthority"); - expect(err.program.equals(program.programId)).is.true; - } - try { - await cancelAuthorityChange(program, authority); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsNotAuthority"); - expect(err.program.equals(program.programId)).is.true; - } - await takeAuthority(program, authority); - - await checkState(authority, 0, 0, 0); + it("initialize", async () => { + const ix = await tbtc.initializeIx({ authority: authority.publicKey }); + await expectIxSuccess([ix], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 0, + numGuardians: 0, + supply: BigInt(0), + paused: false, + pendingAuthority: null, + }); }); - it("add minter", async () => { - await checkState(authority, 0, 0, 0); - await addMinter(authority, minterKeys.publicKey); - await checkMinter(program, minterKeys); - await checkState(authority, 1, 0, 0); - - // Transfer lamports to imposter. - await transferLamports(authority.payer, impostorKeys.publicKey, 1000000000); - // await web3.sendAndConfirmTransaction( - // program.provider.connection, - // new web3.Transaction().add( - // web3.SystemProgram.transfer({ - // fromPubkey: authority.publicKey, - // toPubkey: impostorKeys.publicKey, - // lamports: 1000000000, - // }) - // ), - // [authority.payer] - // ); - - try { - await addMinter(impostorKeys, minter2Keys.publicKey); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsNotAuthority"); - expect(err.program.equals(program.programId)).is.true; - } - }); + describe("authority changes", () => { + it("cannot cancel authority if no pending", async () => { + const failedCancelIx = await tbtc.cancelAuthorityChangeIx({ + authority: authority.publicKey, + }); + await expectIxFail( + [failedCancelIx], + [authority], + "NoPendingAuthorityChange" + ); + }); - it("mint", async () => { - await checkState(authority, 1, 0, 0); - const minterInfoPDA = getMinterInfoPDA(minterKeys.publicKey); - await checkMinter(program, minterKeys); - - // await setupMint(program, authority, recipientKeys); - await mint( - program, - minterKeys, - minterInfoPDA, - recipientKeys, - 1000, - authority - ); - - await checkState(authority, 1, 0, 1000); - - // // Burn for next test. - // const ix = spl.createBurnCheckedInstruction( - // account, // PublicKey of Owner's Associated Token Account - // new PublicKey(MINT_ADDRESS), // Public Key of the Token Mint Address - // WALLET.publicKey, // Public Key of Owner's Wallet - // BURN_QUANTITY * (10**MINT_DECIMALS), // Number of tokens to burn - // MINT_DECIMALS // Number of Decimals of the Token Mint - // ) - }); + it("cannot take authority if no pending", async () => { + const failedTakeIx = await tbtc.takeAuthorityIx({ + pendingAuthority: newAuthority.publicKey, + }); + await expectIxFail( + [failedTakeIx], + [newAuthority], + "NoPendingAuthorityChange" + ); + }); - it("won't mint", async () => { - await checkState(authority, 1, 0, 1000); - const minterInfoPDA = getMinterInfoPDA(minterKeys.publicKey); - await checkMinter(program, minterKeys); - - // await setupMint(program, authority, recipientKeys); - - try { - await mint( - program, - impostorKeys, - minterInfoPDA, - recipientKeys, - 1000, - authority - ); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("ConstraintSeeds"); - expect(err.program.equals(program.programId)).is.true; - } - }); + it("change authority to new authority", async () => { + const changeIx = await tbtc.changeAuthorityIx({ + authority: authority.publicKey, + newAuthority: newAuthority.publicKey, + }); + await expectIxSuccess([changeIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 0, + numGuardians: 0, + supply: BigInt(0), + paused: false, + pendingAuthority: newAuthority.publicKey, + }); + }); - it("use two minters", async () => { - await checkState(authority, 1, 0, 1000); - const minterInfoPDA = getMinterInfoPDA(minterKeys.publicKey); - await checkMinter(program, minterKeys); - const minter2InfoPDA = await addMinter(authority, minter2Keys.publicKey); - await checkMinter(program, minter2Keys); - await checkState(authority, 2, 0, 1000); - // await setupMint(program, authority, recipientKeys); - - // cannot mint with wrong keys - try { - await mint( - program, - minter2Keys, - minterInfoPDA, - recipientKeys, - 1000, - authority - ); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("ConstraintSeeds"); - expect(err.program.equals(program.programId)).is.true; - } - - // cannot remove minter with wrong keys - try { - await removeMinter(program, authority, minter2Keys, minterInfoPDA); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("ConstraintSeeds"); - expect(err.program.equals(program.programId)).is.true; - } - - await mint( - program, - minterKeys, - minterInfoPDA, - recipientKeys, - 500, - authority - ); - await checkState(authority, 2, 0, 1500); - }); + it("take as new authority", async () => { + // Bug in validator? Need to wait a bit for new blockhash. + await sleep(10000); + + const takeIx = await tbtc.takeAuthorityIx({ + pendingAuthority: newAuthority.publicKey, + }); + await expectIxSuccess([takeIx], [newAuthority]); + await tbtc.checkConfig({ + authority: newAuthority.publicKey, + numMinters: 0, + numGuardians: 0, + supply: BigInt(0), + paused: false, + pendingAuthority: null, + }); + }); - it("remove minter", async () => { - await checkState(authority, 2, 0, 1500); - const minter2InfoPDA = getMinterInfoPDA(minter2Keys.publicKey); - await checkMinter(program, minter2Keys); - await removeMinter(program, authority, minter2Keys, minter2InfoPDA); - await checkState(authority, 1, 0, 1500); - }); + it("change pending authority back to original authority", async () => { + const changeBackIx = await tbtc.changeAuthorityIx({ + authority: newAuthority.publicKey, + newAuthority: authority.publicKey, + }); + await expectIxSuccess([changeBackIx], [newAuthority]); + await tbtc.checkConfig({ + authority: newAuthority.publicKey, + numMinters: 0, + numGuardians: 0, + supply: BigInt(0), + paused: false, + pendingAuthority: authority.publicKey, + }); + }); - it("won't remove minter", async () => { - await checkState(authority, 1, 0, 1500); - const minterInfoPDA = getMinterInfoPDA(minterKeys.publicKey); - await checkMinter(program, minterKeys); - - try { - await removeMinter(program, impostorKeys, minterKeys, minterInfoPDA); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsNotAuthority"); - expect(err.program.equals(program.programId)).is.true; - } - - await removeMinter(program, authority, minterKeys, minterInfoPDA); - await checkState(authority, 0, 0, 1500); - - try { - await removeMinter(program, authority, minterKeys, minterInfoPDA); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("AccountNotInitialized"); - expect(err.program.equals(program.programId)).is.true; - } - }); + it("cannot take as signers that are not pending authority", async () => { + const failedImposterTakeIx = await tbtc.takeAuthorityIx({ + pendingAuthority: imposter.publicKey, + }); + await expectIxFail( + [failedImposterTakeIx], + [imposter], + "IsNotPendingAuthority" + ); - it("add guardian", async () => { - await checkState(authority, 0, 0, 1500); - await addGuardian(program, authority, guardianKeys, authority); - await checkGuardian(program, guardianKeys); - await checkState(authority, 0, 1, 1500); - - try { - await addGuardian(program, impostorKeys, guardian2Keys, authority); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsNotAuthority"); - expect(err.program.equals(program.programId)).is.true; - } - }); + const failedNewAuthorityTakeIx = await tbtc.takeAuthorityIx({ + pendingAuthority: newAuthority.publicKey, + }); + await expectIxFail( + [failedNewAuthorityTakeIx], + [newAuthority], + "IsNotPendingAuthority" + ); + }); - it("remove guardian", async () => { - await checkState(authority, 0, 1, 1500); - const guardianInfoPDA = getGuardianPDA(guardianKeys); - await checkGuardian(program, guardianKeys); - - try { - await removeGuardian( - program, - impostorKeys, - guardianKeys, - guardianInfoPDA - ); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsNotAuthority"); - expect(err.program.equals(program.programId)).is.true; - } - - await removeGuardian(program, authority, guardianKeys, guardianInfoPDA); - await checkState(authority, 0, 0, 1500); - - try { - await removeGuardian(program, authority, guardianKeys, guardianInfoPDA); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("AccountNotInitialized"); - expect(err.program.equals(program.programId)).is.true; - } - }); + it("cannot cancel as someone else", async () => { + const anotherFailedCancelIx = await tbtc.cancelAuthorityChangeIx({ + authority: authority.publicKey, + }); + await expectIxFail( + [anotherFailedCancelIx], + [authority], + "IsNotAuthority" + ); + }); - it("pause", async () => { - await checkState(authority, 0, 0, 1500); - await addGuardian(program, authority, guardianKeys, authority); - await checkPaused(program, false); - await pause(program, guardianKeys); - await checkPaused(program, true); + it("finally take as authority", async () => { + const anotherTakeIx = await tbtc.takeAuthorityIx({ + pendingAuthority: authority.publicKey, + }); + await expectIxSuccess([anotherTakeIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 0, + numGuardians: 0, + supply: BigInt(0), + paused: false, + pendingAuthority: null, + }); + }); }); - it("unpause", async () => { - await checkState(authority, 0, 1, 1500); - await checkPaused(program, true); - await unpause(program, authority); - await checkPaused(program, false); - - try { - await unpause(program, authority); - - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsNotPaused"); - expect(err.program.equals(program.programId)).is.true; - } - }); + describe("minting", () => { + it("cannot add minter without authority", async () => { + const cannotAddMinterIx = await tbtc.addMinterIx({ + authority: imposter.publicKey, + minter: minter.publicKey, + }); + await expectIxFail([cannotAddMinterIx], [imposter], "IsNotAuthority"); + }); + + it("add minter", async () => { + const mustBeNull = await tbtc + .checkMinterInfo(minter.publicKey) + .catch((_) => null); + assert(mustBeNull === null, "minter info found"); + + const addMinterIx = await tbtc.addMinterIx({ + authority: authority.publicKey, + minter: minter.publicKey, + }); + await expectIxSuccess([addMinterIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 0, + supply: BigInt(0), + paused: false, + pendingAuthority: null, + }); + await tbtc.checkMinterInfo(minter.publicKey); + }); + + it("mint", async () => { + const amount = BigInt(1000); - it("won't mint when paused", async () => { - await checkState(authority, 0, 1, 1500); - const minterInfoPDA = await addMinter(authority, minterKeys.publicKey); - await pause(program, guardianKeys); - // await setupMint(program, authority, recipientKeys); - - try { - await mint( - program, - minterKeys, - minterInfoPDA, - recipientKeys, - 1000, - authority - ); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsPaused"); - expect(err.program.equals(program.programId)).is.true; - } - - await unpause(program, authority); - await checkPaused(program, false); + const recipientToken = await getOrCreateAta( + authority, + tbtc.getMintPDA(), + recipient.publicKey + ); + const recipientBefore = await getTokenBalance(recipientToken); + expect(recipientBefore).to.equal(BigInt(0)); + + const mintIx = await tbtc.mintIx( + { + minter: minter.publicKey, + recipientToken, + }, + new anchor.BN(amount.toString()) + ); + await expectIxSuccess([mintIx], [txPayer, minter]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 0, + supply: BigInt(1000), + paused: false, + pendingAuthority: null, + }); + + const recipientAfter = await getTokenBalance(recipientToken); + expect(recipientAfter).to.equal(amount); + }); + + it("cannot mint without minter", async () => { + const recipientToken = spl.getAssociatedTokenAddressSync( + tbtc.getMintPDA(), + recipient.publicKey + ); + + const cannotMintIx = await tbtc.mintIx( + { + minter: imposter.publicKey, + recipientToken, + }, + new anchor.BN(420) + ); + await expectIxFail( + [cannotMintIx], + [txPayer, imposter], + "AccountNotInitialized" + ); + + // Now try with actual minter's info account. + const minterInfo = tbtc.getMinterInfoPDA(minter.publicKey); + + const cannotMintAgainIx = await tbtc.mintIx( + { + minterInfo, + minter: imposter.publicKey, + recipientToken, + }, + new anchor.BN(420) + ); + await expectIxFail( + [cannotMintAgainIx], + [txPayer, imposter], + "ConstraintSeeds" + ); + }); + + it("add another minter", async () => { + const mustBeNull = await tbtc + .checkMinterInfo(anotherMinter.publicKey) + .catch((_) => null); + assert(mustBeNull === null, "minter info found"); + + const addMinterIx = await tbtc.addMinterIx({ + authority: authority.publicKey, + minter: anotherMinter.publicKey, + }); + await expectIxSuccess([addMinterIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 2, + numGuardians: 0, + supply: BigInt(1000), + paused: false, + pendingAuthority: null, + }); + await tbtc.checkMinterInfo(anotherMinter.publicKey); + }); + + it("cannot remove minter with wrong key", async () => { + const minterInfo = tbtc.getMinterInfoPDA(minter.publicKey); + const cannotRemoveIx = await tbtc.removeMinterIx({ + authority: authority.publicKey, + minterInfo, + minter: anotherMinter.publicKey, + }); + await expectIxFail([cannotRemoveIx], [authority], "ConstraintSeeds"); + }); + + it("mint with another minter", async () => { + const amount = BigInt(500); + + const recipientToken = await spl.getAssociatedTokenAddressSync( + tbtc.getMintPDA(), + recipient.publicKey + ); + const recipientBefore = await getTokenBalance(recipientToken); + expect(recipientBefore).to.equal(BigInt(1000)); + + const mintIx = await tbtc.mintIx( + { + minter: anotherMinter.publicKey, + recipientToken, + }, + new anchor.BN(amount.toString()) + ); + await expectIxSuccess([mintIx], [txPayer, anotherMinter]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 2, + numGuardians: 0, + supply: BigInt(1500), + paused: false, + pendingAuthority: null, + }); + + const recipientAfter = await getTokenBalance(recipientToken); + expect(recipientAfter).to.equal(recipientBefore + amount); + }); + + it("cannot remove minter without authority", async () => { + const cannotRemoveIx = await tbtc.removeMinterIx({ + authority: imposter.publicKey, + minter: anotherMinter.publicKey, + }); + await expectIxFail([cannotRemoveIx], [imposter], "IsNotAuthority"); + }); + + it("remove minter", async () => { + const removeIx = await tbtc.removeMinterIx({ + authority: authority.publicKey, + minter: anotherMinter.publicKey, + }); + await expectIxSuccess([removeIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 0, + supply: BigInt(1500), + paused: false, + pendingAuthority: null, + }); + const mustBeNull = await tbtc + .checkMinterInfo(anotherMinter.publicKey) + .catch((_) => null); + assert(mustBeNull === null, "minter info found"); + }); + + it("cannot remove same minter again", async () => { + const cannotRemoveIx = await tbtc.removeMinterIx({ + authority: authority.publicKey, + minter: anotherMinter.publicKey, + }); + await expectIxFail( + [cannotRemoveIx], + [authority], + "AccountNotInitialized" + ); + }); + + it("remove last minter", async () => { + const removeIx = await tbtc.removeMinterIx({ + authority: authority.publicKey, + minter: minter.publicKey, + }); + await expectIxSuccess([removeIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 0, + numGuardians: 0, + supply: BigInt(1500), + paused: false, + pendingAuthority: null, + }); + const mustBeNull = await tbtc + .checkMinterInfo(minter.publicKey) + .catch((_) => null); + assert(mustBeNull === null, "minter info found"); + }); }); - it("use two guardians", async () => { - await checkState(authority, 1, 1, 1500); - const guardianInfoPDA = getGuardianPDA(guardianKeys); - await checkGuardian(program, guardianKeys); - await addGuardian(program, authority, guardian2Keys, authority); - await checkGuardian(program, guardian2Keys); - - await pause(program, guardianKeys); - - try { - await pause(program, guardian2Keys); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("IsPaused"); - expect(err.program.equals(program.programId)).is.true; - } - - await unpause(program, authority); - await pause(program, guardian2Keys); - await checkPaused(program, true); - await unpause(program, authority); - - // cannot remove guardian with wrong keys - try { - await removeGuardian(program, authority, guardian2Keys, guardianInfoPDA); - chai.assert(false, "should've failed but didn't"); - } catch (_err) { - expect(_err).to.be.instanceOf(AnchorError); - const err: AnchorError = _err; - expect(err.error.errorCode.code).to.equal("ConstraintSeeds"); - expect(err.program.equals(program.programId)).is.true; - } + describe("guardians", () => { + it("cannot add guardian without authority", async () => { + const cannotAddIx = await tbtc.addGuardianIx({ + authority: imposter.publicKey, + guardian: guardian.publicKey, + }); + await expectIxFail([cannotAddIx], [imposter], "IsNotAuthority"); + }); + + it("add guardian", async () => { + const mustBeNull = await tbtc + .checkGuardianInfo(guardian.publicKey) + .catch((_) => null); + assert(mustBeNull === null, "guardian info found"); + + const addIx = await tbtc.addGuardianIx({ + authority: authority.publicKey, + guardian: guardian.publicKey, + }); + await expectIxSuccess([addIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 0, + numGuardians: 1, + supply: BigInt(1500), + paused: false, + pendingAuthority: null, + }); + await tbtc.checkGuardianInfo(guardian.publicKey); + }); + + it("cannot pause without guardian", async () => { + const cannotPauseIx = await tbtc.pauseIx({ + guardian: imposter.publicKey, + }); + await expectIxFail( + [cannotPauseIx], + [txPayer, imposter], + "AccountNotInitialized" + ); + + // Now try with actual guardian's info account. + const guardianInfo = tbtc.getGuardianInfoPDA(guardian.publicKey); + + const cannotPauseAgainIx = await tbtc.pauseIx({ + guardianInfo, + guardian: imposter.publicKey, + }); + await expectIxFail( + [cannotPauseAgainIx], + [txPayer, imposter], + "ConstraintSeeds" + ); + }); + + it("add minter and mint", async () => { + const addMinterIx = await tbtc.addMinterIx({ + authority: authority.publicKey, + minter: minter.publicKey, + }); + await expectIxSuccess([addMinterIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 1, + supply: BigInt(1500), + paused: false, + pendingAuthority: null, + }); + + const amount = BigInt(100); + + const recipientToken = spl.getAssociatedTokenAddressSync( + tbtc.getMintPDA(), + recipient.publicKey + ); + const recipientBefore = await getTokenBalance(recipientToken); + expect(recipientBefore).to.equal(BigInt(1500)); + + const mintIx = await tbtc.mintIx( + { + minter: minter.publicKey, + recipientToken, + }, + new anchor.BN(amount.toString()) + ); + await expectIxSuccess([mintIx], [txPayer, minter]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 1, + supply: BigInt(1600), + paused: false, + pendingAuthority: null, + }); + + const recipientAfter = await getTokenBalance(recipientToken); + expect(recipientAfter).to.equal(recipientBefore + amount); + }); + + it("pause", async () => { + const pauseIx = await tbtc.pauseIx({ + guardian: guardian.publicKey, + }); + await expectIxSuccess([pauseIx], [txPayer, guardian]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 1, + supply: BigInt(1600), + paused: true, + pendingAuthority: null, + }); + }); + + it("cannot mint while paused", async () => { + const recipientToken = spl.getAssociatedTokenAddressSync( + tbtc.getMintPDA(), + recipient.publicKey + ); + + const mintIx = await tbtc.mintIx( + { + minter: minter.publicKey, + recipientToken, + }, + new anchor.BN(100) + ); + await expectIxFail([mintIx], [txPayer, minter], "IsPaused"); + }); + + it("add another guardian", async () => { + const mustBeNull = await tbtc + .checkGuardianInfo(anotherGuardian.publicKey) + .catch((_) => null); + assert(mustBeNull === null, "guardian info found"); + + const addIx = await tbtc.addGuardianIx({ + authority: authority.publicKey, + guardian: anotherGuardian.publicKey, + }); + await expectIxSuccess([addIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 2, + supply: BigInt(1600), + paused: true, + pendingAuthority: null, + }); + await tbtc.checkGuardianInfo(anotherGuardian.publicKey); + }); + + it("cannot pause again", async () => { + const cannotPauseIx = await tbtc.pauseIx({ + guardian: anotherGuardian.publicKey, + }); + await expectIxFail( + [cannotPauseIx], + [txPayer, anotherGuardian], + "IsPaused" + ); + }); + + it("unpause", async () => { + const unpauseIx = await tbtc.unpauseIx({ + authority: authority.publicKey, + }); + await expectIxSuccess([unpauseIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 2, + supply: BigInt(1600), + paused: false, + pendingAuthority: null, + }); + }); + + it("cannot unpause again", async () => { + const cannotUnpauseIx = await tbtc.unpauseIx({ + authority: authority.publicKey, + }); + await expectIxFail( + [cannotUnpauseIx], + [txPayer, authority], + "IsNotPaused" + ); + }); + + it("mint while unpaused", async () => { + const amount = BigInt(200); + + const recipientToken = spl.getAssociatedTokenAddressSync( + tbtc.getMintPDA(), + recipient.publicKey + ); + const recipientBefore = await getTokenBalance(recipientToken); + expect(recipientBefore).to.equal(BigInt(1600)); + + const mintIx = await tbtc.mintIx( + { + minter: minter.publicKey, + recipientToken, + }, + new anchor.BN(amount.toString()) + ); + await expectIxSuccess([mintIx], [txPayer, minter]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 2, + supply: BigInt(1800), + paused: false, + pendingAuthority: null, + }); + + const recipientAfter = await getTokenBalance(recipientToken); + expect(recipientAfter).to.equal(recipientBefore + amount); + }); + + it("pause as another guardian", async () => { + const pauseIx = await tbtc.pauseIx({ + guardian: anotherGuardian.publicKey, + }); + await expectIxSuccess([pauseIx], [txPayer, anotherGuardian]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 2, + supply: BigInt(1800), + paused: true, + pendingAuthority: null, + }); + }); + + it("cannot mint again while paused", async () => { + const recipientToken = spl.getAssociatedTokenAddressSync( + tbtc.getMintPDA(), + recipient.publicKey + ); + + const mintIx = await tbtc.mintIx( + { + minter: minter.publicKey, + recipientToken, + }, + new anchor.BN(100) + ); + await expectIxFail([mintIx], [txPayer, minter], "IsPaused"); + }); + + it("cannot remove guardian without authority", async () => { + const cannotRemoveIx = await tbtc.removeGuardianIx({ + authority: imposter.publicKey, + guardian: anotherGuardian.publicKey, + }); + await expectIxFail([cannotRemoveIx], [imposter], "IsNotAuthority"); + }); + + it("cannot remove guardian with mismatched info", async () => { + const guardianInfo = tbtc.getGuardianInfoPDA(anotherGuardian.publicKey); + const cannotRemoveIx = await tbtc.removeGuardianIx({ + authority: authority.publicKey, + guardianInfo, + guardian: guardian.publicKey, + }); + await expectIxFail([cannotRemoveIx], [authority], "ConstraintSeeds"); + }); + + it("remove guardian", async () => { + const removeIx = await tbtc.removeGuardianIx({ + authority: authority.publicKey, + guardian: anotherGuardian.publicKey, + }); + await expectIxSuccess([removeIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 1, + supply: BigInt(1800), + paused: true, + pendingAuthority: null, + }); + const mustBeNull = await tbtc + .checkGuardianInfo(anotherGuardian.publicKey) + .catch((_) => null); + assert(mustBeNull === null, "guardian info found"); + }); + + it("unpause", async () => { + const unpauseIx = await tbtc.unpauseIx({ + authority: authority.publicKey, + }); + await expectIxSuccess([unpauseIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 1, + supply: BigInt(1800), + paused: false, + pendingAuthority: null, + }); + }); + + it("cannot pause with removed guardian", async () => { + const pauseIx = await tbtc.pauseIx({ + guardian: anotherGuardian.publicKey, + }); + await expectIxFail( + [pauseIx], + [txPayer, anotherGuardian], + "AccountNotInitialized" + ); + }); + + it("pause and remove last guardian", async () => { + const pauseIx = await tbtc.pauseIx({ + guardian: guardian.publicKey, + }); + await expectIxSuccess([pauseIx], [txPayer, guardian]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 1, + supply: BigInt(1800), + paused: true, + pendingAuthority: null, + }); + + const removeIx = await tbtc.removeGuardianIx({ + authority: authority.publicKey, + guardian: guardian.publicKey, + }); + await expectIxSuccess([removeIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 0, + supply: BigInt(1800), + paused: true, + pendingAuthority: null, + }); + const mustBeNull = await tbtc + .checkGuardianInfo(guardian.publicKey) + .catch((_) => null); + assert(mustBeNull === null, "guardian info found"); + }); + + it("cannot mint yet again", async () => { + const recipientToken = spl.getAssociatedTokenAddressSync( + tbtc.getMintPDA(), + recipient.publicKey + ); + + const mintIx = await tbtc.mintIx( + { + minter: minter.publicKey, + recipientToken, + }, + new anchor.BN(100) + ); + await expectIxFail([mintIx], [txPayer, minter], "IsPaused"); + }); + + it("unpause without any guardians then mint", async () => { + const unpauseIx = await tbtc.unpauseIx({ + authority: authority.publicKey, + }); + await expectIxSuccess([unpauseIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 0, + supply: BigInt(1800), + paused: false, + pendingAuthority: null, + }); + + const recipientToken = spl.getAssociatedTokenAddressSync( + tbtc.getMintPDA(), + recipient.publicKey + ); + + const amount = BigInt(200); + + const recipientBefore = await getTokenBalance(recipientToken); + const mintIx = await tbtc.mintIx( + { + minter: minter.publicKey, + recipientToken, + }, + new anchor.BN(amount.toString()) + ); + await expectIxSuccess([mintIx], [txPayer, minter]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 0, + supply: BigInt(2000), + paused: false, + pendingAuthority: null, + }); + + const recipientAfter = await getTokenBalance(recipientToken); + expect(recipientAfter).to.equal(recipientBefore + amount); + }); + + it("remove minter", async () => { + const removeIx = await tbtc.removeMinterIx({ + authority: authority.publicKey, + minter: minter.publicKey, + }); + await expectIxSuccess([removeIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 0, + numGuardians: 0, + supply: BigInt(2000), + paused: false, + pendingAuthority: null, + }); + }); }); }); diff --git a/cross-chain/solana/tests/02__wormholeGateway.ts b/cross-chain/solana/tests/02__wormholeGateway.ts index b792253f4..021dc290c 100644 --- a/cross-chain/solana/tests/02__wormholeGateway.ts +++ b/cross-chain/solana/tests/02__wormholeGateway.ts @@ -40,7 +40,7 @@ async function setup( mintingLimit: bigint ) { const custodian = wormholeGateway.getCustodianPDA(); - const tbtcMint = tbtc.getTokenPDA(); + const tbtcMint = tbtc.getMintPDA(); const gatewayWrappedTbtcToken = wormholeGateway.getWrappedTbtcTokenPDA(); const tokenBridgeSender = wormholeGateway.getTokenBridgeSenderPDA(); @@ -66,7 +66,7 @@ describe("wormhole-gateway", () => { const connection = program.provider.connection; const custodian = wormholeGateway.getCustodianPDA(); - const tbtcMint = tbtc.getTokenPDA(); + const tbtcMint = tbtc.getMintPDA(); const tbtcConfig = tbtc.getConfigPDA(); const gatewayWrappedTbtcToken = wormholeGateway.getWrappedTbtcTokenPDA(); const tokenBridgeSender = wormholeGateway.getTokenBridgeSenderPDA(); @@ -86,7 +86,7 @@ describe("wormhole-gateway", () => { const commonTokenOwner = anchor.web3.Keypair.generate(); - // Mock foreign emitter. + // Mock foreign emitter. const ethereumTokenBridge = new MockEthereumTokenBridge( ETHEREUM_TOKEN_BRIDGE_ADDRESS ); @@ -98,13 +98,20 @@ describe("wormhole-gateway", () => { // Initialize the program. await setup(program, authority, mintingLimit); await wormholeGateway.checkState(authority.publicKey, mintingLimit); - await tbtc.checkState(authority, 1, 2, 1500); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 0, + numGuardians: 0, + supply: BigInt(2000), + paused: false, + pendingAuthority: null, + }); // Also set up common token account. await transferLamports(authority, commonTokenOwner.publicKey, 100000000000); await getOrCreateAta( authority, - tbtc.getTokenPDA(), + tbtc.getMintPDA(), commonTokenOwner.publicKey ); @@ -113,7 +120,7 @@ describe("wormhole-gateway", () => { }); it("update minting limit", async () => { - // Update minting limit as authority. + // Update minting limit as authority. const newLimit = BigInt(20000); const ix = await wormholeGateway.updateMintingLimitIx( { @@ -152,7 +159,7 @@ describe("wormhole-gateway", () => { tbtcMint, payer.publicKey ); - + const depositAmount = BigInt(500); // Attempt to deposit before the custodian is a minter. @@ -167,7 +174,19 @@ describe("wormhole-gateway", () => { await expectIxFail([ix], [payer], "AccountNotInitialized"); // Add custodian as minter. - await tbtc.addMinter(authority, custodian); + const addMinterIx = await tbtc.addMinterIx({ + authority: authority.publicKey, + minter: custodian, + }); + await expectIxSuccess([addMinterIx], [authority]); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 0, + supply: BigInt(2000), + paused: false, + pendingAuthority: null, + }); // Check token account balances before deposit. const [wrappedBefore, tbtcBefore, gatewayBefore] = await Promise.all([ @@ -177,7 +196,14 @@ describe("wormhole-gateway", () => { ]); await expectIxSuccess([ix], [payer]); - await tbtc.checkState(authority, 2, 2, 2000); + await tbtc.checkConfig({ + authority: authority.publicKey, + numMinters: 1, + numGuardians: 0, + supply: BigInt(2500), + paused: false, + pendingAuthority: null, + }); const [wrappedAfter, tbtcAfter, gatewayAfter] = await Promise.all([ getAccount(connection, recipientWrappedToken), @@ -253,7 +279,7 @@ describe("wormhole-gateway", () => { // Use common token account. const recipient = commonTokenOwner.publicKey; const recipientToken = getAssociatedTokenAddressSync( - tbtc.getTokenPDA(), + tbtc.getMintPDA(), recipient ); @@ -296,18 +322,18 @@ describe("wormhole-gateway", () => { expect(tbtcAfter.amount).to.equal(tbtcBefore.amount + sentAmount); expect(gatewayAfter.amount).to.equal(gatewayBefore.amount + sentAmount); - // Cannot receive tbtc again. + // Cannot receive tbtc again. await expectIxFail([ix], [payer], "TransferAlreadyRedeemed"); }); it("receive wrapped tbtc (ata doesn't exist)", async () => { // Set up new wallet const payer = await generatePayer(authority); - + // Use common token account. const recipient = commonTokenOwner.publicKey; const recipientToken = getAssociatedTokenAddressSync( - tbtc.getTokenPDA(), + tbtc.getMintPDA(), recipient ); const recipientWrappedToken = getAssociatedTokenAddressSync( @@ -315,12 +341,12 @@ describe("wormhole-gateway", () => { recipient ); - // Verify that the wrapped token account doesn't exist yet. + // Verify that the wrapped token account doesn't exist yet. try { await getAccount(connection, recipientWrappedToken); } catch (e: any) { expect(e.toString()).to.equal("TokenAccountNotFoundError"); - } + } // Get foreign gateway. const fromGateway = await wormholeGateway @@ -350,7 +376,7 @@ describe("wormhole-gateway", () => { await wormholeGateway.checkState(authority.publicKey, newLimit); // Balance check before receiving wrapped tbtc. We can't - // check the balance of the recipient's wrapped tbtc yet, + // check the balance of the recipient's wrapped tbtc yet, // since the contract will create the ATA. const [tbtcBefore, gatewayBefore] = await Promise.all([ getAccount(connection, recipientToken), @@ -367,7 +393,7 @@ describe("wormhole-gateway", () => { ); await expectIxSuccess([ix], [payer]); - // Check token accounts after receiving wrapped tbtc. We should + // Check token accounts after receiving wrapped tbtc. We should // be able to fetch the recipient's wrapped tbtc now. const [tbtcAfter, wrappedTbtcAfter, gatewayAfter] = await Promise.all([ getAccount(connection, recipientToken), @@ -384,11 +410,11 @@ describe("wormhole-gateway", () => { it("receive wrapped tbtc (ata exists)", async () => { // Set up new wallet const payer = await generatePayer(authority); - + // Use common token account. const recipient = commonTokenOwner.publicKey; const recipientToken = getAssociatedTokenAddressSync( - tbtc.getTokenPDA(), + tbtc.getMintPDA(), recipient ); const recipientWrappedToken = await getOrCreateAta( @@ -424,7 +450,7 @@ describe("wormhole-gateway", () => { await expectIxSuccess([updateLimitIx], [authority]); await wormholeGateway.checkState(authority.publicKey, newLimit); - // Balance check before receiving wrapped tbtc. If this + // Balance check before receiving wrapped tbtc. If this // line successfully executes, then the recipient's // wrapped tbtc account already exists. const [tbtcBefore, wrappedTbtcBefore, gatewayBefore] = await Promise.all([ @@ -443,7 +469,7 @@ describe("wormhole-gateway", () => { ); await expectIxSuccess([ix], [payer]); - // Check token accounts after receiving wrapped tbtc. + // Check token accounts after receiving wrapped tbtc. const [tbtcAfter, wrappedTbtcAfter, gatewayAfter] = await Promise.all([ getAccount(connection, recipientToken), getAccount(connection, recipientWrappedToken), @@ -453,7 +479,9 @@ describe("wormhole-gateway", () => { // Check balance change. expect(tbtcAfter.amount).to.equal(tbtcBefore.amount); expect(gatewayAfter.amount).to.equal(gatewayBefore.amount); - expect(wrappedTbtcAfter.amount).to.equal(wrappedTbtcBefore.amount + sentAmount); + expect(wrappedTbtcAfter.amount).to.equal( + wrappedTbtcBefore.amount + sentAmount + ); }); it("cannot receive non-tbtc transfers", async () => { @@ -463,7 +491,7 @@ describe("wormhole-gateway", () => { // Use common token account. const recipient = commonTokenOwner.publicKey; const recipientToken = getAssociatedTokenAddressSync( - tbtc.getTokenPDA(), + tbtc.getMintPDA(), recipient ); @@ -502,7 +530,7 @@ describe("wormhole-gateway", () => { // Use common token account. const recipient = commonTokenOwner.publicKey; const recipientToken = getAssociatedTokenAddressSync( - tbtc.getTokenPDA(), + tbtc.getMintPDA(), recipient ); @@ -538,7 +566,11 @@ describe("wormhole-gateway", () => { // Use common token account. Set the recipient to the zero address. const recipient = PublicKey.default; - const defaultTokenAccount = await getOrCreateAta(payer, tbtc.getTokenPDA(), recipient); + const defaultTokenAccount = await getOrCreateAta( + payer, + tbtc.getMintPDA(), + recipient + ); // Get foreign gateway. const fromGateway = await wormholeGateway @@ -559,7 +591,7 @@ describe("wormhole-gateway", () => { { payer: payer.publicKey, recipientToken: defaultTokenAccount, - recipient + recipient, }, signedVaa ); @@ -570,7 +602,7 @@ describe("wormhole-gateway", () => { // Use common token account. const sender = commonTokenOwner.publicKey; const senderToken = getAssociatedTokenAddressSync( - tbtc.getTokenPDA(), + tbtc.getMintPDA(), sender ); @@ -583,7 +615,7 @@ describe("wormhole-gateway", () => { // Get destination gateway. const recipientChain = 2; const recipient = Array.from(Buffer.alloc(32, "deadbeef", "hex")); - const nonce = 420; + const nonce = 420; // This should work. const sendAmount = BigInt(2000); @@ -601,14 +633,16 @@ describe("wormhole-gateway", () => { ); await expectIxSuccess([ix], [commonTokenOwner]); - // Check token accounts after sending tbtc. + // Check token accounts after sending tbtc. const [senderTbtcAfter, gatewayAfter] = await Promise.all([ getAccount(connection, senderToken), getAccount(connection, gatewayWrappedTbtcToken), ]); // Check balance change. - expect(senderTbtcAfter.amount).to.equal(senderTbtcBefore.amount - sendAmount); + expect(senderTbtcAfter.amount).to.equal( + senderTbtcBefore.amount - sendAmount + ); expect(gatewayAfter.amount).to.equal(gatewayBefore.amount - sendAmount); }); @@ -616,17 +650,20 @@ describe("wormhole-gateway", () => { // Use common token account. const sender = commonTokenOwner.publicKey; const senderToken = getAssociatedTokenAddressSync( - tbtc.getTokenPDA(), + tbtc.getMintPDA(), sender ); // Get destination gateway. const recipientChain = 2; const recipient = Array.from(Buffer.alloc(32, "deadbeef", "hex")); - const nonce = 420; + const nonce = 420; // Check token accounts. - const gatewayWrappedBalance = await getAccount(connection, gatewayWrappedTbtcToken); + const gatewayWrappedBalance = await getAccount( + connection, + gatewayWrappedTbtcToken + ); // Try an amount that won't work. const sendAmount = gatewayWrappedBalance.amount + BigInt(69); @@ -649,7 +686,7 @@ describe("wormhole-gateway", () => { // Use common token account. const sender = commonTokenOwner.publicKey; const senderToken = getAssociatedTokenAddressSync( - tbtc.getTokenPDA(), + tbtc.getMintPDA(), sender ); @@ -679,7 +716,7 @@ describe("wormhole-gateway", () => { // Use common token account. const sender = commonTokenOwner.publicKey; const senderToken = getAssociatedTokenAddressSync( - tbtc.getTokenPDA(), + tbtc.getMintPDA(), sender ); @@ -728,14 +765,16 @@ describe("wormhole-gateway", () => { ); await expectIxSuccess([ix], [commonTokenOwner]); - // Check token accounts after sending tbtc. + // Check token accounts after sending tbtc. const [senderTbtcAfter, gatewayAfter] = await Promise.all([ getAccount(connection, senderToken), getAccount(connection, gatewayWrappedTbtcToken), ]); // Check balance change. - expect(senderTbtcAfter.amount).to.equal(senderTbtcBefore.amount - goodAmount); + expect(senderTbtcAfter.amount).to.equal( + senderTbtcBefore.amount - goodAmount + ); expect(gatewayAfter.amount).to.equal(gatewayBefore.amount - goodAmount); }); }); diff --git a/cross-chain/solana/tests/helpers/tbtc.ts b/cross-chain/solana/tests/helpers/tbtc.ts index 4f918a62c..7302c3ed3 100644 --- a/cross-chain/solana/tests/helpers/tbtc.ts +++ b/cross-chain/solana/tests/helpers/tbtc.ts @@ -1,13 +1,10 @@ -import { Program, Wallet, workspace } from "@coral-xyz/anchor"; +import { BN, Program, Wallet, workspace } from "@coral-xyz/anchor"; import { getMint } from "@solana/spl-token"; -import { PublicKey } from "@solana/web3.js"; -import { expect } from "chai"; +import { PublicKey, TransactionInstruction } from "@solana/web3.js"; +import { config, expect } from "chai"; import { Tbtc } from "../../target/types/tbtc"; import { TBTC_PROGRAM_ID } from "./consts"; - -export function maybeAuthorityAnd(signer, signers) { - return signers.concat(signer instanceof (Wallet as any) ? [] : [signer]); -} +import { PROGRAM_ID as METADATA_PROGRAM_ID } from "@metaplex-foundation/mpl-token-metadata"; export function getConfigPDA(): PublicKey { return PublicKey.findProgramAddressSync( @@ -16,13 +13,24 @@ export function getConfigPDA(): PublicKey { )[0]; } -export function getTokenPDA(): PublicKey { +export function getMintPDA(): PublicKey { return PublicKey.findProgramAddressSync( [Buffer.from("tbtc-mint")], TBTC_PROGRAM_ID )[0]; } +export function getTbtcMetadataPDA(): PublicKey { + return PublicKey.findProgramAddressSync( + [ + Buffer.from("metadata"), + METADATA_PROGRAM_ID.toBuffer(), + getMintPDA().toBuffer(), + ], + METADATA_PROGRAM_ID + )[0]; +} + export function getMinterInfoPDA(minter: PublicKey): PublicKey { return PublicKey.findProgramAddressSync( [Buffer.from("minter-info"), minter.toBuffer()], @@ -30,9 +38,9 @@ export function getMinterInfoPDA(minter: PublicKey): PublicKey { )[0]; } -export function getGuardianPDA(guardian): PublicKey { +export function getGuardianInfoPDA(guardian: PublicKey): PublicKey { return PublicKey.findProgramAddressSync( - [Buffer.from("guardian-info"), guardian.publicKey.toBuffer()], + [Buffer.from("guardian-info"), guardian.toBuffer()], TBTC_PROGRAM_ID )[0]; } @@ -51,52 +59,452 @@ export function getMintersPDA(): PublicKey { )[0]; } -export async function checkState( - expectedAuthority, - expectedMinters: number, - expectedGuardians: number, - expectedTokensSupply -) { +export async function getConfigData() { const program = workspace.Tbtc as Program; - const config = getConfigPDA(); - let configState = await program.account.config.fetch(config); - - expect(configState.authority).to.eql(expectedAuthority.publicKey); - expect(configState.numMinters).to.equal(expectedMinters); - expect(configState.numGuardians).to.equal(expectedGuardians); + return program.account.config.fetch(config); +} - let tbtcMint = configState.mint; +export async function checkConfig(expected: { + authority: PublicKey; + numMinters: number; + numGuardians: number; + supply: bigint; + paused: boolean; + pendingAuthority: PublicKey | null; +}) { + let { + authority, + numMinters, + numGuardians, + supply, + paused, + pendingAuthority, + } = expected; + const program = workspace.Tbtc as Program; + const configState = await getConfigData(); - let mintState = await getMint(program.provider.connection, tbtcMint); + expect(configState.authority).to.eql(authority); + expect(configState.numMinters).to.equal(numMinters); + expect(configState.numGuardians).to.equal(numGuardians); + expect(configState.paused).to.equal(paused); + expect(configState.pendingAuthority).to.eql(pendingAuthority); - expect(mintState.supply).to.equal(BigInt(expectedTokensSupply)); + const mintState = await getMint( + program.provider.connection, + configState.mint + ); + expect(mintState.supply).to.equal(supply); const guardians = getGuardiansPDA(); - let guardiansState = await program.account.guardians.fetch(guardians); - expect(guardiansState.keys).has.length(expectedGuardians); + const guardiansState = await program.account.guardians.fetch(guardians); + expect(guardiansState.keys).has.length(numGuardians); const minters = getMintersPDA(); - let mintersState = await program.account.minters.fetch(minters); - expect(mintersState.keys).has.length(expectedMinters); + const mintersState = await program.account.minters.fetch(minters); + expect(mintersState.keys).has.length(numMinters); } -export async function addMinter(authority, minter): Promise { +export async function getMinterInfo(minter: PublicKey) { const program = workspace.Tbtc as Program; - - const config = getConfigPDA(); - const minters = getMintersPDA(); const minterInfoPDA = getMinterInfoPDA(minter); - await program.methods + return program.account.minterInfo.fetch(minterInfoPDA); +} + +export async function checkMinterInfo(minter: PublicKey) { + const minterInfo = await getMinterInfo(minter); + expect(minterInfo.minter).to.eql(minter); +} + +export async function getGuardianInfo(guardian: PublicKey) { + const program = workspace.Tbtc as Program; + const guardianInfoPDA = getGuardianInfoPDA(guardian); + return program.account.guardianInfo.fetch(guardianInfoPDA); +} + +export async function checkGuardianInfo(guardian: PublicKey) { + let guardianInfo = await getGuardianInfo(guardian); + expect(guardianInfo.guardian).to.eql(guardian); +} + +type AddGuardianContext = { + config?: PublicKey; + authority: PublicKey; + guardians?: PublicKey; + guardianInfo?: PublicKey; + guardian: PublicKey; +}; + +export async function addGuardianIx( + accounts: AddGuardianContext +): Promise { + const program = workspace.Tbtc as Program; + + let { config, authority, guardians, guardianInfo, guardian } = accounts; + if (config === undefined) { + config = getConfigPDA(); + } + + if (guardians === undefined) { + guardians = getGuardiansPDA(); + } + + if (guardianInfo === undefined) { + guardianInfo = getGuardianInfoPDA(guardian); + } + + return program.methods + .addGuardian() + .accounts({ + config, + authority, + guardians, + guardianInfo, + guardian, + }) + .instruction(); +} + +type AddMinterContext = { + config?: PublicKey; + authority: PublicKey; + minters?: PublicKey; + minterInfo?: PublicKey; + minter: PublicKey; +}; + +export async function addMinterIx( + accounts: AddMinterContext +): Promise { + const program = workspace.Tbtc as Program; + + let { config, authority, minters, minterInfo, minter } = accounts; + if (config === undefined) { + config = getConfigPDA(); + } + + if (minters === undefined) { + minters = getMintersPDA(); + } + + if (minterInfo === undefined) { + minterInfo = getMinterInfoPDA(minter); + } + + return program.methods .addMinter() .accounts({ config, - authority: authority.publicKey, + authority, minters, + minterInfo, + minter, + }) + .instruction(); +} + +type CancelAuthorityChange = { + config?: PublicKey; + authority: PublicKey; +}; + +export async function cancelAuthorityChangeIx( + accounts: CancelAuthorityChange +): Promise { + const program = workspace.Tbtc as Program; + + let { config, authority } = accounts; + if (config === undefined) { + config = getConfigPDA(); + } + + return program.methods + .cancelAuthorityChange() + .accounts({ + config, + authority, + }) + .instruction(); +} + +type ChangeAuthorityContext = { + config?: PublicKey; + authority: PublicKey; + newAuthority: PublicKey; +}; + +export async function changeAuthorityIx( + accounts: ChangeAuthorityContext +): Promise { + const program = workspace.Tbtc as Program; + + let { config, authority, newAuthority } = accounts; + if (config === undefined) { + config = getConfigPDA(); + } + + return program.methods + .changeAuthority() + .accounts({ + config, + authority, + newAuthority, + }) + .instruction(); +} + +type InitializeContext = { + mint?: PublicKey; + config?: PublicKey; + guardians?: PublicKey; + minters?: PublicKey; + authority: PublicKey; + tbtcMetadata?: PublicKey; + mplTokenMetadataProgram?: PublicKey; +}; + +export async function initializeIx( + accounts: InitializeContext +): Promise { + const program = workspace.Tbtc as Program; + + let { + mint, + config, + guardians, + minters, + authority, + tbtcMetadata, + mplTokenMetadataProgram, + } = accounts; + + if (mint === undefined) { + mint = getMintPDA(); + } + + if (config === undefined) { + config = getConfigPDA(); + } + + if (guardians === undefined) { + guardians = getGuardiansPDA(); + } + + if (minters === undefined) { + minters = getMintersPDA(); + } + + if (tbtcMetadata === undefined) { + tbtcMetadata = getTbtcMetadataPDA(); + } + + if (mplTokenMetadataProgram === undefined) { + mplTokenMetadataProgram = METADATA_PROGRAM_ID; + } + + return program.methods + .initialize() + .accounts({ + mint, + config, + guardians, + minters, + authority, + tbtcMetadata, + mplTokenMetadataProgram, + }) + .instruction(); +} + +type PauseContext = { + config?: PublicKey; + guardianInfo?: PublicKey; + guardian: PublicKey; +}; + +export async function pauseIx( + accounts: PauseContext +): Promise { + const program = workspace.Tbtc as Program; + + let { config, guardianInfo, guardian } = accounts; + if (config === undefined) { + config = getConfigPDA(); + } + + if (guardianInfo === undefined) { + guardianInfo = getGuardianInfoPDA(guardian); + } + + return program.methods + .pause() + .accounts({ + config, + guardianInfo, + guardian, + }) + .instruction(); +} + +type RemoveGuardianContext = { + config?: PublicKey; + authority: PublicKey; + guardians?: PublicKey; + guardianInfo?: PublicKey; + guardian: PublicKey; +}; + +export async function removeGuardianIx( + accounts: RemoveGuardianContext +): Promise { + const program = workspace.Tbtc as Program; + + let { config, authority, guardians, guardianInfo, guardian } = accounts; + if (config === undefined) { + config = getConfigPDA(); + } + + if (guardians === undefined) { + guardians = getGuardiansPDA(); + } + + if (guardianInfo === undefined) { + guardianInfo = getGuardianInfoPDA(guardian); + } + + return program.methods + .removeGuardian() + .accounts({ + config, + authority, + guardians, + guardianInfo, + guardian, + }) + .instruction(); +} + +type RemoveMinterContext = { + config?: PublicKey; + authority: PublicKey; + minters?: PublicKey; + minterInfo?: PublicKey; + minter: PublicKey; +}; + +export async function removeMinterIx( + accounts: RemoveMinterContext +): Promise { + const program = workspace.Tbtc as Program; + + let { config, authority, minters, minterInfo, minter } = accounts; + if (config === undefined) { + config = getConfigPDA(); + } + + if (minters === undefined) { + minters = getMintersPDA(); + } + + if (minterInfo === undefined) { + minterInfo = getMinterInfoPDA(minter); + } + + return program.methods + .removeMinter() + .accounts({ + config, + authority, + minters, + minterInfo, + minter, + }) + .instruction(); +} + +type TakeAuthorityContext = { + config?: PublicKey; + pendingAuthority: PublicKey; +}; + +export async function takeAuthorityIx( + accounts: TakeAuthorityContext +): Promise { + const program = workspace.Tbtc as Program; + + let { config, pendingAuthority } = accounts; + if (config === undefined) { + config = getConfigPDA(); + } + + return program.methods + .takeAuthority() + .accounts({ + config, + pendingAuthority, + }) + .instruction(); +} + +type UnpauseContext = { + config?: PublicKey; + authority: PublicKey; +}; + +export async function unpauseIx( + accounts: UnpauseContext +): Promise { + const program = workspace.Tbtc as Program; + + let { config, authority } = accounts; + if (config === undefined) { + config = getConfigPDA(); + } + + return program.methods + .unpause() + .accounts({ + config, + authority, + }) + .instruction(); +} + +type MintContext = { + mint?: PublicKey; + config?: PublicKey; + minterInfo?: PublicKey; + minter: PublicKey; + recipientToken: PublicKey; +}; + +export async function mintIx( + accounts: MintContext, + amount: BN +): Promise { + const program = workspace.Tbtc as Program; + + let { mint, config, minterInfo, minter, recipientToken } = accounts; + if (mint === undefined) { + mint = getMintPDA(); + } + + if (config === undefined) { + config = getConfigPDA(); + } + + if (minterInfo === undefined) { + minterInfo = getMinterInfoPDA(minter); + } + + return program.methods + .mint(amount) + .accounts({ + mint, + config, + minterInfo, minter, - minterInfo: minterInfoPDA, + recipientToken, }) - .signers(maybeAuthorityAnd(authority, [])) - .rpc(); - return minterInfoPDA; + .instruction(); } diff --git a/cross-chain/solana/tests/helpers/utils.ts b/cross-chain/solana/tests/helpers/utils.ts index 18e2a8e1b..40980e668 100644 --- a/cross-chain/solana/tests/helpers/utils.ts +++ b/cross-chain/solana/tests/helpers/utils.ts @@ -1,8 +1,15 @@ +import { + postVaaSolana, + redeemOnSolana, + tryNativeToHexString, +} from "@certusone/wormhole-sdk"; import { MockEthereumTokenBridge, MockGuardians, } from "@certusone/wormhole-sdk/lib/cjs/mock"; -import { Idl, Program, web3, workspace } from "@coral-xyz/anchor"; +import { NodeWallet } from "@certusone/wormhole-sdk/lib/cjs/solana"; +import * as coreBridge from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole"; +import { Program, web3, workspace } from "@coral-xyz/anchor"; import { Account, TokenAccountNotFoundError, @@ -11,7 +18,6 @@ import { getAssociatedTokenAddressSync, } from "@solana/spl-token"; import { - Connection, Keypair, PublicKey, SystemProgram, @@ -19,29 +25,25 @@ import { TransactionInstruction, sendAndConfirmTransaction, } from "@solana/web3.js"; +import { assert, expect } from "chai"; +import { WormholeGateway } from "../../target/types/wormhole_gateway"; // This is only here to hack a connection. import { + CORE_BRIDGE_PROGRAM_ID, ETHEREUM_TBTC_ADDRESS, + GUARDIAN_DEVNET_PRIVATE_KEYS, GUARDIAN_SET_INDEX, - CORE_BRIDGE_PROGRAM_ID, TOKEN_BRIDGE_PROGRAM_ID, WRAPPED_TBTC_MINT, - GUARDIAN_DEVNET_PRIVATE_KEYS, } from "./consts"; -import { - postVaaSolana, - redeemOnSolana, - tryNativeToHexString, -} from "@certusone/wormhole-sdk"; -import { NodeWallet } from "@certusone/wormhole-sdk/lib/cjs/solana"; -import { expect } from "chai"; -import * as coreBridge from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole"; -export async function transferLamports( +export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +export async function transferLamports( fromSigner: web3.Keypair, toPubkey: web3.PublicKey, lamports: number ) { - const program = workspace.WormholeGateway as Program; + const program = workspace.WormholeGateway as Program; return sendAndConfirmTransaction( program.provider.connection, new Transaction().add( @@ -55,12 +57,9 @@ export async function transferLamports( ); } -export async function generatePayer( - funder: Keypair, - lamports?: number -) { +export async function generatePayer(funder: Keypair, lamports?: number) { const newPayer = Keypair.generate(); - await transferLamports( + await transferLamports( funder, newPayer.publicKey, lamports === undefined ? 1000000000 : lamports @@ -68,12 +67,12 @@ export async function generatePayer( return newPayer; } -export async function getOrCreateAta( +export async function getOrCreateAta( payer: Keypair, mint: PublicKey, owner: PublicKey ) { - const program = workspace.WormholeGateway as Program; + const program = workspace.WormholeGateway as Program; const connection = program.provider.connection; const token = getAssociatedTokenAddressSync(mint, owner); @@ -105,16 +104,23 @@ export async function getOrCreateAta( return token; } -export async function preloadWrappedTbtc( +export async function getTokenBalance(token: PublicKey): Promise { + const program = workspace.WormholeGateway as Program; + return getAccount(program.provider.connection, token).then( + (account) => account.amount + ); +} + +export async function preloadWrappedTbtc( payer: Keypair, ethereumTokenBridge: MockEthereumTokenBridge, amount: bigint, tokenOwner: PublicKey ) { - const program = workspace.WormholeGateway as Program; + const program = workspace.WormholeGateway as Program; const connection = program.provider.connection; - const wrappedTbtcToken = await getOrCreateAta( + const wrappedTbtcToken = await getOrCreateAta( payer, WRAPPED_TBTC_MINT, tokenOwner @@ -146,11 +152,11 @@ export async function preloadWrappedTbtc( return wrappedTbtcToken; } -export async function mockSignAndPostVaa( +export async function mockSignAndPostVaa( payer: web3.Keypair, published: Buffer ) { - const program = workspace.WormholeGateway as Program; + const program = workspace.WormholeGateway as Program; const guardians = new MockGuardians(GUARDIAN_SET_INDEX, [ "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0", @@ -171,7 +177,7 @@ export async function mockSignAndPostVaa( return signedVaa; } -export async function ethereumGatewaySendTbtc( +export async function ethereumGatewaySendTbtc( payer: web3.Keypair, ethereumTokenBridge: MockEthereumTokenBridge, amount: bigint, @@ -181,7 +187,7 @@ export async function ethereumGatewaySendTbtc( tokenAddress?: string, tokenChain?: number ) { - const program = workspace.WormholeGateway as Program; + const program = workspace.WormholeGateway as Program; const published = ethereumTokenBridge.publishTransferTokensWithPayload( tryNativeToHexString(tokenAddress ?? ETHEREUM_TBTC_ADDRESS, "ethereum"), @@ -215,35 +221,40 @@ export async function ethereumGatewaySendTbtc( return signedVaa; } -export async function expectIxSuccess( +export async function expectIxSuccess( ixes: TransactionInstruction[], signers: Keypair[] ) { - const program = workspace.WormholeGateway as Program; + const program = workspace.WormholeGateway as Program; await sendAndConfirmTransaction( program.provider.connection, new Transaction().add(...ixes), signers ).catch((err) => { - console.log(err.logs); + if (err.logs !== undefined) { + console.log(err.logs); + } throw err; }); } -export async function expectIxFail( +export async function expectIxFail( ixes: TransactionInstruction[], signers: Keypair[], errorMessage: string ) { - const program = workspace.WormholeGateway as Program; + const program = workspace.WormholeGateway as Program; try { const txSig = await sendAndConfirmTransaction( program.provider.connection, new Transaction().add(...ixes), signers ); - chai.assert(false, `transaction should have failed: ${txSig}`); + assert(false, `transaction should have failed: ${txSig}`); } catch (err) { + if (err.logs === undefined) { + throw err; + } const logs: string[] = err.logs; expect(logs.join("\n")).includes(errorMessage); } @@ -258,8 +269,8 @@ export function getTokenBridgeCoreEmitter() { return tokenBridgeCoreEmitter; } -export async function getTokenBridgeSequence() { - const program = workspace.WormholeGateway as Program; +export async function getTokenBridgeSequence() { + const program = workspace.WormholeGateway as Program; const emitter = getTokenBridgeCoreEmitter(); return coreBridge .getSequenceTracker( diff --git a/cross-chain/solana/tests/helpers/wormholeGateway.ts b/cross-chain/solana/tests/helpers/wormholeGateway.ts index 025df24f1..7666d69c9 100644 --- a/cross-chain/solana/tests/helpers/wormholeGateway.ts +++ b/cross-chain/solana/tests/helpers/wormholeGateway.ts @@ -204,7 +204,7 @@ export async function depositWormholeTbtcIx( } if (tbtcMint === undefined) { - tbtcMint = tbtc.getTokenPDA(); + tbtcMint = tbtc.getMintPDA(); } if (tbtcConfig === undefined) { @@ -320,7 +320,7 @@ export async function receiveTbtcIx( } if (tbtcMint === undefined) { - tbtcMint = tbtc.getTokenPDA(); + tbtcMint = tbtc.getMintPDA(); } if (recipientWrappedToken == undefined) { @@ -483,7 +483,7 @@ export async function sendTbtcGatewayIx( } if (tbtcMint === undefined) { - tbtcMint = tbtc.getTokenPDA(); + tbtcMint = tbtc.getMintPDA(); } if (tokenBridgeConfig === undefined) { @@ -643,7 +643,7 @@ export async function sendTbtcWrappedIx( } if (tbtcMint === undefined) { - tbtcMint = tbtc.getTokenPDA(); + tbtcMint = tbtc.getMintPDA(); } if (tokenBridgeConfig === undefined) {