Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
barnjamin committed Dec 29, 2023
1 parent 7575b4c commit da3b608
Show file tree
Hide file tree
Showing 11 changed files with 519 additions and 592 deletions.
5 changes: 1 addition & 4 deletions examples/src/algoTokenBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
TokenTransfer,
TransferState,
Wormhole,
encoding,
isTokenId,
normalizeAmount,
} from "@wormhole-foundation/connect-sdk";
Expand All @@ -21,8 +20,6 @@ import { SolanaPlatform } from "@wormhole-foundation/connect-sdk-solana";
import "@wormhole-foundation/connect-sdk-algorand-tokenbridge";
import "@wormhole-foundation/connect-sdk-evm-tokenbridge";
import "@wormhole-foundation/connect-sdk-solana-tokenbridge";
import algosdk, { Algodv2 } from "algosdk";
import { AlgorandSigner } from "@wormhole-foundation/connect-sdk-algorand/src/testing";

/*
# Scenario | Status | TxID
Expand All @@ -46,7 +43,7 @@ import { AlgorandSigner } from "@wormhole-foundation/connect-sdk-algorand/src/te
const rcvChain = wh.getChain("Solana");

// Shortcut to allow transferring native gas token
// const token: TokenId | "native" = "native";
//const token: TokenId | "native" = "native";

const token = Wormhole.chainAddress("Algorand", "10458941"); // USDC on Algorand
// const token = Wormhole.chainAddress("Avalanche", "0x12EB0d635FD4C5692d779755Ba82b33F6439fc73"); // wUSDC on Avalanche
Expand Down
229 changes: 224 additions & 5 deletions platforms/algorand/protocols/core/src/core.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {
keccak256,
serialize,
ChainId,
ChainsConfig,
Contracts,
Expand All @@ -16,19 +18,28 @@ import {
AlgorandPlatformType,
AlgorandUnsignedTransaction,
AnyAlgorandAddress,
StorageLogicSig,
TransactionSet,
TransactionSignerPair,
safeBigIntToNumber,
ALGO_VERIFY,
ALGO_VERIFY_HASH,
MAX_SIGS_PER_TXN,
} from "@wormhole-foundation/connect-sdk-algorand";
import {
Algodv2,
LogicSigAccount,
OnApplicationComplete,
SuggestedParams,
Transaction,
getApplicationAddress,
makeApplicationCallTxnFromObject,
makeApplicationOptInTxnFromObject,
makePaymentTxnWithSuggestedParamsFromObject,
modelsv2,
signLogicSigTransaction,
} from "algosdk";
import { maybeCreateStorageTx } from "./storage";
import { submitVAAHeader } from "./vaa";

import { SEED_AMT, StorageLogicSig } from "./storage";

export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
implements WormholeCore<N, AlgorandPlatformType, C>
Expand All @@ -39,6 +50,8 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
readonly tokenBridgeAppId: bigint;
readonly tokenBridgeAppAddress: string;

// global state key for message fee
static feeKey = encoding.b64.encode("MessageFee");
// method selector for verifying a VAA
static verifyVaa = encoding.bytes.encode("verifyVAA");
// method selector for verifying signatures of a VAA
Expand Down Expand Up @@ -71,7 +84,7 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>

async *verifyMessage(sender: AnyAlgorandAddress, vaa: VAA, appId?: bigint) {
const address = new AlgorandAddress(sender).toString();
const txset = await submitVAAHeader(
const txset = await AlgorandWormholeCore.submitVAAHeader(
this.connection,
this.coreAppId,
appId ?? this.coreAppId,
Expand Down Expand Up @@ -106,7 +119,7 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
const {
accounts: [storageAddress],
txs,
} = await maybeCreateStorageTx(
} = await AlgorandWormholeCore.maybeCreateStorageTx(
this.connection,
address,
this.coreAppId,
Expand All @@ -130,6 +143,21 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
yield this.createUnsignedTx({ tx: act }, "Core.publishMessage", true);
}

/**
* Return the message fee for the core bridge
* @param client An Algodv2 client
* @param bridgeId The application ID of the core bridge
* @returns Promise with the message fee for the core bridge
*/
async getMessageFee(): Promise<bigint> {
const applInfoResp: Record<string, any> = await this.connection
.getApplicationByID(safeBigIntToNumber(this.coreAppId))
.do();
const appInfo = modelsv2.Application.from_obj_for_encoding(applInfoResp);
const val = appInfo.params.globalState.find((kv) => kv.key === AlgorandWormholeCore.feeKey);
return val ? BigInt(val.value.uint) : 0n;
}

async parseTransaction(txId: string): Promise<WormholeMessageId[]> {
const result = await this.connection.pendingTransactionInformation(txId).do();
const ptr = modelsv2.PendingTransactionResponse.from_obj_for_encoding(result);
Expand Down Expand Up @@ -164,6 +192,197 @@ export class AlgorandWormholeCore<N extends Network, C extends AlgorandChains>
return msgs;
}

