TEAL and lib fixes

This commit is contained in:
Hernán Di Pietro 2021-10-04 17:12:25 -03:00
parent 1234f315f3
commit 659df98e39
7 changed files with 85 additions and 28 deletions

View File

@ -2,5 +2,19 @@
This service consumes prices from Pyth and feeds a TEAL program with messages containing signed price data. The program code validates signature and message validity, and if successful, subsequently stores the price information in the global application information for other contracts to retrieve. This service consumes prices from Pyth and feeds a TEAL program with messages containing signed price data. The program code validates signature and message validity, and if successful, subsequently stores the price information in the global application information for other contracts to retrieve.
## Backend Configuration
The fetcher will get information as soon as Pyth reports a price-change. Since publishing to the pricekeeper contract will be much slower, a buffered is approach is taken where a last set of prices is kept.
The number of prices kept is controlled by the `buffersize` setting.
The ratio of message publications currently is to publish again as soon as the last call finished and there is any buffer data available. This is configured with the `ratio` setting.
As prices may vary greatly in markets in short periods of time between publications, a set of strategies are provided to decide how to select data from the buffer.
Available strategies are:
* `avg` Select the average price in-buffer.
* `wavg` Select the weighted-by-confidence average prices in buffer.
* `maxconf` Select the price with the maximum confidence (lowest deviation between publishers)
Enabling the 'phony' setting will **simulate** publications but no real calls will be made. Just useful for debugging.

View File

@ -1,5 +1,7 @@
const algosdk = require('algosdk') const algosdk = require('algosdk')
const fs = require('fs') const fs = require('fs')
// eslint-disable-next-line camelcase
const { sha512_256 } = require('js-sha512')
const tools = require('../tools/app-tools') const tools = require('../tools/app-tools')
const approvalProgramFilename = 'teal/pricekeeper.teal' const approvalProgramFilename = 'teal/pricekeeper.teal'
@ -96,6 +98,7 @@ class PricecasterLib {
this.compileProgram = async function (programBytes) { this.compileProgram = async function (programBytes) {
const compileResponse = await this.algodClient.compile(programBytes).do() const compileResponse = await this.algodClient.compile(programBytes).do()
const compiledBytes = new Uint8Array(Buffer.from(compileResponse.result, 'base64')) const compiledBytes = new Uint8Array(Buffer.from(compileResponse.result, 'base64'))
this.programHash = compileResponse.hash
return compiledBytes return compiledBytes
} }
@ -307,7 +310,7 @@ class PricecasterLib {
* @returns A base64-encoded message. * @returns A base64-encoded message.
*/ */
this.createMessage = function (nonce, symbol, price, confidence, sk) { this.createMessage = function (nonce, symbol, price, confidence, sk) {
const buf = Buffer.alloc(131) const buf = Buffer.alloc(130)
buf.write('PRICEDATA', 0) buf.write('PRICEDATA', 0)
buf.writeInt8(1, 9) buf.writeInt8(1, 9)
buf.writeBigUInt64BE(BigInt(this.appId), 10) buf.writeBigUInt64BE(BigInt(this.appId), 10)
@ -317,11 +320,12 @@ class PricecasterLib {
buf.writeDoubleBE(confidence, 50) buf.writeDoubleBE(confidence, 50)
buf.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 1000)), 58) buf.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 1000)), 58)
const signature = Buffer.from(algosdk.signBytes(buf, sk)) const digest = sha512_256(buf.slice(0, 65))
const digestu8 = Buffer.from(digest, 'hex')
console.log(digestu8.toString('base64'))
const signature = Buffer.from(algosdk.tealSign(sk, digestu8, this.programHash))
console.log(Buffer.from(signature).toString('base64'))
signature.copy(buf, 66) signature.copy(buf, 66)
// v-component (ignored in Algorand it seems)
buf.writeInt8(1, 130)
return buf.toString('base64') return buf.toString('base64')
} }

26
package-lock.json generated
View File

