sdk/js: fix transfer and redeem for native SOL

sdk/js: add optional argument for MockTokenBridge

testing: add tests
This commit is contained in:
A5 Pickle 2022-11-21 23:22:41 +00:00 committed by Evan Gray
parent 68fbc1e77d
commit 5d9ce7227e
4 changed files with 154 additions and 33 deletions

View File

@ -6,8 +6,13 @@ import { MockEmitter } from "./wormhole";
export class MockTokenBridge extends MockEmitter { export class MockTokenBridge extends MockEmitter {
consistencyLevel: number; consistencyLevel: number;
constructor(emitterAddress: string, chain: number, consistencyLevel: number) { constructor(
super(emitterAddress, chain); emitterAddress: string,
chain: number,
consistencyLevel: number,
startSequence?: number
) {
super(emitterAddress, chain, startSequence);
this.consistencyLevel = consistencyLevel; this.consistencyLevel = consistencyLevel;
} }
@ -156,9 +161,14 @@ export class MockTokenBridge extends MockEmitter {
} }
export class MockEthereumTokenBridge extends MockTokenBridge { export class MockEthereumTokenBridge extends MockTokenBridge {
constructor(emitterAddress: string) { constructor(emitterAddress: string, startSequence?: number) {
const chain = 2; const chain = 2;
super(tryNativeToHexString(emitterAddress, chain as ChainId), chain, 15); super(
tryNativeToHexString(emitterAddress, chain as ChainId),
chain,
15,
startSequence
);
} }
publishAttestMeta( publishAttestMeta(

View File

@ -1,9 +1,9 @@
import { import {
AccountLayout, ACCOUNT_SIZE,
createCloseAccountInstruction, createCloseAccountInstruction,
createInitializeAccountInstruction, createInitializeAccountInstruction,
createTransferInstruction, createTransferInstruction,
getMinimumBalanceForRentExemptMint, getMinimumBalanceForRentExemptAccount,
getMint, getMint,
NATIVE_MINT, NATIVE_MINT,
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
@ -135,7 +135,10 @@ export async function redeemAndUnwrapOnSolana(
(info) => (info) =>
parsed.amount * BigInt(Math.pow(10, info.decimals - MAX_VAA_DECIMALS)) 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) { if (Buffer.compare(parsed.tokenAddress, NATIVE_MINT.toBuffer()) != 0) {
return Promise.reject("tokenAddress != NATIVE_MINT"); return Promise.reject("tokenAddress != NATIVE_MINT");
} }
@ -154,12 +157,12 @@ export async function redeemAndUnwrapOnSolana(
fromPubkey: payerPublicKey, fromPubkey: payerPublicKey,
newAccountPubkey: ancillaryKeypair.publicKey, newAccountPubkey: ancillaryKeypair.publicKey,
lamports: rentBalance, //spl token accounts need rent exemption lamports: rentBalance, //spl token accounts need rent exemption
space: AccountLayout.span, space: ACCOUNT_SIZE,
programId: TOKEN_PROGRAM_ID, programId: TOKEN_PROGRAM_ID,
}); });
//Initialize the account as a WSOL account, with the original payerAddress as owner //Initialize the account as a WSOL account, with the original payerAddress as owner
const initAccountIx = await createInitializeAccountInstruction( const initAccountIx = createInitializeAccountInstruction(
ancillaryKeypair.publicKey, ancillaryKeypair.publicKey,
NATIVE_MINT, NATIVE_MINT,
payerPublicKey payerPublicKey

View File

@ -1,8 +1,8 @@
import { import {
AccountLayout, ACCOUNT_SIZE,
createCloseAccountInstruction, createCloseAccountInstruction,
createInitializeAccountInstruction, createInitializeAccountInstruction,
getMinimumBalanceForRentExemptMint, getMinimumBalanceForRentExemptAccount,
NATIVE_MINT, NATIVE_MINT,
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
} from "@solana/spl-token"; } from "@solana/spl-token";
@ -43,7 +43,6 @@ import {
Bridge__factory, Bridge__factory,
TokenImplementation__factory, TokenImplementation__factory,
} from "../ethers-contracts"; } from "../ethers-contracts";
import { createBridgeFeeTransferInstruction } from "../solana";
import { import {
createApproveAuthoritySignerInstruction, createApproveAuthoritySignerInstruction,
createTransferNativeInstruction, createTransferNativeInstruction,
@ -452,8 +451,10 @@ export async function transferNativeSol(
payload: Uint8Array | Buffer | null = null, payload: Uint8Array | Buffer | null = null,
commitment?: Commitment commitment?: Commitment
) { ) {
const rentBalance = await getMinimumBalanceForRentExemptMint(connection); const rentBalance = await getMinimumBalanceForRentExemptAccount(
const mintPublicKey = NATIVE_MINT; connection,
commitment
);
const payerPublicKey = new PublicKey(payerAddress); const payerPublicKey = new PublicKey(payerAddress);
const ancillaryKeypair = Keypair.generate(); const ancillaryKeypair = Keypair.generate();
@ -462,30 +463,24 @@ export async function transferNativeSol(
fromPubkey: payerPublicKey, fromPubkey: payerPublicKey,
newAccountPubkey: ancillaryKeypair.publicKey, newAccountPubkey: ancillaryKeypair.publicKey,
lamports: rentBalance, //spl token accounts need rent exemption lamports: rentBalance, //spl token accounts need rent exemption
space: AccountLayout.span, space: ACCOUNT_SIZE,
programId: TOKEN_PROGRAM_ID, programId: TOKEN_PROGRAM_ID,
}); });
//Send in the amount of SOL which we want converted to wSOL //Send in the amount of SOL which we want converted to wSOL
const initialBalanceTransferIx = SystemProgram.transfer({ const initialBalanceTransferIx = SystemProgram.transfer({
fromPubkey: payerPublicKey, fromPubkey: payerPublicKey,
lamports: Number(amount), lamports: amount,
toPubkey: ancillaryKeypair.publicKey, toPubkey: ancillaryKeypair.publicKey,
}); });
//Initialize the account as a WSOL account, with the original payerAddress as owner //Initialize the account as a WSOL account, with the original payerAddress as owner
const initAccountIx = await createInitializeAccountInstruction( const initAccountIx = createInitializeAccountInstruction(
ancillaryKeypair.publicKey, ancillaryKeypair.publicKey,
mintPublicKey, NATIVE_MINT,
payerPublicKey payerPublicKey
); );
//Normal approve & transfer instructions, except that the wSOL is sent from the ancillary account. //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( const approvalIx = createApproveAuthoritySignerInstruction(
tokenBridgeAddress, tokenBridgeAddress,
ancillaryKeypair.publicKey, ancillaryKeypair.publicKey,
@ -494,6 +489,7 @@ export async function transferNativeSol(
); );
const message = Keypair.generate(); const message = Keypair.generate();
const nonce = createNonce().readUInt32LE(0);
const tokenBridgeTransferIx = const tokenBridgeTransferIx =
payload !== null payload !== null
? createTransferNativeWithPayloadInstruction( ? createTransferNativeWithPayloadInstruction(
@ -538,7 +534,6 @@ export async function transferNativeSol(
createAncillaryAccountIx, createAncillaryAccountIx,
initialBalanceTransferIx, initialBalanceTransferIx,
initAccountIx, initAccountIx,
transferIx,
approvalIx, approvalIx,
tokenBridgeTransferIx, tokenBridgeTransferIx,
closeAccountIx closeAccountIx
@ -571,11 +566,6 @@ export async function transferFromSolana(
fromOwnerAddress = payerAddress; fromOwnerAddress = payerAddress;
} }
const nonce = createNonce().readUInt32LE(0); const nonce = createNonce().readUInt32LE(0);
const transferIx = await createBridgeFeeTransferInstruction(
connection,
bridgeAddress,
payerAddress
);
const approvalIx = createApproveAuthoritySignerInstruction( const approvalIx = createApproveAuthoritySignerInstruction(
tokenBridgeAddress, tokenBridgeAddress,
fromAddress, fromAddress,
@ -650,7 +640,6 @@ export async function transferFromSolana(
coalesceChainId(targetChain) coalesceChainId(targetChain)
); );
const transaction = new SolanaTransaction().add( const transaction = new SolanaTransaction().add(
transferIx,
approvalIx, approvalIx,
tokenBridgeTransferIx tokenBridgeTransferIx
); );

View File

@ -52,6 +52,7 @@ import {
getTransferNativeWithPayloadCpiAccounts, getTransferNativeWithPayloadCpiAccounts,
getTransferWrappedWithPayloadCpiAccounts, getTransferWrappedWithPayloadCpiAccounts,
NodeWallet, NodeWallet,
signSendAndConfirmTransaction,
SplTokenMetadataProgram, SplTokenMetadataProgram,
} from "../../../sdk/js/src/solana"; } from "../../../sdk/js/src/solana";
import { import {
@ -86,6 +87,10 @@ import {
getOriginalAssetSolana, getOriginalAssetSolana,
} from "../../../sdk/js/src/token_bridge"; } from "../../../sdk/js/src/token_bridge";
import { ChainId } from "../../../sdk/js/src"; import { ChainId } from "../../../sdk/js/src";
import {
transferNativeSol,
redeemAndUnwrapOnSolana,
} from "../../../sdk/js/src/token_bridge";
describe("Token Bridge", () => { describe("Token Bridge", () => {
const connection = new web3.Connection(LOCALHOST, "processed"); const connection = new web3.Connection(LOCALHOST, "processed");
@ -1886,10 +1891,11 @@ describe("Token Bridge", () => {
}); });
}); });
describe("Asset Queries", () => { describe("SDK Methods", () => {
// nft bridge on Ethereum // nft bridge on Ethereum
const ethereumTokenBridge = new MockEthereumTokenBridge( const ethereumTokenBridge = new MockEthereumTokenBridge(
ETHEREUM_TOKEN_BRIDGE_ADDRESS ETHEREUM_TOKEN_BRIDGE_ADDRESS,
3 // startSequence
); );
describe("getOriginalAssetSolana", () => { describe("getOriginalAssetSolana", () => {
@ -2014,5 +2020,118 @@ describe("Token Bridge", () => {
expect(isWrapped).is.true; 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
);
});
});
}); });
}); });