diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/deposit_wormhole_tbtc.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/deposit_wormhole_tbtc.rs index f8888ef2a..ecfa92492 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/deposit_wormhole_tbtc.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/deposit_wormhole_tbtc.rs @@ -51,7 +51,7 @@ pub struct DepositWormholeTbtc<'info> { tbtc_config: UncheckedAccount<'info>, /// CHECK: TBTC program requires this account. - minter_info: UncheckedAccount<'info>, + tbtc_minter_info: UncheckedAccount<'info>, token_program: Program<'info, token::Token>, tbtc_program: Program<'info, tbtc::Tbtc>, @@ -102,7 +102,7 @@ pub fn deposit_wormhole_tbtc(ctx: Context, amount: u64) -> tbtc::cpi::accounts::Mint { mint: ctx.accounts.tbtc_mint.to_account_info(), config: ctx.accounts.tbtc_config.to_account_info(), - minter_info: ctx.accounts.minter_info.to_account_info(), + minter_info: ctx.accounts.tbtc_minter_info.to_account_info(), minter: custodian.to_account_info(), recipient_token: ctx.accounts.recipient_token.to_account_info(), token_program: ctx.accounts.token_program.to_account_info(), diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/initialize.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/initialize.rs index f9545fdf5..67e51d1d5 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/initialize.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/initialize.rs @@ -45,7 +45,7 @@ pub struct Initialize<'info> { init, payer = authority, token::mint = wrapped_tbtc_mint, - token::authority = authority, + token::authority = custodian, seeds = [b"wrapped-token"], bump )] @@ -59,14 +59,13 @@ pub struct Initialize<'info> { )] token_bridge_sender: AccountInfo<'info>, - /// CHECK: This account is needed for the Token Bridge program. This PDA is specifically used to - /// sign for transferring via Token Bridge program with a message. - #[account( - seeds = [token_bridge::SEED_PREFIX_REDEEMER], - bump, - )] - token_bridge_redeemer: AccountInfo<'info>, - + // /// CHECK: This account is needed for the Token Bridge program. This PDA is specifically used to + // /// sign for transferring via Token Bridge program with a message. + // #[account( + // seeds = [token_bridge::SEED_PREFIX_REDEEMER], + // bump, + // )] + // token_bridge_redeemer: AccountInfo<'info>, system_program: Program<'info, System>, token_program: Program<'info, token::Token>, } @@ -80,8 +79,8 @@ pub fn initialize(ctx: Context, minting_limit: u64) -> Result<()> { wrapped_tbtc_token: ctx.accounts.wrapped_tbtc_token.key(), token_bridge_sender: ctx.accounts.token_bridge_sender.key(), token_bridge_sender_bump: ctx.bumps["token_bridge_sender"], - token_bridge_redeemer: ctx.accounts.token_bridge_sender.key(), - token_bridge_redeemer_bump: ctx.bumps["token_bridge_redeemer"], + // token_bridge_redeemer: ctx.accounts.token_bridge_redeemer.key(), + // token_bridge_redeemer_bump: ctx.bumps["token_bridge_redeemer"], minting_limit, minted_amount: 0, }); diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/receive_tbtc.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/receive_tbtc.rs index b23269187..ce183fae9 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/receive_tbtc.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/receive_tbtc.rs @@ -23,7 +23,7 @@ pub struct ReceiveTbtc<'info> { has_one = wrapped_tbtc_token, has_one = wrapped_tbtc_mint, has_one = tbtc_mint, - has_one = token_bridge_redeemer, + //has_one = token_bridge_redeemer, )] custodian: Account<'info, Custodian>, @@ -38,11 +38,18 @@ pub struct ReceiveTbtc<'info> { /// transfer. By checking whether this account exists is a short-circuit way of bailing out /// early if this transfer has already been redeemed (as opposed to letting the Token Bridge /// instruction fail). + #[account(mut)] token_bridge_claim: AccountInfo<'info>, /// Custody account. + #[account(mut)] wrapped_tbtc_token: Box>, + /// This mint is owned by the Wormhole Token Bridge program. This PDA address is stored in the + /// custodian account. + #[account(mut)] + wrapped_tbtc_mint: Box>, + #[account(mut)] tbtc_mint: Box>, @@ -83,9 +90,6 @@ pub struct ReceiveTbtc<'info> { /// CHECK: This account is needed for the TBTC program. tbtc_minter_info: UncheckedAccount<'info>, - /// CHECK: This account is needed for the Token Bridge program. - wrapped_tbtc_mint: UncheckedAccount<'info>, - /// CHECK: This account is needed for the Token Bridge program. token_bridge_config: UncheckedAccount<'info>, @@ -93,7 +97,7 @@ pub struct ReceiveTbtc<'info> { token_bridge_registered_emitter: UncheckedAccount<'info>, /// CHECK: This account is needed for the Token Bridge program. - token_bridge_redeemer: UncheckedAccount<'info>, + //token_bridge_redeemer: UncheckedAccount<'info>, /// CHECK: This account is needed for the Token Bridge program. token_bridge_wrapped_asset: UncheckedAccount<'info>, @@ -105,9 +109,9 @@ pub struct ReceiveTbtc<'info> { rent: UncheckedAccount<'info>, tbtc_program: Program<'info, tbtc::Tbtc>, - associated_token_program: Program<'info, associated_token::AssociatedToken>, token_bridge_program: Program<'info, TokenBridge>, core_bridge_program: Program<'info, CoreBridge>, + associated_token_program: Program<'info, associated_token::AssociatedToken>, token_program: Program<'info, token::Token>, system_program: Program<'info, System>, } @@ -164,7 +168,7 @@ pub fn receive_tbtc(ctx: Context, _message_hash: [u8; 32]) -> Resul .token_bridge_registered_emitter .to_account_info(), to: wrapped_tbtc_token.to_account_info(), - redeemer: ctx.accounts.token_bridge_redeemer.to_account_info(), + redeemer: ctx.accounts.custodian.to_account_info(), wrapped_mint: wrapped_tbtc_mint.to_account_info(), wrapped_metadata: ctx.accounts.token_bridge_wrapped_asset.to_account_info(), mint_authority: ctx.accounts.token_bridge_mint_authority.to_account_info(), @@ -175,7 +179,7 @@ pub fn receive_tbtc(ctx: Context, _message_hash: [u8; 32]) -> Resul }, &[&[ token_bridge::SEED_PREFIX_REDEEMER, - &[ctx.accounts.custodian.token_bridge_redeemer_bump], + &[ctx.accounts.custodian.bump], ]], ))?; @@ -195,6 +199,8 @@ pub fn receive_tbtc(ctx: Context, _message_hash: [u8; 32]) -> Resul // We send Wormhole tBTC OR mint canonical tBTC. We do not want to send dust. Sending Wormhole // tBTC is an exceptional situation and we want to keep it simple. if updated_minted_amount > ctx.accounts.custodian.minting_limit { + msg!("Insufficient minted amount. Sending Wormhole tBTC instead"); + let ata = &ctx.accounts.recipient_wrapped_token; // Create associated token account for recipient if it doesn't exist already. diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc/gateway.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc/gateway.rs index 13fac606f..9fdbf0d02 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc/gateway.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc/gateway.rs @@ -16,6 +16,7 @@ pub struct SendTbtcGateway<'info> { has_one = wrapped_tbtc_mint, has_one = tbtc_mint, has_one = token_bridge_sender, + // has_one = tbtc_minter_info, TODO: add this guy to custodian )] custodian: Account<'info, Custodian>, @@ -26,9 +27,11 @@ pub struct SendTbtcGateway<'info> { gateway_info: Account<'info, GatewayInfo>, /// Custody account. + #[account(mut)] wrapped_tbtc_token: Box>, /// CHECK: This account is needed for the Token Bridge program. + #[account(mut)] wrapped_tbtc_mint: UncheckedAccount<'info>, #[account(mut)] @@ -149,6 +152,7 @@ pub fn send_tbtc_gateway(ctx: Context, args: SendTbtcGatewayArg let custodian = &ctx.accounts.custodian; // Finally transfer wrapped tBTC with the recipient encoded as this transfer's message. + // TODO: fix bug here: InvalidSigner(GZqbpJ4J1d4TwEG76fnQk48za4JE2FA13qaqWF8h1rvs) token_bridge::transfer_wrapped_with_payload( CpiContext::new_with_signer( ctx.accounts.token_bridge_program.to_account_info(), diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc/wrapped.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc/wrapped.rs index 71bacbf57..61c939b28 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc/wrapped.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc/wrapped.rs @@ -19,9 +19,11 @@ pub struct SendTbtcWrapped<'info> { custodian: Account<'info, Custodian>, /// Custody account. + #[account(mut)] wrapped_tbtc_token: Box>, /// CHECK: This account is needed for the Token Bridge program. + #[account(mut)] wrapped_tbtc_mint: UncheckedAccount<'info>, #[account(mut)] diff --git a/cross-chain/solana/programs/wormhole-gateway/src/state/custodian.rs b/cross-chain/solana/programs/wormhole-gateway/src/state/custodian.rs index cd792c027..c3e38396a 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/state/custodian.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/state/custodian.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use wormhole_anchor_sdk::token_bridge; #[account] #[derive(Debug, InitSpace)] @@ -11,13 +12,13 @@ pub struct Custodian { pub wrapped_tbtc_token: Pubkey, pub token_bridge_sender: Pubkey, pub token_bridge_sender_bump: u8, - pub token_bridge_redeemer: Pubkey, - pub token_bridge_redeemer_bump: u8, - + // pub token_bridge_redeemer: Pubkey, + // pub token_bridge_redeemer_bump: u8, pub minting_limit: u64, pub minted_amount: u64, } impl Custodian { - pub const SEED_PREFIX: &'static [u8] = b"custodian"; + /// TODO: This is an undesirable pattern in the Token Bridge due to how transfers are redeemed. + pub const SEED_PREFIX: &'static [u8] = token_bridge::SEED_PREFIX_REDEEMER; } diff --git a/cross-chain/solana/tests/01__tbtc.ts b/cross-chain/solana/tests/01__tbtc.ts index d40a34df1..b9beb26b2 100644 --- a/cross-chain/solana/tests/01__tbtc.ts +++ b/cross-chain/solana/tests/01__tbtc.ts @@ -10,7 +10,7 @@ import { getConfigPDA, getGuardianPDA, getGuardiansPDA, - getMinterPDA, + getMinterInfoPDA, getMintersPDA, getTokenPDA, maybeAuthorityAnd, @@ -95,7 +95,7 @@ async function checkPaused(program: Program, paused: boolean) { } async function checkMinter(program: Program, minter) { - const minterInfoPDA = getMinterPDA(minter.publicKey); + const minterInfoPDA = getMinterInfoPDA(minter.publicKey); let minterInfo = await program.account.minterInfo.fetch(minterInfoPDA); expect(minterInfo.minter).to.eql(minter.publicKey); @@ -343,12 +343,7 @@ describe("tbtc", () => { await checkState(authority, 1, 0, 0); // Transfer lamports to imposter. - await transferLamports( - program.provider.connection, - authority.payer, - impostorKeys.publicKey, - 1000000000 - ); + await transferLamports(authority.payer, impostorKeys.publicKey, 1000000000); // await web3.sendAndConfirmTransaction( // program.provider.connection, // new web3.Transaction().add( @@ -374,7 +369,7 @@ describe("tbtc", () => { it("mint", async () => { await checkState(authority, 1, 0, 0); - const minterInfoPDA = getMinterPDA(minterKeys.publicKey); + const minterInfoPDA = getMinterInfoPDA(minterKeys.publicKey); await checkMinter(program, minterKeys); // await setupMint(program, authority, recipientKeys); @@ -401,7 +396,7 @@ describe("tbtc", () => { it("won't mint", async () => { await checkState(authority, 1, 0, 1000); - const minterInfoPDA = getMinterPDA(minterKeys.publicKey); + const minterInfoPDA = getMinterInfoPDA(minterKeys.publicKey); await checkMinter(program, minterKeys); // await setupMint(program, authority, recipientKeys); @@ -426,7 +421,7 @@ describe("tbtc", () => { it("use two minters", async () => { await checkState(authority, 1, 0, 1000); - const minterInfoPDA = getMinterPDA(minterKeys.publicKey); + const minterInfoPDA = getMinterInfoPDA(minterKeys.publicKey); await checkMinter(program, minterKeys); const minter2InfoPDA = await addMinter(authority, minter2Keys.publicKey); await checkMinter(program, minter2Keys); @@ -475,7 +470,7 @@ describe("tbtc", () => { it("remove minter", async () => { await checkState(authority, 2, 0, 1500); - const minter2InfoPDA = getMinterPDA(minter2Keys.publicKey); + const minter2InfoPDA = getMinterInfoPDA(minter2Keys.publicKey); await checkMinter(program, minter2Keys); await removeMinter(program, authority, minter2Keys, minter2InfoPDA); await checkState(authority, 1, 0, 1500); @@ -483,7 +478,7 @@ describe("tbtc", () => { it("won't remove minter", async () => { await checkState(authority, 1, 0, 1500); - const minterInfoPDA = getMinterPDA(minterKeys.publicKey); + const minterInfoPDA = getMinterInfoPDA(minterKeys.publicKey); await checkMinter(program, minterKeys); try { diff --git a/cross-chain/solana/tests/02__wormholeGateway.ts b/cross-chain/solana/tests/02__wormholeGateway.ts index 964e46260..22b2a8219 100644 --- a/cross-chain/solana/tests/02__wormholeGateway.ts +++ b/cross-chain/solana/tests/02__wormholeGateway.ts @@ -3,77 +3,61 @@ import { MockEthereumTokenBridge } from "@certusone/wormhole-sdk/lib/cjs/mock"; import * as tokenBridge from "@certusone/wormhole-sdk/lib/cjs/solana/tokenBridge"; import * as coreBridge from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole"; import * as anchor from "@coral-xyz/anchor"; -import { AnchorError, Program, web3 } from "@coral-xyz/anchor"; -import { getMint } from "@solana/spl-token"; +import { AnchorError, Program } from "@coral-xyz/anchor"; +import { + getAccount, + getAssociatedTokenAddressSync, + getMint, +} from "@solana/spl-token"; import { expect } from "chai"; -import { Tbtc } from "../target/types/tbtc"; import { WormholeGateway } from "../target/types/wormhole_gateway"; import { ETHEREUM_TBTC_ADDRESS, ETHEREUM_TOKEN_BRIDGE_ADDRESS, GUARDIAN_SET_INDEX, - SOLANA_CORE_BRIDGE_ADDRESS, - SOLANA_TOKEN_BRIDGE_ADDRESS, + CORE_BRIDGE_PROGRAM_ID, + TOKEN_BRIDGE_PROGRAM_ID, + TBTC_PROGRAM_ID, + WORMHOLE_GATEWAY_PROGRAM_ID, WRAPPED_TBTC_MINT, + expectIxFail, + expectIxSuccess, generatePayer, - getOrCreateTokenAccount, + getOrCreateAta, mockSignAndPostVaa, preloadWrappedTbtc, + ethereumGatewaySendTbtc, + transferLamports, + getTokenBridgeSequence, } from "./helpers"; import * as tbtc from "./helpers/tbtc"; -import { - getCustodianPDA, - getTokenBridgeRedeemerPDA, - getTokenBridgeSenderPDA, - getWrappedTbtcTokenPDA, -} from "./helpers/wormholeGateway"; +import * as wormholeGateway from "./helpers/wormholeGateway"; async function setup( program: Program, - tbtcProgram: Program, authority, - mintingLimit: number + mintingLimit: bigint ) { - const custodian = getCustodianPDA(); + const custodian = wormholeGateway.getCustodianPDA(); const tbtcMint = tbtc.getTokenPDA(); - const gatewayWrappedTbtcToken = getWrappedTbtcTokenPDA(); - const tokenBridgeSender = getTokenBridgeSenderPDA(); - const tokenBridgeRedeemer = getTokenBridgeRedeemerPDA(); - - const wrappedTbtcMint = tokenBridge.deriveWrappedMintKey( - SOLANA_TOKEN_BRIDGE_ADDRESS, - 2, - ETHEREUM_TBTC_ADDRESS - ); + const gatewayWrappedTbtcToken = wormholeGateway.getWrappedTbtcTokenPDA(); + const tokenBridgeSender = wormholeGateway.getTokenBridgeSenderPDA(); + //const tokenBridgeRedeemer = wormholeGateway.getTokenBridgeRedeemerPDA(); await program.methods - .initialize(new anchor.BN(mintingLimit)) + .initialize(new anchor.BN(mintingLimit.toString())) .accounts({ authority: authority.publicKey, custodian, tbtcMint, - wrappedTbtcMint, + wrappedTbtcMint: WRAPPED_TBTC_MINT, wrappedTbtcToken: gatewayWrappedTbtcToken, tokenBridgeSender, - tokenBridgeRedeemer, + //tokenBridgeRedeemer, }) .rpc(); } -async function checkState( - program: Program, - expectedAuthority, - expectedMintingLimit - // expectedMintedAmount, -) { - const custodian = getCustodianPDA(); - let custodianState = await program.account.custodian.fetch(custodian); - - expect(custodianState.mintingLimit.eq(new anchor.BN(expectedMintingLimit))).to - .be.true; - expect(custodianState.authority).to.eql(expectedAuthority.publicKey); -} - describe("wormhole-gateway", () => { // Configure the client to use the local cluster. anchor.setProvider(anchor.AnchorProvider.env()); @@ -81,17 +65,16 @@ describe("wormhole-gateway", () => { const program = anchor.workspace.WormholeGateway as Program; const connection = program.provider.connection; - const tbtcProgram = anchor.workspace.Tbtc as Program; - - const custodian = getCustodianPDA(); + const custodian = wormholeGateway.getCustodianPDA(); const tbtcMint = tbtc.getTokenPDA(); const tbtcConfig = tbtc.getConfigPDA(); - const gatewayWrappedTbtcToken = getWrappedTbtcTokenPDA(); - const tokenBridgeSender = getTokenBridgeSenderPDA(); - const tokenBridgeRedeemer = getTokenBridgeRedeemerPDA(); + const gatewayWrappedTbtcToken = wormholeGateway.getWrappedTbtcTokenPDA(); + const tokenBridgeSender = wormholeGateway.getTokenBridgeSenderPDA(); + const tokenBridgeRedeemer = wormholeGateway.getTokenBridgeRedeemerPDA(); - 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(); @@ -101,145 +84,244 @@ describe("wormhole-gateway", () => { const recipientKeys = anchor.web3.Keypair.generate(); + const commonTokenOwner = anchor.web3.Keypair.generate(); + const ethereumTokenBridge = new MockEthereumTokenBridge( ETHEREUM_TOKEN_BRIDGE_ADDRESS ); - it("check core bridge and token bridge", async () => { - // Check core bridge guardian set. - const guardianSetData = await coreBridge.getGuardianSet( - connection, - SOLANA_CORE_BRIDGE_ADDRESS, - GUARDIAN_SET_INDEX + it("setup", async () => { + const mintingLimit = BigInt(10000); + await setup(program, authority, mintingLimit); + await wormholeGateway.checkState(authority.publicKey, mintingLimit); + await tbtc.checkState(authority, 1, 2, 1500); + + // Also set up common token account. + await transferLamports(authority, commonTokenOwner.publicKey, 100000000000); + await getOrCreateAta( + authority, + tbtc.getTokenPDA(), + commonTokenOwner.publicKey + ); + }); + + it("update minting limit", async () => { + const newLimit = BigInt(20000); + const ix = await wormholeGateway.updateMintingLimitIx( + { + authority: authority.publicKey, + }, + newLimit ); - expect(guardianSetData.keys).has.length(1); + await expectIxSuccess([ix], [authority]); + await wormholeGateway.checkState(authority.publicKey, newLimit); + }); + + it("deposit wrapped tokens", async () => { + const custodian = wormholeGateway.getCustodianPDA(); + // TODO: cannot deposit if custodian isn't minter yet. + + // Add custodian as minter. + await tbtc.addMinter(authority, custodian); // Set up new wallet - const payer = await generatePayer(connection, authority.payer); + const payer = await generatePayer(authority); // Check wrapped tBTC mint. - const wrappedTbtcMint = tokenBridge.deriveWrappedMintKey( - SOLANA_TOKEN_BRIDGE_ADDRESS, - 2, - ETHEREUM_TBTC_ADDRESS + const recipientWrappedToken = await preloadWrappedTbtc( + payer, + ethereumTokenBridge, + BigInt("100000000000"), + payer.publicKey ); - const mintData = await getMint(connection, wrappedTbtcMint); - expect(mintData.decimals).to.equal(8); - expect(mintData.supply).to.equal(BigInt(90)); - const wrappedTbtcToken = await getOrCreateTokenAccount( - connection, + const recipientToken = await getOrCreateAta( payer, - wrappedTbtcMint, + tbtcMint, payer.publicKey ); - // Bridge tbtc to token account. - const published = ethereumTokenBridge.publishTransferTokens( - tryNativeToHexString(ETHEREUM_TBTC_ADDRESS, "ethereum"), - 2, - BigInt("100000000000"), - 1, - wrappedTbtcToken.address.toBuffer().toString("hex"), - BigInt(0), - 0, - 0 + const [wrappedBefore, tbtcBefore, gatewayBefore] = await Promise.all([ + getAccount(connection, recipientWrappedToken), + getAccount(connection, recipientToken), + getAccount(connection, gatewayWrappedTbtcToken), + ]); + + const depositAmount = BigInt(500); + + const ix = await wormholeGateway.depositWormholeTbtcIx( + { + recipientWrappedToken, + recipientToken, + recipient: payer.publicKey, + }, + depositAmount ); + await expectIxSuccess([ix], [payer]); + await tbtc.checkState(authority, 2, 2, 2000); - const signedVaa = await mockSignAndPostVaa(connection, payer, published); + const [wrappedAfter, tbtcAfter, gatewayAfter] = await Promise.all([ + getAccount(connection, recipientWrappedToken), + getAccount(connection, recipientToken), + getAccount(connection, gatewayWrappedTbtcToken), + ]); - const tx = await redeemOnSolana( - connection, - SOLANA_CORE_BRIDGE_ADDRESS, - SOLANA_TOKEN_BRIDGE_ADDRESS, - payer.publicKey, - signedVaa + // Check balance change. + expect(wrappedAfter.amount).to.equal(wrappedBefore.amount - depositAmount); + expect(tbtcAfter.amount).to.equal(tbtcBefore.amount + depositAmount); + expect(gatewayAfter.amount).to.equal(gatewayBefore.amount + depositAmount); + + // Cannot deposit past minting limit. + const failingIx = await wormholeGateway.depositWormholeTbtcIx( + { + recipientWrappedToken, + recipientToken, + recipient: payer.publicKey, + }, + BigInt(50000) ); - await web3.sendAndConfirmTransaction(connection, tx, [payer]); - }); + await expectIxFail([failingIx], [payer], "MintingLimitExceeded"); - it("setup", async () => { - await setup(program, tbtcProgram, authority, 10000); - await checkState(program, authority, 10000); - await tbtc.checkState(authority, 1, 2, 1500); + // Will succeed if minting limit is increased. + const newLimit = BigInt(70000); + const updateLimitIx = await wormholeGateway.updateMintingLimitIx( + { + authority: authority.publicKey, + }, + newLimit + ); + await expectIxSuccess([updateLimitIx], [authority]); + await wormholeGateway.checkState(authority.publicKey, newLimit); + await expectIxSuccess([failingIx], [payer]); }); - it("update minting limit", async () => { - await program.methods - .updateMintingLimit(new anchor.BN(20000)) - .accounts({ - custodian, + it("update gateway address", async () => { + const chain = 2; + + // demonstrate gateway address does not exist + const gatewayInfo = await connection.getAccountInfo( + wormholeGateway.getGatewayInfoPDA(chain) + ); + expect(gatewayInfo).is.null; + + // Make new gateway. + const firstAddress = Array.from(Buffer.alloc(32, "deadbeef", "hex")); + const firstIx = await wormholeGateway.updateGatewayAddress( + { authority: authority.publicKey, - }) - .rpc(); - await checkState(program, authority, 20000); - }); + }, + { chain, address: firstAddress } + ); + await expectIxSuccess([firstIx], [authority]); + await wormholeGateway.checkGateway(chain, firstAddress); - it("deposit wrapped tokens", async () => { - const custodian = getCustodianPDA(); - const minterInfo = await tbtc.addMinter(authority, custodian); + // Update gateway. + const goodAddress = Array.from(ethereumTokenBridge.address); + const secondIx = await wormholeGateway.updateGatewayAddress( + { + authority: authority.publicKey, + }, + { chain, address: goodAddress } + ); + await expectIxSuccess([secondIx], [authority]); + await wormholeGateway.checkGateway(chain, goodAddress); + }); + it("receive tbtc", async () => { // Set up new wallet - const payer = await generatePayer(connection, authority.payer); + const payer = await generatePayer(authority); - // Check wrapped tBTC mint. - const wrappedTbtcToken = await preloadWrappedTbtc( - connection, + // Use common token account. + const recipient = commonTokenOwner.publicKey; + const recipientToken = getAssociatedTokenAddressSync( + tbtc.getTokenPDA(), + recipient + ); + + // Get foreign gateway. + const fromGateway = await wormholeGateway + .getGatewayInfo(2) + .then((info) => info.address); + + const sentAmount = BigInt(5000); + const signedVaa = await ethereumGatewaySendTbtc( payer, ethereumTokenBridge, - BigInt("100000000000"), - payer.publicKey + sentAmount, + fromGateway, + WORMHOLE_GATEWAY_PROGRAM_ID, + recipient ); - const recipientToken = await getOrCreateTokenAccount( - connection, - payer, - tbtcMint, - payer.publicKey + const [tbtcBefore, gatewayBefore] = await Promise.all([ + getAccount(connection, recipientToken), + getAccount(connection, gatewayWrappedTbtcToken), + ]); + + const ix = await wormholeGateway.receiveTbtcIx( + { + payer: payer.publicKey, + recipientToken, + recipient, + }, + signedVaa ); + await expectIxSuccess([ix], [payer]); - await program.methods - .depositWormholeTbtc(new anchor.BN(500)) - .accounts({ - custodian, - wrappedTbtcToken: gatewayWrappedTbtcToken, - wrappedTbtcMint: WRAPPED_TBTC_MINT, - tbtcMint, - recipientWrappedToken: wrappedTbtcToken, - recipientToken: recipientToken.address, - recipient: payer.publicKey, - tbtcConfig, - minterInfo, - tbtcProgram: tbtcProgram.programId, - }) - .signers(tbtc.maybeAuthorityAnd(payer, [])) - .rpc(); + const [tbtcAfter, gatewayAfter] = await Promise.all([ + getAccount(connection, recipientToken), + getAccount(connection, gatewayWrappedTbtcToken), + ]); - await tbtc.checkState(authority, 2, 2, 2000); + // TODO: compare balances + }); + + it("send tbtc to gateway", async () => { + // Use common token account. + const sender = commonTokenOwner.publicKey; + const senderToken = getAssociatedTokenAddressSync( + tbtc.getTokenPDA(), + sender + ); + + // Check token account. + const gatewayBefore = await getAccount(connection, gatewayWrappedTbtcToken); - try { - await program.methods - .depositWormholeTbtc(new anchor.BN(50000)) - .accounts({ - custodian, - wrappedTbtcToken: gatewayWrappedTbtcToken, - wrappedTbtcMint: WRAPPED_TBTC_MINT, - tbtcMint, - recipientWrappedToken: wrappedTbtcToken, - recipientToken: recipientToken.address, - recipient: payer.publicKey, - tbtcConfig, - minterInfo, - tbtcProgram: tbtcProgram.programId, - }) - .signers(tbtc.maybeAuthorityAnd(payer, [])) - .rpc(); - 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("MintingLimitExceeded"); - expect(err.program.equals(program.programId)).is.true; - } + // Get destination gateway. + const recipientChain = 2; + const recipient = Array.from(Buffer.alloc(32, "deadbeef", "hex")); + const nonce = 420; + + // Try an amount that won't work. + const badAmount = BigInt(123000); + const badIx = await wormholeGateway.sendTbtcGatewayIx( + { + senderToken, + sender, + }, + { + amount: new anchor.BN(badAmount.toString()), + recipientChain, + recipient, + nonce, + } + ); + await expectIxFail([badIx], [commonTokenOwner], "NotEnoughWrappedTbtc"); + + // // This should work. + const goodAmount = BigInt(2000); + const ix = await wormholeGateway.sendTbtcGatewayIx( + { + senderToken, + sender, + }, + { + amount: new anchor.BN(goodAmount.toString()), + recipientChain, + recipient, + nonce, + } + ); + await expectIxSuccess([ix], [commonTokenOwner]); }); }); diff --git a/cross-chain/solana/tests/helpers/consts.ts b/cross-chain/solana/tests/helpers/consts.ts index e27570ec2..748be2689 100644 --- a/cross-chain/solana/tests/helpers/consts.ts +++ b/cross-chain/solana/tests/helpers/consts.ts @@ -7,10 +7,10 @@ export const WORMHOLE_GATEWAY_PROGRAM_ID = new PublicKey( "8H9F5JGbEMyERycwaGuzLS5MQnV7dn2wm2h6egJ3Leiu" ); -export const SOLANA_CORE_BRIDGE_ADDRESS = new PublicKey( +export const CORE_BRIDGE_PROGRAM_ID = new PublicKey( "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth" ); -export const SOLANA_TOKEN_BRIDGE_ADDRESS = new PublicKey( +export const TOKEN_BRIDGE_PROGRAM_ID = new PublicKey( "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb" ); diff --git a/cross-chain/solana/tests/helpers/tbtc.ts b/cross-chain/solana/tests/helpers/tbtc.ts index 93cd9194e..4f918a62c 100644 --- a/cross-chain/solana/tests/helpers/tbtc.ts +++ b/cross-chain/solana/tests/helpers/tbtc.ts @@ -1,5 +1,4 @@ -import * as anchor from "@coral-xyz/anchor"; -import { Program } from "@coral-xyz/anchor"; +import { Program, Wallet, workspace } from "@coral-xyz/anchor"; import { getMint } from "@solana/spl-token"; import { PublicKey } from "@solana/web3.js"; import { expect } from "chai"; @@ -7,9 +6,7 @@ import { Tbtc } from "../../target/types/tbtc"; import { TBTC_PROGRAM_ID } from "./consts"; export function maybeAuthorityAnd(signer, signers) { - return signers.concat( - signer instanceof (anchor.Wallet as any) ? [] : [signer] - ); + return signers.concat(signer instanceof (Wallet as any) ? [] : [signer]); } export function getConfigPDA(): PublicKey { @@ -26,7 +23,7 @@ export function getTokenPDA(): PublicKey { )[0]; } -export function getMinterPDA(minter: PublicKey): PublicKey { +export function getMinterInfoPDA(minter: PublicKey): PublicKey { return PublicKey.findProgramAddressSync( [Buffer.from("minter-info"), minter.toBuffer()], TBTC_PROGRAM_ID @@ -60,7 +57,7 @@ export async function checkState( expectedGuardians: number, expectedTokensSupply ) { - const program = anchor.workspace.Tbtc as Program; + const program = workspace.Tbtc as Program; const config = getConfigPDA(); let configState = await program.account.config.fetch(config); @@ -84,15 +81,12 @@ export async function checkState( expect(mintersState.keys).has.length(expectedMinters); } -export async function addMinter( - authority, - minter -): Promise { - const program = anchor.workspace.Tbtc as Program; +export async function addMinter(authority, minter): Promise { + const program = workspace.Tbtc as Program; const config = getConfigPDA(); const minters = getMintersPDA(); - const minterInfoPDA = getMinterPDA(minter); + const minterInfoPDA = getMinterInfoPDA(minter); await program.methods .addMinter() .accounts({ diff --git a/cross-chain/solana/tests/helpers/utils.ts b/cross-chain/solana/tests/helpers/utils.ts index 901f2f980..a71044a2e 100644 --- a/cross-chain/solana/tests/helpers/utils.ts +++ b/cross-chain/solana/tests/helpers/utils.ts @@ -2,7 +2,7 @@ import { MockEthereumTokenBridge, MockGuardians, } from "@certusone/wormhole-sdk/lib/cjs/mock"; -import { web3 } from "@coral-xyz/anchor"; +import { Idl, Program, web3, workspace } from "@coral-xyz/anchor"; import { Account, TokenAccountNotFoundError, @@ -16,14 +16,16 @@ import { PublicKey, SystemProgram, Transaction, + TransactionInstruction, sendAndConfirmTransaction, } from "@solana/web3.js"; import { ETHEREUM_TBTC_ADDRESS, GUARDIAN_SET_INDEX, - SOLANA_CORE_BRIDGE_ADDRESS, - SOLANA_TOKEN_BRIDGE_ADDRESS, + CORE_BRIDGE_PROGRAM_ID, + TOKEN_BRIDGE_PROGRAM_ID, WRAPPED_TBTC_MINT, + GUARDIAN_DEVNET_PRIVATE_KEYS, } from "./consts"; import { postVaaSolana, @@ -31,15 +33,17 @@ import { 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( - connection: web3.Connection, +export async function transferLamports( fromSigner: web3.Keypair, toPubkey: web3.PublicKey, lamports: number ) { + const program = workspace.WormholeGateway as Program; return sendAndConfirmTransaction( - connection, + program.provider.connection, new Transaction().add( SystemProgram.transfer({ fromPubkey: fromSigner.publicKey, @@ -51,27 +55,27 @@ export async function transferLamports( ); } -export async function generatePayer( - connection: web3.Connection, - payer: Keypair, +export async function generatePayer( + funder: Keypair, lamports?: number ) { const newPayer = Keypair.generate(); - await transferLamports( - connection, - payer, + await transferLamports( + funder, newPayer.publicKey, lamports === undefined ? 1000000000 : lamports ); return newPayer; } -export async function getOrCreateTokenAccount( - connection: Connection, +export async function getOrCreateAta( payer: Keypair, mint: PublicKey, owner: PublicKey ) { + const program = workspace.WormholeGateway as Program; + const connection = program.provider.connection; + const token = getAssociatedTokenAddressSync(mint, owner); const tokenData: Account = await getAccount(connection, token).catch( (err) => { @@ -96,22 +100,21 @@ export async function getOrCreateTokenAccount( ), [payer] ); - - return getAccount(connection, token); - } else { - return tokenData; } + + return token; } -export async function preloadWrappedTbtc( - connection: Connection, +export async function preloadWrappedTbtc( payer: Keypair, ethereumTokenBridge: MockEthereumTokenBridge, amount: bigint, tokenOwner: PublicKey ) { - const wrappedTbtcToken = await getOrCreateTokenAccount( - connection, + const program = workspace.WormholeGateway as Program; + const connection = program.provider.connection; + + const wrappedTbtcToken = await getOrCreateAta( payer, WRAPPED_TBTC_MINT, tokenOwner @@ -123,31 +126,32 @@ export async function preloadWrappedTbtc( 2, amount, 1, - wrappedTbtcToken.address.toBuffer().toString("hex"), + wrappedTbtcToken.toBuffer().toString("hex"), BigInt(0), 0, 0 ); - const signedVaa = await mockSignAndPostVaa(connection, payer, published); + const signedVaa = await mockSignAndPostVaa(payer, published); const tx = await redeemOnSolana( connection, - SOLANA_CORE_BRIDGE_ADDRESS, - SOLANA_TOKEN_BRIDGE_ADDRESS, + CORE_BRIDGE_PROGRAM_ID, + TOKEN_BRIDGE_PROGRAM_ID, payer.publicKey, signedVaa ); await web3.sendAndConfirmTransaction(connection, tx, [payer]); - return wrappedTbtcToken.address; + return wrappedTbtcToken; } -export async function mockSignAndPostVaa( - connection: web3.Connection, +export async function mockSignAndPostVaa( payer: web3.Keypair, published: Buffer ) { + const program = workspace.WormholeGateway as Program; + const guardians = new MockGuardians(GUARDIAN_SET_INDEX, [ "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0", ]); @@ -157,9 +161,51 @@ export async function mockSignAndPostVaa( // Verify and post VAA. await postVaaSolana( - connection, + program.provider.connection, + new NodeWallet(payer).signTransaction, + CORE_BRIDGE_PROGRAM_ID, + payer.publicKey, + signedVaa + ); + + return signedVaa; +} + +export async function ethereumGatewaySendTbtc( + payer: web3.Keypair, + ethereumTokenBridge: MockEthereumTokenBridge, + amount: bigint, + fromGateway: number[], + toGateway: PublicKey, + recipient: PublicKey +) { + const program = workspace.WormholeGateway as Program; + + const published = ethereumTokenBridge.publishTransferTokensWithPayload( + tryNativeToHexString(ETHEREUM_TBTC_ADDRESS, "ethereum"), + 2, + amount, + 1, + toGateway.toBuffer().toString("hex"), + Buffer.from(fromGateway), + recipient.toBuffer(), + 0, + 0 + ); + + const guardians = new MockGuardians( + GUARDIAN_SET_INDEX, + GUARDIAN_DEVNET_PRIVATE_KEYS + ); + + // Add guardian signature. + const signedVaa = guardians.addSignatures(published, [0]); + + // Verify and post VAA. + await postVaaSolana( + program.provider.connection, new NodeWallet(payer).signTransaction, - SOLANA_CORE_BRIDGE_ADDRESS, + CORE_BRIDGE_PROGRAM_ID, payer.publicKey, signedVaa ); @@ -167,28 +213,57 @@ export async function mockSignAndPostVaa( return signedVaa; } -// export function ethereumGatewaySendTbtc( -// ethereumTokenBridge: MockEthereumTokenBridge, -// amount: bigint, -// recipient: Buffer -// ) { -// const wrappedTbtcMint = getWrappedTbtcMintPDA(); -// const custodianWrappedTbtcToken = getWrappedTbtcTokenPDA; -// const published = ethereumTokenBridge.publishTransferTokens( -// tryNativeToHexString(ETHEREUM_TBTC_ADDRESS, "ethereum"), -// 2, -// BigInt("100000000000"), -// 1, -// wrappedTbtcToken.address.toBuffer().toString("hex"), -// BigInt(0), -// 0, -// 0 -// ); - -// const guardians = new mock.MockGuardians(GUARDIAN_SET_INDEX, [ -// "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0", -// ]); - -// // Add guardian signature. -// const signedVaa = guardians.addSignatures(published, [0]); -// } +export async function expectIxSuccess( + ixes: TransactionInstruction[], + signers: Keypair[] +) { + const program = workspace.WormholeGateway as Program; + await sendAndConfirmTransaction( + program.provider.connection, + new Transaction().add(...ixes), + signers + ).catch((err) => { + console.log(err.logs); + throw err; + }); +} + +export async function expectIxFail( + ixes: TransactionInstruction[], + signers: Keypair[], + errorMessage: string +) { + 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}`); + } catch (err) { + const logs: string[] = err.logs; + expect(logs.join("\n")).includes(errorMessage); + } +} + +export function getTokenBridgeCoreEmitter() { + const [tokenBridgeCoreEmitter] = PublicKey.findProgramAddressSync( + [Buffer.from("emitter")], + TOKEN_BRIDGE_PROGRAM_ID + ); + + return tokenBridgeCoreEmitter; +} + +export async function getTokenBridgeSequence() { + const program = workspace.WormholeGateway as Program; + const emitter = getTokenBridgeCoreEmitter(); + return coreBridge + .getSequenceTracker( + program.provider.connection, + emitter, + CORE_BRIDGE_PROGRAM_ID + ) + .then((tracker) => tracker.sequence); +} diff --git a/cross-chain/solana/tests/helpers/wormholeGateway.ts b/cross-chain/solana/tests/helpers/wormholeGateway.ts index 747da5a10..54a89dd46 100644 --- a/cross-chain/solana/tests/helpers/wormholeGateway.ts +++ b/cross-chain/solana/tests/helpers/wormholeGateway.ts @@ -1,14 +1,46 @@ -import { PublicKey } from "@solana/web3.js"; -import { WORMHOLE_GATEWAY_PROGRAM_ID } from "./consts"; +import { parseVaa } from "@certusone/wormhole-sdk"; +import * as tokenBridge from "@certusone/wormhole-sdk/lib/cjs/solana/tokenBridge"; +import * as coreBridge from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole"; +import { BN, Program, workspace } from "@coral-xyz/anchor"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; +import { + PublicKey, + SYSVAR_CLOCK_PUBKEY, + SYSVAR_RENT_PUBKEY, + TransactionInstruction, +} from "@solana/web3.js"; +import { expect } from "chai"; +import { WormholeGateway } from "../../target/types/wormhole_gateway"; +import { + CORE_BRIDGE_DATA, + CORE_BRIDGE_PROGRAM_ID, + ETHEREUM_ENDPOINT, + TBTC_PROGRAM_ID, + TOKEN_BRIDGE_PROGRAM_ID, + WORMHOLE_GATEWAY_PROGRAM_ID, + WRAPPED_TBTC_ASSET, + WRAPPED_TBTC_MINT, +} from "./consts"; +import * as tbtc from "./tbtc"; +import { getTokenBridgeCoreEmitter, getTokenBridgeSequence } from "./utils"; export function getCustodianPDA(): PublicKey { return PublicKey.findProgramAddressSync( - [Buffer.from("custodian")], + [Buffer.from("redeemer")], WORMHOLE_GATEWAY_PROGRAM_ID )[0]; } -export function getGatewayInfoPDA(targetChain): PublicKey { +export function getCoreMessagePDA(sequence: bigint): PublicKey { + const encodedSequence = Buffer.alloc(8); + encodedSequence.writeBigUInt64LE(sequence); + return PublicKey.findProgramAddressSync( + [Buffer.from("msg"), encodedSequence], + WORMHOLE_GATEWAY_PROGRAM_ID + )[0]; +} + +export function getGatewayInfoPDA(targetChain: number): PublicKey { const encodedChain = Buffer.alloc(2); encodedChain.writeUInt16LE(targetChain); return PublicKey.findProgramAddressSync( @@ -37,3 +69,673 @@ export function getTokenBridgeRedeemerPDA(): PublicKey { WORMHOLE_GATEWAY_PROGRAM_ID )[0]; } + +export async function getCustodianData() { + const program = workspace.WormholeGateway as Program; + const custodian = getCustodianPDA(); + return program.account.custodian.fetch(custodian); +} + +export async function checkState( + expectedAuthority: PublicKey, + expectedMintingLimit: bigint +) { + const custodianState = await getCustodianData(); + + expect( + custodianState.mintingLimit.eq(new BN(expectedMintingLimit.toString())) + ).to.be.true; + expect(custodianState.authority).to.eql(expectedAuthority); +} + +export async function getGatewayInfo(chain: number) { + const program = workspace.WormholeGateway as Program; + const gatewayInfo = getGatewayInfoPDA(chain); + return program.account.gatewayInfo.fetch(gatewayInfo); +} + +export async function checkGateway(chain: number, expectedAddress: number[]) { + const gatewayInfoState = await getGatewayInfo(chain); + expect(gatewayInfoState.address).to.eql(expectedAddress); +} + +type UpdateMintingLimitContext = { + custodian?: PublicKey; + authority: PublicKey; +}; + +export async function updateMintingLimitIx( + accounts: UpdateMintingLimitContext, + amount: bigint +): Promise { + const program = workspace.WormholeGateway as Program; + + let { custodian, authority } = accounts; + if (custodian === undefined) { + custodian = getCustodianPDA(); + } + + return program.methods + .updateMintingLimit(new BN(amount.toString())) + .accounts({ + custodian, + authority, + }) + .instruction(); +} + +type UpdateGatewayAddressContext = { + custodian?: PublicKey; + gatewayInfo?: PublicKey; + authority: PublicKey; +}; + +type UpdateGatewayAddressArgs = { + chain: number; + address: number[]; +}; + +export async function updateGatewayAddress( + accounts: UpdateGatewayAddressContext, + args: UpdateGatewayAddressArgs +) { + const program = workspace.WormholeGateway as Program; + let { custodian, gatewayInfo, authority } = accounts; + + if (custodian === undefined) { + custodian = getCustodianPDA(); + } + + if (gatewayInfo === undefined) { + gatewayInfo = getGatewayInfoPDA(args.chain); + } + + return program.methods + .updateGatewayAddress(args) + .accounts({ + custodian, + gatewayInfo, + authority, + }) + .instruction(); +} + +type DepositWormholeTbtcContext = { + custodian?: PublicKey; + wrappedTbtcToken?: PublicKey; + wrappedTbtcMint?: PublicKey; + tbtcMint?: PublicKey; + recipientWrappedToken: PublicKey; + recipientToken: PublicKey; + recipient: PublicKey; + tbtcConfig?: PublicKey; + tbtcMinterInfo?: PublicKey; + tbtcProgram?: PublicKey; +}; + +export async function depositWormholeTbtcIx( + accounts: DepositWormholeTbtcContext, + amount: bigint +): Promise { + const program = workspace.WormholeGateway as Program; + let { + custodian, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + recipientWrappedToken, + recipientToken, + recipient, + tbtcConfig, + tbtcMinterInfo, + tbtcProgram, + } = accounts; + + if (custodian === undefined) { + custodian = getCustodianPDA(); + } + + if (wrappedTbtcToken === undefined) { + wrappedTbtcToken = getWrappedTbtcTokenPDA(); + } + + if (wrappedTbtcMint === undefined) { + wrappedTbtcMint = WRAPPED_TBTC_MINT; + } + + if (tbtcMint === undefined) { + tbtcMint = tbtc.getTokenPDA(); + } + + if (tbtcConfig === undefined) { + tbtcConfig = tbtc.getConfigPDA(); + } + + if (tbtcMinterInfo === undefined) { + tbtcMinterInfo = tbtc.getMinterInfoPDA(custodian); + } + + if (tbtcProgram === undefined) { + tbtcProgram = TBTC_PROGRAM_ID; + } + + return program.methods + .depositWormholeTbtc(new BN(amount.toString())) + .accounts({ + custodian, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + recipientWrappedToken, + recipientToken, + recipient, + tbtcConfig, + tbtcMinterInfo, + tbtcProgram, + }) + .instruction(); +} + +type ReceiveTbtcContext = { + payer: PublicKey; + custodian?: PublicKey; + postedVaa?: PublicKey; + tokenBridgeClaim?: PublicKey; + wrappedTbtcToken?: PublicKey; + wrappedTbtcMint?: PublicKey; + tbtcMint?: PublicKey; + recipientToken: PublicKey; + recipient: PublicKey; + recipientWrappedToken?: PublicKey; + tbtcConfig?: PublicKey; + tbtcMinterInfo?: PublicKey; + tokenBridgeConfig?: PublicKey; + tokenBridgeRegisteredEmitter?: PublicKey; + //tokenBridgeRedeemer?: PublicKey; + tokenBridgeWrappedAsset?: PublicKey; + tokenBridgeMintAuthority?: PublicKey; + rent?: PublicKey; + tbtcProgram?: PublicKey; + tokenBridgeProgram?: PublicKey; + coreBridgeProgram?: PublicKey; +}; + +export async function receiveTbtcIx( + accounts: ReceiveTbtcContext, + signedVaa: Buffer +): Promise { + const parsed = parseVaa(signedVaa); + + const program = workspace.WormholeGateway as Program; + let { + payer, + custodian, + postedVaa, + tokenBridgeClaim, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + recipientToken, + recipient, + recipientWrappedToken, + tbtcConfig, + tbtcMinterInfo, + tokenBridgeConfig, + tokenBridgeRegisteredEmitter, + //tokenBridgeRedeemer, + tokenBridgeWrappedAsset, + tokenBridgeMintAuthority, + rent, + tbtcProgram, + tokenBridgeProgram, + coreBridgeProgram, + } = accounts; + + if (custodian === undefined) { + custodian = getCustodianPDA(); + } + + if (postedVaa === undefined) { + postedVaa = coreBridge.derivePostedVaaKey( + CORE_BRIDGE_PROGRAM_ID, + parsed.hash + ); + } + + if (tokenBridgeClaim === undefined) { + tokenBridgeClaim = coreBridge.deriveClaimKey( + TOKEN_BRIDGE_PROGRAM_ID, + parsed.emitterAddress, + parsed.emitterChain, + parsed.sequence + ); + } + + if (wrappedTbtcToken === undefined) { + wrappedTbtcToken = getWrappedTbtcTokenPDA(); + } + + if (wrappedTbtcMint === undefined) { + wrappedTbtcMint = WRAPPED_TBTC_MINT; + } + + if (tbtcMint === undefined) { + tbtcMint = tbtc.getTokenPDA(); + } + + if (recipientWrappedToken == undefined) { + recipientWrappedToken = getAssociatedTokenAddressSync( + wrappedTbtcMint, + recipient + ); + } + + if (tbtcConfig === undefined) { + tbtcConfig = tbtc.getConfigPDA(); + } + + if (tbtcMinterInfo === undefined) { + tbtcMinterInfo = tbtc.getMinterInfoPDA(custodian); + } + + if (tokenBridgeConfig === undefined) { + tokenBridgeConfig = tokenBridge.deriveTokenBridgeConfigKey( + TOKEN_BRIDGE_PROGRAM_ID + ); + } + + if (tokenBridgeRegisteredEmitter === undefined) { + tokenBridgeRegisteredEmitter = ETHEREUM_ENDPOINT; + } + + // if (tokenBridgeRedeemer === undefined) { + // tokenBridgeRedeemer = tokenBridge.deriveRedeemerAccountKey( + // WORMHOLE_GATEWAY_PROGRAM_ID + // ); + // } + + if (tokenBridgeWrappedAsset === undefined) { + tokenBridgeWrappedAsset = WRAPPED_TBTC_ASSET; + } + + if (tokenBridgeMintAuthority === undefined) { + tokenBridgeMintAuthority = tokenBridge.deriveMintAuthorityKey( + TOKEN_BRIDGE_PROGRAM_ID + ); + } + + if (rent === undefined) { + rent = SYSVAR_RENT_PUBKEY; + } + + if (tbtcProgram === undefined) { + tbtcProgram = TBTC_PROGRAM_ID; + } + + if (tokenBridgeProgram === undefined) { + tokenBridgeProgram = TOKEN_BRIDGE_PROGRAM_ID; + } + + if (coreBridgeProgram === undefined) { + coreBridgeProgram = CORE_BRIDGE_PROGRAM_ID; + } + + return program.methods + .receiveTbtc(Array.from(parsed.hash)) + .accounts({ + payer, + custodian, + postedVaa, + tokenBridgeClaim, + wrappedTbtcToken, + tbtcMint, + recipientToken, + recipient, + recipientWrappedToken, + tbtcConfig, + tbtcMinterInfo, + wrappedTbtcMint, + tokenBridgeConfig, + tokenBridgeRegisteredEmitter, + //tokenBridgeRedeemer, + tokenBridgeWrappedAsset, + tokenBridgeMintAuthority, + rent, + tbtcProgram, + tokenBridgeProgram, + coreBridgeProgram, + }) + .instruction(); +} + +type SendTbtcGatewayContext = { + custodian?: PublicKey; + gatewayInfo?: PublicKey; + wrappedTbtcToken?: PublicKey; + wrappedTbtcMint?: PublicKey; + tbtcMint?: PublicKey; + senderToken: PublicKey; + sender: PublicKey; + tokenBridgeConfig?: PublicKey; + tokenBridgeWrappedAsset?: PublicKey; + tokenBridgeTransferAuthority?: PublicKey; + coreBridgeData?: PublicKey; + coreMessage?: PublicKey; + tokenBridgeCoreEmitter?: PublicKey; + coreEmitterSequence?: PublicKey; + coreFeeCollector?: PublicKey; + clock?: PublicKey; + tokenBridgeSender?: PublicKey; + rent?: PublicKey; + tokenBridgeProgram?: PublicKey; + coreBridgeProgram?: PublicKey; +}; + +type SendTbtcGatewayArgs = { + amount: BN; + recipientChain: number; + recipient: number[]; + nonce: number; +}; + +export async function sendTbtcGatewayIx( + accounts: SendTbtcGatewayContext, + args: SendTbtcGatewayArgs +): Promise { + const program = workspace.WormholeGateway as Program; + let { + custodian, + gatewayInfo, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + senderToken, + sender, + tokenBridgeConfig, + tokenBridgeWrappedAsset, + tokenBridgeTransferAuthority, + coreBridgeData, + coreMessage, + tokenBridgeCoreEmitter, + coreEmitterSequence, + coreFeeCollector, + clock, + tokenBridgeSender, + rent, + tokenBridgeProgram, + coreBridgeProgram, + } = accounts; + + if (custodian === undefined) { + custodian = getCustodianPDA(); + } + + if (gatewayInfo === undefined) { + gatewayInfo = getGatewayInfoPDA(args.recipientChain); + } + + if (wrappedTbtcToken === undefined) { + wrappedTbtcToken = getWrappedTbtcTokenPDA(); + } + + if (wrappedTbtcMint === undefined) { + wrappedTbtcMint = WRAPPED_TBTC_MINT; + } + + if (tbtcMint === undefined) { + tbtcMint = tbtc.getTokenPDA(); + } + + if (tokenBridgeConfig === undefined) { + tokenBridgeConfig = tokenBridge.deriveTokenBridgeConfigKey( + TOKEN_BRIDGE_PROGRAM_ID + ); + } + + if (tokenBridgeWrappedAsset === undefined) { + tokenBridgeWrappedAsset = WRAPPED_TBTC_ASSET; + } + + if (tokenBridgeTransferAuthority === undefined) { + tokenBridgeTransferAuthority = tokenBridge.deriveAuthoritySignerKey( + TOKEN_BRIDGE_PROGRAM_ID + ); + } + + if (coreBridgeData === undefined) { + coreBridgeData = CORE_BRIDGE_DATA; + } + + if (coreMessage === undefined) { + const sequence = await getTokenBridgeSequence(); + coreMessage = getCoreMessagePDA(sequence); + } + + if (tokenBridgeCoreEmitter === undefined) { + tokenBridgeCoreEmitter = getTokenBridgeCoreEmitter(); + } + + if (coreEmitterSequence === undefined) { + coreEmitterSequence = coreBridge.deriveEmitterSequenceKey( + tokenBridgeCoreEmitter, + CORE_BRIDGE_PROGRAM_ID + ); + } + + if (coreFeeCollector === undefined) { + coreFeeCollector = coreBridge.deriveFeeCollectorKey(CORE_BRIDGE_PROGRAM_ID); + } + + if (clock === undefined) { + clock = SYSVAR_CLOCK_PUBKEY; + } + + if (tokenBridgeSender === undefined) { + tokenBridgeSender = tokenBridge.deriveSenderAccountKey( + WORMHOLE_GATEWAY_PROGRAM_ID + ); + } + + if (rent === undefined) { + rent = SYSVAR_RENT_PUBKEY; + } + + if (tokenBridgeProgram === undefined) { + tokenBridgeProgram = TOKEN_BRIDGE_PROGRAM_ID; + } + + if (coreBridgeProgram === undefined) { + coreBridgeProgram = CORE_BRIDGE_PROGRAM_ID; + } + + return program.methods + .sendTbtcGateway(args) + .accounts({ + custodian, + gatewayInfo, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + senderToken, + sender, + tokenBridgeConfig, + tokenBridgeWrappedAsset, + tokenBridgeTransferAuthority, + coreBridgeData, + coreMessage, + tokenBridgeCoreEmitter, + coreEmitterSequence, + coreFeeCollector, + clock, + tokenBridgeSender, + rent, + tokenBridgeProgram, + coreBridgeProgram, + }) + .instruction(); +} + +// type SendTbtcWrappedContext = { +// custodian?: PublicKey; +// gatewayInfo?: PublicKey; +// wrappedTbtcToken?: PublicKey; +// wrappedTbtcMint?: PublicKey; +// tbtcMint?: PublicKey; +// senderToken: PublicKey; +// sender: PublicKey; +// tokenBridgeConfig?: PublicKey; +// tokenBridgeWrappedAsset?: PublicKey; +// tokenBridgeTransferAuthority?: PublicKey; +// coreBridgeData?: PublicKey; +// coreMessage: PublicKey; +// tokenBridgeCoreEmitter?: PublicKey; +// coreEmitterSequence?: PublicKey; +// coreFeeCollector?: PublicKey; +// clock?: PublicKey; +// tokenBridgeSender?: PublicKey; +// rent?: PublicKey; +// tokenBridgeProgram?: PublicKey; +// coreBridgeProgram?: PublicKey; +// }; + +// type SendTbtcWrappedArgs = { +// amount: BN; +// recipientChain: number; +// recipient: number[]; +// nonce: number; +// }; + +// export async function sendTbtcGatewayIx( +// accounts: SendTbtcGatewayContext, +// args: SendTbtcGatewayArgs +// ): Promise { +// const program = workspace.WormholeGateway as Program; +// let { +// custodian, +// gatewayInfo, +// wrappedTbtcToken, +// wrappedTbtcMint, +// tbtcMint, +// senderToken, +// sender, +// tokenBridgeConfig, +// tokenBridgeWrappedAsset, +// tokenBridgeTransferAuthority, +// coreBridgeData, +// coreMessage, +// tokenBridgeCoreEmitter, +// coreEmitterSequence, +// coreFeeCollector, +// clock, +// tokenBridgeSender, +// rent, +// tokenBridgeProgram, +// coreBridgeProgram, +// } = accounts; + +// if (custodian === undefined) { +// custodian = getCustodianPDA(); +// } + +// if (gatewayInfo === undefined) { +// gatewayInfo = getGatewayInfoPDA(args.recipientChain); +// } + +// if (wrappedTbtcToken === undefined) { +// wrappedTbtcToken = getWrappedTbtcTokenPDA(); +// } + +// if (wrappedTbtcMint === undefined) { +// wrappedTbtcMint = WRAPPED_TBTC_MINT; +// } + +// if (tbtcMint === undefined) { +// tbtcMint = tbtc.getTokenPDA(); +// } + +// if (tokenBridgeConfig === undefined) { +// tokenBridgeConfig = tokenBridge.deriveTokenBridgeConfigKey( +// TOKEN_BRIDGE_PROGRAM_ID +// ); +// } + +// if (tokenBridgeWrappedAsset === undefined) { +// tokenBridgeWrappedAsset = WRAPPED_TBTC_ASSET; +// } + +// if (tokenBridgeTransferAuthority === undefined) { +// tokenBridgeTransferAuthority = tokenBridge.deriveAuthoritySignerKey( +// TOKEN_BRIDGE_PROGRAM_ID +// ); +// } + +// if (coreBridgeData === undefined) { +// coreBridgeData = CORE_BRIDGE_DATA; +// } + +// if (tokenBridgeCoreEmitter === undefined) { +// [tokenBridgeCoreEmitter] = PublicKey.findProgramAddressSync( +// [Buffer.from("emitter")], +// TOKEN_BRIDGE_PROGRAM_ID +// ); +// } + +// if (coreEmitterSequence === undefined) { +// coreEmitterSequence = coreBridge.deriveEmitterSequenceKey( +// tokenBridgeCoreEmitter, +// CORE_BRIDGE_PROGRAM_ID +// ); +// } + +// if (coreFeeCollector === undefined) { +// coreFeeCollector = coreBridge.deriveFeeCollectorKey(CORE_BRIDGE_PROGRAM_ID); +// } + +// if (clock === undefined) { +// clock = SYSVAR_CLOCK_PUBKEY; +// } + +// if (tokenBridgeSender === undefined) { +// tokenBridgeSender = tokenBridge.deriveSenderAccountKey( +// WORMHOLE_GATEWAY_PROGRAM_ID +// ); +// } + +// if (rent === undefined) { +// rent = SYSVAR_RENT_PUBKEY; +// } + +// if (tokenBridgeProgram === undefined) { +// tokenBridgeProgram = TOKEN_BRIDGE_PROGRAM_ID; +// } + +// if (coreBridgeProgram === undefined) { +// coreBridgeProgram = CORE_BRIDGE_PROGRAM_ID; +// } + +// return program.methods +// .sendTbtcGateway(args) +// .accounts({ +// custodian, +// gatewayInfo, +// wrappedTbtcToken, +// wrappedTbtcMint, +// tbtcMint, +// senderToken, +// sender, +// tokenBridgeConfig, +// tokenBridgeWrappedAsset, +// tokenBridgeTransferAuthority, +// coreBridgeData, +// coreMessage, +// tokenBridgeCoreEmitter, +// coreEmitterSequence, +// coreFeeCollector, +// clock, +// tokenBridgeSender, +// rent, +// tokenBridgeProgram, +// coreBridgeProgram, +// }) +// .instruction(); +// }