From 5b6dd035e1e735ca738c71e24a4c4c6c6743009f Mon Sep 17 00:00:00 2001 From: spacemandev Date: Sun, 25 Sep 2022 16:12:19 -0500 Subject: [PATCH] ITS ALIVE. solana purchase works --- .../solana/programs/solana/src/account.rs | 8 ++ .../solana/programs/solana/src/context.rs | 67 ++++++++++++++-- .../solana/programs/solana/src/error.rs | 3 + .../chains/solana/programs/solana/src/lib.rs | 76 ++++++++++++------- projects/xmint/handlers/solana.ts | 42 ++++++++-- 5 files changed, 157 insertions(+), 39 deletions(-) diff --git a/projects/xmint/chains/solana/programs/solana/src/account.rs b/projects/xmint/chains/solana/programs/solana/src/account.rs index 9aec679..a994e79 100644 --- a/projects/xmint/chains/solana/programs/solana/src/account.rs +++ b/projects/xmint/chains/solana/programs/solana/src/account.rs @@ -23,4 +23,12 @@ pub struct Redeemer {} #[account] pub struct MintInfo { pub mint: Pubkey +} + +#[account] +pub struct Receipt { + pub amt_to_mint: u64, + pub foreign_receipient: [u8; 32], + pub foreign_chain: u16, + pub claimed: bool } \ No newline at end of file diff --git a/projects/xmint/chains/solana/programs/solana/src/context.rs b/projects/xmint/chains/solana/programs/solana/src/context.rs index 3ac60fd..3664c14 100644 --- a/projects/xmint/chains/solana/programs/solana/src/context.rs +++ b/projects/xmint/chains/solana/programs/solana/src/context.rs @@ -6,7 +6,7 @@ use crate::wormhole::*; use crate::constant::*; use std::str::FromStr; use anchor_spl::token::ID as spl_id; - +use crate::*; #[derive(Accounts)] pub struct Initialize<'info>{ @@ -82,6 +82,7 @@ pub struct RegisterChain<'info> { pub emitter_acc: Account<'info, EmitterAddrAccount>, } + #[derive(Accounts)] pub struct SubmitForeignPurchase<'info> { #[account(mut)] @@ -93,6 +94,16 @@ pub struct SubmitForeignPurchase<'info> { bump, )] pub config: Account<'info, Config>, + #[account( + init, + seeds = [ + vaa_hash(core_bridge_vaa.clone()).as_slice() + ], + bump, + payer = payer, + space = 64 + )] + pub receipt: Account<'info, Receipt>, // Fetch the VAA /// CHECK: Checked in lib.rs because it requires some fancy hashing @@ -187,20 +198,37 @@ pub struct SubmitForeignPurchase<'info> { )] pub mint_authority_wrapped: AccountInfo<'info>, pub rent_account: Sysvar<'info, Rent>, - /// CHECK: Make sure this is the right token bridge account + /// CHECK: Make sure this is the right core bridge account #[account( - constraint = token_bridge_program.key() == Pubkey::from_str(TOKEN_BRIDGE_ADDRESS).unwrap() + constraint = core_bridge.key() == Pubkey::from_str(CORE_BRIDGE_ADDRESS).unwrap() )] - pub token_bridge_program: AccountInfo<'info>, + pub core_bridge: AccountInfo<'info>, /// CHECK: SPL Program should be actual SPL Program #[account( constraint = spl_program.key() == spl_id )] pub spl_program: AccountInfo<'info>, + /// CHECK: Make sure this is the right token bridge account + #[account( + constraint = token_bridge.key() == Pubkey::from_str(TOKEN_BRIDGE_ADDRESS).unwrap() + )] + pub token_bridge: AccountInfo<'info> +} - - +#[derive(Accounts)] +pub struct ClaimForeignPurchase<'info> { + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, + #[account( + mut, + seeds = [b"config"], + bump, + )] + pub config: Account<'info, Config>, + #[account(mut)] + pub receipt: Account<'info, Receipt>, // Mint SOL#T SPL tokens to Contract PDA @@ -209,6 +237,7 @@ pub struct SubmitForeignPurchase<'info> { #[account( seeds=[b"mint_authority"], bump, + mut )] pub xmint_authority: Account<'info, MintInfo>, /// CHECK: TODO: Check if owned by SPL program @@ -219,7 +248,7 @@ pub struct SubmitForeignPurchase<'info> { // P1 Portal Transfer to Recepient #[account( seeds = [ - b"mint", + xmint_token_mint.key().to_bytes().as_slice(), ], bump, seeds::program = Pubkey::from_str(TOKEN_BRIDGE_ADDRESS).unwrap(), @@ -255,6 +284,7 @@ pub struct SubmitForeignPurchase<'info> { )] /// CHECK: The seeds constraint should check validity pub core_bridge_config: AccountInfo<'info>, + #[account(mut)] pub xmint_transfer_msg_key: Signer<'info>, #[account( seeds=[ @@ -292,4 +322,27 @@ pub struct SubmitForeignPurchase<'info> { constraint = core_bridge.key() == Pubkey::from_str(CORE_BRIDGE_ADDRESS).unwrap() )] pub core_bridge: AccountInfo<'info>, + + + + /// CHECK: SPL Program should be actual SPL Program + #[account( + constraint = spl_program.key() == spl_id + )] + pub spl_program: AccountInfo<'info>, + #[account( + mut, + seeds = [b"config"], + bump, + seeds::program = Pubkey::from_str(TOKEN_BRIDGE_ADDRESS).unwrap() + )] + /// CHECK: Token Bridge Config + pub token_bridge_config: AccountInfo<'info>, + pub rent_account: Sysvar<'info, Rent>, + + /// CHECK: Make sure this is the right token bridge account + #[account( + constraint = token_bridge.key() == Pubkey::from_str(TOKEN_BRIDGE_ADDRESS).unwrap() + )] + pub token_bridge: AccountInfo<'info> } \ No newline at end of file diff --git a/projects/xmint/chains/solana/programs/solana/src/error.rs b/projects/xmint/chains/solana/programs/solana/src/error.rs index 59a9529..52e1209 100644 --- a/projects/xmint/chains/solana/programs/solana/src/error.rs +++ b/projects/xmint/chains/solana/programs/solana/src/error.rs @@ -9,4 +9,7 @@ pub enum XmintError { #[msg("Posted VAA Emitter Chain ID or Address Mismatch")] VAAEmitterMismatch, + + #[msg("Receipt already claimed!")] + ReceiptClaimed, } \ No newline at end of file diff --git a/projects/xmint/chains/solana/programs/solana/src/lib.rs b/projects/xmint/chains/solana/programs/solana/src/lib.rs index 5d65e43..9a6d5d8 100644 --- a/projects/xmint/chains/solana/programs/solana/src/lib.rs +++ b/projects/xmint/chains/solana/programs/solana/src/lib.rs @@ -4,8 +4,7 @@ use mpl_token_metadata::ID as metadata_program_id; use anchor_lang::solana_program::program::invoke_signed; use anchor_lang::solana_program::instruction::{Instruction, AccountMeta}; use anchor_lang::solana_program::{sysvar::rent, system_program}; -use anchor_spl::token::{ID as spl_id, mint_to}; -use anchor_spl::token::MintTo; +use anchor_spl::token::{ID as spl_id, mint_to, approve, MintTo, Approve}; use sha3::Digest; use byteorder::{ @@ -97,17 +96,11 @@ pub mod solana { Ok(()) } - - //Submit Foreign Purchase - pub fn submit_foreign_purchase(ctx:Context) -> Result <()> { + pub fn submit_foreign_purchase(ctx:Context) -> Result<()> { // Fetch the VAA //Hash a VAA Extract and derive a VAA Key let vaa = PostedMessageData::try_from_slice(&ctx.accounts.core_bridge_vaa.data.borrow())?.0; - let serialized_vaa = serialize_vaa(&vaa); - - let mut h = sha3::Keccak256::default(); - h.write(serialized_vaa.as_slice()).unwrap(); - let vaa_hash: [u8; 32] = h.finalize().into(); + let vaa_hash: [u8; 32] = vaa_hash(ctx.accounts.core_bridge_vaa.clone()); let (vaa_key, _) = Pubkey::find_program_address(&[ b"PostedVAA", @@ -129,7 +122,6 @@ pub mod solana { - // Complete Transfer of P3 on Portal let complete_wrapped_ix = Instruction { program_id: Pubkey::from_str(TOKEN_BRIDGE_ADDRESS).unwrap(), @@ -147,7 +139,7 @@ pub mod solana { AccountMeta::new_readonly(ctx.accounts.mint_authority_wrapped.key(), false), AccountMeta::new_readonly(rent::id(), false), AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new_readonly(ctx.accounts.token_bridge_program.key(), false), + AccountMeta::new_readonly(ctx.accounts.core_bridge.key(), false), AccountMeta::new_readonly(spl_id, false), ], data: ( @@ -170,7 +162,7 @@ pub mod solana { ctx.accounts.mint_authority_wrapped.to_account_info(), ctx.accounts.rent_account.to_account_info(), ctx.accounts.system_program.to_account_info(), - ctx.accounts.token_bridge_program.to_account_info(), + ctx.accounts.core_bridge.to_account_info(), ctx.accounts.spl_program.to_account_info() ]; @@ -185,18 +177,24 @@ pub mod solana { &[&complete_p3_seeds[..]] )?; - - - - // Mint SOL#T SPL tokens to Contract PDA - //// Figure out how many tokens to mint based on p3 payload let payload: PayloadTransferWithPayload = PayloadTransferWithPayload::deserialize(&mut vaa.payload.as_slice())?; - let amt_to_mint:u64 = payload.amount.as_u64() * 100; + ctx.accounts.receipt.amt_to_mint = payload.amount.as_u64() * 100; + ctx.accounts.receipt.foreign_chain = vaa.emitter_chain; + ctx.accounts.receipt.foreign_receipient = payload.payload.as_slice().try_into().unwrap(); + ctx.accounts.receipt.claimed = false; + + Ok(()) + } + + pub fn claim_foreign_purchase(ctx:Context) -> Result<()> { + if ctx.accounts.receipt.claimed { + return err!(XmintError::ReceiptClaimed); + } let mint_seeds:&[&[u8]] = &[ b"mint_authority", - &[*ctx.bumps.get("mint_authority").unwrap()] + &[*ctx.bumps.get("xmint_authority").unwrap()] ]; mint_to( @@ -210,7 +208,21 @@ pub mod solana { program: ctx.accounts.spl_program.to_account_info(), signer_seeds: &[&mint_seeds[..]] }, - amt_to_mint + ctx.accounts.receipt.amt_to_mint + )?; + + // Delgate transfer authority to Token Bridge for the newly minted tokens + approve( + CpiContext::new_with_signer( + ctx.accounts.spl_program.to_account_info(), + Approve { + to: ctx.accounts.xmint_ata_account.to_account_info(), + delegate: ctx.accounts.token_bridge_authority_signer.to_account_info(), + authority: ctx.accounts.xmint_authority.to_account_info() + }, + &[&mint_seeds[..]] + ), + ctx.accounts.receipt.amt_to_mint )?; // Transfer tokens from Contract PDA to P1 on Portal @@ -242,10 +254,10 @@ pub mod solana { crate::portal::Instruction::TransferNative, TransferNativeData { nonce: ctx.accounts.config.nonce, - amount: amt_to_mint, + amount: ctx.accounts.receipt.amt_to_mint, fee: 0, - target_address: payload.payload.as_slice().try_into().unwrap(), - target_chain: vaa.emitter_chain + target_address: ctx.accounts.receipt.foreign_receipient, + target_chain: ctx.accounts.receipt.foreign_chain } ).try_to_vec()? }; @@ -276,7 +288,7 @@ pub mod solana { // Signer Seeds let xmint_authority_seeds:&[&[u8]] = &[ b"mint_authority", - &[*ctx.bumps.get("mint_authority").unwrap()] + &[*ctx.bumps.get("xmint_authority").unwrap()] ]; invoke_signed( @@ -286,7 +298,8 @@ pub mod solana { )?; ctx.accounts.config.nonce += 1; - Ok(()) + ctx.accounts.receipt.claimed = true; + Ok(()) } } @@ -303,4 +316,13 @@ pub fn serialize_vaa(vaa: &MessageData) -> Vec { v.write_u8(vaa.consistency_level).unwrap(); v.write(&vaa.payload).unwrap(); v.into_inner() -} \ No newline at end of file +} + +pub fn vaa_hash(vaa: AccountInfo) -> [u8; 32] { + let vaa = PostedMessageData::try_from_slice(&vaa.data.borrow()).unwrap().0; + let serialized_vaa = serialize_vaa(&vaa); + let mut h = sha3::Keccak256::default(); + h.write(serialized_vaa.as_slice()).unwrap(); + let vaa_hash: [u8; 32] = h.finalize().into(); + return vaa_hash; +} diff --git a/projects/xmint/handlers/solana.ts b/projects/xmint/handlers/solana.ts index 715e093..40cad20 100644 --- a/projects/xmint/handlers/solana.ts +++ b/projects/xmint/handlers/solana.ts @@ -488,7 +488,7 @@ export async function submitForeignPurchase(src:string, target:string, vaa:strin ); // P1 Portal Transfer back to Recepient Accounts - const tokenBridgeMintCustody = findProgramAddressSync([Buffer.from("mint")], tokenBridgePubKey)[0]; + const tokenBridgeMintCustody = findProgramAddressSync([xmintTokenMint.toBuffer()], tokenBridgePubKey)[0]; const tokenBridgeAuthoritySigner = findProgramAddressSync([Buffer.from("authority_signer")], tokenBridgePubKey)[0]; const tokenBridgeCustodySigner = findProgramAddressSync([Buffer.from("custody_signer")], tokenBridgePubKey)[0]; const coreBridgeConfig = findProgramAddressSync([Buffer.from("Bridge")], coreBridgePubKey)[0]; @@ -500,7 +500,11 @@ export async function submitForeignPurchase(src:string, target:string, vaa:strin ], coreBridgePubKey)[0]; const coreBridgeFeeCollector = findProgramAddressSync([Buffer.from("fee_collector")], coreBridgePubKey)[0]; - const tx = await xmint.methods + // need to split the following into two because Solana account limit + const receiptAcc = findProgramAddressSync([ Buffer.from(vaaHash, "hex") ], xmint.programId)[0]; + + // Submit Foreign Purchase + const submitTx = await xmint.methods .submitForeignPurchase() .accounts({ payer: srcKey.publicKey, @@ -509,6 +513,7 @@ export async function submitForeignPurchase(src:string, target:string, vaa:strin coreBridgeVaa: coreBridgeVaaKey, processedVaa: processedVaaKey, emitterAcc: emitterAddressAcc, + receipt: receiptAcc, tokenBridgeConfig: tokenBridgeConfigAcc, tokenBridgeClaimKey: tokenBridgeClaimAcc, @@ -519,7 +524,32 @@ export async function submitForeignPurchase(src:string, target:string, vaa:strin wethMintWrappedMeta: wethWrappedMeta, mintAuthorityWrapped: mintAuthorityWrapped, rentAccount: anchor.web3.SYSVAR_RENT_PUBKEY, - tokenBridgeProgram: tokenBridgePubKey, + coreBridge: coreBridgePubKey, + splProgram: TOKEN_PROGRAM_ID, + + tokenBridge: tokenBridgePubKey + }) + .preInstructions([ + anchor.web3.ComputeBudgetProgram.requestUnits({ + units: 1400000, + additionalFee: 0, + }) + ]) + .rpc(); + + await new Promise((r) => setTimeout(r, 16000)); //wait for accounts to finialize + + // Claim Foreign Purchase + const claimTx = await xmint.methods + .claimForeignPurchase() + .accounts({ + payer: srcKey.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + config: configAcc, + receipt: receiptAcc, + + rentAccount: anchor.web3.SYSVAR_RENT_PUBKEY, + tokenBridgeConfig: tokenBridgeConfigAcc, splProgram: TOKEN_PROGRAM_ID, xmintTokenMint: xmintTokenMint, @@ -535,7 +565,9 @@ export async function submitForeignPurchase(src:string, target:string, vaa:strin tokenBridgeSequenceKey: tokenBridgeSequenceKey, coreBridgeFeeCollector: coreBridgeFeeCollector, clock: anchor.web3.SYSVAR_CLOCK_PUBKEY, - coreBridge: coreBridgePubKey + coreBridge: coreBridgePubKey, + + tokenBridge: tokenBridgePubKey }) .preInstructions([ anchor.web3.ComputeBudgetProgram.requestUnits({ @@ -546,7 +578,7 @@ export async function submitForeignPurchase(src:string, target:string, vaa:strin .signers([transferMsgKey]) .rpc(); - const sfpVaa = await fetchVaa(src, tx, true); + const sfpVaa = await fetchVaa(src, claimTx, true); console.log(sfpVaa); return sfpVaa; }