@ -10,7 +10,9 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@pythnetwork/client": "^2.3.1", "@pythnetwork/client": "^2.3.1",
"algosdk": "^1.11.1" "algosdk": "^1.11.1",
"fastpriorityqueue": "^0.7.1",
"js-sha512": "^0.8.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.32.0", "@typescript-eslint/eslint-plugin": "^4.32.0",
@ -1891,6 +1893,14 @@
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
}, },
"node_modules/fastpriorityqueue": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/fastpriorityqueue/-/fastpriorityqueue-0.7.1.tgz",
"integrity": "sha512-XJ+vbiXYjmxc32VEpXScAq7mBg3vqh90OjLfiuyQ0zAtXpgICdVgGjKHep1kLGQufyuCBiEYpl6ZKcw79chTpA==",
"dependencies": {
"minimist": "^1.2.5"
}
},
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.13.0", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
@ -2930,8 +2940,7 @@
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"dev": true
}, },
"node_modules/mocha": { "node_modules/mocha": {
"version": "9.1.2", "version": "9.1.2",
@ -5854,6 +5863,14 @@
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
}, },
"fastpriorityqueue": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/fastpriorityqueue/-/fastpriorityqueue-0.7.1.tgz",
"integrity": "sha512-XJ+vbiXYjmxc32VEpXScAq7mBg3vqh90OjLfiuyQ0zAtXpgICdVgGjKHep1kLGQufyuCBiEYpl6ZKcw79chTpA==",
"requires": {
"minimist": "^1.2.5"
}
},
"fastq": { "fastq": {
"version": "1.13.0", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
@ -6601,8 +6618,7 @@
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"dev": true
}, },
"mocha": { "mocha": {
"version": "9.1.2", "version": "9.1.2",

View File

@ -4,13 +4,16 @@
"description": "Experimentation with pyth", "description": "Experimentation with pyth",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "npx ts-node backend/pricefetch.ts",
"test": "mocha --timeout 60000" "test": "mocha --timeout 60000"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@pythnetwork/client": "^2.3.1", "@pythnetwork/client": "^2.3.1",
"algosdk": "^1.11.1" "algosdk": "^1.11.1",
"fastpriorityqueue": "^0.7.1",
"js-sha512": "^0.8.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.32.0", "@typescript-eslint/eslint-plugin": "^4.32.0",

13
settings.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
token: '',
api: 'https://api.betanet.algoexplorer.io',
port: '',
'BTC/USD': {
appId: 3020301,
verifierpk: 'OPDM7ACAW64Q4VBWAL77Z5SHSJVZZ44V3BAN7W44U43SUXEOUENZMZYOQU',
buffersize: 50,
ratio: 'asap',
strategy: 'maxconf',
phony: false
}
}

View File

@ -28,9 +28,8 @@
// 8 ts timestamp of this price // 8 ts timestamp of this price
// 32 s Signature s-component // 32 s Signature s-component
// 32 r Signature r-component // 32 r Signature r-component
// 1 v Signature v-component (ignored)
// //
// Size: 131 bytes. // Size: 130 bytes.
// //
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
@ -76,6 +75,10 @@ int 16
assert assert
app_global_put app_global_put
byte "nonce"
int 0
app_global_put
b success b success
// ----------------------------------------------------- // -----------------------------------------------------
@ -103,7 +106,7 @@ store 0
load 0 load 0
len len
int 131 int 130
== ==
assert assert
@ -151,27 +154,23 @@ global LatestTimestamp
<= <=
assert assert
// Hash message block // ed25519verify args in stack:
// data (hash of message)
load 0 load 0
extract 0 65 extract 0 65
sha512_256 sha512_256
store 1
// push data, followed by signature S, signature R, pubkey X, pubkey Y // (B) signature
load 0 load 0
dup extract 66 64
extract 66 32
load 0
extract 98 32
// Unpack pubkey X,Y components // validator-address
byte "vaddr" byte "vaddr"
app_global_get app_global_get
ecdsa_pk_decompress Secp256k1
// Verify signature // Verify signature
ecdsa_verify Secp256k1 ed25519verify
int 1 int 1
== ==
assert assert

View File

@ -1,6 +1,9 @@
const PricecasterLib = require('../lib/pricecaster') const PricecasterLib = require('../lib/pricecaster')
const tools = require('../tools/app-tools') const tools = require('../tools/app-tools')
const algosdk = require('algosdk') const algosdk = require('algosdk')
const { expect } = require('chai')
const chai = require('chai')
chai.use(require('chai-as-promised'))
// Test general configuration for Betanet // Test general configuration for Betanet
const validatorAddr = 'OPDM7ACAW64Q4VBWAL77Z5SHSJVZZ44V3BAN7W44U43SUXEOUENZMZYOQU' const validatorAddr = 'OPDM7ACAW64Q4VBWAL77Z5SHSJVZZ44V3BAN7W44U43SUXEOUENZMZYOQU'
@ -41,10 +44,15 @@ describe('Price-Keeper contract tests', function () {
const txId = await pclib.createApp(validatorAddr, validatorAddr, symbol, signCallback) const txId = await pclib.createApp(validatorAddr, validatorAddr, symbol, 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)
console.log('App Id: %d', appId) console.log('App Id: %d', appId)
}) })
it('x', function () { it('Must fail to create app with bad symbol', async function () {
await expect(pclib.createApp(validatorAddr, validatorAddr, 'XXXXX', signCallback)).to.be.rejectedWith('Bad Request')
})
it('Must accept valid message and store data', async function () {
const msgb64 = pclib.createMessage(BigInt(1), 'BTC/USD ', 48526.50, 8.99999967, signatures[validatorAddr].sk)
console.log(msgb64)
await expect(pclib.submitMessage(validatorAddr, msgb64, signCallback)).to.eventually.have.length(52)
}) })
}) })