Skip to content

Commit

Permalink
push everything into attestations
Browse files Browse the repository at this point in the history
  • Loading branch information
barnjamin committed Jan 2, 2024
1 parent bfb7a61 commit 77b6637
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 56 deletions.
151 changes: 98 additions & 53 deletions connect/src/protocols/gatewayTransfer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {
ChainToPlatform,
Network,
PlatformToChains,
chainToPlatform,
encoding,
toChain,
PlatformToChains,
} from "@wormhole-foundation/sdk-base";
import {
AttestationId,
AttestationReceipt,
ChainAddress,
ChainContext,
GatewayTransferDetails,
Expand All @@ -23,38 +25,42 @@ import {
WormholeMessageId,
gatewayTransferMsg,
isGatewayTransferDetails,
isIbcMessageId,
isTransactionIdentifier,
isWormholeMessageId,
toGatewayMsg,
toNative,
AttestationId,
} from "@wormhole-foundation/sdk-definitions";
import { signSendWait } from "../common";
import { fetchIbcXfer, isTokenBridgeVaaRedeemed, retry } from "../tasks";
import { Wormhole } from "../wormhole";
import { TransferState, WormholeTransfer } from "../wormholeTransfer";
import { TransferReceipt, TransferState, WormholeTransfer } from "../wormholeTransfer";

type GatewayContext<N extends Network> = ChainContext<
N,
ChainToPlatform<typeof GatewayTransfer.chain>,
typeof GatewayTransfer.chain
>;

export class GatewayTransfer<N extends Network = Network> implements WormholeTransfer<"IbcBridge"> {
type GatewayProtocols = "IbcBridge" | "TokenBridge";

export class GatewayTransfer<N extends Network = Network>
implements WormholeTransfer<GatewayProtocols>
{
static chain: "Wormchain" = "Wormchain";

private readonly wh: Wormhole<N>;

// state machine tracker
private _state: TransferState;

// Wormchain context
private readonly gateway: GatewayContext<N>;
// Wormchain IBC Bridge
private readonly gatewayIbcBridge: IbcBridge<N, "Cosmwasm", PlatformToChains<"Cosmwasm">>;
// Contract address
private readonly gatewayAddress: ChainAddress;

// state machine tracker
private _state: TransferState;

// cached message derived from transfer details
// note: we dont want to create multiple different ones since
// the nonce may change and we want to keep it consistent
Expand All @@ -64,17 +70,14 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra
transfer: GatewayTransferDetails;

// Transaction Ids from source chain
transactions: TransactionId[] = [];
txids: TransactionId[] = [];

// The corresponding vaa representing the GatewayTransfer
// on the source chain (if it came from outside cosmos and if its been completed and finalized)
vaas?: {
id: WormholeMessageId;
vaa?: TokenBridge.TransferVAA;
}[];
attestations?: AttestationReceipt<GatewayProtocols>[];

// Any transfers we do over ibc
ibcTransfers: IbcTransferInfo[] = [];
//ibcTransfers: IbcTransferInfo[] = [];

private constructor(
wh: Wormhole<N>,
Expand Down Expand Up @@ -152,7 +155,7 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra
}

const gt = new GatewayTransfer(wh, gtd, wc, wcibc);
gt.transactions = txns;
gt.txids = txns;

// Since we're picking up from somewhere we can move the
// state maching to initiated
Expand Down Expand Up @@ -301,13 +304,11 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra
if (this._state !== TransferState.Created)
throw new Error("Invalid state transition in `start`");

this.transactions = await (this.fromGateway()
? this._transferIbc(signer)
: this._transfer(signer));
this.txids = await (this.fromGateway() ? this._transferIbc(signer) : this._transfer(signer));

// Update State Machine
this._state = TransferState.SourceInitiated;
return this.transactions.map((tx) => tx.txid);
return this.txids.map((tx) => tx.txid);
}

private async _transfer(signer: Signer): Promise<TransactionId[]> {
Expand Down Expand Up @@ -353,9 +354,10 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra
if (this._state < TransferState.SourceInitiated || this._state > TransferState.Attested)
throw new Error("Invalid state transition in `fetchAttestation`");

const attestations: AttestationId[] = [];
if (!this.attestations) this.attestations = [];

const chain = this.wh.getChain(this.transfer.from.chain);

// collect ibc transfers and additional transaction ids
if (this.fromGateway()) {
// assume all the txs are from the same chain
Expand All @@ -368,16 +370,13 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra

// start by getting the IBC transfers into wormchain
// from the cosmos chain
this.ibcTransfers = (
await Promise.all(
this.transactions.map((tx) => originIbcbridge.lookupTransferFromTx(tx.txid)),
)
const ibcTransfers = (
await Promise.all(this.txids.map((tx) => originIbcbridge.lookupTransferFromTx(tx.txid)))
).flat();

// I don't know why this would happen so lmk if you see this
if (this.ibcTransfers.length != 1) throw new Error("why?");
this.attestations.push(...ibcTransfers);

const [xfer] = this.ibcTransfers;
const [xfer] = ibcTransfers;
if (!this.toGateway()) {
// If we're leaving cosmos, grab the VAA from the gateway
// now find the corresponding wormchain transaction given the ibcTransfer info
Expand All @@ -392,27 +391,23 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra
if (!whm) throw new Error("Matching wormhole message not found after retries exhausted");

const vaa = await GatewayTransfer.getTransferVaa(this.wh, whm);
this.vaas = [{ id: whm, vaa }];

attestations.push(whm);
this.attestations.push({ id: whm, attestation: vaa });
} else {
// Otherwise we need to get the transfer on the destination chain
const dstChain = this.wh.getChain(this.transfer.to.chain);
const dstIbcBridge = await dstChain.getIbcBridge();
const ibcXfer = await dstIbcBridge.lookupTransferFromIbcMsgId(xfer!.id);
this.ibcTransfers.push(ibcXfer);
this.attestations.push(ibcXfer);
}
} else {
// Otherwise, we're coming from outside cosmos and
// we need to find the wormchain ibc transaction information
// by searching for the transaction containing the
// GatewayTransferMsg
const transferTransaction = this.transactions[this.transactions.length - 1]!;
const transferTransaction = this.txids[this.txids.length - 1]!;
const [whm] = await Wormhole.parseMessageFromTx(chain, transferTransaction.txid);
const vaa = await GatewayTransfer.getTransferVaa(this.wh, whm!);
this.vaas = [{ id: whm!, vaa }];

attestations.push(whm!);
this.attestations.push({ id: whm!, attestation: vaa });

// TODO: conf for these settings? how do we choose them?
const vaaRedeemedRetryInterval = 2000;
Expand Down Expand Up @@ -449,12 +444,15 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra
// TODO: check if pending and bail(?) if so
}

this.ibcTransfers.push(wcTransfer);
this.attestations.push(wcTransfer);

// Finally, get the IBC transfer to the destination chain
const destChain = this.wh.getChain(this.transfer.to.chain);
const destIbcBridge = await destChain.getIbcBridge();
//@ts-ignore
const destIbcBridge = (await destChain.getIbcBridge()) as IbcBridge<
N,
"Cosmwasm",
PlatformToChains<"Cosmwasm">
>;
const destTransferTask = () => fetchIbcXfer(destIbcBridge, wcTransfer.id);
const destTransfer = await retry<IbcTransferInfo>(
destTransferTask,
Expand All @@ -468,16 +466,11 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra
JSON.stringify(wcTransfer.id),
);

this.ibcTransfers.push(destTransfer);
this.attestations.push(destTransfer);
}

// Add transfers to attestations we return
// Note: there is no ordering guarantee here
attestations.push(...this.ibcTransfers.map((xfer) => xfer.id));

this._state = TransferState.Attested;

return attestations;
return this.attestations.map((xfer) => xfer.id);
}

// finish the WormholeTransfer by submitting transactions to the destination chain
Expand All @@ -494,10 +487,10 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra

if (this.toGateway())
// TODO: assuming the last transaction captured is the one from gateway to the destination
return [this.transactions[this.transactions.length - 1]!.txid];
return [this.txids[this.txids.length - 1]!.txid];

if (!this.vaas) throw new Error("No VAA details available to redeem");
if (this.vaas.length > 1) throw new Error("Expected 1 vaa");
if (!this.attestations) throw new Error("No VAA details available to redeem");
if (this.attestations.length > 1) throw new Error("Expected 1 vaa");

const { chain, address } = this.transfer.to;

Expand All @@ -508,12 +501,12 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra

const tb = await toChain.getTokenBridge();

const { vaa } = this.vaas[0]!;
if (!vaa) throw new Error(`No VAA found for ${this.vaas[0]!.id.sequence}`);
const { attestation } = this.attestations[0]!;
if (!attestation) throw new Error(`No VAA found for ${this.attestations[0]!.id.sequence}`);

const xfer = tb.redeem(toAddress, vaa);
const xfer = tb.redeem(toAddress, attestation as TokenBridge.TransferVAA);
const redeemTxs = await signSendWait<N, typeof toChain.chain>(toChain, xfer, signer);
this.transactions.push(...redeemTxs);
this.txids.push(...redeemTxs);
this._state = TransferState.DestinationInitiated;
return redeemTxs.map(({ txid }) => txid);
}
Expand All @@ -528,13 +521,65 @@ export class GatewayTransfer<N extends Network = Network> implements WormholeTra
return vaa;
}

