Skip to content

Commit

Permalink
add isTransferComplete for solana cctp
Browse files Browse the repository at this point in the history
  • Loading branch information
barnjamin committed Dec 28, 2023
1 parent 40e74a1 commit 090ca53
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 16 deletions.
10 changes: 9 additions & 1 deletion connect/src/protocols/cctpTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,15 @@ export class CircleTransfer<N extends Network = Network>
toChain: ChainContext<N, Platform, Chain>,
attestation: Attestation<CircleTransferProtocol>,
) {
throw new Error("Not implemented");
// TODO: inferring from fields what type this is, we should
// have typeguards or require another argument to better deterimine
if ("message" in attestation) {
const cb = await toChain.getCircleBridge();
return cb.isTransferCompleted(attestation.message);
}
throw new Error("Not implemented for automatic circle bridge");
// const acb = await toChain.getAutomaticCircleBridge();
// return acb.isTransferCompleted(attestation);
}

static async getTransferVaa<N extends Network>(
Expand Down
3 changes: 1 addition & 2 deletions connect/src/wormholeTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
TokenTransferDetails,
TransactionId,
TxHash,
VAA,
} from "@wormhole-foundation/sdk-definitions";
import { Wormhole } from "./wormhole";

Expand Down Expand Up @@ -89,7 +88,7 @@ export type TransferQuote = {
export interface TransferProtocol<PN extends ProtocolName> {
isTransferComplete<N extends Network, P extends Platform, C extends PlatformToChains<P>>(
toChain: ChainContext<N, P, C>,
vaa: VAA,
attestation: AttestationId<PN>,
): Promise<boolean>;
validateTransferDetails<N extends Network>(
wh: Wormhole<N>,
Expand Down
1 change: 1 addition & 0 deletions core/definitions/src/protocols/circleBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export interface CircleBridge<
recipient: ChainAddress,
amount: bigint,
): AsyncGenerator<UnsignedTransaction<N, C>>;
isTransferCompleted(message: CircleBridge.Message): Promise<boolean>;
parseTransactionDetails(txid: string): Promise<CircleTransferMessage>;
}

Expand Down
11 changes: 10 additions & 1 deletion platforms/evm/protocols/cctp/src/circleBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
addChainId,
addFrom,
} from '@wormhole-foundation/connect-sdk-evm';
import { LogDescription, Provider, TransactionRequest } from 'ethers';
import { LogDescription, Provider, TransactionRequest, ethers } from 'ethers';
import { ethers_contracts } from '.';
//https://github.com/circlefin/evm-cctp-contracts

Expand Down Expand Up @@ -160,6 +160,15 @@ export class EvmCircleBridge<N extends Network, C extends EvmChains>
);
}

async isTransferCompleted(message: CircleBridge.Message): Promise<boolean> {
const cctpDomain = circle.circleChainId(message.sourceDomain);
const hash = ethers.keccak256(
ethers.solidityPacked(['uint32', 'uint64'], [cctpDomain, message.nonce]),
);
const result = this.msgTransmitter.usedNonces.staticCall(hash);
return result.toString() === '1';
}

// Fetch the transaction logs and parse the CircleTransferMessage
async parseTransactionDetails(txid: string): Promise<CircleTransferMessage> {
const receipt = await this.provider.getTransactionReceipt(txid);
Expand Down
33 changes: 32 additions & 1 deletion platforms/solana/protocols/cctp/src/circleBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
circle,
} from '@wormhole-foundation/connect-sdk';

import { EventParser, Program } from '@project-serum/anchor';
import { BN, EventParser, Program } from '@project-serum/anchor';
import { getAssociatedTokenAddressSync } from '@solana/spl-token';
import {
SolanaAddress,
Expand All @@ -26,8 +26,10 @@ import {
createReadOnlyTokenMessengerProgramInterface,
} from './utils';
import {
calculateFirstNonce,
createDepositForBurnInstruction,
createReceiveMessageInstruction,
nonceAccount,
} from './utils/instructions';

export class SolanaCircleBridge<N extends Network, C extends SolanaChains>
Expand Down Expand Up @@ -147,6 +149,35 @@ export class SolanaCircleBridge<N extends Network, C extends SolanaChains>
yield this.createUnsignedTx(transaction, 'CircleBridge.Transfer');
}

