sdk/js: parallelize tests
This commit is contained in:
parent
c5a7d3e517
commit
577d9b35f8
|
@ -36,7 +36,7 @@ const interateToStandardTransactionCount = async () => {
|
|||
return Promise.resolve();
|
||||
};
|
||||
|
||||
module.exports = async function (callback) {
|
||||
module.exports = async function(callback) {
|
||||
try {
|
||||
const accounts = await web3.eth.getAccounts();
|
||||
|
||||
|
@ -89,14 +89,16 @@ module.exports = async function (callback) {
|
|||
|
||||
console.log("WETH token deployed at: " + wethAddress);
|
||||
|
||||
await token.methods.mint(accounts[2], "1000000000000000000000").send({
|
||||
from: accounts[0],
|
||||
gas: 1000000,
|
||||
});
|
||||
for (let idx = 2; idx < 10; idx++) {
|
||||
await token.methods.mint(accounts[idx], "1000000000000000000000").send({
|
||||
from: accounts[0],
|
||||
gas: 1000000,
|
||||
});
|
||||
}
|
||||
|
||||
// devnet WETH token address should be deterministic
|
||||
if (wethAddress !== "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E") {
|
||||
throw new Error("unexpected WETH token address")
|
||||
throw new Error("unexpected WETH token address");
|
||||
}
|
||||
|
||||
callback();
|
||||
|
|
|
@ -7,10 +7,6 @@ const ci = !!process.env.CI;
|
|||
export const ETH_NODE_URL = ci ? "ws://eth-devnet:8545" : "ws://localhost:8545";
|
||||
export const ETH_PRIVATE_KEY =
|
||||
"0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1"; // account 1
|
||||
export const ETH_CORE_BRIDGE_ADDRESS =
|
||||
"0xC89Ce4735882C9F0f0FE26686c53074E09B0D550";
|
||||
export const ETH_NFT_BRIDGE_ADDRESS =
|
||||
"0x26b4afb60d6c903165150c6f0aa14f8016be4aec";
|
||||
export const SOLANA_HOST = ci
|
||||
? "http://solana-devnet:8899"
|
||||
: "http://localhost:8899";
|
||||
|
@ -20,10 +16,6 @@ export const SOLANA_PRIVATE_KEY = new Uint8Array([
|
|||
8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47,
|
||||
44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141,
|
||||
]);
|
||||
export const SOLANA_CORE_BRIDGE_ADDRESS =
|
||||
"Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
|
||||
export const SOLANA_NFT_BRIDGE_ADDRESS =
|
||||
"NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA";
|
||||
export const TERRA_NODE_URL = ci
|
||||
? "http://terra-terrad:1317"
|
||||
: "http://localhost:1317";
|
||||
|
@ -31,10 +23,6 @@ export const TERRA_CHAIN_ID = "localterra";
|
|||
export const TERRA_GAS_PRICES_URL = ci
|
||||
? "http://terra-fcd:3060/v1/txs/gas_prices"
|
||||
: "http://localhost:3060/v1/txs/gas_prices";
|
||||
export const TERRA_CORE_BRIDGE_ADDRESS =
|
||||
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
|
||||
export const TERRA_NFT_BRIDGE_ADDRESS =
|
||||
"terra1plju286nnfj3z54wgcggd4enwaa9fgf5kgrgzl";
|
||||
export const TERRA_PRIVATE_KEY =
|
||||
"quality vacuum heart guard buzz spike sight swarm shove special gym robust assume sudden deposit grid alcohol choice devote leader tilt noodle tide penalty";
|
||||
export const TEST_ERC721 = "0x5b9b42d6e4B2e4Bf8d42Eba32D46918e10899B66";
|
||||
|
|
|
@ -1,30 +1,25 @@
|
|||
import {
|
||||
Coins,
|
||||
LCDClient,
|
||||
MnemonicKey,
|
||||
Msg,
|
||||
MsgExecuteContract,
|
||||
Fee,
|
||||
TxInfo,
|
||||
WaitTxBroadcastResult,
|
||||
Wallet,
|
||||
isTxError,
|
||||
} from "@terra-money/terra.js";
|
||||
import axios from "axios";
|
||||
import Web3 from "web3";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
import { describe, expect, jest, test } from "@jest/globals";
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
jest,
|
||||
test,
|
||||
} from "@jest/globals";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { MsgInstantiateContract } from "@terra-money/terra.js";
|
||||
import { BigNumberish, ethers } from "ethers";
|
||||
import { BigNumber, BigNumberish, ethers } from "ethers";
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_TERRA,
|
||||
CONTRACTS,
|
||||
getEmitterAddressEth,
|
||||
getEmitterAddressTerra,
|
||||
parseSequenceFromLogEth,
|
||||
|
@ -38,12 +33,9 @@ import {
|
|||
import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
|
||||
import { importCoreWasm, setDefaultWasm } from "../../solana/wasm";
|
||||
import {
|
||||
ETH_CORE_BRIDGE_ADDRESS,
|
||||
ETH_NODE_URL,
|
||||
ETH_PRIVATE_KEY,
|
||||
ETH_NFT_BRIDGE_ADDRESS,
|
||||
TERRA_GAS_PRICES_URL,
|
||||
TERRA_NFT_BRIDGE_ADDRESS,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
TERRA_CW721_CODE_ID,
|
||||
TERRA_NODE_URL,
|
||||
|
@ -52,8 +44,6 @@ import {
|
|||
SOLANA_PRIVATE_KEY,
|
||||
TEST_SOLANA_TOKEN,
|
||||
SOLANA_HOST,
|
||||
SOLANA_CORE_BRIDGE_ADDRESS,
|
||||
SOLANA_NFT_BRIDGE_ADDRESS,
|
||||
} from "./consts";
|
||||
import {
|
||||
NFTImplementation,
|
||||
|
@ -68,6 +58,7 @@ import {
|
|||
} from "@solana/web3.js";
|
||||
import { postVaaSolanaWithRetry } from "../../solana";
|
||||
import { tryNativeToUint8Array } from "../../utils";
|
||||
import { arrayify } from "ethers/lib/utils";
|
||||
const ERC721 = require("@openzeppelin/contracts/build/contracts/ERC721PresetMinterPauserAutoId.json");
|
||||
|
||||
setDefaultWasm("node");
|
||||
|
@ -76,18 +67,6 @@ jest.setTimeout(60000);
|
|||
|
||||
type Address = string;
|
||||
|
||||
// terra setup
|
||||
const lcd = new LCDClient({
|
||||
URL: TERRA_NODE_URL,
|
||||
chainID: TERRA_CHAIN_ID,
|
||||
isClassic: true,
|
||||
});
|
||||
const terraWallet: Wallet = lcd.wallet(
|
||||
new MnemonicKey({
|
||||
mnemonic: TERRA_PRIVATE_KEY,
|
||||
})
|
||||
);
|
||||
|
||||
// ethereum setup
|
||||
const web3 = new Web3(ETH_NODE_URL);
|
||||
|
||||
|
@ -110,165 +89,73 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
describe("Integration Tests", () => {
|
||||
test("Send Ethereum ERC-721 to Terra and back", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
const erc721 = await deployNFTOnEth(
|
||||
"Not an APE 🐒",
|
||||
"APE🐒",
|
||||
"https://cloudflare-ipfs.com/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/",
|
||||
11 // mint ids 0..10 (inclusive)
|
||||
);
|
||||
const transaction = await _transferFromEth(
|
||||
erc721.address,
|
||||
10,
|
||||
terraWallet.key.accAddress,
|
||||
CHAIN_ID_TERRA
|
||||
);
|
||||
let signedVAA = await waitUntilEthTxObserved(transaction);
|
||||
(await expectReceivedOnTerra(signedVAA)).toBe(false);
|
||||
await _redeemOnTerra(signedVAA);
|
||||
(await expectReceivedOnTerra(signedVAA)).toBe(true);
|
||||
// TODO: figure out why this isn't working
|
||||
// test("Send Ethereum ERC-721 to Solana and back", (done) => {
|
||||
// (async () => {
|
||||
// try {
|
||||
// const erc721 = await deployNFTOnEth(
|
||||
// "Not an APE 🐒",
|
||||
// "APE🐒",
|
||||
// "https://cloudflare-ipfs.com/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/",
|
||||
// 11 // mint ids 0..10 (inclusive)
|
||||
// );
|
||||
// const sol_addr = await nft_bridge.getForeignAssetSol(
|
||||
// CONTRACTS.DEVNET.solana.nft_bridge,
|
||||
// CHAIN_ID_ETH,
|
||||
// tryNativeToUint8Array(erc721.address, CHAIN_ID_ETH),
|
||||
// arrayify(BigNumber.from("10"))
|
||||
// );
|
||||
// const fromAddress = await Token.getAssociatedTokenAddress(
|
||||
// ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
// TOKEN_PROGRAM_ID,
|
||||
// new PublicKey(sol_addr),
|
||||
// keypair.publicKey
|
||||
// );
|
||||
// const transaction = await _transferFromEth(
|
||||
// erc721.address,
|
||||
// 10,
|
||||
// fromAddress.toString(),
|
||||
// CHAIN_ID_SOLANA
|
||||
// );
|
||||
// let signedVAA = await waitUntilEthTxObserved(transaction);
|
||||
// await _redeemOnSolana(signedVAA);
|
||||
|
||||
// Check we have the wrapped NFT contract
|
||||
const terra_addr = await nft_bridge.getForeignAssetTerra(
|
||||
TERRA_NFT_BRIDGE_ADDRESS,
|
||||
lcd,
|
||||
CHAIN_ID_ETH,
|
||||
tryNativeToUint8Array(erc721.address, CHAIN_ID_ETH)
|
||||
);
|
||||
if (!terra_addr) {
|
||||
throw new Error("Terra address is null");
|
||||
}
|
||||
// let ownerEth = await erc721.ownerOf(10);
|
||||
// expect(ownerEth).not.toBe(signer.address);
|
||||
|
||||
// 10 => "10"
|
||||
const info: any = await lcd.wasm.contractQuery(terra_addr, {
|
||||
nft_info: { token_id: "10" },
|
||||
});
|
||||
expect(info.token_uri).toBe(
|
||||
"https://cloudflare-ipfs.com/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/10"
|
||||
);
|
||||
// // TODO: check wrapped balance
|
||||
|
||||
const ownerInfo: any = await lcd.wasm.contractQuery(terra_addr, {
|
||||
owner_of: { token_id: "10" },
|
||||
});
|
||||
expect(ownerInfo.owner).toBe(terraWallet.key.accAddress);
|
||||
// // Send back the NFT to ethereum
|
||||
// const transaction2 = await _transferFromSolana(
|
||||
// fromAddress,
|
||||
// sol_addr,
|
||||
// signer.address,
|
||||
// CHAIN_ID_ETH,
|
||||
// tryNativeToUint8Array(erc721.address, CHAIN_ID_ETH),
|
||||
// CHAIN_ID_ETH,
|
||||
// arrayify(BigNumber.from("10"))
|
||||
// );
|
||||
// signedVAA = await waitUntilSolanaTxObserved(transaction2);
|
||||
// (await expectReceivedOnEth(signedVAA)).toBe(false);
|
||||
// await _redeemOnEth(signedVAA);
|
||||
// (await expectReceivedOnEth(signedVAA)).toBe(true);
|
||||
|
||||
let ownerEth = await erc721.ownerOf(10);
|
||||
expect(ownerEth).not.toBe(signer.address);
|
||||
// // ensure that the transaction roundtrips back to the original native asset
|
||||
// ownerEth = await erc721.ownerOf(10);
|
||||
// expect(ownerEth).toBe(signer.address);
|
||||
|
||||
// Send back the NFT to ethereum
|
||||
const transaction2 = await _transferFromTerra(
|
||||
terra_addr,
|
||||
"10",
|
||||
signer.address,
|
||||
CHAIN_ID_ETH
|
||||
);
|
||||
signedVAA = await waitUntilTerraTxObserved(transaction2);
|
||||
(await expectReceivedOnEth(signedVAA)).toBe(false);
|
||||
await _redeemOnEth(signedVAA);
|
||||
(await expectReceivedOnEth(signedVAA)).toBe(true);
|
||||
// // TODO: the wrapped token should no longer exist
|
||||
|
||||
// ensure that the transaction roundtrips back to the original native asset
|
||||
ownerEth = await erc721.ownerOf(10);
|
||||
expect(ownerEth).toBe(signer.address);
|
||||
|
||||
// the wrapped token should no longer exist
|
||||
let error;
|
||||
try {
|
||||
await lcd.wasm.contractQuery(terra_addr, {
|
||||
owner_of: { token_id: "10" },
|
||||
});
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).not.toBeNull();
|
||||
|
||||
done();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
done(
|
||||
`An error occured while trying to transfer from Ethereum to Terra and back: ${e}`
|
||||
);
|
||||
}
|
||||
})();
|
||||
});
|
||||
test("Send Terra CW721 to Ethereum and back", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
const token_id = "first";
|
||||
const cw721 = await deployNFTOnTerra(
|
||||
"Integration Test NFT",
|
||||
"INT",
|
||||
"https://ixmfkhnh2o4keek2457f2v2iw47cugsx23eynlcfpstxihsay7nq.arweave.net/RdhVHafTuKIRWud-XVdItz4qGlfWyYasRXyndB5Ax9s/",
|
||||
token_id
|
||||
);
|
||||
// transfer NFT
|
||||
const transaction = await _transferFromTerra(
|
||||
cw721,
|
||||
token_id,
|
||||
signer.address,
|
||||
CHAIN_ID_ETH
|
||||
);
|
||||
let signedVAA = await waitUntilTerraTxObserved(transaction);
|
||||
(await expectReceivedOnEth(signedVAA)).toBe(false);
|
||||
await _redeemOnEth(signedVAA);
|
||||
(await expectReceivedOnEth(signedVAA)).toBe(true);
|
||||
|
||||
// Check we have the wrapped NFT contract
|
||||
const eth_addr = await nft_bridge.getForeignAssetEth(
|
||||
ETH_NFT_BRIDGE_ADDRESS,
|
||||
provider,
|
||||
CHAIN_ID_TERRA,
|
||||
tryNativeToUint8Array(cw721, CHAIN_ID_TERRA)
|
||||
);
|
||||
if (!eth_addr) {
|
||||
throw new Error("Ethereum address is null");
|
||||
}
|
||||
|
||||
const token = NFTImplementation__factory.connect(eth_addr, signer);
|
||||
// the id on eth will be the keccak256 hash of the terra id
|
||||
const eth_id = "0x" + sha3.keccak256(token_id);
|
||||
const owner = await token.ownerOf(eth_id);
|
||||
expect(owner).toBe(signer.address);
|
||||
|
||||
// send back the NFT to terra
|
||||
const transaction2 = await _transferFromEth(
|
||||
eth_addr,
|
||||
eth_id,
|
||||
terraWallet.key.accAddress,
|
||||
CHAIN_ID_TERRA
|
||||
);
|
||||
signedVAA = await waitUntilEthTxObserved(transaction2);
|
||||
(await expectReceivedOnTerra(signedVAA)).toBe(false);
|
||||
await _redeemOnTerra(signedVAA);
|
||||
(await expectReceivedOnTerra(signedVAA)).toBe(true);
|
||||
|
||||
const ownerInfo: any = await lcd.wasm.contractQuery(cw721, {
|
||||
owner_of: { token_id: token_id },
|
||||
});
|
||||
expect(ownerInfo.owner).toBe(terraWallet.key.accAddress);
|
||||
|
||||
// the wrapped token should no longer exist
|
||||
let error;
|
||||
try {
|
||||
await token.ownerOf(eth_id);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).not.toBeNull();
|
||||
expect(error.message).toContain("nonexistent token");
|
||||
|
||||
done();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
done(
|
||||
`An error occured while trying to transfer from Terra to Ethereum: ${e}`
|
||||
);
|
||||
}
|
||||
})();
|
||||
});
|
||||
test("Send Solana SPL to Terra to Ethereum to Solana", (done) => {
|
||||
// done();
|
||||
// } catch (e) {
|
||||
// console.error(e);
|
||||
// done(
|
||||
// `An error occured while trying to transfer from Ethereum to Solana and back: ${e}`
|
||||
// );
|
||||
// }
|
||||
// })();
|
||||
// });
|
||||
test("Send Solana SPL to Ethereum and back", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
const { parse_vaa } = await importCoreWasm();
|
||||
|
@ -280,11 +167,12 @@ describe("Integration Tests", () => {
|
|||
keypair.publicKey
|
||||
);
|
||||
|
||||
// send to terra
|
||||
// send to eth
|
||||
const transaction1 = await _transferFromSolana(
|
||||
fromAddress,
|
||||
terraWallet.key.accAddress,
|
||||
CHAIN_ID_TERRA
|
||||
TEST_SOLANA_TOKEN,
|
||||
signer.address,
|
||||
CHAIN_ID_ETH
|
||||
);
|
||||
let signedVAA = await waitUntilSolanaTxObserved(transaction1);
|
||||
|
||||
|
@ -293,28 +181,9 @@ describe("Integration Tests", () => {
|
|||
Buffer.from(new Uint8Array(parse_vaa(signedVAA).payload))
|
||||
);
|
||||
|
||||
await _redeemOnTerra(signedVAA);
|
||||
const terra_addr = await nft_bridge.getForeignAssetTerra(
|
||||
TERRA_NFT_BRIDGE_ADDRESS,
|
||||
lcd,
|
||||
CHAIN_ID_SOLANA,
|
||||
tryNativeToUint8Array(TEST_SOLANA_TOKEN, CHAIN_ID_SOLANA)
|
||||
);
|
||||
if (!terra_addr) {
|
||||
throw new Error("Terra address is null");
|
||||
}
|
||||
// send to ethereum
|
||||
const transaction2 = await _transferFromTerra(
|
||||
terra_addr,
|
||||
tokenId.toString(),
|
||||
signer.address,
|
||||
CHAIN_ID_ETH
|
||||
);
|
||||
signedVAA = await waitUntilTerraTxObserved(transaction2);
|
||||
|
||||
await _redeemOnEth(signedVAA);
|
||||
const eth_addr = await nft_bridge.getForeignAssetEth(
|
||||
ETH_NFT_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.ethereum.nft_bridge,
|
||||
provider,
|
||||
CHAIN_ID_SOLANA,
|
||||
tryNativeToUint8Array(TEST_SOLANA_TOKEN, CHAIN_ID_SOLANA)
|
||||
|
@ -350,39 +219,6 @@ describe("Integration Tests", () => {
|
|||
}
|
||||
})();
|
||||
});
|
||||
test("Handles invalid utf8", (done) => {
|
||||
(async () => {
|
||||
const erc721 = await deployNFTOnEth(
|
||||
// 31 bytes of valid characters + a 3 byte character
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaࠀ",
|
||||
"test",
|
||||
"https://foo.com",
|
||||
1
|
||||
);
|
||||
const transaction = await _transferFromEth(
|
||||
erc721.address,
|
||||
0,
|
||||
terraWallet.key.accAddress,
|
||||
CHAIN_ID_TERRA
|
||||
);
|
||||
let signedVAA = await waitUntilEthTxObserved(transaction);
|
||||
await _redeemOnTerra(signedVAA);
|
||||
const terra_addr = await nft_bridge.getForeignAssetTerra(
|
||||
TERRA_NFT_BRIDGE_ADDRESS,
|
||||
lcd,
|
||||
CHAIN_ID_ETH,
|
||||
tryNativeToUint8Array(erc721.address, CHAIN_ID_ETH)
|
||||
);
|
||||
if (!terra_addr) {
|
||||
throw new Error("Terra address is null");
|
||||
}
|
||||
const info: any = await lcd.wasm.contractQuery(terra_addr, {
|
||||
contract_info: {},
|
||||
});
|
||||
expect(info.name).toBe("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<61>");
|
||||
done();
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -420,91 +256,17 @@ async function deployNFTOnEth(
|
|||
return NFTImplementation__factory.connect(nft.options.address, signer);
|
||||
}
|
||||
|
||||
async function deployNFTOnTerra(
|
||||
name: string,
|
||||
symbol: string,
|
||||
uri: string,
|
||||
token_id: string
|
||||
): Promise<Address> {
|
||||
var address: any;
|
||||
await terraWallet
|
||||
.createAndSignTx({
|
||||
msgs: [
|
||||
new MsgInstantiateContract(
|
||||
terraWallet.key.accAddress,
|
||||
terraWallet.key.accAddress,
|
||||
TERRA_CW721_CODE_ID,
|
||||
{
|
||||
name,
|
||||
symbol,
|
||||
minter: terraWallet.key.accAddress,
|
||||
}
|
||||
),
|
||||
],
|
||||
memo: "",
|
||||
})
|
||||
.then((tx) => lcd.tx.broadcast(tx))
|
||||
.then((rs) => {
|
||||
const match = /"contract_address","value":"([^"]+)/gm.exec(rs.raw_log);
|
||||
if (match) {
|
||||
address = match[1];
|
||||
}
|
||||
});
|
||||
await mint_cw721(address, token_id, uri);
|
||||
return address;
|
||||
}
|
||||
|
||||
async function getGasPrices() {
|
||||
return axios.get(TERRA_GAS_PRICES_URL).then((result) => result.data);
|
||||
}
|
||||
|
||||
async function estimateTerraFee(
|
||||
gasPrices: Coins.Input,
|
||||
msgs: Msg[]
|
||||
): Promise<Fee> {
|
||||
const feeEstimate = await lcd.tx.estimateFee(
|
||||
[
|
||||
{
|
||||
sequenceNumber: await terraWallet.sequence(),
|
||||
publicKey: terraWallet.key.publicKey,
|
||||
},
|
||||
],
|
||||
{
|
||||
msgs,
|
||||
memo: "localhost",
|
||||
feeDenoms: ["uluna"],
|
||||
gasPrices,
|
||||
}
|
||||
);
|
||||
return feeEstimate;
|
||||
}
|
||||
|
||||
async function waitUntilTerraTxObserved(
|
||||
txresult: WaitTxBroadcastResult
|
||||
): Promise<Uint8Array> {
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const info = await waitForTerraExecution(txresult.txhash);
|
||||
const sequence = parseSequenceFromLogTerra(info);
|
||||
const emitterAddress = await getEmitterAddressTerra(TERRA_NFT_BRIDGE_ADDRESS);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
CHAIN_ID_TERRA,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
return signedVAA;
|
||||
}
|
||||
|
||||
async function waitUntilEthTxObserved(
|
||||
receipt: ethers.ContractReceipt
|
||||
): Promise<Uint8Array> {
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
let sequence = parseSequenceFromLogEth(receipt, ETH_CORE_BRIDGE_ADDRESS);
|
||||
let emitterAddress = getEmitterAddressEth(ETH_NFT_BRIDGE_ADDRESS);
|
||||
let sequence = parseSequenceFromLogEth(
|
||||
receipt,
|
||||
CONTRACTS.DEVNET.ethereum.core
|
||||
);
|
||||
let emitterAddress = getEmitterAddressEth(
|
||||
CONTRACTS.DEVNET.ethereum.nft_bridge
|
||||
);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
|
@ -524,7 +286,7 @@ async function waitUntilSolanaTxObserved(
|
|||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = parseSequenceFromLogSolana(response);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOLANA_NFT_BRIDGE_ADDRESS
|
||||
CONTRACTS.DEVNET.solana.nft_bridge
|
||||
);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
|
@ -539,67 +301,10 @@ async function waitUntilSolanaTxObserved(
|
|||
return signedVAA;
|
||||
}
|
||||
|
||||
async function mint_cw721(
|
||||
contract_address: string,
|
||||
token_id: string,
|
||||
token_uri: any
|
||||
): Promise<void> {
|
||||
await terraWallet
|
||||
.createAndSignTx({
|
||||
msgs: [
|
||||
new MsgExecuteContract(
|
||||
terraWallet.key.accAddress,
|
||||
contract_address,
|
||||
{
|
||||
mint: {
|
||||
token_id,
|
||||
owner: terraWallet.key.accAddress,
|
||||
token_uri: token_uri,
|
||||
},
|
||||
},
|
||||
{ uluna: 1000 }
|
||||
),
|
||||
],
|
||||
memo: "",
|
||||
fee: new Fee(2000000, {
|
||||
uluna: "100000",
|
||||
}),
|
||||
})
|
||||
.then((tx) => lcd.tx.broadcast(tx));
|
||||
}
|
||||
|
||||
async function waitForTerraExecution(txHash: string): Promise<TxInfo> {
|
||||
let info: TxInfo | undefined = undefined;
|
||||
while (!info) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
try {
|
||||
info = await lcd.tx.txInfo(txHash);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
if (isTxError(info)) {
|
||||
// error code
|
||||
throw new Error(`Tx ${txHash}: error code ${info.code}: ${info.raw_log}`);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
async function expectReceivedOnTerra(signedVAA: Uint8Array) {
|
||||
return expect(
|
||||
await nft_bridge.getIsTransferCompletedTerra(
|
||||
TERRA_NFT_BRIDGE_ADDRESS,
|
||||
signedVAA,
|
||||
lcd,
|
||||
TERRA_GAS_PRICES_URL
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function expectReceivedOnEth(signedVAA: Uint8Array) {
|
||||
return expect(
|
||||
await nft_bridge.getIsTransferCompletedEth(
|
||||
ETH_NFT_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.ethereum.nft_bridge,
|
||||
provider,
|
||||
signedVAA
|
||||
)
|
||||
|
@ -613,7 +318,7 @@ async function _transferFromEth(
|
|||
chain: ChainId
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
return nft_bridge.transferFromEth(
|
||||
ETH_NFT_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.ethereum.nft_bridge,
|
||||
signer,
|
||||
erc721,
|
||||
token_id,
|
||||
|
@ -622,45 +327,27 @@ async function _transferFromEth(
|
|||
);
|
||||
}
|
||||
|
||||
async function _transferFromTerra(
|
||||
terra_addr: string,
|
||||
token_id: string,
|
||||
address: string,
|
||||
chain: ChainId
|
||||
): Promise<WaitTxBroadcastResult> {
|
||||
const gasPrices = await getGasPrices();
|
||||
const msgs = await nft_bridge.transferFromTerra(
|
||||
terraWallet.key.accAddress,
|
||||
TERRA_NFT_BRIDGE_ADDRESS,
|
||||
terra_addr,
|
||||
token_id,
|
||||
chain,
|
||||
tryNativeToUint8Array(address, chain)
|
||||
);
|
||||
const tx = await terraWallet.createAndSignTx({
|
||||
msgs: msgs,
|
||||
memo: "test",
|
||||
feeDenoms: ["uluna"],
|
||||
gasPrices,
|
||||
fee: await estimateTerraFee(gasPrices, msgs),
|
||||
});
|
||||
return lcd.tx.broadcast(tx);
|
||||
}
|
||||
|
||||
async function _transferFromSolana(
|
||||
fromAddress: PublicKey,
|
||||
tokenAddress: string,
|
||||
targetAddress: string,
|
||||
chain: ChainId
|
||||
chain: ChainId,
|
||||
originAddress?: Uint8Array,
|
||||
originChain?: ChainId,
|
||||
originTokenId?: Uint8Array
|
||||
): Promise<TransactionResponse> {
|
||||
const transaction = await nft_bridge.transferFromSolana(
|
||||
connection,
|
||||
SOLANA_CORE_BRIDGE_ADDRESS,
|
||||
SOLANA_NFT_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
CONTRACTS.DEVNET.solana.nft_bridge,
|
||||
payerAddress,
|
||||
fromAddress.toString(),
|
||||
TEST_SOLANA_TOKEN,
|
||||
tokenAddress,
|
||||
tryNativeToUint8Array(targetAddress, chain),
|
||||
chain
|
||||
chain,
|
||||
originAddress,
|
||||
originChain,
|
||||
originTokenId
|
||||
);
|
||||
// sign, send, and confirm transaction
|
||||
transaction.partialSign(keypair);
|
||||
|
@ -676,26 +363,11 @@ async function _transferFromSolana(
|
|||
async function _redeemOnEth(
|
||||
signedVAA: Uint8Array
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
return nft_bridge.redeemOnEth(ETH_NFT_BRIDGE_ADDRESS, signer, signedVAA);
|
||||
}
|
||||
|
||||
async function _redeemOnTerra(
|
||||
signedVAA: Uint8Array
|
||||
): Promise<WaitTxBroadcastResult> {
|
||||
const msg = await nft_bridge.redeemOnTerra(
|
||||
TERRA_NFT_BRIDGE_ADDRESS,
|
||||
terraWallet.key.accAddress,
|
||||
return nft_bridge.redeemOnEth(
|
||||
CONTRACTS.DEVNET.ethereum.nft_bridge,
|
||||
signer,
|
||||
signedVAA
|
||||
);
|
||||
const gasPrices = await getGasPrices();
|
||||
const tx = await terraWallet.createAndSignTx({
|
||||
msgs: [msg],
|
||||
memo: "localhost",
|
||||
feeDenoms: ["uluna"],
|
||||
gasPrices,
|
||||
fee: await estimateTerraFee(gasPrices, [msg]),
|
||||
});
|
||||
return lcd.tx.broadcast(tx);
|
||||
}
|
||||
|
||||
async function _redeemOnSolana(signedVAA: Uint8Array) {
|
||||
|
@ -706,9 +378,24 @@ async function _redeemOnSolana(signedVAA: Uint8Array) {
|
|||
transaction.partialSign(keypair);
|
||||
return transaction;
|
||||
},
|
||||
SOLANA_CORE_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
payerAddress,
|
||||
Buffer.from(signedVAA),
|
||||
maxRetries
|
||||
);
|
||||
const transaction = await nft_bridge.redeemOnSolana(
|
||||
connection,
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
CONTRACTS.DEVNET.solana.nft_bridge,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
);
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(transaction.serialize());
|
||||
await connection.confirmTransaction(txid);
|
||||
const info = await connection.getTransaction(txid);
|
||||
if (!info) {
|
||||
throw new Error("An error occurred while fetching the transaction info");
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,13 +6,24 @@ const ci = !!process.env.CI;
|
|||
// see devnet.md
|
||||
export const ETH_NODE_URL = ci ? "ws://eth-devnet:8545" : "ws://localhost:8545";
|
||||
export const ETH_PRIVATE_KEY =
|
||||
"0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d";
|
||||
"0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"; // account 0
|
||||
// account 1 used by NFT tests
|
||||
export const ETH_PRIVATE_KEY2 =
|
||||
"0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c"; // account 2
|
||||
export const ETH_CORE_BRIDGE_ADDRESS =
|
||||
"0xC89Ce4735882C9F0f0FE26686c53074E09B0D550";
|
||||
export const ETH_TOKEN_BRIDGE_ADDRESS =
|
||||
"0x0290FB167208Af455bB137780163b7B7a9a10C16";
|
||||
export const ETH_PRIVATE_KEY3 =
|
||||
"0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913"; // account 3
|
||||
export const ETH_PRIVATE_KEY4 =
|
||||
"0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743"; // account 4
|
||||
export const ETH_PRIVATE_KEY5 =
|
||||
"0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd"; // account 5
|
||||
export const ETH_PRIVATE_KEY6 =
|
||||
"0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52"; // account 6
|
||||
export const ETH_PRIVATE_KEY7 =
|
||||
"0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3"; // account 7
|
||||
export const ETH_PRIVATE_KEY8 =
|
||||
"0x829e924fdf021ba3dbbc4225edfece9aca04b929d6e75613329ca6f1d31c0bb4"; // account 8
|
||||
export const ETH_PRIVATE_KEY9 =
|
||||
"0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773"; // account 9
|
||||
export const SOLANA_HOST = ci
|
||||
? "http://solana-devnet:8899"
|
||||
: "http://localhost:8899";
|
||||
|
@ -22,10 +33,6 @@ export const SOLANA_PRIVATE_KEY = new Uint8Array([
|
|||
8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47,
|
||||
44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141,
|
||||
]);
|
||||
export const SOLANA_CORE_BRIDGE_ADDRESS =
|
||||
"Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
|
||||
export const SOLANA_TOKEN_BRIDGE_ADDRESS =
|
||||
"B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE";
|
||||
export const TERRA_NODE_URL = ci
|
||||
? "http://terra-terrad:1317"
|
||||
: "http://localhost:1317";
|
||||
|
@ -36,12 +43,6 @@ export const TERRA_GAS_PRICES_URL = ci
|
|||
export const TERRA2_GAS_PRICES_URL = ci
|
||||
? "http://terra2-fcd:3060/v1/txs/gas_prices"
|
||||
: "http://localhost:3061/v1/txs/gas_prices";
|
||||
export const TERRA_CORE_BRIDGE_ADDRESS =
|
||||
"terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
|
||||
export const TERRA_TOKEN_BRIDGE_ADDRESS =
|
||||
"terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4";
|
||||
export const TERRA2_TOKEN_BRIDGE_ADDRESS =
|
||||
"terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6";
|
||||
export const TERRA_PRIVATE_KEY =
|
||||
"notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius";
|
||||
export const TERRA2_PRIVATE_KEY =
|
||||
|
|
|
@ -0,0 +1,524 @@
|
|||
import { formatUnits, parseUnits } from "@ethersproject/units";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
import { describe, expect, jest, test } from "@jest/globals";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import {
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TokenAccountsFilter,
|
||||
Transaction,
|
||||
} from "@solana/web3.js";
|
||||
import { ethers } from "ethers";
|
||||
import {
|
||||
approveEth,
|
||||
attestFromEth,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CONTRACTS,
|
||||
createWrappedOnSolana,
|
||||
getEmitterAddressEth,
|
||||
getForeignAssetSolana,
|
||||
getIsTransferCompletedSolana,
|
||||
hexToUint8Array,
|
||||
nativeToHexString,
|
||||
parseSequenceFromLogEth,
|
||||
postVaaSolana,
|
||||
redeemOnSolana,
|
||||
TokenImplementation__factory,
|
||||
transferFromEth,
|
||||
tryNativeToUint8Array,
|
||||
} from "../..";
|
||||
import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
|
||||
import { postVaaWithRetry } from "../../solana/postVaa";
|
||||
import { setDefaultWasm } from "../../solana/wasm";
|
||||
import {
|
||||
ETH_NODE_URL,
|
||||
ETH_PRIVATE_KEY,
|
||||
SOLANA_HOST,
|
||||
SOLANA_PRIVATE_KEY,
|
||||
TEST_ERC20,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "./consts";
|
||||
|
||||
setDefaultWasm("node");
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
async function transferFromEthToSolana(): Promise<string> {
|
||||
// create a keypair for Solana
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||
// determine destination address - an associated token account
|
||||
const solanaMintKey = new PublicKey(
|
||||
(await getForeignAssetSolana(
|
||||
connection,
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
CHAIN_ID_ETH,
|
||||
hexToUint8Array(nativeToHexString(TEST_ERC20, CHAIN_ID_ETH) || "")
|
||||
)) || ""
|
||||
);
|
||||
const recipient = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
solanaMintKey,
|
||||
keypair.publicKey
|
||||
);
|
||||
// create the associated token account if it doesn't exist
|
||||
const associatedAddressInfo = await connection.getAccountInfo(recipient);
|
||||
if (!associatedAddressInfo) {
|
||||
const transaction = new Transaction().add(
|
||||
await Token.createAssociatedTokenAccountInstruction(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
solanaMintKey,
|
||||
recipient,
|
||||
keypair.publicKey, // owner
|
||||
keypair.publicKey // payer
|
||||
)
|
||||
);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = keypair.publicKey;
|
||||
// sign, send, and confirm transaction
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(transaction.serialize());
|
||||
await connection.confirmTransaction(txid);
|
||||
}
|
||||
// create a signer for Eth
|
||||
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||
const amount = parseUnits("1", 18);
|
||||
// approve the bridge to spend tokens
|
||||
await approveEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
TEST_ERC20,
|
||||
signer,
|
||||
amount
|
||||
);
|
||||
// transfer tokens
|
||||
const receipt = await transferFromEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
TEST_ERC20,
|
||||
amount,
|
||||
CHAIN_ID_SOLANA,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(recipient.toString(), CHAIN_ID_SOLANA) || ""
|
||||
)
|
||||
);
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = await parseSequenceFromLogEth(
|
||||
receipt,
|
||||
CONTRACTS.DEVNET.ethereum.core
|
||||
);
|
||||
provider.destroy();
|
||||
return sequence;
|
||||
}
|
||||
|
||||
describe("Ethereum to Solana and Back", () => {
|
||||
test("Attest Ethereum ERC-20 to Solana", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
// create a signer for Eth
|
||||
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||
// attest the test token
|
||||
const receipt = await attestFromEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
TEST_ERC20
|
||||
);
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = parseSequenceFromLogEth(
|
||||
receipt,
|
||||
CONTRACTS.DEVNET.ethereum.core
|
||||
);
|
||||
const emitterAddress = getEmitterAddressEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge
|
||||
);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
// create a keypair for Solana
|
||||
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||
const payerAddress = keypair.publicKey.toString();
|
||||
// post vaa to Solana
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
await postVaaSolana(
|
||||
connection,
|
||||
async (transaction) => {
|
||||
transaction.partialSign(keypair);
|
||||
return transaction;
|
||||
},
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
payerAddress,
|
||||
Buffer.from(signedVAA)
|
||||
);
|
||||
// create wormhole wrapped token (mint and metadata) on solana
|
||||
const transaction = await createWrappedOnSolana(
|
||||
connection,
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
);
|
||||
// sign, send, and confirm transaction
|
||||
try {
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(
|
||||
transaction.serialize()
|
||||
);
|
||||
await connection.confirmTransaction(txid);
|
||||
} catch (e) {
|
||||
// this could fail because the token is already attested (in an unclean env)
|
||||
}
|
||||
provider.destroy();
|
||||
done();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
done(
|
||||
"An error occurred while trying to attest from Ethereum to Solana"
|
||||
);
|
||||
}
|
||||
})();
|
||||
});
|
||||
test("Ethereum ERC-20 is attested on Solana", async () => {
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const address = getForeignAssetSolana(
|
||||
connection,
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
"ethereum",
|
||||
tryNativeToUint8Array(TEST_ERC20, "ethereum")
|
||||
);
|
||||
expect(address).toBeTruthy();
|
||||
});
|
||||
test("Send Ethereum ERC-20 to Solana", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
const DECIMALS: number = 18;
|
||||
// create a keypair for Solana
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||
const payerAddress = keypair.publicKey.toString();
|
||||
// determine destination address - an associated token account
|
||||
const SolanaForeignAsset = await getForeignAssetSolana(
|
||||
connection,
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
CHAIN_ID_ETH,
|
||||
tryNativeToUint8Array(TEST_ERC20, CHAIN_ID_ETH)
|
||||
);
|
||||
const solanaMintKey = new PublicKey(SolanaForeignAsset || "");
|
||||
const recipient = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
solanaMintKey,
|
||||
keypair.publicKey
|
||||
);
|
||||
// create the associated token account if it doesn't exist
|
||||
const associatedAddressInfo = await connection.getAccountInfo(
|
||||
recipient
|
||||
);
|
||||
if (!associatedAddressInfo) {
|
||||
const transaction = new Transaction().add(
|
||||
await Token.createAssociatedTokenAccountInstruction(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
solanaMintKey,
|
||||
recipient,
|
||||
keypair.publicKey, // owner
|
||||
keypair.publicKey // payer
|
||||
)
|
||||
);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = keypair.publicKey;
|
||||
// sign, send, and confirm transaction
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(
|
||||
transaction.serialize()
|
||||
);
|
||||
await connection.confirmTransaction(txid);
|
||||
}
|
||||
// create a signer for Eth
|
||||
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||
const amount = parseUnits("1", DECIMALS);
|
||||
|
||||
// Get the initial wallet balance of ERC20 on Eth
|
||||
let token = TokenImplementation__factory.connect(TEST_ERC20, signer);
|
||||
const initialErc20BalOnEth = await token.balanceOf(
|
||||
await signer.getAddress()
|
||||
);
|
||||
const initialErc20BalOnEthFormatted = formatUnits(
|
||||
initialErc20BalOnEth._hex,
|
||||
DECIMALS
|
||||
);
|
||||
|
||||
// Get the initial balance on Solana
|
||||
const tokenFilter: TokenAccountsFilter = {
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
};
|
||||
let results = await connection.getParsedTokenAccountsByOwner(
|
||||
keypair.publicKey,
|
||||
tokenFilter
|
||||
);
|
||||
let initialSolanaBalance: number = 0;
|
||||
for (const item of results.value) {
|
||||
const tokenInfo = item.account.data.parsed.info;
|
||||
const address = tokenInfo.mint;
|
||||
const amount = tokenInfo.tokenAmount.uiAmount;
|
||||
if (tokenInfo.mint === SolanaForeignAsset) {
|
||||
initialSolanaBalance = amount;
|
||||
}
|
||||
}
|
||||
|
||||
// approve the bridge to spend tokens
|
||||
await approveEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
TEST_ERC20,
|
||||
signer,
|
||||
amount
|
||||
);
|
||||
// transfer tokens
|
||||
const receipt = await transferFromEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
TEST_ERC20,
|
||||
amount,
|
||||
CHAIN_ID_SOLANA,
|
||||
tryNativeToUint8Array(recipient.toString(), CHAIN_ID_SOLANA)
|
||||
);
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = parseSequenceFromLogEth(
|
||||
receipt,
|
||||
CONTRACTS.DEVNET.ethereum.core
|
||||
);
|
||||
const emitterAddress = getEmitterAddressEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge
|
||||
);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
// post vaa to Solana
|
||||
await postVaaSolana(
|
||||
connection,
|
||||
async (transaction) => {
|
||||
transaction.partialSign(keypair);
|
||||
return transaction;
|
||||
},
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
payerAddress,
|
||||
Buffer.from(signedVAA)
|
||||
);
|
||||
expect(
|
||||
await getIsTransferCompletedSolana(
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
signedVAA,
|
||||
connection
|
||||
)
|
||||
).toBe(false);
|
||||
// redeem tokens on solana
|
||||
const transaction = await redeemOnSolana(
|
||||
connection,
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
);
|
||||
// sign, send, and confirm transaction
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(
|
||||
transaction.serialize()
|
||||
);
|
||||
await connection.confirmTransaction(txid);
|
||||
expect(
|
||||
await getIsTransferCompletedSolana(
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
signedVAA,
|
||||
connection
|
||||
)
|
||||
).toBe(true);
|
||||
|
||||
// Get the final wallet balance of ERC20 on Eth
|
||||
const finalErc20BalOnEth = await token.balanceOf(
|
||||
await signer.getAddress()
|
||||
);
|
||||
const finalErc20BalOnEthFormatted = formatUnits(
|
||||
finalErc20BalOnEth._hex,
|
||||
DECIMALS
|
||||
);
|
||||
expect(
|
||||
parseInt(initialErc20BalOnEthFormatted) -
|
||||
parseInt(finalErc20BalOnEthFormatted) ===
|
||||
1
|
||||
).toBe(true);
|
||||
|
||||
// Get final balance on Solana
|
||||
results = await connection.getParsedTokenAccountsByOwner(
|
||||
keypair.publicKey,
|
||||
tokenFilter
|
||||
);
|
||||
let finalSolanaBalance: number = 0;
|
||||
for (const item of results.value) {
|
||||
const tokenInfo = item.account.data.parsed.info;
|
||||
const address = tokenInfo.mint;
|
||||
const amount = tokenInfo.tokenAmount.uiAmount;
|
||||
if (tokenInfo.mint === SolanaForeignAsset) {
|
||||
finalSolanaBalance = amount;
|
||||
}
|
||||
}
|
||||
expect(finalSolanaBalance - initialSolanaBalance === 1).toBe(true);
|
||||
provider.destroy();
|
||||
done();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
done("An error occurred while trying to send from Ethereum to Solana");
|
||||
}
|
||||
})();
|
||||
});
|
||||
describe("Post VAA with retry", () => {
|
||||
test("postVAA with retry, no failures", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
// create a keypair for Solana
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||
const payerAddress = keypair.publicKey.toString();
|
||||
const sequence = await transferFromEthToSolana();
|
||||
const emitterAddress = getEmitterAddressEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge
|
||||
);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
let maxFailures = 0;
|
||||
// post vaa to Solana
|
||||
|
||||
const postPromise = postVaaWithRetry(
|
||||
connection,
|
||||
async (transaction) => {
|
||||
await new Promise(function (resolve) {
|
||||
//We delay here so the connection has time to get wrecked
|
||||
setTimeout(function () {
|
||||
resolve(500);
|
||||
});
|
||||
});
|
||||
transaction.partialSign(keypair);
|
||||
return transaction;
|
||||
},
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
payerAddress,
|
||||
Buffer.from(signedVAA),
|
||||
maxFailures
|
||||
);
|
||||
|
||||
await postPromise;
|
||||
// redeem tokens on solana
|
||||
const transaction = await redeemOnSolana(
|
||||
connection,
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
payerAddress,
|
||||
signedVAA
|
||||
);
|
||||
// sign, send, and confirm transaction
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(
|
||||
transaction.serialize()
|
||||
);
|
||||
await connection.confirmTransaction(txid);
|
||||
expect(
|
||||
await getIsTransferCompletedSolana(
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
signedVAA,
|
||||
connection
|
||||
)
|
||||
).toBe(true);
|
||||
done();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
done(
|
||||
"An error occurred while happy-path testing post VAA with retry."
|
||||
);
|
||||
}
|
||||
})();
|
||||
});
|
||||
test("Reject on signature failure", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
// create a keypair for Solana
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||
const payerAddress = keypair.publicKey.toString();
|
||||
const sequence = await transferFromEthToSolana();
|
||||
const emitterAddress = getEmitterAddressEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge
|
||||
);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
let maxFailures = 5;
|
||||
// post vaa to Solana
|
||||
|
||||
let error = false;
|
||||
try {
|
||||
const postPromise = postVaaWithRetry(
|
||||
connection,
|
||||
async (transaction) => {
|
||||
return Promise.reject();
|
||||
},
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
payerAddress,
|
||||
Buffer.from(signedVAA),
|
||||
maxFailures
|
||||
);
|
||||
|
||||
await postPromise;
|
||||
} catch (e) {
|
||||
error = true;
|
||||
}
|
||||
expect(error).toBe(true);
|
||||
done();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
done(
|
||||
"An error occurred while trying to send from Ethereum to Solana"
|
||||
);
|
||||
}
|
||||
})();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,109 +1,15 @@
|
|||
import { parseUnits } from "@ethersproject/units";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
|
||||
import { LCDClient, MnemonicKey, TxInfo } from "@terra-money/terra.js";
|
||||
import axios from "axios";
|
||||
import { ethers } from "ethers";
|
||||
import { ChainId, getSignedVAAWithRetry } from "../..";
|
||||
import {
|
||||
approveEth,
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
getForeignAssetSolana,
|
||||
getSignedVAAWithRetry,
|
||||
hexToUint8Array,
|
||||
nativeToHexString,
|
||||
parseSequenceFromLogEth,
|
||||
transferFromEth,
|
||||
} from "../..";
|
||||
import {
|
||||
ETH_CORE_BRIDGE_ADDRESS,
|
||||
ETH_NODE_URL,
|
||||
ETH_PRIVATE_KEY,
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOLANA_PRIVATE_KEY,
|
||||
SOLANA_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA_CHAIN_ID,
|
||||
TERRA_GAS_PRICES_URL,
|
||||
TERRA_HOST,
|
||||
TERRA_NODE_URL,
|
||||
TERRA_PRIVATE_KEY,
|
||||
TEST_ERC20,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "./consts";
|
||||
|
||||
export async function transferFromEthToSolana(): Promise<string> {
|
||||
// create a keypair for Solana
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||
// determine destination address - an associated token account
|
||||
const solanaMintKey = new PublicKey(
|
||||
(await getForeignAssetSolana(
|
||||
connection,
|
||||
SOLANA_TOKEN_BRIDGE_ADDRESS,
|
||||
CHAIN_ID_ETH,
|
||||
hexToUint8Array(nativeToHexString(TEST_ERC20, CHAIN_ID_ETH) || "")
|
||||
)) || ""
|
||||
);
|
||||
const recipient = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
solanaMintKey,
|
||||
keypair.publicKey
|
||||
);
|
||||
// create the associated token account if it doesn't exist
|
||||
const associatedAddressInfo = await connection.getAccountInfo(recipient);
|
||||
if (!associatedAddressInfo) {
|
||||
const transaction = new Transaction().add(
|
||||
await Token.createAssociatedTokenAccountInstruction(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
solanaMintKey,
|
||||
recipient,
|
||||
keypair.publicKey, // owner
|
||||
keypair.publicKey // payer
|
||||
)
|
||||
);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = keypair.publicKey;
|
||||
// sign, send, and confirm transaction
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(transaction.serialize());
|
||||
await connection.confirmTransaction(txid);
|
||||
}
|
||||
// create a signer for Eth
|
||||
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||
const amount = parseUnits("1", 18);
|
||||
// approve the bridge to spend tokens
|
||||
await approveEth(ETH_TOKEN_BRIDGE_ADDRESS, TEST_ERC20, signer, amount);
|
||||
// transfer tokens
|
||||
const receipt = await transferFromEth(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
signer,
|
||||
TEST_ERC20,
|
||||
amount,
|
||||
CHAIN_ID_SOLANA,
|
||||
hexToUint8Array(
|
||||
nativeToHexString(recipient.toString(), CHAIN_ID_SOLANA) || ""
|
||||
)
|
||||
);
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = await parseSequenceFromLogEth(
|
||||
receipt,
|
||||
ETH_CORE_BRIDGE_ADDRESS
|
||||
);
|
||||
provider.destroy();
|
||||
return sequence;
|
||||
}
|
||||
|
||||
export async function waitForTerraExecution(
|
||||
transaction: string,
|
||||
lcd: LCDClient
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,280 @@
|
|||
import { formatUnits, parseUnits } from "@ethersproject/units";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
import { describe, expect, jest, test } from "@jest/globals";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import {
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TokenAccountsFilter,
|
||||
} from "@solana/web3.js";
|
||||
import { ethers } from "ethers";
|
||||
import {
|
||||
attestFromSolana,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
CONTRACTS,
|
||||
createWrappedOnEth,
|
||||
getEmitterAddressSolana,
|
||||
getForeignAssetEth,
|
||||
getIsTransferCompletedEth,
|
||||
hexToUint8Array,
|
||||
parseSequenceFromLogSolana,
|
||||
redeemOnEth,
|
||||
TokenImplementation__factory,
|
||||
transferFromSolana,
|
||||
tryNativeToHexString,
|
||||
tryNativeToUint8Array,
|
||||
} from "../..";
|
||||
import getSignedVAAWithRetry from "../../rpc/getSignedVAAWithRetry";
|
||||
import { setDefaultWasm } from "../../solana/wasm";
|
||||
import {
|
||||
ETH_NODE_URL,
|
||||
ETH_PRIVATE_KEY3,
|
||||
SOLANA_HOST,
|
||||
SOLANA_PRIVATE_KEY,
|
||||
TEST_SOLANA_TOKEN,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "./consts";
|
||||
|
||||
setDefaultWasm("node");
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
describe("Solana to Ethereum", () => {
|
||||
test("Attest Solana SPL to Ethereum", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
// create a keypair for Solana
|
||||
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||
const payerAddress = keypair.publicKey.toString();
|
||||
// attest the test token
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const transaction = await attestFromSolana(
|
||||
connection,
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
payerAddress,
|
||||
TEST_SOLANA_TOKEN
|
||||
);
|
||||
// sign, send, and confirm transaction
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(
|
||||
transaction.serialize()
|
||||
);
|
||||
await connection.confirmTransaction(txid);
|
||||
const info = await connection.getTransaction(txid);
|
||||
if (!info) {
|
||||
throw new Error(
|
||||
"An error occurred while fetching the transaction info"
|
||||
);
|
||||
}
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = parseSequenceFromLogSolana(info);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
CONTRACTS.DEVNET.solana.token_bridge
|
||||
);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
// create a signer for Eth
|
||||
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
const signer = new ethers.Wallet(ETH_PRIVATE_KEY3, provider);
|
||||
try {
|
||||
await createWrappedOnEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
signedVAA
|
||||
);
|
||||
} catch (e) {
|
||||
// this could fail because the token is already attested (in an unclean env)
|
||||
}
|
||||
provider.destroy();
|
||||
done();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
done(
|
||||
"An error occurred while trying to attest from Solana to Ethereum"
|
||||
);
|
||||
}
|
||||
})();
|
||||
});
|
||||
test("Solana SPL is attested on Ethereum", async () => {
|
||||
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
const address = getForeignAssetEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
provider,
|
||||
"solana",
|
||||
tryNativeToUint8Array(TEST_SOLANA_TOKEN, "solana")
|
||||
);
|
||||
expect(address).toBeTruthy();
|
||||
expect(address).not.toBe(ethers.constants.AddressZero);
|
||||
provider.destroy();
|
||||
});
|
||||
test("Send Solana SPL to Ethereum", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
// create a signer for Eth
|
||||
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
const signer = new ethers.Wallet(ETH_PRIVATE_KEY3, provider);
|
||||
const targetAddress = await signer.getAddress();
|
||||
// create a keypair for Solana
|
||||
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||
const payerAddress = keypair.publicKey.toString();
|
||||
// find the associated token account
|
||||
const fromAddress = (
|
||||
await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(TEST_SOLANA_TOKEN),
|
||||
keypair.publicKey
|
||||
)
|
||||
).toString();
|
||||
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
|
||||
// Get the initial solana token balance
|
||||
const tokenFilter: TokenAccountsFilter = {
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
};
|
||||
let results = await connection.getParsedTokenAccountsByOwner(
|
||||
keypair.publicKey,
|
||||
tokenFilter
|
||||
);
|
||||
let initialSolanaBalance: number = 0;
|
||||
for (const item of results.value) {
|
||||
const tokenInfo = item.account.data.parsed.info;
|
||||
const address = tokenInfo.mint;
|
||||
const amount = tokenInfo.tokenAmount.uiAmount;
|
||||
if (tokenInfo.mint === TEST_SOLANA_TOKEN) {
|
||||
initialSolanaBalance = amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the initial wallet balance on Eth
|
||||
const originAssetHex = tryNativeToHexString(
|
||||
TEST_SOLANA_TOKEN,
|
||||
CHAIN_ID_SOLANA
|
||||
);
|
||||
if (!originAssetHex) {
|
||||
throw new Error("originAssetHex is null");
|
||||
}
|
||||
const foreignAsset = await getForeignAssetEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
provider,
|
||||
CHAIN_ID_SOLANA,
|
||||
hexToUint8Array(originAssetHex)
|
||||
);
|
||||
if (!foreignAsset) {
|
||||
throw new Error("foreignAsset is null");
|
||||
}
|
||||
let token = TokenImplementation__factory.connect(foreignAsset, signer);
|
||||
const initialBalOnEth = await token.balanceOf(
|
||||
await signer.getAddress()
|
||||
);
|
||||
const initialBalOnEthFormatted = formatUnits(initialBalOnEth._hex, 9);
|
||||
|
||||
// transfer the test token
|
||||
const amount = parseUnits("1", 9).toBigInt();
|
||||
const transaction = await transferFromSolana(
|
||||
connection,
|
||||
CONTRACTS.DEVNET.solana.core,
|
||||
CONTRACTS.DEVNET.solana.token_bridge,
|
||||
payerAddress,
|
||||
fromAddress,
|
||||
TEST_SOLANA_TOKEN,
|
||||
amount,
|
||||
tryNativeToUint8Array(targetAddress, CHAIN_ID_ETH),
|
||||
CHAIN_ID_ETH
|
||||
);
|
||||
// sign, send, and confirm transaction
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(
|
||||
transaction.serialize()
|
||||
);
|
||||
await connection.confirmTransaction(txid);
|
||||
const info = await connection.getTransaction(txid);
|
||||
if (!info) {
|
||||
throw new Error(
|
||||
"An error occurred while fetching the transaction info"
|
||||
);
|
||||
}
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = parseSequenceFromLogSolana(info);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
CONTRACTS.DEVNET.solana.token_bridge
|
||||
);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
expect(
|
||||
await getIsTransferCompletedEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
provider,
|
||||
signedVAA
|
||||
)
|
||||
).toBe(false);
|
||||
await redeemOnEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
signedVAA
|
||||
);
|
||||
expect(
|
||||
await getIsTransferCompletedEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
provider,
|
||||
signedVAA
|
||||
)
|
||||
).toBe(true);
|
||||
|
||||
// Get final balance on Solana
|
||||
results = await connection.getParsedTokenAccountsByOwner(
|
||||
keypair.publicKey,
|
||||
tokenFilter
|
||||
);
|
||||
let finalSolanaBalance: number = 0;
|
||||
for (const item of results.value) {
|
||||
const tokenInfo = item.account.data.parsed.info;
|
||||
const address = tokenInfo.mint;
|
||||
const amount = tokenInfo.tokenAmount.uiAmount;
|
||||
if (tokenInfo.mint === TEST_SOLANA_TOKEN) {
|
||||
finalSolanaBalance = amount;
|
||||
}
|
||||
}
|
||||
expect(initialSolanaBalance - finalSolanaBalance).toBeCloseTo(1);
|
||||
|
||||
// Get the final balance on Eth
|
||||
const finalBalOnEth = await token.balanceOf(await signer.getAddress());
|
||||
const finalBalOnEthFormatted = formatUnits(finalBalOnEth._hex, 9);
|
||||
expect(
|
||||
parseInt(finalBalOnEthFormatted) -
|
||||
parseInt(initialBalOnEthFormatted) ===
|
||||
1
|
||||
).toBe(true);
|
||||
provider.destroy();
|
||||
done();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
done("An error occurred while trying to send from Solana to Ethereum");
|
||||
}
|
||||
})();
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -21,16 +21,13 @@ import {
|
|||
updateWrappedOnEth,
|
||||
} from "../..";
|
||||
import { tryNativeToUint8Array } from "../../utils";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_TERRA2 } from "../../utils/consts";
|
||||
import { CHAIN_ID_ETH, CHAIN_ID_TERRA2, CONTRACTS } from "../../utils/consts";
|
||||
import { attestFromEth, attestFromTerra } from "../attest";
|
||||
import { approveEth, transferFromEth, transferFromTerra } from "../transfer";
|
||||
import {
|
||||
ETH_CORE_BRIDGE_ADDRESS,
|
||||
ETH_NODE_URL,
|
||||
ETH_PRIVATE_KEY2,
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA2_GAS_PRICES_URL,
|
||||
TERRA2_TOKEN_BRIDGE_ADDRESS,
|
||||
TERRA2_PRIVATE_KEY,
|
||||
TEST_ERC20,
|
||||
} from "./consts";
|
||||
|
@ -47,7 +44,9 @@ const terraWalletAddress = terraWallet.key.accAddress;
|
|||
|
||||
const provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
const signer = new ethers.Wallet(ETH_PRIVATE_KEY2, provider);
|
||||
const ethEmitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
|
||||
const ethEmitterAddress = getEmitterAddressEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge
|
||||
);
|
||||
const ethTransferAmount = parseUnits("1", 18);
|
||||
|
||||
let ethWalletAddress: string;
|
||||
|
@ -56,7 +55,7 @@ let terraEmitterAddress: string;
|
|||
beforeAll(async () => {
|
||||
ethWalletAddress = await signer.getAddress();
|
||||
terraEmitterAddress = await getEmitterAddressTerra(
|
||||
TERRA2_TOKEN_BRIDGE_ADDRESS
|
||||
CONTRACTS.DEVNET.terra2.token_bridge
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -96,7 +95,10 @@ const terraBroadcastTxAndGetSignedVaa = async (msgs: Msg[], wallet: Wallet) => {
|
|||
};
|
||||
|
||||
const ethParseLogAndGetSignedVaa = async (receipt: ethers.ContractReceipt) => {
|
||||
const sequence = parseSequenceFromLogEth(receipt, ETH_CORE_BRIDGE_ADDRESS);
|
||||
const sequence = parseSequenceFromLogEth(
|
||||
receipt,
|
||||
CONTRACTS.DEVNET.ethereum.core
|
||||
);
|
||||
return await getSignedVAABySequence(
|
||||
CHAIN_ID_ETH,
|
||||
sequence,
|
||||
|
@ -107,7 +109,7 @@ const ethParseLogAndGetSignedVaa = async (receipt: ethers.ContractReceipt) => {
|
|||
test("Attest and transfer token from Terra2 to Ethereum", async () => {
|
||||
// Attest
|
||||
const attestMsg = await attestFromTerra(
|
||||
TERRA2_TOKEN_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.terra2.token_bridge,
|
||||
terraWalletAddress,
|
||||
"uluna"
|
||||
);
|
||||
|
@ -116,14 +118,22 @@ test("Attest and transfer token from Terra2 to Ethereum", async () => {
|
|||
terraWallet
|
||||
);
|
||||
try {
|
||||
await createWrappedOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, attestSignedVaa);
|
||||
await createWrappedOnEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
attestSignedVaa
|
||||
);
|
||||
} catch {
|
||||
await updateWrappedOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, attestSignedVaa);
|
||||
await updateWrappedOnEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
attestSignedVaa
|
||||
);
|
||||
}
|
||||
// Transfer
|
||||
const transferMsgs = await transferFromTerra(
|
||||
terraWalletAddress,
|
||||
TERRA2_TOKEN_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.terra2.token_bridge,
|
||||
"uluna",
|
||||
"1000000",
|
||||
CHAIN_ID_ETH,
|
||||
|
@ -133,32 +143,36 @@ test("Attest and transfer token from Terra2 to Ethereum", async () => {
|
|||
transferMsgs,
|
||||
terraWallet
|
||||
);
|
||||
await redeemOnEth(ETH_TOKEN_BRIDGE_ADDRESS, signer, transferSignedVaa);
|
||||
await redeemOnEth(
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
transferSignedVaa
|
||||
);
|
||||
});
|
||||
|
||||
test("Attest and transfer token from Ethereum to Terra2", async () => {
|
||||
// Attest
|
||||
const attestReceipt = await attestFromEth(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
TEST_ERC20
|
||||
);
|
||||
const attestSignedVaa = await ethParseLogAndGetSignedVaa(attestReceipt);
|
||||
const createWrappedMsg = await createWrappedOnTerra(
|
||||
TERRA2_TOKEN_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.terra2.token_bridge,
|
||||
terraWalletAddress,
|
||||
attestSignedVaa
|
||||
);
|
||||
await terraBroadcastAndWaitForExecution([createWrappedMsg], terraWallet);
|
||||
// Transfer
|
||||
await approveEth(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
TEST_ERC20,
|
||||
signer,
|
||||
ethTransferAmount
|
||||
);
|
||||
const transferReceipt = await transferFromEth(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.ethereum.token_bridge,
|
||||
signer,
|
||||
TEST_ERC20,
|
||||
ethTransferAmount,
|
||||
|
@ -167,14 +181,14 @@ test("Attest and transfer token from Ethereum to Terra2", async () => {
|
|||
);
|
||||
const transferSignedVaa = await ethParseLogAndGetSignedVaa(transferReceipt);
|
||||
const redeemMsg = await redeemOnTerra(
|
||||
TERRA2_TOKEN_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.terra2.token_bridge,
|
||||
terraWalletAddress,
|
||||
transferSignedVaa
|
||||
);
|
||||
await terraBroadcastAndWaitForExecution([redeemMsg], terraWallet);
|
||||
expect(
|
||||
await getIsTransferCompletedTerra(
|
||||
TERRA2_TOKEN_BRIDGE_ADDRESS,
|
||||
CONTRACTS.DEVNET.terra2.token_bridge,
|
||||
transferSignedVaa,
|
||||
lcd,
|
||||
TERRA2_GAS_PRICES_URL
|
||||
|
|
|
@ -358,7 +358,7 @@ const DEVNET = {
|
|||
terra: {
|
||||
core: "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5",
|
||||
token_bridge: "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4",
|
||||
nft_bridge: undefined,
|
||||
nft_bridge: "terra1plju286nnfj3z54wgcggd4enwaa9fgf5kgrgzl",
|
||||
},
|
||||
ethereum: {
|
||||
core: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
|
||||
|
@ -456,8 +456,9 @@ const DEVNET = {
|
|||
nft_bridge: undefined,
|
||||
},
|
||||
terra2: {
|
||||
core: undefined,
|
||||
token_bridge: undefined,
|
||||
core: "terra14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9ssrc8au",
|
||||
token_bridge:
|
||||
"terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6",
|
||||
nft_bridge: undefined,
|
||||
},
|
||||
arbitrum: {
|
||||
|
|
|
@ -12,5 +12,5 @@
|
|||
"lib": ["dom", "es5", "scripthost", "es2020.bigint"]
|
||||
},
|
||||
"include": ["src", "types"],
|
||||
"exclude": ["node_modules", "**/__tests__/*"]
|
||||
"exclude": ["node_modules", "**/__tests__/*", "**/*.test.ts"]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue