From 831b55cc2a08d1b256fa96cc8f9f9aa118c1e09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A1n=20Di=20Pietro?= Date: Mon, 8 Nov 2021 19:42:02 -0300 Subject: [PATCH] First VAA processor deployment :party: --- lib/pricecaster.js | 75 +++++++++++++++++++++++++++++++-- package-lock.json | 14 +++--- package.json | 3 +- tools/deploy-wh.js | 103 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 tools/deploy-wh.js diff --git a/lib/pricecaster.js b/lib/pricecaster.js index 569d7d76..23ec694c 100644 --- a/lib/pricecaster.js +++ b/lib/pricecaster.js @@ -4,8 +4,11 @@ const fs = require('fs') const { sha512_256 } = require('js-sha512') const tools = require('../tools/app-tools') -const approvalProgramFilename = 'teal/pricekeeper.teal' -const clearProgramFilename = 'teal/clearstate.teal' +const approvalProgramFilename = 'teal/pricekeeper/pricekeeper.teal' +const clearProgramFilename = 'teal/pricekeeper/clearstate.teal' +const vaaProcessorApprovalProgramFilename = 'teal/wormhole/build/vaa-processor-approval.teal' +const vaaProcessorClearProgramFilename = 'teal/wormhole/build/vaa-processor-clear.teal' +const vaaVerifyStatelessProgramFilename = 'teal/wormhole/build/vaa-verify.teal' class PricecasterLib { constructor (algodClient, ownerAddr = undefined) { @@ -113,7 +116,17 @@ class PricecasterLib { /** * Internal function. - * Compile application approval program. + * Compile application clear state program. + * @return {String} base64 string containing the compiled program + */ + this.compileVAAProcessorClearProgram = function () { + const program = fs.readFileSync(vaaProcessorClearProgramFilename, 'utf8') + return this.compileProgram(program) + } + + /** + * Internal function. + * Compile pricekeeper application approval program. * @return {String} base64 string containing the compiled program */ this.compileApprovalProgram = async function () { @@ -123,6 +136,18 @@ class PricecasterLib { return compiledApprovalProgram } + /** + * Internal function. + * Compile VAA Processor application approval program. + * @return {String} base64 string containing the compiled program + */ + this.compileVAAProcessorApprovalProgram = async function () { + const program = fs.readFileSync(vaaProcessorApprovalProgramFilename, 'utf8') + const compiledApprovalProgram = await this.compileProgram(program) + this.approvalProgramHash = compiledApprovalProgram.hash + return compiledApprovalProgram + } + /** * Helper function to retrieve the application id from a createApp transaction response. * @param {Object} txResponse object containig the transactionResponse of the createApp call @@ -180,6 +205,50 @@ class PricecasterLib { return txId } + /** + * Create the VAA Processor application based on the default approval and clearState programs or based on the specified files. + * @param {String} sender account used to sign the createApp transaction + * @param {String} gexpTime Guardian key set expiration time + * @param {String} gkeys Guardian keys listed as a single array + * @param {Function} signCallback callback with prototype signCallback(sender, tx) used to sign transactions + * @return {String} transaction id of the created application + */ + this.createVaaProcessorApp = async function (sender, gexpTime, gkeys, signCallback) { + const localInts = 0 + const localBytes = 0 + const globalInts = 2 + const globalBytes = 20 + + // declare onComplete as NoOp + const onComplete = algosdk.OnApplicationComplete.NoOpOC + + // get node suggested parameters + const params = await algodClient.getTransactionParams().do() + + params.fee = this.minFee + params.flatFee = true + + const compiledProgram = await this.compileVAAProcessorApprovalProgram() + const approvalProgramCompiled = compiledProgram.compiledBytes + const clearProgramCompiled = (await this.compileVAAProcessorClearProgram()).compiledBytes + const appArgs = [new Uint8Array(Buffer.from(gkeys, 'hex')), algosdk.encodeUint64(parseInt(gexpTime))] + + // create unsigned transaction + const txApp = algosdk.makeApplicationCreateTxn( + sender, params, onComplete, + approvalProgramCompiled, clearProgramCompiled, + localInts, localBytes, globalInts, globalBytes, appArgs + ) + const txId = txApp.txID().toString() + + // Sign the transaction + const txAppSigned = signCallback(sender, txApp) + + // Submit the transaction + await algodClient.sendRawTransaction(txAppSigned).do() + return txId + } + /** * Internal function. * Call application specifying args and accounts. diff --git a/package-lock.json b/package-lock.json index dac85951..5e0c298a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@certusone/wormhole-sdk": "^0.0.5", "@pythnetwork/client": "^2.3.1", "@randlabs/js-config-reader": "^1.1.0", - "algosdk": "^1.11.1", + "algosdk": "^1.12.0", "charm": "^1.0.2", "fastpriorityqueue": "^0.7.1", "js-sha512": "^0.8.0" @@ -1321,9 +1321,9 @@ } }, "node_modules/algosdk": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.11.1.tgz", - "integrity": "sha512-2B7Tz8NSaME1aQDrzvhAuTm67c/2KCXTeuzgKvB61YXUU3pe0zyDzEw7bgv1sC6lbbD6BihroiUtidP+Fdh+cQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.12.0.tgz", + "integrity": "sha512-Iqek0AwcCeXLywVg4E8gWWjmuPZ10P7PUmpZrlR71FSNyEtX4Ie+UgrNHWhkYnhyykRU5mjtvD4Hrb2eOepsGA==", "dependencies": { "algo-msgpack-with-bigint": "^2.1.1", "buffer": "^6.0.2", @@ -6960,9 +6960,9 @@ "integrity": "sha512-F1tGh056XczEaEAqu7s+hlZUDWwOBT70Eq0lfMpBP2YguSQVyxRbprLq5rELXKQOyOaixTWYhMeMQMzP0U5FoQ==" }, "algosdk": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.11.1.tgz", - "integrity": "sha512-2B7Tz8NSaME1aQDrzvhAuTm67c/2KCXTeuzgKvB61YXUU3pe0zyDzEw7bgv1sC6lbbD6BihroiUtidP+Fdh+cQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.12.0.tgz", + "integrity": "sha512-Iqek0AwcCeXLywVg4E8gWWjmuPZ10P7PUmpZrlR71FSNyEtX4Ie+UgrNHWhkYnhyykRU5mjtvD4Hrb2eOepsGA==", "requires": { "algo-msgpack-with-bigint": "^2.1.1", "buffer": "^6.0.2", diff --git a/package.json b/package.json index e52df5db..f3bb2788 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "start-doge": "npm run compile && cross-env PRICECASTER_SETTINGS=./settings/settings-doge.js node build/main.js", "test-pkeeper-sc": "mocha test/pkeeper-sc-test.js --timeout 60000", "test-wormhole-sc": "mocha test/wormhole-sc-test.js --timeout 60000" - }, "author": "Randlabs inc", "license": "ISC", @@ -20,7 +19,7 @@ "@certusone/wormhole-sdk": "^0.0.5", "@pythnetwork/client": "^2.3.1", "@randlabs/js-config-reader": "^1.1.0", - "algosdk": "^1.11.1", + "algosdk": "^1.12.0", "charm": "^1.0.2", "fastpriorityqueue": "^0.7.1", "js-sha512": "^0.8.0" diff --git a/tools/deploy-wh.js b/tools/deploy-wh.js new file mode 100644 index 00000000..e9779352 --- /dev/null +++ b/tools/deploy-wh.js @@ -0,0 +1,103 @@ +/* eslint-disable linebreak-style */ +const algosdk = require('algosdk') +const { exit } = require('process') +const readline = require('readline') +const PricecasterLib = require('../lib/pricecaster') +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}) +const spawnSync = require('child_process').spawnSync +const fs = require('fs') + +function ask (questionText) { + return new Promise((resolve) => { + rl.question(questionText, input => resolve(input)) + }) +} + +let globalMnemo = '' + +function signCallback (sender, tx) { + const txSigned = tx.signTxn(algosdk.mnemonicToSecretKey(globalMnemo).sk) + return txSigned +} + +async function startOp (algodClient, fromAddress, gexpTime, gkeys) { + console.log('Compiling VAA Processor program code...') + const out = spawnSync('python', ['teal/wormhole/pyteal/vaa-processor.py']) + console.log(out.output.toString()) + + // console.log('Compiling VAA Verify stateless program code...') + // out = spawnSync('python', ['teal/wormhole/pyteal/vaa-verify.py']) + // console.log(out.output.toString()) + + const pclib = new PricecasterLib.PricecasterLib(algodClient) + console.log('Creating new app...') + const txId = await pclib.createVaaProcessorApp(fromAddress, gexpTime, gkeys.join(''), signCallback) + console.log('txId: ' + txId) + const txResponse = await pclib.waitForTransactionResponse(txId) + const appId = pclib.appIdFromCreateAppResponse(txResponse) + console.log('Deployment App Id: %d', appId) +} + +(async () => { + console.log('\nVAA Processor for Wormhole Deployment Tool -- (c)2021-22 Randlabs, Inc.') + console.log('-----------------------------------------------------------------------\n') + + if (process.argv.length !== 6) { + console.log('Usage: deploy \n') + console.log('where:\n') + console.log('glistfile File containing the initial list of guardians') + console.log('gexptime Guardian set expiration time') + console.log('from Deployer account') + console.log('network Testnet, betanet or mainnet') + console.log('\nFile must contain one guardian key per line, formatted in hex, without hex prefix.') + exit(0) + } + + const listfile = process.argv[2] + const gexpTime = process.argv[3] + const fromAddress = process.argv[4] + const network = process.argv[5] + + const config = { server: '', apiToken: '', port: '' } + if (network === 'betanet') { + config.server = 'https://api.betanet.algoexplorer.io' + } else if (network === 'mainnet') { + config.server = 'https://api.algoexplorer.io' + } else if (network === 'testnet') { + config.server = 'https://api.testnet.algoexplorer.io' + } else { + console.error('Invalid network: ' + network) + exit(1) + } + + const fileDataStr = fs.readFileSync(listfile).toString() + const gkeys = fileDataStr.match(/[^\r\n]+/g) + if (!algosdk.isValidAddress(fromAddress)) { + console.error('Invalid deployer address: ' + fromAddress) + exit(1) + } + + const algodClient = new algosdk.Algodv2(config.apiToken, config.server, config.port) + + console.log('Parameters for deployment: ') + console.log('From: ' + fromAddress) + console.log('Network: ' + network) + console.log('Guardian expiration time: ' + gexpTime) + console.log(`Guardian Keys: (${gkeys.length}) ` + gkeys) + const answer = await ask('\nEnter YES to confirm parameters, anything else to abort. ') + if (answer !== 'YES') { + console.warn('Aborted by user.') + exit(1) + } + globalMnemo = await ask('\nEnter mnemonic for sender account.\nBE SURE TO DO THIS FROM A SECURED SYSTEM\n') + try { + await startOp(algodClient, fromAddress, gexpTime, gkeys) + } catch (e) { + console.error('(!) Deployment Failed: ' + e.toString()) + } + console.log('Bye.') + exit(0) +})()