static getReceipt<N extends Network>(
xfer: GatewayTransfer<N>,
): TransferReceipt<GatewayProtocols> {
const { transfer } = xfer;

const protocol = xfer.fromGateway() ? "IbcBridge" : "TokenBridge";
const from = transfer.from.chain;
const to = transfer.to.chain;

let receipt: Partial<TransferReceipt<GatewayProtocols>> = {
protocol,
request: transfer,
from: from,
to: to,
state: TransferState.Created,
};

const originTxs = xfer.txids.filter((txid) => txid.chain === transfer.from.chain);
if (originTxs.length > 0) {
receipt = { ...receipt, state: TransferState.SourceInitiated, originTxs: originTxs };
}

const ibcAtt = xfer.attestations.filter((a) =>
isIbcMessageId(a.id),
) as AttestationReceipt<"IbcBridge">[];

const whAtt = xfer.attestations.filter((a) =>
isWormholeMessageId(a.id),
) as AttestationReceipt<"TokenBridge">[];

const attestation = whAtt.length > 0 ? whAtt[0] : ibcAtt.length > 0 ? ibcAtt[0] : undefined;

if (attestation && attestation.attestation) {
receipt = {
...receipt,
state: TransferState.Attested,
attestation: attestation,
};
}

const destinationTxs = xfer.txids.filter((txid) => txid.chain === transfer.to.chain);
if (destinationTxs.length > 0) {
receipt = {
...receipt,
state: TransferState.DestinationInitiated,
destinationTxs: destinationTxs,
};
}

return receipt as TransferReceipt<GatewayProtocols>;
}

