Skip to content

Commit 5f530e2

Browse files
authored
Core Protocol: Add method to provide unsigned VAAs from core bridge (#383)
1 parent 720e218 commit 5f530e2

File tree

10 files changed

+368
-90
lines changed

10 files changed

+368
-90
lines changed

core/definitions/src/protocols/core.ts

+15
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ declare global {
2222
export interface WormholeCore<N extends Network, C extends Chain> {
2323
/** Get the fee for publishing a message */
2424
getMessageFee(): Promise<bigint>;
25+
26+
/** Get the current guardian set index */
27+
getGuardianSetIndex(): Promise<number>;
28+
2529
/**
2630
* Publish a message
2731
*
@@ -39,6 +43,7 @@ export interface WormholeCore<N extends Network, C extends Chain> {
3943
nonce: number,
4044
consistencyLevel: number,
4145
): AsyncGenerator<UnsignedTransaction<N, C>>;
46+
4247
/**
4348
* Verify a VAA against the core contract
4449
* @param sender the sender of the transaction
@@ -47,6 +52,7 @@ export interface WormholeCore<N extends Network, C extends Chain> {
4752
* @returns a stream of unsigned transactions to be signed and submitted on chain
4853
*/
4954
verifyMessage(sender: AccountAddress<C>, vaa: VAA): AsyncGenerator<UnsignedTransaction<N, C>>;
55+
5056
/**
5157
* Parse a transaction to get its message id
5258
*
@@ -55,4 +61,13 @@ export interface WormholeCore<N extends Network, C extends Chain> {
5561
* @returns the message ids produced by the transaction
5662
*/
5763
parseTransaction(txid: TxHash): Promise<WormholeMessageId[]>;
64+
65+
/**
66+
* Parse a transaction to get the VAA message it produced
67+
*
68+
* @param txid the transaction hash to parse
69+
*
70+
* @returns the VAA message produced by the transaction
71+
*/
72+
parseMessages(txid: TxHash): Promise<VAA[]>;
5873
}

package-lock.json

+8-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

platforms/algorand/protocols/core/src/core.ts

+35-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
VAA,
77
WormholeCore,
88
WormholeMessageId,
9+
createVAA,
910
encoding,
1011
keccak256,
1112
serialize,
@@ -162,14 +163,30 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
162163
return val ? BigInt(val.value.uint) : 0n;
163164
}
164165

166+
async getGuardianSetIndex(): Promise<number> {
167+
throw new Error("Not implemented");
168+
}
169+
165170
async parseTransaction(txId: string): Promise<WormholeMessageId[]> {
171+
const result = await this.connection.pendingTransactionInformation(txId).do();
172+
const ptr = modelsv2.PendingTransactionResponse.from_obj_for_encoding(result);
173+
return this.parseTx(ptr).map((v) => {
174+
return {
175+
chain: v.emitterChain,
176+
emitter: v.emitterAddress,
177+
sequence: v.sequence,
178+
};
179+
});
180+
}
181+
182+
async parseMessages(txId: string): Promise<VAA[]> {
166183
const result = await this.connection.pendingTransactionInformation(txId).do();
167184
const ptr = modelsv2.PendingTransactionResponse.from_obj_for_encoding(result);
168185
return this.parseTx(ptr);
169186
}
170187

171-
private parseTx(ptr: modelsv2.PendingTransactionResponse): WormholeMessageId[] {
172-
const msgs: WormholeMessageId[] = [];
188+
private parseTx(ptr: modelsv2.PendingTransactionResponse): VAA[] {
189+
const msgs: VAA[] = [];
173190

174191
if (ptr.innerTxns && ptr.innerTxns.length > 0) {
175192
msgs.push(...ptr.innerTxns.flatMap((tx) => this.parseTx(tx)));
@@ -191,7 +208,22 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
191208

192209
const sequence = encoding.bignum.decode(ptr.logs[0]!);
193210
const emitter = new AlgorandAddress(ptr.txn.txn.snd).toUniversalAddress();
194-
msgs.push({ chain: this.chain, emitter, sequence });
211+
const payload = new Uint8Array(args[1]!);
212+
const nonce = encoding.bignum.decode(args[2]!);
213+
214+
msgs.push(
215+
createVAA("Uint8Array", {
216+
emitterChain: this.chain,
217+
emitterAddress: emitter,
218+
sequence,
219+
guardianSet: 0, // TODO: should we get this from the contract on init?
220+
timestamp: 0, // TODO: Would need to get the full block to get the timestamp
221+
consistencyLevel: 0,
222+
nonce: Number(nonce),
223+
payload,
224+
signatures: [],
225+
}),
226+
);
195227

196228
return msgs;
197229
}

platforms/aptos/protocols/core/src/core.ts

+43-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
VAA,
99
WormholeCore,
1010
WormholeMessageId,
11+
createVAA,
12+
encoding,
1113
toChainId,
1214
} from "@wormhole-foundation/connect-sdk";
1315
import {
@@ -36,6 +38,9 @@ export class AptosWormholeCore<N extends Network, C extends AptosChains>
3638
throw new Error(`CoreBridge contract Address for chain ${chain} not found`);
3739
this.coreBridge = coreBridgeAddress;
3840
}
41+
getGuardianSetIndex(): Promise<number> {
42+
throw new Error("Method not implemented.");
43+
}
3944
getMessageFee(): Promise<bigint> {
4045
throw new Error("Method not implemented.");
4146
}
@@ -62,20 +67,51 @@ export class AptosWormholeCore<N extends Network, C extends AptosChains>
6267
}
6368

6469
async parseTransaction(txid: string): Promise<WormholeMessageId[]> {
70+
const msgs = await this.parseMessages(txid);
71+
72+
return msgs.map((message) => {
73+
return {
74+
chain: message.emitterChain,
75+
emitter: message.emitterAddress,
76+
sequence: message.sequence,
77+
};
78+
});
79+
}
80+
async parseMessages(txid: string): Promise<VAA[]> {
6581
const transaction = await this.connection.getTransactionByHash(txid);
6682
if (transaction.type !== "user_transaction")
6783
throw new Error(`${txid} is not a user_transaction`);
6884

6985
const userTransaction = transaction as Types.UserTransaction;
70-
const message = userTransaction.events.find((event) => event.type.endsWith("WormholeMessage"));
71-
if (!message || !message.data) {
86+
const messages = userTransaction.events.filter((event) =>
87+
event.type.endsWith("WormholeMessage"),
88+
);
89+
if (!messages || messages.length === 0)
7290
throw new Error(`WormholeMessage not found for ${txid}`);
73-
}
7491

75-
const { sender, sequence } = message.data as { sender: string; sequence: string };
76-
// TODO: make this work for address
77-
const emitter = new UniversalAddress(BigInt(sender).toString(16).padStart(64, "0"));
92+
return messages.map((message) => {
93+
const msg = message.data as {
94+
consistency_level: number;
95+
nonce: string;
96+
payload: string;
97+
sender: string;
98+
sequence: string;
99+
timestamp: string;
100+
};
101+
102+
const emitter = new UniversalAddress(BigInt(msg.sender).toString(16).padStart(64, "0"));
78103

79-
return [{ chain: this.chain, emitter, sequence: BigInt(sequence) }] as WormholeMessageId[];
104+
return createVAA("Uint8Array", {
105+
guardianSet: 0, // TODO: need to implement guardian set idx
106+
emitterChain: this.chain,
107+
emitterAddress: emitter,
108+
sequence: BigInt(msg.sequence),
109+
timestamp: Number(msg.timestamp),
110+
consistencyLevel: msg.consistency_level,
111+
nonce: Number(msg.nonce),
112+
signatures: [],
113+
payload: encoding.hex.decode(msg.payload),
114+
});
115+
});
80116
}
81117
}

