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 {
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(

View File

@ -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

View File

@ -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
);

View File

@ -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
);
});
});
});
});