First VAA processor deployment :party:

This commit is contained in:
Hernán Di Pietro 2021-11-08 19:42:02 -03:00
parent 1a7343b50a
commit 831b55cc2a
4 changed files with 183 additions and 12 deletions

View File

@ -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.

14
package-lock.json generated
View File

@ -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",

View File

@ -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"

103
tools/deploy-wh.js Normal file
View File

@ -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 <glistfile> <from> <network>\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)
})()