platforms/cosmwasm/protocols/core/src/core.ts

+39-11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
VAA,
1111
WormholeCore,
1212
WormholeMessageId,
13+
createVAA,
14+
encoding,
1315
} from "@wormhole-foundation/connect-sdk";
1416
import {
1517
AnyCosmwasmAddress,
@@ -36,6 +38,9 @@ export class CosmwasmWormholeCore<N extends Network, C extends CosmwasmChains>
3638

3739
this.coreAddress = coreAddress;
3840
}
41+
getGuardianSetIndex(): Promise<number> {
42+
throw new Error("Method not implemented.");
43+
}
3944

4045
getMessageFee(): Promise<bigint> {
4146
throw new Error("Method not implemented.");
@@ -66,13 +71,18 @@ export class CosmwasmWormholeCore<N extends Network, C extends CosmwasmChains>
6671
}
6772

6873
async parseTransaction(txid: string): Promise<WormholeMessageId[]> {
74+
const tx = await this.rpc.getTx(txid);
75+
if (!tx) throw new Error("No transaction found for txid: " + txid);
76+
return [CosmwasmWormholeCore.parseWormholeMessageId(this.chain, this.coreAddress, tx)];
77+
}
78+
79+
async parseMessages(txid: string): Promise<VAA[]> {
6980
const tx = await this.rpc.getTx(txid);
7081
if (!tx) throw new Error("No transaction found for txid: " + txid);
7182
return [CosmwasmWormholeCore.parseWormholeMessage(this.chain, this.coreAddress, tx)];
7283
}
7384

