Skip to content

Commit

Permalink
make core bridge work
Browse files Browse the repository at this point in the history
  • Loading branch information
barnjamin committed Dec 29, 2023
1 parent d0fd509 commit a0f6fb5
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 67 deletions.
5 changes: 3 additions & 2 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"demo": "tsx src/index.ts",
"cosmos": "tsx src/cosmos.ts",
"retb": "cd .. && npm run build && cd - && npm run tb",
"msg": "tsx src/messaging.ts",
"msg": "cd ../platforms/algorand/protocols/core && npm run build && cd - && tsx src/messaging.ts",
"clean": "rm -rf ./dist && rm -f ./*.tsbuildinfo",
"lint": "npm run prettier && eslint --fix",
"prettier": "prettier --write ./src",
Expand All @@ -63,6 +63,7 @@
"@wormhole-foundation/connect-sdk-cosmwasm-tokenbridge": "^0.3.0-beta.6",
"@wormhole-foundation/connect-sdk-evm-cctp": "^0.3.0-beta.6",
"@wormhole-foundation/connect-sdk-solana-cctp": "^0.3.0-beta.6",
"@wormhole-foundation/connect-sdk-cosmwasm-ibc": "^0.3.0-beta.6"
"@wormhole-foundation/connect-sdk-cosmwasm-ibc": "^0.3.0-beta.6",
"algosdk":"^2.7.0"
}
}
21 changes: 13 additions & 8 deletions examples/src/messaging.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
import { Wormhole, encoding, signSendWait } from "@wormhole-foundation/connect-sdk";
import { SolanaPlatform } from "@wormhole-foundation/connect-sdk-solana";
import { AlgorandPlatform } from "@wormhole-foundation/connect-sdk-algorand";
import { getStuff } from "./helpers";

// register the protocol
import "@wormhole-foundation/connect-sdk-solana-core";
import "@wormhole-foundation/connect-sdk-algorand-core";