// Implicitly determine if the chain is Gateway enabled by
// checking to see if the Gateway IBC bridge has a transfer channel setup
// If this is a new chain, add the channels to the constants file
private fromGateway(): boolean {
fromGateway(): boolean {
return this.gatewayIbcBridge.getTransferChannel(this.transfer.from.chain) !== null;
}
private toGateway(): boolean {
toGateway(): boolean {
return this.gatewayIbcBridge.getTransferChannel(this.transfer.to.chain) !== null;
}
}
4 changes: 2 additions & 2 deletions connect/src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export async function isTokenBridgeVaaRedeemed<
}
}

export async function fetchIbcXfer<N extends Network, C extends PlatformToChains<"Cosmwasm">>(
wcIbc: IbcBridge<N, "Cosmwasm", C>,
export async function fetchIbcXfer<N extends Network>(
wcIbc: IbcBridge<N, "Cosmwasm", PlatformToChains<"Cosmwasm">>,
msg: TxHash | TransactionId | IbcMessageId | GatewayTransferMsg | GatewayTransferWithPayloadMsg,
): Promise<IbcTransferInfo | null> {
try {
Expand Down
2 changes: 1 addition & 1 deletion platforms/cosmwasm/protocols/ibc/src/ibc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class CosmwasmIbcBridge<N extends Network, C extends CosmwasmChains>
recipient: ChainAddress,
token: AnyCosmwasmAddress | "native",
amount: bigint,
): AsyncGenerator<CosmwasmUnsignedTransaction<N, C>> {
) {
const senderAddress = new CosmwasmAddress(sender).toString();
const nonce = Math.round(Math.random() * 10000);

Expand Down

0 comments on commit 77b6637

Please sign in to comment.