74-
// TODO: make consts
75-
static parseWormholeMessage(chain: Chain, coreAddress: string, tx: IndexedTx): WormholeMessageId {
85+
static parseWormholeMessage(chain: Chain, coreAddress: string, tx: IndexedTx): VAA {
7686
const events = tx.events.filter(
7787
(ev) =>
7888
ev.type === "wasm" &&
@@ -85,18 +95,36 @@ export class CosmwasmWormholeCore<N extends Network, C extends CosmwasmChains>
8595

8696
const [wasm] = events;
8797

88-
const sequence = wasm!.attributes.find((e) => {
89-
return e.key === "message.sequence";
90-
})!.value;
98+
const obj = Object.fromEntries(
99+
wasm!.attributes.map((attr) => {
100+
return [attr.key.split(".")[1]!, attr.value];
101+
}),
102+
);
103+
104+
return createVAA("Uint8Array", {
105+
emitterChain: chain,
106+
emitterAddress: new UniversalAddress(encoding.hex.decode(obj["sender"]!)),
107+
sequence: BigInt(obj["sequence"]!),
91108

92-
const emitter = wasm!.attributes.find((e) => {
93-
return e.key === "message.sender";
94-
})!.value;
109+
guardianSet: 0, // TODO: need to implement guardian set idx
110+
timestamp: Number(obj["block_time"]),
111+
consistencyLevel: 0,
112+
nonce: Number(obj["nonce"]),
113+
signatures: [],
114+
payload: encoding.hex.decode(obj["message"]!),
115+
});
116+
}
117+
static parseWormholeMessageId(
118+
chain: Chain,
119+
coreAddress: string,
120+
tx: IndexedTx,
121+
): WormholeMessageId {
122+
const unsignedVaa = CosmwasmWormholeCore.parseWormholeMessage(chain, coreAddress, tx);
95123

96124
return {
97-
chain: chain,
98-
emitter: new UniversalAddress(emitter),
99-
sequence: BigInt(sequence),
125+
chain: unsignedVaa.emitterChain,
126+
emitter: unsignedVaa.emitterAddress,
127+
sequence: unsignedVaa.sequence,
100128
};
101129
}
102130
}

platforms/cosmwasm/protocols/ibc/src/ibc.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export class CosmwasmIbcBridge<N extends Network, C extends CosmwasmChains>
172172
async lookupMessageFromIbcMsgId(msg: IbcMessageId): Promise<WormholeMessageId | null> {
173173
const tx = await this.lookupTxFromIbcMsgId(msg);
174174
if (!tx) return null;
175-
return CosmwasmWormholeCore.parseWormholeMessage(
175+
return CosmwasmWormholeCore.parseWormholeMessageId(
176176
Gateway.chain,
177177
Gateway.coreAddress(this.network),
178178
tx,

platforms/evm/protocols/core/src/core.ts

+38
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
VAA,
77
WormholeCore,
88
WormholeMessageId,
9+
createVAA,
910
isWormholeMessageId,
1011
} from '@wormhole-foundation/connect-sdk';
1112
import { Provider, TransactionRequest } from 'ethers';
@@ -61,6 +62,10 @@ export class EvmWormholeCore<N extends Network, C extends EvmChains>
6162
return await this.core.messageFee.staticCall();
6263
}
6364

65+
async getGuardianSetIndex(): Promise<number> {
66+
return Number(await this.core.getCurrentGuardianSetIndex.staticCall());
67+
}
68+
6469
static async fromRpc<N extends Network>(
6570
provider: Provider,
6671
config: ChainsConfig<N, EvmPlatformType>,
@@ -129,6 +134,39 @@ export class EvmWormholeCore<N extends Network, C extends EvmChains>
129134
.filter(isWormholeMessageId);
130135
}
131136

137+
async parseMessages(txid: string): Promise<VAA[]> {
138+
const receipt = await this.provider.getTransactionReceipt(txid);
139+
if (receipt === null) throw new Error('Could not get transaction receipt');
140+
const gsIdx = await this.getGuardianSetIndex();
141+
142+
return receipt.logs
143+
.filter((l: any) => {
144+
return l.address === this.coreAddress;
145+
})
146+
.map((log): VAA | undefined => {
147+
const { topics, data } = log;
148+
const parsed = this.coreIface.parseLog({
149+
topics: topics.slice(),
150+
data,
151+
});
152+
if (parsed === null) return undefined;
153+
154+
const emitterAddress = new EvmAddress(parsed.args['sender']);
155+
return createVAA('Uint8Array', {
156+
guardianSet: gsIdx, // TODO: should we get this from the contract on init?
157+
timestamp: 0, // TODO: Would need to get the full block to get the timestamp
158+
emitterChain: this.chain,
159+
emitterAddress: emitterAddress.toUniversalAddress(),
160+
consistencyLevel: Number(parsed.args['consistencyLevel']),
161+
sequence: BigInt(parsed.args['sequence']),
162+
nonce: Number(parsed.args['nonce']),
163+
signatures: [],
164+
payload: parsed.args['payload'],
165+
});
166+
})
167+
.filter((vaa) => !!vaa) as VAA[];
168+
}
169+
132170
private createUnsignedTx(
133171
txReq: TransactionRequest,
134172
description: string,

0 commit comments

Comments
 (0)