(async function () {
const wh = new Wormhole("Testnet", [SolanaPlatform]);
const wh = new Wormhole("Testnet", [AlgorandPlatform]);

const chain = wh.getChain("Solana");
const chain = wh.getChain("Algorand");
const { signer, address } = await getStuff(chain);

// Get a reference to the core messaging bridge
const coreBridge = await chain.getWormholeCore();

// Generate transactions, sign and send them
const publishTxs = coreBridge.publishMessage(address.address, encoding.bytes.encode("lol"), 0, 0);
const [txid] = await signSendWait(chain, publishTxs, signer);
const txids = await signSendWait(chain, publishTxs, signer);

// Grab the wormhole message from the transaction logs or storage
// Take the last txid in case multiple were sent
// the last one should be the one containing the relevant
// event or log info
const txid = txids[txids.length - 1];

// // Grab the wormhole message from the transaction logs or storage
const [whm] = await chain.parseTransaction(txid!.txid);

// Wait for the vaa with a timeout,
// // Wait for the vaa with a timeout,
const vaa = await wh.getVaa(whm!, "Uint8Array", 60_000);
console.log(vaa);
// can also search by txid but it takes longer to show up
// console.log(await wh.getVaaByTxHash(txid!.txid, "Uint8Array"));

const verifyTxs = coreBridge.verifyMessage(address.address, vaa!);
await signSendWait(chain, verifyTxs, signer);
console.log(await signSendWait(chain, verifyTxs, signer));

// reposting the vaa should result in 0 transactions being issued
// assuming you're reading your writes
Expand Down
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 75 additions & 40 deletions platforms/algorand/protocols/core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
ChainsConfig,
Contracts,
Network,
UniversalAddress,
VAA,
WormholeCore,
WormholeMessageId,
Expand All @@ -17,9 +16,18 @@ import {
AlgorandPlatformType,
AlgorandUnsignedTransaction,
AnyAlgorandAddress,
StorageLogicSig,
TransactionSignerPair,
safeBigIntToNumber,
} from "@wormhole-foundation/connect-sdk-algorand";
import { Algodv2, bytesToBigInt, decodeAddress, getApplicationAddress, modelsv2 } from "algosdk";
import {
Algodv2,
OnApplicationComplete,
getApplicationAddress,
makeApplicationCallTxnFromObject,
modelsv2,
} from "algosdk";
import { maybeCreateStorageTx } from "./storage";
import { submitVAAHeader } from "./vaa";

export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
Expand All @@ -31,6 +39,13 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
readonly tokenBridgeAppId: bigint;
readonly tokenBridgeAppAddress: string;

// method selector for verifying a VAA
static verifyVaa = encoding.bytes.encode("verifyVAA");
// method selector for verifying signatures of a VAA
static verifySigs = encoding.bytes.encode("verifySigs");
// method selector string for publishing a message
static publishMessage = encoding.bytes.encode("publishMessage");

constructor(
readonly network: N,
readonly chain: C,
Expand All @@ -55,9 +70,14 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
}

async *verifyMessage(sender: AnyAlgorandAddress, vaa: VAA) {
const appId = 0n;
const address = new AlgorandAddress(sender).toString();
const txset = await submitVAAHeader(this.connection, this.coreAppId, appId, vaa, address);
const txset = await submitVAAHeader(
this.connection,
this.coreAppId,
this.coreAppId,
vaa,
address,
);
for (const tx of txset.txs) {
yield this.createUnsignedTx(tx, "Core.verifyMessage");
}
Expand All @@ -74,49 +94,64 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
return new AlgorandWormholeCore(network as N, chain, connection, conf.contracts);
}

async *publishMessage(
sender: AnyAlgorandAddress,
message: string | Uint8Array,
): AsyncGenerator<AlgorandUnsignedTransaction<N, C>> {
throw new Error("Method not implemented.");
async *publishMessage(sender: AnyAlgorandAddress, message: Uint8Array) {
// Call core bridge to publish message
const _sender = new AlgorandAddress(sender);
const address = _sender.toString();
const suggestedParams = await this.connection.getTransactionParams().do();

const storage = StorageLogicSig.forEmitter(this.coreAppId, _sender.toUint8Array());

const {
accounts: [storageAddress],
txs,
} = await maybeCreateStorageTx(
this.connection,
address,
this.coreAppId,
storage,
suggestedParams,
);

for (const tx of txs) {
yield this.createUnsignedTx(tx, "Core.publishMessage", true);
}

const act = makeApplicationCallTxnFromObject({
from: address,
appIndex: safeBigIntToNumber(this.coreAppId),
appArgs: [AlgorandWormholeCore.publishMessage, message, encoding.bignum.toBytes(0n, 8)],
accounts: [storageAddress],
onComplete: OnApplicationComplete.NoOpOC,
suggestedParams,
});

yield this.createUnsignedTx({ tx: act }, "Core.publishMessage", true);
}

async parseTransaction(txId: string): Promise<WormholeMessageId[]> {
const result = await this.connection.pendingTransactionInformation(txId).do();
const emitterAddr = new UniversalAddress(this.getEmitterAddressAlgorand(this.tokenBridgeAppId));
const sequence = this.parseSequenceFromLogAlgorand(result);
return [
{
chain: this.chain,
emitter: emitterAddr,
sequence,
} as WormholeMessageId,
];
}
const ptr = modelsv2.PendingTransactionResponse.from_obj_for_encoding(result);

private getEmitterAddressAlgorand(appId: bigint): string {
const appAddr: string = getApplicationAddress(appId);
const decodedAppAddr: Uint8Array = decodeAddress(appAddr).publicKey;
const hexAppAddr: string = encoding.hex.encode(decodedAppAddr);
return hexAppAddr;
}
// Expect target is core app
if (BigInt(ptr.txn.txn.apid) !== this.coreAppId) throw new Error("Invalid app id");

private parseSequenceFromLogAlgorand(result: Record<string, any>): bigint {
let sequence: bigint | undefined;
const ptr = modelsv2.PendingTransactionResponse.from_obj_for_encoding(result);
if (ptr.innerTxns) {
const innerTxns = ptr.innerTxns;
innerTxns.forEach((txn) => {
if (txn?.logs && txn.logs.length > 0 && txn.logs[0]) {
sequence = bytesToBigInt(txn.logs[0].subarray(0, 8));
}
});
}
if (!sequence) {
throw new Error("parseSequenceFromLogAlgorand - Sequence not found");
}
return sequence;
// Expect publish messeage as first arg
const args = ptr.txn.txn.apaa;
if (
args.length !== 3 ||
!encoding.bytes.equals(new Uint8Array(args[0]), AlgorandWormholeCore.publishMessage)
)
throw new Error("Invalid transaction arguments");

if (!ptr.logs || ptr.logs.length === 0) throw new Error("No logs found to parse sequence");

const sequence = encoding.bignum.decode(ptr.logs[0]);
const emitter = new AlgorandAddress(ptr.txn.txn.snd).toUniversalAddress();

return [{ chain: this.chain, emitter, sequence }];
}

private createUnsignedTx(
txReq: TransactionSignerPair,
description: string,
Expand Down
15 changes: 8 additions & 7 deletions platforms/algorand/protocols/core/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ export async function storageAccountExists(
appId: bigint,
): Promise<boolean> {
try {
const acctAppInfo = client
const acctAppInfo = await client
.accountApplicationInformation(address, safeBigIntToNumber(appId))
.do();
return Object.keys(acctAppInfo).length > 0;
} catch (e) {}
} catch {}
return false;
}

Expand All @@ -56,13 +56,12 @@ export async function maybeCreateStorageTx(

const txs: TransactionSignerPair[] = [];

try {
const exists = await storageAccountExists(client, storageAddress, appId);
if (exists) return { accounts: [storageAddress], txs };
} catch {}
if (await storageAccountExists(client, storageAddress, appId))
return { accounts: [storageAddress], txs };

suggestedParams = suggestedParams ?? (await client.getTransactionParams().do());

// Pay the storage account some ALGO to min balance requirements
const seedTxn = makePaymentTxnWithSuggestedParamsFromObject({
from: senderAddr,
to: storageAddress,
Expand All @@ -72,11 +71,13 @@ export async function maybeCreateStorageTx(
seedTxn.fee = seedTxn.fee * 2;
txs.push({ tx: seedTxn, signer: null });

// Opt in to the app and rekey to the app address that is using
// this as storage
const optinTxn = makeApplicationOptInTxnFromObject({
from: storageAddress,
suggestedParams,
appIndex: safeBigIntToNumber(appId),
rekeyTo: appAddr,
suggestedParams,
});
optinTxn.fee = 0;
txs.push({
Expand Down
15 changes: 8 additions & 7 deletions platforms/algorand/protocols/core/src/vaa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
signLogicSigTransaction,
} from "algosdk";
import { maybeCreateStorageTx } from "./storage";
import { AlgorandWormholeCore } from "./core";

/**
* Submits just the header of the VAA
Expand All @@ -35,7 +36,10 @@ export async function submitVAAHeader(
appid: bigint,
vaa: VAA,
senderAddr: string,
suggestedParams?: SuggestedParams,
): Promise<TransactionSet> {
suggestedParams = suggestedParams ?? (await client.getTransactionParams().do());

let txs: TransactionSignerPair[] = [];

// Get storage acct for message ID
Expand All @@ -47,24 +51,22 @@ export async function submitVAAHeader(
const {
accounts: [seqAddr],
txs: seqOptInTxs,
} = await maybeCreateStorageTx(client, senderAddr, appid, msgStorage);
} = await maybeCreateStorageTx(client, senderAddr, appid, msgStorage, suggestedParams);
txs.push(...seqOptInTxs);

// Get storage account for Guardian set
const gsStorage = StorageLogicSig.forGuardianSet(coreId, vaa.guardianSet);
const {
accounts: [guardianAddr],
txs: guardianOptInTxs,
} = await maybeCreateStorageTx(client, senderAddr, coreId, gsStorage);
} = await maybeCreateStorageTx(client, senderAddr, coreId, gsStorage, suggestedParams);
txs.push(...guardianOptInTxs);

let accts: string[] = [seqAddr, guardianAddr];

// Get the Guardian keys
const keys: Uint8Array = await decodeLocalState(client, coreId, guardianAddr);

const suggestedParams: SuggestedParams = await client.getTransactionParams().do();

// We don't pass the entire payload in but instead just pass it pre-digested. This gets around size
// limitations with lsigs AND reduces the cost of the entire operation on a congested network by reducing the
// bytes passed into the transaction
Expand All @@ -78,7 +80,6 @@ export async function submitVAAHeader(

const SIG_LEN: number = 66;
const GuardianKeyLen: number = 20;
const verifySigArg: Uint8Array = encoding.bytes.encode("verifySigs");
const lsa = new LogicSigAccount(ALGO_VERIFY);

for (let nt = 0; nt < numTxns; nt++) {
Expand All @@ -103,7 +104,7 @@ export async function submitVAAHeader(

const appTxn = makeApplicationCallTxnFromObject({
appArgs: [
verifySigArg,
AlgorandWormholeCore.verifySigs,
encoding.bytes.concat(
...sigs.map((s) =>
encoding.bytes.concat(new Uint8Array([s.guardianIndex]), s.signature.encode()),
Expand All @@ -128,7 +129,7 @@ export async function submitVAAHeader(
});
}
const appTxn = makeApplicationCallTxnFromObject({
appArgs: [encoding.bytes.encode("verifyVAA"), serialize(vaa)],
appArgs: [AlgorandWormholeCore.verifyVaa, serialize(vaa)],
accounts: accts,
appIndex: safeBigIntToNumber(coreId),
from: senderAddr,
Expand Down
4 changes: 2 additions & 2 deletions platforms/algorand/src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ export class AlgorandPlatform<N extends Network> extends PlatformContext<N, Algo
});

// Simulation
const resp = await rpc.simulateRawTransactions(stxns).do();
console.log("Simulation Response: ", JSON.stringify(resp, null, 2));
// const resp = await rpc.simulateRawTransactions(stxns).do();
// console.log("Simulation Response: ", JSON.stringify(resp, null, 2));
// End simulation

const { txId } = await rpc.sendRawTransaction(stxns).do();
Expand Down
10 changes: 10 additions & 0 deletions platforms/algorand/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ export const StorageLogicSig = {
});
},

forEmitter: (appId: bigint, emitter: Uint8Array) => {
const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey;
return StorageLogicSig.fromData({
appId,
appAddress,
idx: 0n,
address: emitter,
});
},

fromData: (data: PopulateData) => {
// This patches the binary of the TEAL program used to store data
// to produce a logic sig that can be used to sign transactions
Expand Down

0 comments on commit a0f6fb5

Please sign in to comment.