Test and lib fixes.

Change-Id: I4af5e0313ba04b322f428a15a19bc7b30c6ae027
This commit is contained in:
Hernán Di Pietro 2021-12-06 15:14:09 -03:00 committed by Leopold Schabel
parent 6c44c9f999
commit d9f35a1338
3 changed files with 127 additions and 51 deletions

View File

@ -5,6 +5,7 @@
* *
*/ */
const { LogicSigAccount } = require('algosdk')
const algosdk = require('algosdk') const algosdk = require('algosdk')
const fs = require('fs') const fs = require('fs')
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
@ -231,11 +232,12 @@ class PricecasterLib {
* Create the VAA Processor application based on the default approval and clearState programs or based on the specified files. * 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} sender account used to sign the createApp transaction
* @param {String} gexpTime Guardian key set expiration time * @param {String} gexpTime Guardian key set expiration time
* @param {String} gsindex Index of the guardian key set
* @param {String} gkeys Guardian keys listed as a single array * @param {String} gkeys Guardian keys listed as a single array
* @param {Function} signCallback callback with prototype signCallback(sender, tx) used to sign transactions * @param {Function} signCallback callback with prototype signCallback(sender, tx) used to sign transactions
* @return {String} transaction id of the created application * @return {String} transaction id of the created application
*/ */
this.createVaaProcessorApp = async function (sender, gexpTime, gkeys, signCallback) { this.createVaaProcessorApp = async function (sender, gexpTime, gsindex, gkeys, signCallback) {
const localInts = 0 const localInts = 0
const localBytes = 0 const localBytes = 0
const globalInts = 4 const globalInts = 4
@ -253,7 +255,9 @@ class PricecasterLib {
const compiledProgram = await this.compileVAAProcessorApprovalProgram() const compiledProgram = await this.compileVAAProcessorApprovalProgram()
const approvalProgramCompiled = compiledProgram.compiledBytes const approvalProgramCompiled = compiledProgram.compiledBytes
const clearProgramCompiled = (await this.compileVAAProcessorClearProgram()).compiledBytes const clearProgramCompiled = (await this.compileVAAProcessorClearProgram()).compiledBytes
const appArgs = [new Uint8Array(Buffer.from(gkeys, 'hex')), algosdk.encodeUint64(parseInt(gexpTime))] const appArgs = [new Uint8Array(Buffer.from(gkeys, 'hex')),
algosdk.encodeUint64(parseInt(gexpTime)),
algosdk.encodeUint64(parseInt(gsindex))]
// create unsigned transaction // create unsigned transaction
const txApp = algosdk.makeApplicationCreateTxn( const txApp = algosdk.makeApplicationCreateTxn(
@ -514,6 +518,7 @@ class PricecasterLib {
*/ */
this.beginTxGroup = function () { this.beginTxGroup = function () {
this.groupTx = [] this.groupTx = []
this.lsigs = {}
} }
/** /**
@ -544,22 +549,49 @@ class PricecasterLib {
return tx.txId return tx.txId
} }
/**
* @param {*} sender The sender account.
* @param {function} signCallback The sign callback routine.
* @returns Transaction id.
*/
this.commitTxGroupSignedByLogic = async function () {
algosdk.assignGroupID(this.groupTx)
const signedGroup = []
for (const tx of this.groupTx) {
const stxn = algosdk.signLogicSigTransaction(tx, this.lsigs[tx.txID])
signedGroup.push(stxn.blob)
}
// Submit the transaction
const tx = await this.algodClient.sendRawTransaction(signedGroup).do()
this.groupTx = []
this.lsigs = {}
return tx.txId
}
/** /**
* VAA Processor: Add a verification step to a transaction group. * VAA Processor: Add a verification step to a transaction group.
* @param {*} sender The sender account (typically the VAA verification stateless program) * @param {*} sender The sender account (typically the VAA verification stateless program)
* @param {*} payload The VAA payload as Uint8Array. * @param {*} payload The VAA payload as Uint8Array.
* @param {*} gksubset An hex string containing the keys for the guardian subset in this step. * @param {*} gksubset An hex string containing the keys for the guardian subset in this step.
* @param {*} totalguardians The total number of known guardians. * @param {*} totalguardians The total number of known guardians.
* @param {LogicSigAccount} lsig The logic sig account that will sign this transaction
*/ */
this.addVerifyTx = function (sender, payload, gksubset, totalguardians) { this.addVerifyTx = function (sender, params, payload, gksubset, totalguardians, lsig) {
const appArgs = [] const appArgs = []
appArgs.push(new Uint8Array(Buffer.from(gksubset, 'hex')), algosdk.encodeUint64(parseInt(totalguardians))) appArgs.push(new Uint8Array(Buffer.from('verify')),
this.groupTx.push(algosdk.makeApplicationNoOpTxn(sender, new Uint8Array(Buffer.from(gksubset.join(''), 'hex')),
appArgs, algosdk.encodeUint64(parseInt(totalguardians)))
const tx = algosdk.makeApplicationNoOpTxn(sender,
params,
this.appId, this.appId,
undefined, undefined, undefined, undefined, appArgs, undefined, undefined, undefined,
payload)) new Uint8Array(payload))
this.groupTx.push()
this.groupTx.push(tx)
this.lsigs[tx.txID] = lsig
} }
} }
} }

View File

@ -64,7 +64,10 @@ SLOT_TEMP = ScratchVar(TealType.uint64, SLOTID_TEMP_0)
GOVERNANCE_CHAIN_ID = 1 GOVERNANCE_CHAIN_ID = 1
GOVERNANCE_EMITTER_ID = '00000000000000000000000000000000000000000000' GOVERNANCE_EMITTER_ID = '00000000000000000000000000000000000000000000'
PYTH2WORMHOLE_CHAIN_ID = 1 PYTH2WORMHOLE_CHAIN_ID = 1
PYTH2WORMHOLE_EMITTER_ID = '0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b' # PYTH2WORMHOLE_EMITTER_ID = '0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b'
# Testnet emitter.
PYTH2WORMHOLE_EMITTER_ID = '0x3afda841c1f43dd7d546c8a581ba1f92a139f4133f9f6ab095558f6a359df5d4'
# VAA fields # VAA fields

View File

@ -7,9 +7,12 @@ const chai = require('chai')
chai.use(require('chai-as-promised')) chai.use(require('chai-as-promised'))
const spawnSync = require('child_process').spawnSync const spawnSync = require('child_process').spawnSync
const fs = require('fs') const fs = require('fs')
const TestLib = require('../test/testlib.js')
const testLib = new TestLib.TestLib()
let pclib let pclib
let algodClient let algodClient
let verifyProgramHash
let compiledVerifyProgram
const OWNER_ADDR = 'OPDM7ACAW64Q4VBWAL77Z5SHSJVZZ44V3BAN7W44U43SUXEOUENZMZYOQU' const OWNER_ADDR = 'OPDM7ACAW64Q4VBWAL77Z5SHSJVZZ44V3BAN7W44U43SUXEOUENZMZYOQU'
const OWNER_MNEMO = 'assault approve result rare float sugar power float soul kind galaxy edit unusual pretty tone tilt net range pelican avoid unhappy amused recycle abstract master' const OWNER_MNEMO = 'assault approve result rare float sugar power float soul kind galaxy edit unusual pretty tone tilt net range pelican avoid unhappy amused recycle abstract master'
const OTHER_ADDR = 'DMTBK62XZ6KNI7L5E6TRBTPB4B3YNVB4WYGSWR42SEV4XKV4LYHGBW4O34' const OTHER_ADDR = 'DMTBK62XZ6KNI7L5E6TRBTPB4B3YNVB4WYGSWR42SEV4XKV4LYHGBW4O34'
@ -39,13 +42,33 @@ const gkeys = [
'f5F2b82576e6CA17965dee853d08bbB471FA2433', 'f5F2b82576e6CA17965dee853d08bbB471FA2433',
'2bC2B1204599D4cA0d4Dde4a658a42c4dD13103a' '2bC2B1204599D4cA0d4Dde4a658a42c4dD13103a'
] ]
const sigkeys = [
'563d8d2fd4e701901d3846dee7ae7a92c18f1975195264d676f8407ac5976757',
'8d97f25916a755df1d9ef74eb4dbebc5f868cb07830527731e94478cdc2b9d5f',
'9bd728ad7617c05c31382053b57658d4a8125684c0098f740a054d87ddc0e93b',
'5a02c4cd110d20a83a7ce8d1a2b2ae5df252b4e5f6781c7855db5cc28ed2d1b4',
'93d4e3b443bf11f99a00901222c032bd5f63cf73fc1bcfa40829824d121be9b2',
'ea40e40c63c6ff155230da64a2c44fcd1f1c9e50cacb752c230f77771ce1d856',
'87eaabe9c27a82198e618bca20f48f9679c0f239948dbd094005e262da33fe6a',
'61ffed2bff38648a6d36d6ed560b741b1ca53d45391441124f27e1e48ca04770',
'bd12a242c6da318fef8f98002efb98efbf434218a78730a197d981bebaee826e',
'20d3597bb16525b6d09e5fb56feb91b053d961ab156f4807e37d980f50e71aff',
'344b313ffbc0199ff6ca08cacdaf5dc1d85221e2f2dc156a84245bd49b981673',
'848b93264edd3f1a521274ca4da4632989eb5303fd15b14e5ec6bcaa91172b05',
'c6f2046c1e6c172497fc23bd362104e2f4460d0f61984938fa16ef43f27d93f6',
'693b256b1ee6b6fb353ba23274280e7166ab3be8c23c203cc76d716ba4bc32bf',
'13c41508c0da03018d61427910b9922345ced25e2bbce50652e939ee6e5ea56d',
'460ee0ee403be7a4f1eb1c63dd1edaa815fbaa6cf0cf2344dcba4a8acf9aca74',
'b25148579b99b18c8994b0b86e4dd586975a78fa6e7ad6ec89478d7fbafd2683',
'90d7ac6a82166c908b8cf1b352f3c9340a8d1f2907d7146fb7cd6354a5436cca',
'b71d23908e4cf5d6cd973394f3a4b6b164eb1065785feee612efdfd8d30005ed'
]
function makeVAA () { const PYTH_EMITTER = '0x3afda841c1f43dd7d546c8a581ba1f92a139f4133f9f6ab095558f6a359df5d4'
const PYTH_PAYLOAD = '50325748000101230abfe0ec3b460bd55fc4fb36356716329915145497202b8eb8bf1af6a0a3b9fe650f0367d4a7ef9815a593ea15d36593f0643aaaf0149bb04be67ab851decd010000002f17254388fffffff70000002eed73d9000000000070d3b43f0000000037faa03d000000000e9e555100000000894af11c0000000037faa03d000000000dda6eb801000000000061a5ff9a'
} async function createApp (gsexptime, gsindex, gkeys) {
const txId = await pclib.createVaaProcessorApp(OWNER_ADDR, gsexptime, gsindex, gkeys.join(''), signCallback)
async function createApp (gsexptime, gkeys) {
const txId = await pclib.createVaaProcessorApp(OWNER_ADDR, gsexptime, gkeys.join(''), signCallback)
const txResponse = await pclib.waitForTransactionResponse(txId) const txResponse = await pclib.waitForTransactionResponse(txId)
const appId = pclib.appIdFromCreateAppResponse(txResponse) const appId = pclib.appIdFromCreateAppResponse(txResponse)
pclib.setAppId(appId) pclib.setAppId(appId)
@ -86,11 +109,11 @@ describe('VAA Processor Smart-contract Tests', function () {
) )
it('Must fail to create app with incorrect guardian keys length', async function () { it('Must fail to create app with incorrect guardian keys length', async function () {
const gsexptime = 2524618800 const gsexptime = 2524618800
await expect(createApp(gsexptime, ['BADADDRESS'])).to.be.rejectedWith('Bad Request') await expect(createApp(gsexptime, 0, ['BADADDRESS'])).to.be.rejectedWith('Bad Request')
}) })
it('Must create app with initial guardians and proper initial state', async function () { it('Must create app with initial guardians and proper initial state', async function () {
const gsexptime = 2524618800 const gsexptime = 2524618800
appId = await createApp(gsexptime, gkeys) appId = await createApp(gsexptime, 0, gkeys)
console.log(' [Created appId: %d]', appId) console.log(' [Created appId: %d]', appId)
const gscount = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'gscount') const gscount = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'gscount')
@ -110,30 +133,19 @@ describe('VAA Processor Smart-contract Tests', function () {
const teal = 'test/temp/vaa-verify.teal' const teal = 'test/temp/vaa-verify.teal'
spawnSync('python', ['teal/wormhole/pyteal/vaa-verify.py', appId, teal]) spawnSync('python', ['teal/wormhole/pyteal/vaa-verify.py', appId, teal])
const program = fs.readFileSync(teal, 'utf8') const program = fs.readFileSync(teal, 'utf8')
const compiledProgram = await pclib.compileProgram(program) compiledVerifyProgram = await pclib.compileProgram(program)
const txid = await pclib.setVAAVerifyProgramHash(OWNER_ADDR, compiledProgram.hash, signCallback) verifyProgramHash = compiledVerifyProgram.hash
const txid = await pclib.setVAAVerifyProgramHash(OWNER_ADDR, verifyProgramHash, signCallback)
await pclib.waitForTransactionResponse(txid) await pclib.waitForTransactionResponse(txid)
const vphstate = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'vphash') const vphstate = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'vphash')
expect(vphstate).to.equal(compiledProgram.hash) expect(vphstate).to.equal(verifyProgramHash)
}) })
it('Must disallow setting stateless logic hash from non-owner', async function () { it('Must disallow setting stateless logic hash from non-owner', async function () {
const teal = 'test/temp/vaa-verify.teal' await expect(pclib.setVAAVerifyProgramHash(OTHER_ADDR, verifyProgramHash, signCallback)).to.be.rejectedWith('Bad Request')
spawnSync('python', ['teal/wormhole/pyteal/vaa-verify.py', appId, teal])
const program = fs.readFileSync(teal, 'utf8')
const compiledProgram = await pclib.compileProgram(program)
await expect(pclib.setVAAVerifyProgramHash(OTHER_ADDR, compiledProgram.hash, signCallback)).to.be.rejectedWith('Bad Request')
}) })
it('Must reject setting stateless logic hash from group transaction', async function () { it('Must reject setting stateless logic hash from group transaction', async function () {
const teal = 'test/temp/vaa-verify.teal' const appArgs = [new Uint8Array(Buffer.from('setvphash')), new Uint8Array(verifyProgramHash)]
spawnSync('python', ['teal/wormhole/pyteal/vaa-verify.py', appId, teal]) const params = await getTxParams()
const program = fs.readFileSync(teal, 'utf8')
const compiledProgram = await pclib.compileProgram(program)
const hash = algosdk.decodeAddress(compiledProgram.hash).publicKey
const appArgs = [new Uint8Array(Buffer.from('setvphash')), hash.subarray(0, 10)]
const params = await algodClient.getTransactionParams().do()
params.fee = 1000
params.flatFee = true
pclib.beginTxGroup() pclib.beginTxGroup()
const appTx = algosdk.makeApplicationNoOpTxn(OWNER_ADDR, params, this.appId, appArgs) const appTx = algosdk.makeApplicationNoOpTxn(OWNER_ADDR, params, this.appId, appArgs)
@ -143,25 +155,25 @@ describe('VAA Processor Smart-contract Tests', function () {
await expect(pclib.commitTxGroup(OWNER_ADDR, signCallback)).to.be.rejectedWith('Bad Request') await expect(pclib.commitTxGroup(OWNER_ADDR, signCallback)).to.be.rejectedWith('Bad Request')
}) })
it('Must reject setting stateless logic hash with invalid address length', async function () { it('Must reject setting stateless logic hash with invalid address length', async function () {
const teal = 'test/temp/vaa-verify.teal' const appArgs = [new Uint8Array(Buffer.from('setvphash')), new Uint8Array(verifyProgramHash).subarray(0, 10)]
spawnSync('python', ['teal/wormhole/pyteal/vaa-verify.py', appId, teal])
const program = fs.readFileSync(teal, 'utf8')
const compiledProgram = await pclib.compileProgram(program)
const hash = algosdk.decodeAddress(compiledProgram.hash).publicKey
const appArgs = [new Uint8Array(Buffer.from('setvphash')), hash.subarray(0, 10)]
await expect(pclib.callApp(OWNER_ADDR, appArgs, [], signCallback)).to.be.rejectedWith('Bad Request') await expect(pclib.callApp(OWNER_ADDR, appArgs, [], signCallback)).to.be.rejectedWith('Bad Request')
})
it('Must verify and handle Pyth VAA', async function () {
})
it('Must verify and handle governance VAA', async function () {
})
it('Must reject unknown VAA', async function () {
}) })
it('Must reject incorrect transaction group size', async function () { it('Must reject incorrect transaction group size', async function () {
const gscount = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'gscount')
const vssize = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'vssize')
const badSize = 1 + Math.ceil(gscount / vssize)
const params = await getTxParams()
const vaa = testLib.createSignedVAA(0, sigkeys, 1, 1, 1, PYTH_EMITTER, 0, 0, PYTH_PAYLOAD)
pclib.beginTxGroup()
const vaaBody = Buffer.from(vaa.substr(12 + sigkeys.length * 132), 'hex')
for (let i = 0; i < badSize; i++) {
const sigSubset = sigkeys.slice(vssize * i, i < badSize - 1 ? ((vssize * i) + vssize) : undefined)
const keySubset = gkeys.slice(vssize * i, i < badSize - 1 ? ((vssize * i) + vssize) : undefined)
const lsig = new algosdk.LogicSigAccount(compiledVerifyProgram.compiledBytes, [new Uint8Array(Buffer.from(sigSubset.join(''), 'hex'))])
pclib.addVerifyTx(verifyProgramHash, params, vaaBody, keySubset, gscount, lsig)
}
await expect(pclib.commitTxGroupSignedByLogic()).to.be.rejectedWith('Bad Request')
}) })
it('Must reject incorrect argument count for verify call', async function () { it('Must reject incorrect argument count for verify call', async function () {
@ -183,6 +195,29 @@ describe('VAA Processor Smart-contract Tests', function () {
}) })
it('Must reject transaction with not verified bit set in group', async function () { it('Must reject transaction with not verified bit set in group', async function () {
})
it('Must verify and handle Pyth VAA', async function () {
const gscount = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'gscount')
const vssize = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'vssize')
const expectedSize = Math.ceil(gscount / vssize)
const params = await getTxParams()
const vaa = testLib.createSignedVAA(0, sigkeys, 1, 1, 1, PYTH_EMITTER, 0, 0, PYTH_PAYLOAD)
const vaaBody = Buffer.from(vaa.substr(12 + sigkeys.length * 132), 'hex')
pclib.beginTxGroup()
for (let i = 0; i < expectedSize; i++) {
const sigSubset = sigkeys.slice(vssize * i, Math.min(gscount, (vssize * i) + (vssize - 1)))
const keySubset = gkeys.slice(vssize * i, Math.min(gscount, (vssize * i) + (vssize - 1)))
const lsig = new algosdk.LogicSigAccount(compiledVerifyProgram, new Uint8Array([Buffer.from(sigSubset)]))
pclib.addVerifyTx(verifyProgramHash, params, vaaBody, keySubset, gscount, lsig)
}
await pclib.commitTxGroupSignedByLogic()
})
it('Must verify and handle governance VAA', async function () {
})
it('Must reject unknown VAA', async function () {
}) })
it('Stateless: Must reject transaction with excess fee', async function () { it('Stateless: Must reject transaction with excess fee', async function () {
@ -209,3 +244,9 @@ describe('VAA Processor Smart-contract Tests', function () {
}) })
}) })
async function getTxParams () {
const params = await algodClient.getTransactionParams().do()
params.fee = 1000
params.flatFee = true
return params
}