async isTransferCompleted(message: CircleBridge.Message): Promise<boolean> {
const usedNoncesAddress = nonceAccount(
message.nonce,
message.sourceDomain,
this.messageTransmitter.programId,
);

const firstNonce = calculateFirstNonce(message.nonce);

// usedNonces should be a [u64;100] where each bit is a nonce flag
const { usedNonces } =
// @ts-ignore --
await this.messageTransmitter.account.usedNonces.fetch(usedNoncesAddress);

// get the nonce index based on the account's first nonce
const nonceIndex = Number(message.nonce - firstNonce);

// get the the u64 the nonce's flag is in
const nonceElement = usedNonces[Math.floor(nonceIndex / 64)];
if (!nonceElement) throw new Error('Invalid nonce byte index');

// get the nonce flag index and build a bitmask
const nonceBitIndex = nonceIndex % 64;
const mask = new BN(1 << nonceBitIndex);

// If the flag is 0 it is _not_ used
return !nonceElement.and(mask).isZero();
}

// Fetch the transaction logs and parse the CircleTransferMessage
async parseTransactionDetails(txid: string): Promise<CircleTransferMessage> {
const tx = await this.connection.getTransaction(txid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,29 @@ import { SolanaAddress } from '@wormhole-foundation/connect-sdk-solana';
import { findProgramAddress } from '../accounts';
import { createMessageTransmitterProgramInterface } from '../program';

const MAX_NONCES_PER_ACCOUNT = 6400n;

export function calculateFirstNonce(nonce: bigint) {
return (
((nonce - BigInt(1)) / MAX_NONCES_PER_ACCOUNT) * MAX_NONCES_PER_ACCOUNT +
BigInt(1)
);
}
export function nonceAccount(
nonce: bigint,
sourceChain: circle.CircleChain,
messageTransmitterProgramId: PublicKey,
) {
const srcDomain = circle.toCircleChainId(sourceChain).toString();
const usedNonces = findProgramAddress(
'used_nonces',
messageTransmitterProgramId,
[srcDomain, calculateFirstNonce(nonce).toString()],
).publicKey;

return usedNonces;
}

export async function createReceiveMessageInstruction(
messageTransmitterProgramId: PublicKey,
tokenMessengerProgramId: PublicKey,
Expand Down Expand Up @@ -84,16 +107,11 @@ export async function createReceiveMessageInstruction(
).publicKey;

// Calculate the nonce PDA.
const maxNoncesPerAccount = 6400n;
const firstNonce =
((circleMessage.nonce - BigInt(1)) / maxNoncesPerAccount) *
maxNoncesPerAccount +
BigInt(1);
const usedNonces = findProgramAddress(
'used_nonces',
const usedNonces = nonceAccount(
circleMessage.nonce,
circleMessage.sourceDomain,
messageTransmitterProgramId,
[srcDomain, firstNonce.toString()],
).publicKey;
);

// Build the accountMetas list. These are passed as remainingAccounts for the TokenMessengerMinter CPI
const accountMetas: AccountMeta[] = [];
Expand Down
4 changes: 2 additions & 2 deletions platforms/solana/protocols/cctp/src/utils/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function createTokenMessengerProgramInterface(
provider?: Provider,
): Program<TokenMessenger> {
return new Program<TokenMessenger>(
idl.TokenMessengerIdl as TokenMessenger,
idl.TokenMessengerIdl,
new PublicKey(programId),
provider === undefined ? ({ connection: null } as any) : provider,
);
Expand All @@ -30,7 +30,7 @@ export function createMessageTransmitterProgramInterface(
provider?: Provider,
): Program<MessageTransmitter> {
return new Program<MessageTransmitter>(
idl.MessageTransmitterIdl as MessageTransmitter,
idl.MessageTransmitterIdl,
new PublicKey(programId),
provider === undefined ? ({ connection: null } as any) : provider,
);
Expand Down

0 comments on commit 090ca53

Please sign in to comment.