solana: couple more things
* remove burn_source_authority * fix Cargo.toml * clean up tests
This commit is contained in:
parent
6c6a41c03d
commit
dc6b868268
|
@ -30,6 +30,10 @@ startup_wait = 16000
|
|||
[test.validator]
|
||||
url = "https://api.devnet.solana.com"
|
||||
|
||||
### At 160 ticks/s, 64 ticks per slot implies that leader rotation and voting will happen
|
||||
### every 400 ms. A fast voting cadence ensures faster finality and convergence
|
||||
ticks_per_slot = 8
|
||||
|
||||
### Forked Wormhole Circle Integration Program
|
||||
[[test.validator.clone]]
|
||||
address = "wCCTPvsyeL9qYqbHTv3DUAyzEfYcyHoYw5c4mgcbBeW"
|
||||
|
|
|
@ -2395,6 +2395,7 @@ dependencies = [
|
|||
name = "wormhole-circle-integration-solana"
|
||||
version = "0.0.1-alpha.7"
|
||||
dependencies = [
|
||||
"ahash 0.8.6",
|
||||
"anchor-lang",
|
||||
"anchor-spl",
|
||||
"cfg-if",
|
||||
|
|
|
@ -39,6 +39,10 @@ ruint = "1.9.0"
|
|||
cfg-if = "1.0"
|
||||
hex-literal = "0.4.1"
|
||||
|
||||
### https://github.com/coral-xyz/anchor/issues/2755
|
||||
### This dependency must be added for each program.
|
||||
ahash = "=0.8.6"
|
||||
|
||||
[profile.release]
|
||||
overflow-checks = true
|
||||
lto = "fat"
|
||||
|
|
|
@ -34,5 +34,7 @@ ruint.workspace = true
|
|||
|
||||
cfg-if.workspace = true
|
||||
|
||||
ahash.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal.workspace = true
|
|
@ -22,10 +22,6 @@ pub struct TransferTokensWithPayload<'info> {
|
|||
)]
|
||||
custodian: Account<'info, Custodian>,
|
||||
|
||||
/// Signer who must have the authority (either as the owner or has been delegated authority)
|
||||
/// over the `burn_source` token account.
|
||||
burn_source_authority: Signer<'info>,
|
||||
|
||||
/// Circle-supported mint.
|
||||
///
|
||||
/// CHECK: Mutable. This token account's mint must be the same as the one found in the CCTP
|
||||
|
@ -38,6 +34,9 @@ pub struct TransferTokensWithPayload<'info> {
|
|||
|
||||
/// Token account where assets are burned from. The CCTP Token Messenger Minter program will
|
||||
/// burn the configured [amount](TransferTokensWithPayloadArgs::amount) from this account.
|
||||
///
|
||||
/// NOTE: Transfer authority must be delegated to the custodian because this instruction
|
||||
/// transfers assets from this account to the custody token account.
|
||||
#[account(
|
||||
mut,
|
||||
token::mint = mint
|
||||
|
@ -160,22 +159,23 @@ pub fn transfer_tokens_with_payload(
|
|||
payload,
|
||||
} = args;
|
||||
|
||||
let custodian_seeds = &[Custodian::SEED_PREFIX, &[ctx.accounts.custodian.bump]];
|
||||
|
||||
// Because the transfer initiator in the Circle message is whoever signs to burn assets, we need
|
||||
// to transfer assets from the source token account to one that belongs to this program.
|
||||
token::transfer(
|
||||
CpiContext::new(
|
||||
CpiContext::new_with_signer(
|
||||
ctx.accounts.token_program.to_account_info(),
|
||||
token::Transfer {
|
||||
from: ctx.accounts.burn_source.to_account_info(),
|
||||
to: ctx.accounts.custody_token.to_account_info(),
|
||||
authority: ctx.accounts.burn_source_authority.to_account_info(),
|
||||
authority: ctx.accounts.custodian.to_account_info(),
|
||||
},
|
||||
&[custodian_seeds],
|
||||
),
|
||||
amount,
|
||||
)?;
|
||||
|
||||
let custodian_seeds = &[Custodian::SEED_PREFIX, &[ctx.accounts.custodian.bump]];
|
||||
|
||||
wormhole_cctp_solana::cpi::burn_and_publish(
|
||||
CpiContext::new_with_signer(
|
||||
ctx.accounts
|
||||
|
|
|
@ -9,7 +9,7 @@ import { NodeWallet, postVaaSolana } from "@certusone/wormhole-sdk/lib/cjs/solan
|
|||
import { derivePostedVaaKey } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
|
||||
import { Connection, Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
|
||||
import "dotenv/config";
|
||||
import { WormholeCctpProgram } from "../src";
|
||||
import { CircleIntegrationProgram } from "../src";
|
||||
|
||||
const PROGRAM_ID = "wCCTPvsyeL9qYqbHTv3DUAyzEfYcyHoYw5c4mgcbBeW";
|
||||
|
||||
|
@ -22,7 +22,7 @@ async function main() {
|
|||
let govSequence = 6900n;
|
||||
|
||||
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
|
||||
const wormholeCctp = new WormholeCctpProgram(connection, PROGRAM_ID);
|
||||
const circleIntegration = new CircleIntegrationProgram(connection, PROGRAM_ID);
|
||||
|
||||
if (process.env.SOLANA_PRIVATE_KEY === undefined) {
|
||||
throw new Error("SOLANA_PRIVATE_KEY is undefined");
|
||||
|
@ -39,7 +39,7 @@ async function main() {
|
|||
const cctpDomain = 0;
|
||||
|
||||
await registerEmitterAndDomain(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
payer,
|
||||
govSequence++,
|
||||
foreignChain,
|
||||
|
@ -53,7 +53,7 @@ async function main() {
|
|||
const cctpDomain = 1;
|
||||
|
||||
await registerEmitterAndDomain(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
payer,
|
||||
govSequence++,
|
||||
foreignChain,
|
||||
|
@ -67,7 +67,7 @@ async function main() {
|
|||
const cctpDomain = 2;
|
||||
|
||||
await registerEmitterAndDomain(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
payer,
|
||||
govSequence++,
|
||||
foreignChain,
|
||||
|
@ -81,7 +81,7 @@ async function main() {
|
|||
const cctpDomain = 3;
|
||||
|
||||
await registerEmitterAndDomain(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
payer,
|
||||
govSequence++,
|
||||
foreignChain,
|
||||
|
@ -91,32 +91,34 @@ async function main() {
|
|||
}
|
||||
}
|
||||
|
||||
async function intialize(wormholeCctp: WormholeCctpProgram, payer: Keypair) {
|
||||
console.log("custodian", wormholeCctp.custodianAddress().toString());
|
||||
async function intialize(circleIntegration: CircleIntegrationProgram, payer: Keypair) {
|
||||
console.log("custodian", circleIntegration.custodianAddress().toString());
|
||||
|
||||
const ix = await wormholeCctp.initializeIx(payer.publicKey);
|
||||
const ix = await circleIntegration.initializeIx(payer.publicKey);
|
||||
|
||||
const connection = wormholeCctp.program.provider.connection;
|
||||
const connection = circleIntegration.program.provider.connection;
|
||||
const txSig = await sendAndConfirmTransaction(connection, new Transaction().add(ix), [payer]);
|
||||
console.log("intialize", txSig);
|
||||
}
|
||||
|
||||
async function registerEmitterAndDomain(
|
||||
wormholeCctp: WormholeCctpProgram,
|
||||
circleIntegration: CircleIntegrationProgram,
|
||||
payer: Keypair,
|
||||
govSequence: bigint,
|
||||
foreignChain: ChainName,
|
||||
foreignEmitter: string,
|
||||
cctpDomain: number,
|
||||
) {
|
||||
const connection = wormholeCctp.program.provider.connection;
|
||||
const connection = circleIntegration.program.provider.connection;
|
||||
|
||||
const registeredEmitter = wormholeCctp.registeredEmitterAddress(coalesceChainId(foreignChain));
|
||||
const registeredEmitter = circleIntegration.registeredEmitterAddress(
|
||||
coalesceChainId(foreignChain),
|
||||
);
|
||||
const emitterAddress = Array.from(tryNativeToUint8Array(foreignEmitter, foreignChain));
|
||||
|
||||
const exists = await connection.getAccountInfo(registeredEmitter).then((acct) => acct != null);
|
||||
if (exists) {
|
||||
const registered = await wormholeCctp.fetchRegisteredEmitter(registeredEmitter);
|
||||
const registered = await circleIntegration.fetchRegisteredEmitter(registeredEmitter);
|
||||
if (Buffer.from(registered.address).equals(Buffer.from(emitterAddress))) {
|
||||
console.log("already registered", foreignChain, foreignEmitter, cctpDomain);
|
||||
return;
|
||||
|
@ -157,14 +159,14 @@ async function registerEmitterAndDomain(
|
|||
await postVaaSolana(
|
||||
connection,
|
||||
new NodeWallet(payer).signTransaction,
|
||||
wormholeCctp.coreBridgeProgramId(),
|
||||
circleIntegration.coreBridgeProgramId(),
|
||||
payer.publicKey,
|
||||
vaaBuf,
|
||||
);
|
||||
|
||||
const vaa = derivePostedVaaKey(wormholeCctp.coreBridgeProgramId(), parseVaa(vaaBuf).hash);
|
||||
const vaa = derivePostedVaaKey(circleIntegration.coreBridgeProgramId(), parseVaa(vaaBuf).hash);
|
||||
|
||||
const ix = await wormholeCctp.registerEmitterAndDomainIx({
|
||||
const ix = await circleIntegration.registerEmitterAndDomainIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import { NodeWallet, postVaaSolana } from "@certusone/wormhole-sdk/lib/cjs/solan
|
|||
import { derivePostedVaaKey } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
|
||||
import { Connection, Keypair, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
|
||||
import "dotenv/config";
|
||||
import { WormholeCctpProgram } from "../src";
|
||||
import { CircleIntegrationProgram } from "../src";
|
||||
|
||||
const PROGRAM_ID = "wCCTPvsyeL9qYqbHTv3DUAyzEfYcyHoYw5c4mgcbBeW";
|
||||
|
||||
|
@ -20,22 +20,22 @@ async function main() {
|
|||
let govSequence = 6910n;
|
||||
|
||||
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
|
||||
const wormholeCctp = new WormholeCctpProgram(connection, PROGRAM_ID);
|
||||
const circleIntegration = new CircleIntegrationProgram(connection, PROGRAM_ID);
|
||||
|
||||
if (process.env.SOLANA_PRIVATE_KEY === undefined) {
|
||||
throw new Error("SOLANA_PRIVATE_KEY is undefined");
|
||||
}
|
||||
const payer = Keypair.fromSecretKey(Buffer.from(process.env.SOLANA_PRIVATE_KEY, "hex"));
|
||||
|
||||
await upgradeContract(wormholeCctp, payer, govSequence);
|
||||
await upgradeContract(circleIntegration, payer, govSequence);
|
||||
}
|
||||
|
||||
async function upgradeContract(
|
||||
wormholeCctp: WormholeCctpProgram,
|
||||
circleIntegration: CircleIntegrationProgram,
|
||||
payer: Keypair,
|
||||
govSequence: bigint,
|
||||
) {
|
||||
const connection = wormholeCctp.program.provider.connection;
|
||||
const connection = circleIntegration.program.provider.connection;
|
||||
|
||||
const govEmitter = new MockEmitter(
|
||||
"0000000000000000000000000000000000000000000000000000000000000004",
|
||||
|
@ -69,14 +69,14 @@ async function upgradeContract(
|
|||
await postVaaSolana(
|
||||
connection,
|
||||
new NodeWallet(payer).signTransaction,
|
||||
wormholeCctp.coreBridgeProgramId(),
|
||||
circleIntegration.coreBridgeProgramId(),
|
||||
payer.publicKey,
|
||||
vaaBuf,
|
||||
);
|
||||
|
||||
const vaa = derivePostedVaaKey(wormholeCctp.coreBridgeProgramId(), parseVaa(vaaBuf).hash);
|
||||
const vaa = derivePostedVaaKey(circleIntegration.coreBridgeProgramId(), parseVaa(vaaBuf).hash);
|
||||
|
||||
const ix = await wormholeCctp.upgradeContractIx({
|
||||
const ix = await circleIntegration.upgradeContractIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
|
|
@ -115,7 +115,7 @@ export type SolanaWormholeCctpTxData = {
|
|||
encodedCctpMessage: Buffer;
|
||||
};
|
||||
|
||||
export class WormholeCctpProgram {
|
||||
export class CircleIntegrationProgram {
|
||||
private _programId: ProgramId;
|
||||
|
||||
program: Program<WormholeCircleIntegrationSolana>;
|
||||
|
@ -346,23 +346,10 @@ export class WormholeCctpProgram {
|
|||
mint: PublicKey;
|
||||
burnSource: PublicKey;
|
||||
coreMessage: PublicKey;
|
||||
burnSourceAuthority?: PublicKey;
|
||||
},
|
||||
args: TransferTokensWithPayloadArgs,
|
||||
): Promise<TransactionInstruction> {
|
||||
let {
|
||||
payer,
|
||||
burnSource,
|
||||
mint,
|
||||
coreMessage,
|
||||
burnSourceAuthority: inputBurnSender,
|
||||
} = accounts;
|
||||
|
||||
const burnSourceAuthority =
|
||||
inputBurnSender ??
|
||||
(await splToken
|
||||
.getAccount(this.program.provider.connection, burnSource)
|
||||
.then((token) => token.owner));
|
||||
let { payer, burnSource, mint, coreMessage } = accounts;
|
||||
|
||||
const { amount, targetChain, mintRecipient, wormholeMessageNonce, payload } = args;
|
||||
|
||||
|
@ -395,7 +382,6 @@ export class WormholeCctpProgram {
|
|||
.accounts({
|
||||
payer,
|
||||
custodian,
|
||||
burnSourceAuthority,
|
||||
mint,
|
||||
burnSource,
|
||||
custodyToken,
|
||||
|
|
|
@ -4,7 +4,14 @@ import { getPostedMessage } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhol
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import * as splToken from "@solana/spl-token";
|
||||
import { expect } from "chai";
|
||||
import { CctpTokenBurnMessage, Deposit, DepositHeader, WormholeCctpProgram } from "../src";
|
||||
import {
|
||||
CctpTokenBurnMessage,
|
||||
Deposit,
|
||||
DepositHeader,
|
||||
CircleIntegrationProgram,
|
||||
VaaAccount,
|
||||
Claim,
|
||||
} from "../src";
|
||||
import {
|
||||
CircleAttester,
|
||||
ETHEREUM_USDC_ADDRESS,
|
||||
|
@ -25,7 +32,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const connection = new anchor.web3.Connection("http://localhost:8899", "processed");
|
||||
const payer = anchor.web3.Keypair.fromSecretKey(PAYER_PRIVATE_KEY);
|
||||
|
||||
const wormholeCctp = new WormholeCctpProgram(
|
||||
const circleIntegration = new CircleIntegrationProgram(
|
||||
connection,
|
||||
"Wormho1eCirc1e1ntegration111111111111111111",
|
||||
);
|
||||
|
@ -34,7 +41,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
|
||||
describe("Setup", () => {
|
||||
it("Invoke `initialize`", async () => {
|
||||
const ix = await wormholeCctp.initializeIx(payer.publicKey);
|
||||
const ix = await circleIntegration.initializeIx(payer.publicKey);
|
||||
await expectIxOk(connection, [ix], [payer]);
|
||||
});
|
||||
|
||||
|
@ -49,7 +56,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
);
|
||||
await expectIxOk(connection, [createIx], [payer]);
|
||||
|
||||
const usdcCommonAccounts = wormholeCctp.commonAccounts(USDC_MINT_ADDRESS);
|
||||
const usdcCommonAccounts = circleIntegration.commonAccounts(USDC_MINT_ADDRESS);
|
||||
|
||||
// Extend.
|
||||
const extendIx = anchor.web3.AddressLookupTableProgram.extendLookupTable({
|
||||
|
@ -85,12 +92,12 @@ describe("Circle Integration -- Localnet", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const ix = await wormholeCctp.registerEmitterAndDomainIx({
|
||||
const ix = await circleIntegration.registerEmitterAndDomainIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
||||
await expectIxErr(connection, [ix], [payer], "GovernanceForAnotherChain");
|
||||
await expectIxErr(connection, [ix], [payer], "Error Code: GovernanceForAnotherChain");
|
||||
});
|
||||
|
||||
it("Cannot Invoke `register_emitter_and_domain` with Invalid Governance", async () => {
|
||||
|
@ -101,7 +108,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const ix = await wormholeCctp.registerEmitterAndDomainIx({
|
||||
const ix = await circleIntegration.registerEmitterAndDomainIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
remoteTokenMessenger: new anchor.web3.PublicKey(
|
||||
|
@ -109,7 +116,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
),
|
||||
});
|
||||
|
||||
await expectIxErr(connection, [ix], [payer], "InvalidGovernanceAction");
|
||||
await expectIxErr(connection, [ix], [payer], "Error Code: InvalidGovernanceAction");
|
||||
});
|
||||
|
||||
it("Cannot Invoke `register_emitter_and_domain` with Invalid CCTP Domain", async () => {
|
||||
|
@ -127,12 +134,12 @@ describe("Circle Integration -- Localnet", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const ix = await wormholeCctp.registerEmitterAndDomainIx({
|
||||
const ix = await circleIntegration.registerEmitterAndDomainIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
||||
await expectIxErr(connection, [ix], [payer], "InvalidCctpDomain");
|
||||
await expectIxErr(connection, [ix], [payer], "Error Code: InvalidCctpDomain");
|
||||
});
|
||||
|
||||
it("Invoke `register_emitter_and_domain`", async () => {
|
||||
|
@ -151,12 +158,12 @@ describe("Circle Integration -- Localnet", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const ix = await wormholeCctp.registerEmitterAndDomainIx({
|
||||
const ix = await circleIntegration.registerEmitterAndDomainIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
||||
const registeredEmitter = wormholeCctp.registeredEmitterAddress(foreignChain);
|
||||
const registeredEmitter = circleIntegration.registeredEmitterAddress(foreignChain);
|
||||
|
||||
// Verify that account does not exist before invoking ix.
|
||||
{
|
||||
|
@ -168,7 +175,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
|
||||
// Now check account contents.
|
||||
const registeredEmitterData =
|
||||
await wormholeCctp.fetchRegisteredEmitter(registeredEmitter);
|
||||
await circleIntegration.fetchRegisteredEmitter(registeredEmitter);
|
||||
expect(registeredEmitterData).to.eql({
|
||||
bump: 255,
|
||||
cctpDomain,
|
||||
|
@ -177,18 +184,32 @@ describe("Circle Integration -- Localnet", () => {
|
|||
});
|
||||
|
||||
localVariables.set("vaa", vaa);
|
||||
localVariables.set("registeredEmitter", registeredEmitter);
|
||||
});
|
||||
|
||||
it("Cannot Invoke `register_emitter_and_domain` with Same Governance Sequence", async () => {
|
||||
const vaa = localVariables.get("vaa") as anchor.web3.PublicKey;
|
||||
expect(localVariables.delete("vaa")).is.true;
|
||||
|
||||
const ix = await wormholeCctp.registerEmitterAndDomainIx({
|
||||
const registeredEmitter = localVariables.get(
|
||||
"registeredEmitter",
|
||||
) as anchor.web3.PublicKey;
|
||||
expect(localVariables.delete("registeredEmitter")).is.true;
|
||||
|
||||
const ix = await circleIntegration.registerEmitterAndDomainIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
||||
await expectIxErr(connection, [ix], [payer], "already in use");
|
||||
// NOTE: This error actually triggers because a registered emitter is already present.
|
||||
// In case something changes with registration, we will keep this test around (it could
|
||||
// fail if registration changes in the future).
|
||||
await expectIxErr(
|
||||
connection,
|
||||
[ix],
|
||||
[payer],
|
||||
`Allocate: account Address { address: ${registeredEmitter.toString()}, base: None } already in use`,
|
||||
);
|
||||
});
|
||||
|
||||
it("Cannot Invoke `register_emitter_and_domain` with Updated Emitter on Same Chain", async () => {
|
||||
|
@ -211,23 +232,28 @@ describe("Circle Integration -- Localnet", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const ix = await wormholeCctp.registerEmitterAndDomainIx({
|
||||
const ix = await circleIntegration.registerEmitterAndDomainIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
||||
const registeredEmtiter = wormholeCctp.registeredEmitterAddress(foreignChain);
|
||||
const registeredEmtiter = circleIntegration.registeredEmitterAddress(foreignChain);
|
||||
|
||||
// Show that the foreign emitter about to be registered is not already written to the
|
||||
// account.
|
||||
{
|
||||
const currentForeignEmitter = await wormholeCctp
|
||||
const currentForeignEmitter = await circleIntegration
|
||||
.fetchRegisteredEmitter(registeredEmtiter)
|
||||
.then((registered) => registered.address);
|
||||
expect(currentForeignEmitter).not.eql(foreignEmitter);
|
||||
}
|
||||
|
||||
await expectIxErr(connection, [ix], [payer], "already in use");
|
||||
await expectIxErr(
|
||||
connection,
|
||||
[ix],
|
||||
[payer],
|
||||
`Allocate: account Address { address: ${registeredEmtiter.toString()}, base: None } already in use`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -244,7 +270,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const inputPayload = Buffer.from("All your base are belong to us.");
|
||||
|
||||
const message = anchor.web3.Keypair.generate();
|
||||
const ix = await wormholeCctp.transferTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.transferTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
mint: USDC_MINT_ADDRESS,
|
||||
|
@ -260,14 +286,27 @@ describe("Circle Integration -- Localnet", () => {
|
|||
},
|
||||
);
|
||||
|
||||
const approveIx = splToken.createApproveInstruction(
|
||||
payerToken,
|
||||
circleIntegration.custodianAddress(),
|
||||
payer.publicKey,
|
||||
1,
|
||||
);
|
||||
|
||||
const lookupTableAccount = await connection
|
||||
.getAddressLookupTable(lookupTableAddress)
|
||||
.then((resp) => resp.value);
|
||||
|
||||
/// NOTE: This is a CCTP Token Messenger Minter program error.
|
||||
await expectIxErr(connection, [ix], [payer, message], "InvalidAmount", {
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
});
|
||||
await expectIxErr(
|
||||
connection,
|
||||
[approveIx, ix],
|
||||
[payer, message],
|
||||
"Error Code: InvalidAmount",
|
||||
{
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("Cannot Invoke `transfer_tokens_with_payload` with Invalid Mint Recipient", async () => {
|
||||
|
@ -282,7 +321,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const inputPayload = Buffer.from("All your base are belong to us.");
|
||||
|
||||
const message = anchor.web3.Keypair.generate();
|
||||
const ix = await wormholeCctp.transferTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.transferTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
mint: USDC_MINT_ADDRESS,
|
||||
|
@ -298,25 +337,35 @@ describe("Circle Integration -- Localnet", () => {
|
|||
},
|
||||
);
|
||||
|
||||
const approveIx = splToken.createApproveInstruction(
|
||||
payerToken,
|
||||
circleIntegration.custodianAddress(),
|
||||
payer.publicKey,
|
||||
amount,
|
||||
);
|
||||
|
||||
const lookupTableAccount = await connection
|
||||
.getAddressLookupTable(lookupTableAddress)
|
||||
.then((resp) => resp.value);
|
||||
|
||||
/// NOTE: This is a CCTP Token Messenger Minter program error.
|
||||
await expectIxErr(connection, [ix], [payer, message], "InvalidMintRecipient", {
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
});
|
||||
await expectIxErr(
|
||||
connection,
|
||||
[approveIx, ix],
|
||||
[payer, message],
|
||||
"Error Code: InvalidMintRecipient",
|
||||
{
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("Cannot Invoke `transfer_tokens_with_payload` if Burn Sender Not Authority for Burn Source", async () => {
|
||||
it("Cannot Invoke `transfer_tokens_with_payload` if Custodian Not Delegated Authority", async () => {
|
||||
const payerToken = splToken.getAssociatedTokenAddressSync(
|
||||
USDC_MINT_ADDRESS,
|
||||
payer.publicKey,
|
||||
);
|
||||
|
||||
// Create another sender authority.
|
||||
const sender = anchor.web3.Keypair.generate();
|
||||
|
||||
const amount = 69n;
|
||||
const targetChain = 2;
|
||||
const mintRecipient = Array.from(Buffer.alloc(32, "deadbeef", "hex"));
|
||||
|
@ -324,13 +373,12 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const inputPayload = Buffer.from("All your base are belong to us.");
|
||||
|
||||
const message = anchor.web3.Keypair.generate();
|
||||
const ix = await wormholeCctp.transferTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.transferTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
mint: USDC_MINT_ADDRESS,
|
||||
burnSource: payerToken,
|
||||
coreMessage: message.publicKey,
|
||||
burnSourceAuthority: sender.publicKey,
|
||||
},
|
||||
{
|
||||
amount,
|
||||
|
@ -346,7 +394,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
.then((resp) => resp.value);
|
||||
|
||||
// NOTE: This is an SPL Token program error.
|
||||
await expectIxErr(connection, [ix], [payer, sender, message], "owner does not match", {
|
||||
await expectIxErr(connection, [ix], [payer, message], "Error: owner does not match", {
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
});
|
||||
});
|
||||
|
@ -364,7 +412,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const inputPayload = Buffer.from("All your base are belong to us.");
|
||||
|
||||
const message = anchor.web3.Keypair.generate();
|
||||
const ix = await wormholeCctp.transferTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.transferTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
mint: USDC_MINT_ADDRESS,
|
||||
|
@ -380,6 +428,13 @@ describe("Circle Integration -- Localnet", () => {
|
|||
},
|
||||
);
|
||||
|
||||
const approveIx = splToken.createApproveInstruction(
|
||||
payerToken,
|
||||
circleIntegration.custodianAddress(),
|
||||
payer.publicKey,
|
||||
amount,
|
||||
);
|
||||
|
||||
const balanceBefore = await splToken
|
||||
.getAccount(connection, payerToken)
|
||||
.then((token) => token.amount);
|
||||
|
@ -387,9 +442,14 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const lookupTableAccount = await connection
|
||||
.getAddressLookupTable(lookupTableAddress)
|
||||
.then((resp) => resp.value);
|
||||
const txReceipt = await expectIxOkDetails(connection, [ix], [payer, message], {
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
});
|
||||
const txReceipt = await expectIxOkDetails(
|
||||
connection,
|
||||
[approveIx, ix],
|
||||
[payer, message],
|
||||
{
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
},
|
||||
);
|
||||
|
||||
// Balance check.
|
||||
const balanceAfter = await splToken
|
||||
|
@ -402,7 +462,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const { deposit, payload } = Deposit.decode(posted.message.payload);
|
||||
expect(payload).to.eql(inputPayload);
|
||||
|
||||
const parsedTxData = await wormholeCctp.parseTransactionReceipt(txReceipt, [
|
||||
const parsedTxData = await circleIntegration.parseTransactionReceipt(txReceipt, [
|
||||
lookupTableAccount,
|
||||
]);
|
||||
expect(parsedTxData).has.length(1);
|
||||
|
@ -412,7 +472,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
|
||||
const burnMessage = CctpTokenBurnMessage.decode(txData.encodedCctpMessage);
|
||||
expect(burnMessage.sender).to.eql(
|
||||
Array.from(wormholeCctp.custodianAddress().toBuffer()),
|
||||
Array.from(circleIntegration.custodianAddress().toBuffer()),
|
||||
);
|
||||
expect(burnMessage.mintRecipient).to.eql(mintRecipient);
|
||||
|
||||
|
@ -435,112 +495,11 @@ describe("Circle Integration -- Localnet", () => {
|
|||
payloadLen: inputPayload.length,
|
||||
} as DepositHeader);
|
||||
|
||||
const foreignEmitter = await wormholeCctp
|
||||
.fetchRegisteredEmitter(wormholeCctp.registeredEmitterAddress(targetChain))
|
||||
const foreignEmitter = await circleIntegration
|
||||
.fetchRegisteredEmitter(circleIntegration.registeredEmitterAddress(targetChain))
|
||||
.then((registered) => registered.address);
|
||||
expect(targetCaller).to.eql(foreignEmitter);
|
||||
});
|
||||
|
||||
it("Invoke `transfer_tokens_with_payload` Multiple Times in One Transaction", async () => {
|
||||
const payerToken = splToken.getAssociatedTokenAddressSync(
|
||||
USDC_MINT_ADDRESS,
|
||||
payer.publicKey,
|
||||
);
|
||||
|
||||
const amount = 69n;
|
||||
const targetChain = 2;
|
||||
const mintRecipient = Array.from(Buffer.alloc(32, "deadbeef", "hex"));
|
||||
const wormholeMessageNonce = 420;
|
||||
const inputPayload = Buffer.from("Boop.");
|
||||
|
||||
const messages = [
|
||||
anchor.web3.Keypair.generate(),
|
||||
anchor.web3.Keypair.generate(),
|
||||
anchor.web3.Keypair.generate(),
|
||||
];
|
||||
const ixs = await Promise.all(
|
||||
messages.map((message) =>
|
||||
wormholeCctp.transferTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
mint: USDC_MINT_ADDRESS,
|
||||
burnSource: payerToken,
|
||||
coreMessage: message.publicKey,
|
||||
},
|
||||
{
|
||||
amount,
|
||||
targetChain,
|
||||
mintRecipient,
|
||||
wormholeMessageNonce,
|
||||
payload: inputPayload,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const balanceBefore = await splToken
|
||||
.getAccount(connection, payerToken)
|
||||
.then((token) => token.amount);
|
||||
|
||||
const lookupTableAccount = await connection
|
||||
.getAddressLookupTable(lookupTableAddress)
|
||||
.then((resp) => resp.value);
|
||||
const txReceipt = await expectIxOkDetails(connection, ixs, [payer].concat(messages), {
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
});
|
||||
|
||||
// Balance check.
|
||||
const balanceAfter = await splToken
|
||||
.getAccount(connection, payerToken)
|
||||
.then((token) => token.amount);
|
||||
expect(balanceAfter + BigInt(messages.length) * amount).to.equal(balanceBefore);
|
||||
|
||||
const parsedTxData = await wormholeCctp.parseTransactionReceipt(txReceipt, [
|
||||
lookupTableAccount,
|
||||
]);
|
||||
expect(parsedTxData).has.length(messages.length);
|
||||
|
||||
const foreignEmitter = await wormholeCctp
|
||||
.fetchRegisteredEmitter(wormholeCctp.registeredEmitterAddress(targetChain))
|
||||
.then((registered) => registered.address);
|
||||
|
||||
for (let i = 0; i < messages.length; ++i) {
|
||||
const txData = parsedTxData[i];
|
||||
const message = messages[i];
|
||||
expect(txData.coreMessageAccount).is.eql(message.publicKey);
|
||||
|
||||
// Check messages.
|
||||
const posted = await getPostedMessage(connection, message.publicKey);
|
||||
const { deposit, payload } = Deposit.decode(posted.message.payload);
|
||||
expect(payload).to.eql(inputPayload);
|
||||
|
||||
const burnMessage = CctpTokenBurnMessage.decode(txData.encodedCctpMessage);
|
||||
expect(burnMessage.sender).to.eql(
|
||||
Array.from(wormholeCctp.custodianAddress().toBuffer()),
|
||||
);
|
||||
expect(burnMessage.mintRecipient).to.eql(mintRecipient);
|
||||
|
||||
const {
|
||||
cctp: {
|
||||
sourceDomain: sourceCctpDomain,
|
||||
destinationDomain: destinationCctpDomain,
|
||||
nonce: cctpNonce,
|
||||
targetCaller,
|
||||
},
|
||||
} = burnMessage;
|
||||
expect(deposit).to.eql({
|
||||
tokenAddress: Array.from(USDC_MINT_ADDRESS.toBuffer()),
|
||||
amount,
|
||||
sourceCctpDomain,
|
||||
destinationCctpDomain,
|
||||
cctpNonce,
|
||||
burnSource: Array.from(payerToken.toBuffer()),
|
||||
mintRecipient,
|
||||
payloadLen: inputPayload.length,
|
||||
} as DepositHeader);
|
||||
expect(targetCaller).to.eql(foreignEmitter);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Inbound Transfers", () => {
|
||||
|
@ -571,7 +530,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex"));
|
||||
const { burnMessage, destinationCctpDomain, encodedCctpMessage, cctpAttestation } =
|
||||
await craftCctpTokenBurnMessage(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
sourceCctpDomain,
|
||||
cctpNonce,
|
||||
encodedMintRecipient,
|
||||
|
@ -605,7 +564,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const computeIx = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: 250_000,
|
||||
});
|
||||
const ix = await wormholeCctp.redeemTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.redeemTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
|
@ -649,7 +608,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex"));
|
||||
const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } =
|
||||
await craftCctpTokenBurnMessage(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
sourceCctpDomain,
|
||||
cctpNonce,
|
||||
encodedMintRecipient,
|
||||
|
@ -686,7 +645,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const computeIx = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: 250_000,
|
||||
});
|
||||
const ix = await wormholeCctp.redeemTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.redeemTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
|
@ -730,7 +689,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex"));
|
||||
const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } =
|
||||
await craftCctpTokenBurnMessage(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
sourceCctpDomain,
|
||||
cctpNonce,
|
||||
encodedMintRecipient,
|
||||
|
@ -764,7 +723,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const computeIx = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: 250_000,
|
||||
});
|
||||
const ix = await wormholeCctp.redeemTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.redeemTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
|
@ -779,7 +738,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
connection,
|
||||
[computeIx, ix],
|
||||
[payer, mintRecipientAuthority],
|
||||
"InvalidMintRecipient",
|
||||
"Error Code: InvalidMintRecipient",
|
||||
{
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
},
|
||||
|
@ -806,7 +765,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex"));
|
||||
const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } =
|
||||
await craftCctpTokenBurnMessage(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
sourceCctpDomain,
|
||||
cctpNonce,
|
||||
encodedMintRecipient,
|
||||
|
@ -840,7 +799,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const computeIx = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: 250_000,
|
||||
});
|
||||
const ix = await wormholeCctp.redeemTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.redeemTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
|
@ -856,7 +815,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
connection,
|
||||
[computeIx, ix],
|
||||
[payer, someoneElse],
|
||||
"ConstraintTokenOwner",
|
||||
"Error Code: ConstraintTokenOwner",
|
||||
{
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
},
|
||||
|
@ -881,7 +840,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex"));
|
||||
const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } =
|
||||
await craftCctpTokenBurnMessage(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
sourceCctpDomain,
|
||||
cctpNonce,
|
||||
encodedMintRecipient,
|
||||
|
@ -915,7 +874,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const computeIx = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: 250_000,
|
||||
});
|
||||
const ix = await wormholeCctp.redeemTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.redeemTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
|
@ -930,7 +889,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
connection,
|
||||
[computeIx, ix],
|
||||
[payer, mintRecipientAuthority],
|
||||
"SourceCctpDomainMismatch",
|
||||
"Error Code: SourceCctpDomainMismatch",
|
||||
{
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
},
|
||||
|
@ -955,7 +914,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex"));
|
||||
const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } =
|
||||
await craftCctpTokenBurnMessage(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
sourceCctpDomain,
|
||||
cctpNonce,
|
||||
encodedMintRecipient,
|
||||
|
@ -989,7 +948,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const computeIx = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: 250_000,
|
||||
});
|
||||
const ix = await wormholeCctp.redeemTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.redeemTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
|
@ -1004,7 +963,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
connection,
|
||||
[computeIx, ix],
|
||||
[payer, mintRecipientAuthority],
|
||||
"DestinationCctpDomainMismatch",
|
||||
"Error Code: DestinationCctpDomainMismatch",
|
||||
{
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
},
|
||||
|
@ -1029,7 +988,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex"));
|
||||
const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } =
|
||||
await craftCctpTokenBurnMessage(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
sourceCctpDomain,
|
||||
cctpNonce,
|
||||
encodedMintRecipient,
|
||||
|
@ -1063,7 +1022,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const computeIx = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: 250_000,
|
||||
});
|
||||
const ix = await wormholeCctp.redeemTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.redeemTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
|
@ -1078,7 +1037,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
connection,
|
||||
[computeIx, ix],
|
||||
[payer, mintRecipientAuthority],
|
||||
"CctpNonceMismatch",
|
||||
"Error Code: CctpNonceMismatch",
|
||||
{
|
||||
addressLookupTableAccounts: [lookupTableAccount],
|
||||
},
|
||||
|
@ -1103,7 +1062,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const burnSource = Array.from(Buffer.alloc(32, "beefdead", "hex"));
|
||||
const { destinationCctpDomain, burnMessage, encodedCctpMessage, cctpAttestation } =
|
||||
await craftCctpTokenBurnMessage(
|
||||
wormholeCctp,
|
||||
circleIntegration,
|
||||
sourceCctpDomain,
|
||||
cctpNonce,
|
||||
encodedMintRecipient,
|
||||
|
@ -1137,7 +1096,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
const computeIx = anchor.web3.ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: 250_000,
|
||||
});
|
||||
const ix = await wormholeCctp.redeemTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.redeemTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
|
@ -1183,7 +1142,7 @@ describe("Circle Integration -- Localnet", () => {
|
|||
) as anchor.web3.Keypair;
|
||||
expect(localVariables.delete("mintRecipientAuthority")).is.true;
|
||||
|
||||
const ix = await wormholeCctp.redeemTokensWithPayloadIx(
|
||||
const ix = await circleIntegration.redeemTokensWithPayloadIx(
|
||||
{
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
|
@ -1196,14 +1155,14 @@ describe("Circle Integration -- Localnet", () => {
|
|||
connection,
|
||||
[ix],
|
||||
[payer, mintRecipientAuthority],
|
||||
"NonceAlreadyUsed",
|
||||
"Error Code: NonceAlreadyUsed",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function craftCctpTokenBurnMessage(
|
||||
wormholeCctp: WormholeCctpProgram,
|
||||
circleIntegration: CircleIntegrationProgram,
|
||||
sourceCctpDomain: number,
|
||||
cctpNonce: bigint,
|
||||
encodedMintRecipient: number[],
|
||||
|
@ -1213,13 +1172,13 @@ async function craftCctpTokenBurnMessage(
|
|||
) {
|
||||
const { destinationCctpDomain: inputDestinationCctpDomain } = overrides;
|
||||
|
||||
const messageTransmitterProgram = wormholeCctp.messageTransmitterProgram();
|
||||
const messageTransmitterProgram = circleIntegration.messageTransmitterProgram();
|
||||
const { version, localDomain } = await messageTransmitterProgram.fetchMessageTransmitterConfig(
|
||||
messageTransmitterProgram.messageTransmitterConfigAddress(),
|
||||
);
|
||||
const destinationCctpDomain = inputDestinationCctpDomain ?? localDomain;
|
||||
|
||||
const tokenMessengerMinterProgram = wormholeCctp.tokenMessengerMinterProgram();
|
||||
const tokenMessengerMinterProgram = circleIntegration.tokenMessengerMinterProgram();
|
||||
const sourceTokenMessenger = await tokenMessengerMinterProgram
|
||||
.fetchRemoteTokenMessenger(
|
||||
tokenMessengerMinterProgram.remoteTokenMessengerAddress(sourceCctpDomain),
|
||||
|
@ -1234,7 +1193,7 @@ async function craftCctpTokenBurnMessage(
|
|||
nonce: cctpNonce,
|
||||
sender: sourceTokenMessenger,
|
||||
recipient: Array.from(tokenMessengerMinterProgram.ID.toBuffer()), // targetTokenMessenger
|
||||
targetCaller: Array.from(wormholeCctp.custodianAddress().toBuffer()), // targetCaller
|
||||
targetCaller: Array.from(circleIntegration.custodianAddress().toBuffer()), // targetCaller
|
||||
},
|
||||
0,
|
||||
Array.from(wormholeSdk.tryNativeToUint8Array(ETHEREUM_USDC_ADDRESS, "ethereum")), // sourceTokenAddress
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { MockEmitter, MockGuardians } from "@certusone/wormhole-sdk/lib/cjs/mock";
|
||||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { expect } from "chai";
|
||||
import { WormholeCctpProgram } from "../src";
|
||||
import { CircleIntegrationProgram } from "../src";
|
||||
import {
|
||||
GUARDIAN_KEY,
|
||||
PAYER_PRIVATE_KEY,
|
||||
|
@ -22,7 +22,7 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
const connection = new anchor.web3.Connection("http://localhost:8899", "processed");
|
||||
const payer = anchor.web3.Keypair.fromSecretKey(PAYER_PRIVATE_KEY);
|
||||
|
||||
const wormholeCctp = new WormholeCctpProgram(
|
||||
const circleIntegration = new CircleIntegrationProgram(
|
||||
connection,
|
||||
"wCCTPvsyeL9qYqbHTv3DUAyzEfYcyHoYw5c4mgcbBeW",
|
||||
);
|
||||
|
@ -33,7 +33,7 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
it("Deploy Implementation", async () => {
|
||||
const implementation = await loadProgramBpf(
|
||||
ARTIFACTS_PATH,
|
||||
wormholeCctp.upgradeAuthorityAddress(),
|
||||
circleIntegration.upgradeAuthorityAddress(),
|
||||
);
|
||||
|
||||
localVariables.set("implementation", implementation);
|
||||
|
@ -59,7 +59,7 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
},
|
||||
);
|
||||
|
||||
const ix = await wormholeCctp.upgradeContractIx({
|
||||
const ix = await circleIntegration.upgradeContractIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
@ -70,7 +70,7 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
it("Deploy Same Implementation and Invoke `upgrade_contract` with Another VAA", async () => {
|
||||
const implementation = await loadProgramBpf(
|
||||
ARTIFACTS_PATH,
|
||||
wormholeCctp.upgradeAuthorityAddress(),
|
||||
circleIntegration.upgradeAuthorityAddress(),
|
||||
);
|
||||
|
||||
const vaa = await postGovVaa(
|
||||
|
@ -89,7 +89,7 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
},
|
||||
);
|
||||
|
||||
const ix = await wormholeCctp.upgradeContractIx({
|
||||
const ix = await circleIntegration.upgradeContractIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
@ -104,22 +104,25 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
const vaa = localVariables.get("vaa") as anchor.web3.PublicKey;
|
||||
expect(localVariables.delete("vaa")).is.true;
|
||||
|
||||
const ix = await wormholeCctp.upgradeContractIx({
|
||||
const ix = await circleIntegration.upgradeContractIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
||||
// NOTE: The claim account created in the upgrade contract instruction doesn't trigger
|
||||
// the protection for a replay attack. The account data in the program data does. But
|
||||
// we will keep this test here just in case something changes in the future.
|
||||
await expectIxErr(connection, [ix], [payer], "invalid account data for instruction");
|
||||
});
|
||||
|
||||
it("Cannot Invoke `upgrade_contract` with Implementation Mismatch", async () => {
|
||||
const implementation = await loadProgramBpf(
|
||||
ARTIFACTS_PATH,
|
||||
wormholeCctp.upgradeAuthorityAddress(),
|
||||
circleIntegration.upgradeAuthorityAddress(),
|
||||
);
|
||||
const anotherImplementation = await loadProgramBpf(
|
||||
ARTIFACTS_PATH,
|
||||
wormholeCctp.upgradeAuthorityAddress(),
|
||||
circleIntegration.upgradeAuthorityAddress(),
|
||||
);
|
||||
|
||||
const vaa = await postGovVaa(
|
||||
|
@ -139,24 +142,24 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
);
|
||||
|
||||
// Create the upgrade instruction, but pass a different implementation.
|
||||
const ix = await wormholeCctp.upgradeContractIx({
|
||||
const ix = await circleIntegration.upgradeContractIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
buffer: implementation,
|
||||
});
|
||||
|
||||
await expectIxErr(connection, [ix], [payer], "ImplementationMismatch");
|
||||
await expectIxErr(connection, [ix], [payer], "Error Code: ImplementationMismatch");
|
||||
});
|
||||
|
||||
it("Cannot Invoke `upgrade_contract` with Invalid Governance Emitter", async () => {
|
||||
const implementation = await loadProgramBpf(
|
||||
ARTIFACTS_PATH,
|
||||
wormholeCctp.upgradeAuthorityAddress(),
|
||||
circleIntegration.upgradeAuthorityAddress(),
|
||||
);
|
||||
|
||||
// Create a bad governance emitter by using an invalid address.
|
||||
const invalidEmitter = new MockEmitter(
|
||||
wormholeCctp.ID.toBuffer().toString("hex"),
|
||||
circleIntegration.ID.toBuffer().toString("hex"),
|
||||
1,
|
||||
12121212,
|
||||
);
|
||||
|
@ -179,19 +182,19 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
);
|
||||
|
||||
// Create the upgrade instruction, but pass a different implementation.
|
||||
const ix = await wormholeCctp.upgradeContractIx({
|
||||
const ix = await circleIntegration.upgradeContractIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
buffer: implementation,
|
||||
});
|
||||
|
||||
await expectIxErr(connection, [ix], [payer], "InvalidGovernanceEmitter");
|
||||
await expectIxErr(connection, [ix], [payer], "Error Code: InvalidGovernanceEmitter");
|
||||
});
|
||||
|
||||
it("Cannot Invoke `upgrade_contract` with Governance For Another Chain", async () => {
|
||||
const implementation = await loadProgramBpf(
|
||||
ARTIFACTS_PATH,
|
||||
wormholeCctp.upgradeAuthorityAddress(),
|
||||
circleIntegration.upgradeAuthorityAddress(),
|
||||
);
|
||||
|
||||
const vaa = await postGovVaa(
|
||||
|
@ -210,18 +213,18 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
},
|
||||
);
|
||||
|
||||
const ix = await wormholeCctp.upgradeContractIx({
|
||||
const ix = await circleIntegration.upgradeContractIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
});
|
||||
|
||||
await expectIxErr(connection, [ix], [payer], "GovernanceForAnotherChain");
|
||||
await expectIxErr(connection, [ix], [payer], "Error Code: GovernanceForAnotherChain");
|
||||
});
|
||||
|
||||
it("Cannot Invoke `upgrade_contract` with Invalid Governance Action", async () => {
|
||||
const implementation = await loadProgramBpf(
|
||||
ARTIFACTS_PATH,
|
||||
wormholeCctp.upgradeAuthorityAddress(),
|
||||
circleIntegration.upgradeAuthorityAddress(),
|
||||
);
|
||||
|
||||
const vaa = await postGovVaa(
|
||||
|
@ -247,13 +250,13 @@ describe("Circle Integration -- Testnet Fork", () => {
|
|||
},
|
||||
);
|
||||
|
||||
const ix = await wormholeCctp.upgradeContractIx({
|
||||
const ix = await circleIntegration.upgradeContractIx({
|
||||
payer: payer.publicKey,
|
||||
vaa,
|
||||
buffer: implementation,
|
||||
});
|
||||
|
||||
await expectIxErr(connection, [ix], [payer], "InvalidGovernanceAction");
|
||||
await expectIxErr(connection, [ix], [payer], "Error Code: InvalidGovernanceAction");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -181,8 +181,8 @@ export async function loadProgramBpf(
|
|||
);
|
||||
|
||||
// Sometimes the validator fails to fetch a blockhash after this buffer gets loaded, so we wait
|
||||
// a bit to ensure that doesn't happen.
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
// a bit to ensure that doesn't happen. Uncomment this in if this is an issue.
|
||||
//await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
|
||||
// Return the pubkey for the buffer (our new program implementation).
|
||||
return buffer;
|
||||
|
|
Loading…
Reference in New Issue