From 5d9ce7227e76af7712d9fcfc7e9eac5bf89abda0 Mon Sep 17 00:00:00 2001 From: A5 Pickle Date: Mon, 21 Nov 2022 23:22:41 +0000 Subject: [PATCH] sdk/js: fix transfer and redeem for native SOL sdk/js: add optional argument for MockTokenBridge testing: add tests --- sdk/js/src/mock/tokenBridge.ts | 18 ++- sdk/js/src/token_bridge/redeem.ts | 13 +- sdk/js/src/token_bridge/transfer.ts | 33 ++--- .../sdk-tests/2_token_bridge.ts | 123 +++++++++++++++++- 4 files changed, 154 insertions(+), 33 deletions(-) diff --git a/sdk/js/src/mock/tokenBridge.ts b/sdk/js/src/mock/tokenBridge.ts index 3a335aad1..c06023244 100644 --- a/sdk/js/src/mock/tokenBridge.ts +++ b/sdk/js/src/mock/tokenBridge.ts @@ -6,8 +6,13 @@ import { MockEmitter } from "./wormhole"; export class MockTokenBridge extends MockEmitter { consistencyLevel: number; - constructor(emitterAddress: string, chain: number, consistencyLevel: number) { - super(emitterAddress, chain); + constructor( + emitterAddress: string, + chain: number, + consistencyLevel: number, + startSequence?: number + ) { + super(emitterAddress, chain, startSequence); this.consistencyLevel = consistencyLevel; } @@ -156,9 +161,14 @@ export class MockTokenBridge extends MockEmitter { } export class MockEthereumTokenBridge extends MockTokenBridge { - constructor(emitterAddress: string) { + constructor(emitterAddress: string, startSequence?: number) { const chain = 2; - super(tryNativeToHexString(emitterAddress, chain as ChainId), chain, 15); + super( + tryNativeToHexString(emitterAddress, chain as ChainId), + chain, + 15, + startSequence + ); } publishAttestMeta( diff --git a/sdk/js/src/token_bridge/redeem.ts b/sdk/js/src/token_bridge/redeem.ts index 603cd3f7d..89b9c447a 100644 --- a/sdk/js/src/token_bridge/redeem.ts +++ b/sdk/js/src/token_bridge/redeem.ts @@ -1,9 +1,9 @@ import { - AccountLayout, + ACCOUNT_SIZE, createCloseAccountInstruction, createInitializeAccountInstruction, createTransferInstruction, - getMinimumBalanceForRentExemptMint, + getMinimumBalanceForRentExemptAccount, getMint, NATIVE_MINT, TOKEN_PROGRAM_ID, @@ -135,7 +135,10 @@ export async function redeemAndUnwrapOnSolana( (info) => parsed.amount * BigInt(Math.pow(10, info.decimals - MAX_VAA_DECIMALS)) ); - const rentBalance = await getMinimumBalanceForRentExemptMint(connection); + const rentBalance = await getMinimumBalanceForRentExemptAccount( + connection, + commitment + ); if (Buffer.compare(parsed.tokenAddress, NATIVE_MINT.toBuffer()) != 0) { return Promise.reject("tokenAddress != NATIVE_MINT"); } @@ -154,12 +157,12 @@ export async function redeemAndUnwrapOnSolana( fromPubkey: payerPublicKey, newAccountPubkey: ancillaryKeypair.publicKey, lamports: rentBalance, //spl token accounts need rent exemption - space: AccountLayout.span, + space: ACCOUNT_SIZE, programId: TOKEN_PROGRAM_ID, }); //Initialize the account as a WSOL account, with the original payerAddress as owner - const initAccountIx = await createInitializeAccountInstruction( + const initAccountIx = createInitializeAccountInstruction( ancillaryKeypair.publicKey, NATIVE_MINT, payerPublicKey diff --git a/sdk/js/src/token_bridge/transfer.ts b/sdk/js/src/token_bridge/transfer.ts index 6a22ca233..bf77ec6ab 100644 --- a/sdk/js/src/token_bridge/transfer.ts +++ b/sdk/js/src/token_bridge/transfer.ts @@ -1,8 +1,8 @@ import { - AccountLayout, + ACCOUNT_SIZE, createCloseAccountInstruction, createInitializeAccountInstruction, - getMinimumBalanceForRentExemptMint, + getMinimumBalanceForRentExemptAccount, NATIVE_MINT, TOKEN_PROGRAM_ID, } from "@solana/spl-token"; @@ -43,7 +43,6 @@ import { Bridge__factory, TokenImplementation__factory, } from "../ethers-contracts"; -import { createBridgeFeeTransferInstruction } from "../solana"; import { createApproveAuthoritySignerInstruction, createTransferNativeInstruction, @@ -452,8 +451,10 @@ export async function transferNativeSol( payload: Uint8Array | Buffer | null = null, commitment?: Commitment ) { - const rentBalance = await getMinimumBalanceForRentExemptMint(connection); - const mintPublicKey = NATIVE_MINT; + const rentBalance = await getMinimumBalanceForRentExemptAccount( + connection, + commitment + ); const payerPublicKey = new PublicKey(payerAddress); const ancillaryKeypair = Keypair.generate(); @@ -462,30 +463,24 @@ export async function transferNativeSol( fromPubkey: payerPublicKey, newAccountPubkey: ancillaryKeypair.publicKey, lamports: rentBalance, //spl token accounts need rent exemption - space: AccountLayout.span, + space: ACCOUNT_SIZE, programId: TOKEN_PROGRAM_ID, }); //Send in the amount of SOL which we want converted to wSOL const initialBalanceTransferIx = SystemProgram.transfer({ fromPubkey: payerPublicKey, - lamports: Number(amount), + lamports: amount, toPubkey: ancillaryKeypair.publicKey, }); //Initialize the account as a WSOL account, with the original payerAddress as owner - const initAccountIx = await createInitializeAccountInstruction( + const initAccountIx = createInitializeAccountInstruction( ancillaryKeypair.publicKey, - mintPublicKey, + NATIVE_MINT, payerPublicKey ); //Normal approve & transfer instructions, except that the wSOL is sent from the ancillary account. - const nonce = createNonce().readUInt32LE(0); - const transferIx = await createBridgeFeeTransferInstruction( - connection, - bridgeAddress, - payerAddress - ); const approvalIx = createApproveAuthoritySignerInstruction( tokenBridgeAddress, ancillaryKeypair.publicKey, @@ -494,6 +489,7 @@ export async function transferNativeSol( ); const message = Keypair.generate(); + const nonce = createNonce().readUInt32LE(0); const tokenBridgeTransferIx = payload !== null ? createTransferNativeWithPayloadInstruction( @@ -538,7 +534,6 @@ export async function transferNativeSol( createAncillaryAccountIx, initialBalanceTransferIx, initAccountIx, - transferIx, approvalIx, tokenBridgeTransferIx, closeAccountIx @@ -571,11 +566,6 @@ export async function transferFromSolana( fromOwnerAddress = payerAddress; } const nonce = createNonce().readUInt32LE(0); - const transferIx = await createBridgeFeeTransferInstruction( - connection, - bridgeAddress, - payerAddress - ); const approvalIx = createApproveAuthoritySignerInstruction( tokenBridgeAddress, fromAddress, @@ -650,7 +640,6 @@ export async function transferFromSolana( coalesceChainId(targetChain) ); const transaction = new SolanaTransaction().add( - transferIx, approvalIx, tokenBridgeTransferIx ); diff --git a/testing/solana-test-validator/sdk-tests/2_token_bridge.ts b/testing/solana-test-validator/sdk-tests/2_token_bridge.ts index 134d2cac1..702be6415 100644 --- a/testing/solana-test-validator/sdk-tests/2_token_bridge.ts +++ b/testing/solana-test-validator/sdk-tests/2_token_bridge.ts @@ -52,6 +52,7 @@ import { getTransferNativeWithPayloadCpiAccounts, getTransferWrappedWithPayloadCpiAccounts, NodeWallet, + signSendAndConfirmTransaction, SplTokenMetadataProgram, } from "../../../sdk/js/src/solana"; import { @@ -86,6 +87,10 @@ import { getOriginalAssetSolana, } from "../../../sdk/js/src/token_bridge"; import { ChainId } from "../../../sdk/js/src"; +import { + transferNativeSol, + redeemAndUnwrapOnSolana, +} from "../../../sdk/js/src/token_bridge"; describe("Token Bridge", () => { const connection = new web3.Connection(LOCALHOST, "processed"); @@ -1886,10 +1891,11 @@ describe("Token Bridge", () => { }); }); - describe("Asset Queries", () => { + describe("SDK Methods", () => { // nft bridge on Ethereum const ethereumTokenBridge = new MockEthereumTokenBridge( - ETHEREUM_TOKEN_BRIDGE_ADDRESS + ETHEREUM_TOKEN_BRIDGE_ADDRESS, + 3 // startSequence ); describe("getOriginalAssetSolana", () => { @@ -2014,5 +2020,118 @@ describe("Token Bridge", () => { expect(isWrapped).is.true; }); }); + + describe("transferNativeSol", () => { + it("Send SOL To Ethereum", async () => { + const balanceBefore = await connection + .getBalance(wallet.key()) + .then((num) => BigInt(num)); + + const amount = 6969696969n; + const targetAddress = Buffer.alloc(32, "deadbeef", "hex"); + + const transferNativeSolTx = await transferNativeSol( + connection, + CORE_BRIDGE_ADDRESS, + TOKEN_BRIDGE_ADDRESS, + wallet.key(), + amount, + targetAddress, + "ethereum" + ) + .then((transaction) => + signSendAndConfirmTransaction( + connection, + wallet.key(), + wallet.signTransaction, + transaction + ) + ) + .then((response) => response.signature); + //console.log(`transferNativeSolTx: ${transferNativeSolTx}`); + + const balanceAfter = await connection + .getBalance(wallet.key()) + .then((num) => BigInt(num)); + + const transactionCost = 4601551n; + expect(balanceBefore - balanceAfter - transactionCost).to.equal(amount); + }); + }); + + describe("redeemAndUnwrapOnSolana", () => { + it("Receive SOL From Ethereum", async () => { + const balanceBefore = await connection + .getBalance(wallet.key()) + .then((num) => BigInt(num)); + + const tokenChain = 1; + const mintAta = await getOrCreateAssociatedTokenAccount( + connection, + wallet.signer(), + NATIVE_MINT, + wallet.key() + ).then((account) => account.address); + + const amount = 42042042n; + const recipientChain = 1; + const fee = 0n; + const nonce = 420; + const message = ethereumTokenBridge.publishTransferTokens( + NATIVE_MINT.toBuffer().toString("hex"), + tokenChain, + amount, + recipientChain, + mintAta.toBuffer().toString("hex"), + fee, + nonce + ); + + const signedVaa = guardians.addSignatures( + message, + [0, 1, 2, 3, 5, 7, 8, 9, 10, 12, 15, 16, 18] + ); + + const txSignatures = await postVaa( + connection, + wallet.signTransaction, + CORE_BRIDGE_ADDRESS, + wallet.key(), + signedVaa + ).then((results) => results.map((result) => result.signature)); + const postTx = txSignatures.pop()!; + for (const verifyTx of txSignatures) { + // console.log(`verifySignatures: ${verifyTx}`); + } + // console.log(`postVaa: ${postTx}`); + + const transferNativeSolTx = await redeemAndUnwrapOnSolana( + connection, + CORE_BRIDGE_ADDRESS, + TOKEN_BRIDGE_ADDRESS, + wallet.key(), + signedVaa + ) + .then((transaction) => + signSendAndConfirmTransaction( + connection, + wallet.key(), + wallet.signTransaction, + transaction + ) + ) + .then((response) => response.signature); + //console.log(`transferNativeSolTx: ${transferNativeSolTx}`); + + const balanceAfter = await connection + .getBalance(wallet.key()) + .then((num) => BigInt(num)); + + const transactionCost = 6821400n; + expect(balanceAfter - (balanceBefore - transactionCost)).to.equal( + amount * 10n + ); + }); + }); }); });