/**
* Constructs opt in transactions
* @param client An Algodv2 client
* @param senderAddr Sender address
* @param appId Application ID
* @param storage StorageLogicSig
* @returns Address and array of TransactionSignerPairs
*/
static async maybeCreateStorageTx(
client: Algodv2,
senderAddr: string,
appId: bigint,
storage: LogicSigAccount,
suggestedParams?: SuggestedParams,
): Promise<TransactionSet> {
const appAddr: string = getApplicationAddress(appId);
const storageAddress = storage.address();

const txs: TransactionSignerPair[] = [];

if (await StorageLogicSig.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,
amount: SEED_AMT,
suggestedParams,
});
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,
appIndex: safeBigIntToNumber(appId),
rekeyTo: appAddr,
suggestedParams,
});
optinTxn.fee = 0;
txs.push({
tx: optinTxn,
signer: {
address: storage.address(),
signTxn: (txn: Transaction) => Promise.resolve(signLogicSigTransaction(txn, storage).blob),
},
});

return {
accounts: [storageAddress],
txs,
};
}

/**
* Submits just the header of the VAA
* @param client AlgodV2 client
* @param bridgeId Application ID of the core bridge
* @param vaa The VAA (just the header is used)
* @param senderAddr Sending account address
* @param appid Application ID
* @returns Promise with current VAA state
*/
static async submitVAAHeader(
client: Algodv2,
coreId: bigint,
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
const msgStorage = StorageLogicSig.forMessageId(appid, {
chain: vaa.emitterChain,
sequence: vaa.sequence,
emitter: vaa.emitterAddress,
});
const {
accounts: [seqAddr],
txs: seqOptInTxs,
} = await AlgorandWormholeCore.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 AlgorandWormholeCore.maybeCreateStorageTx(
client,
senderAddr,
coreId,
gsStorage,
suggestedParams,
);
txs.push(...guardianOptInTxs);

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

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

// 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
// This is a 2 pass digest
const digest = keccak256(vaa.hash);

// How many signatures can we process in a single txn... we can do 6!
// There are likely upwards of 19 signatures. So, we ned to split things up
const numSigs: number = vaa.signatures.length;
const numTxns: number = Math.floor(numSigs / MAX_SIGS_PER_TXN) + 1;

const SIG_LEN: number = 66;
const GuardianKeyLen: number = 20;
const lsa = new LogicSigAccount(ALGO_VERIFY);

for (let nt = 0; nt < numTxns; nt++) {
let sigs = vaa.signatures.slice(nt, nt + MAX_SIGS_PER_TXN);

// The keyset is the set of Guardians that correspond
// to the current set of signatures in this loop.
// Each signature in 20 bytes and comes from decodeLocalState()
let arraySize: number = sigs.length * GuardianKeyLen;
let keySet: Uint8Array = new Uint8Array(arraySize);

for (let i = 0; i < sigs.length; i++) {
// The first byte of the sig is the relative index of that signature in the signatures array
// Use that index to get the appropriate Guardian key
const sig = sigs[i * SIG_LEN];
const key = keys.slice(
sig.guardianIndex * GuardianKeyLen + 1,
(sig.guardianIndex + 1) * GuardianKeyLen + 1,
);
keySet.set(key, i * 20);
}

const appTxn = makeApplicationCallTxnFromObject({
appArgs: [
AlgorandWormholeCore.verifySigs,
encoding.bytes.concat(
...sigs.map((s) =>
encoding.bytes.concat(new Uint8Array([s.guardianIndex]), s.signature.encode()),
),
),
keySet,
digest,
],
accounts: accts,
appIndex: safeBigIntToNumber(coreId),
from: ALGO_VERIFY_HASH,
onComplete: OnApplicationComplete.NoOpOC,
suggestedParams,
});
appTxn.fee = 0;
txs.push({
tx: appTxn,
signer: {
address: lsa.address(),
signTxn: (txn: Transaction) => Promise.resolve(signLogicSigTransaction(txn, lsa).blob),
},
});
}
const appTxn = makeApplicationCallTxnFromObject({
appArgs: [AlgorandWormholeCore.verifyVaa, serialize(vaa)],
accounts: accts,
appIndex: safeBigIntToNumber(coreId),
from: senderAddr,
onComplete: OnApplicationComplete.NoOpOC,
suggestedParams,
});
appTxn.fee = appTxn.fee * (2 + numTxns); // Was 1
txs.push({ tx: appTxn, signer: null });

return { accounts: accts, txs };
}

private createUnsignedTx(
txReq: TransactionSignerPair,
description: string,
Expand Down
1 change: 0 additions & 1 deletion platforms/algorand/protocols/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ declare global {
registerProtocol(_platform, "WormholeCore", AlgorandWormholeCore);

export * from "./core";
export * from "./vaa";
export * from "./storage";
Loading

0 comments on commit da3b608

Please sign in to comment.