Creation successful. Fixes + test deployment Betanet
This commit is contained in:
parent
0559f2c6b0
commit
43e68eb9b8
|
@ -1,16 +1,14 @@
|
|||
const algosdk = require('algosdk')
|
||||
const fs = require('fs')
|
||||
const { exit } = require('process')
|
||||
const tools = require('./tools/app-tools')
|
||||
const tools = require('../tools/app-tools')
|
||||
|
||||
const approvalProgramFilename = 'contracts/price-keeper.teal'
|
||||
const clearProgramFilename = 'contracts/clearstate.teal'
|
||||
const approvalProgramFilename = 'teal/pricekeeper.teal'
|
||||
const clearProgramFilename = 'teal/clearstate.teal'
|
||||
|
||||
class PricecasterLib {
|
||||
constructor (algodClient, ownerAddr = undefined, assetId = 0) {
|
||||
constructor (algodClient, ownerAddr = undefined) {
|
||||
this.algodClient = algodClient
|
||||
this.ownerAddr = ownerAddr
|
||||
this.assetId = assetId
|
||||
this.minFee = 1000
|
||||
|
||||
/**
|
||||
|
@ -22,15 +20,6 @@ class PricecasterLib {
|
|||
this.appId = applicationId
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the wALGO asset id used in all the functions of this class.
|
||||
* @param {number} assId asset id
|
||||
* @returns {void}
|
||||
*/
|
||||
this.setAssetId = function (assId) {
|
||||
this.assetId = assId
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minimum fee to pay for transactions.
|
||||
* @return {Number} minimum transaction fee
|
||||
|
@ -126,8 +115,7 @@ class PricecasterLib {
|
|||
* @return {String} base64 string containing the compiled program
|
||||
*/
|
||||
this.compileApprovalProgram = async function (validatorAddress) {
|
||||
let program = fs.readFileSync(approvalProgramFilename, 'utf8')
|
||||
program = program.replace(/TMPL_VALIDATOR/g, validatorAddress)
|
||||
const program = fs.readFileSync(approvalProgramFilename, 'utf8')
|
||||
return this.compileProgram(program)
|
||||
}
|
||||
|
||||
|
@ -146,11 +134,11 @@ class PricecasterLib {
|
|||
* @param {Function} signCallback callback with prototype signCallback(sender, tx) used to sign transactions
|
||||
* @return {String} transaction id of the created application
|
||||
*/
|
||||
this.createApp = async function (sender, validatorAddr, signCallback) {
|
||||
const localInts = 2
|
||||
const localBytes = 3
|
||||
const globalInts = 0
|
||||
const globalBytes = 0
|
||||
this.createApp = async function (sender, validatorAddr, symbol, signCallback) {
|
||||
const localInts = 0
|
||||
const localBytes = 0
|
||||
const globalInts = 2
|
||||
const globalBytes = 4
|
||||
|
||||
// declare onComplete as NoOp
|
||||
const onComplete = algosdk.OnApplicationComplete.NoOpOC
|
||||
|
@ -161,14 +149,17 @@ class PricecasterLib {
|
|||
params.fee = this.minFee
|
||||
params.flatFee = true
|
||||
|
||||
const approvalProgramCompiled = await this.compileApprovalProgram(validatorAddr)
|
||||
const approvalProgramCompiled = await this.compileApprovalProgram()
|
||||
const clearProgramCompiled = await this.compileClearProgram()
|
||||
|
||||
const enc = new TextEncoder()
|
||||
const appArgs = [new Uint8Array(algosdk.decodeAddress(validatorAddr).publicKey), enc.encode(symbol)]
|
||||
|
||||
// create unsigned transaction
|
||||
const txApp = algosdk.makeApplicationCreateTxn(
|
||||
sender, params, onComplete,
|
||||
approvalProgramCompiled, clearProgramCompiled,
|
||||
localInts, localBytes, globalInts, globalBytes
|
||||
localInts, localBytes, globalInts, globalBytes, appArgs
|
||||
)
|
||||
const txId = txApp.txID().toString()
|
||||
|
||||
|
@ -240,6 +231,37 @@ class PricecasterLib {
|
|||
return txId
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanent delete the application.
|
||||
* @param {String} sender owner account
|
||||
* @param {Function} signCallback callback with prototype signCallback(sender, tx) used to sign transactions
|
||||
* @param {Function} applicationId use this application id instead of the one set
|
||||
* @return {String} transaction id of one of the transactions of the group
|
||||
*/
|
||||
this.deleteApp = async function (sender, signCallback, applicationId) {
|
||||
// get node suggested parameters
|
||||
const params = await this.algodClient.getTransactionParams().do()
|
||||
|
||||
params.fee = this.minFee
|
||||
params.flatFee = true
|
||||
|
||||
if (!applicationId) {
|
||||
applicationId = this.appId
|
||||
}
|
||||
|
||||
// create unsigned transaction
|
||||
const txApp = algosdk.makeApplicationDeleteTxn(sender, params, applicationId)
|
||||
const txId = txApp.txID().toString()
|
||||
|
||||
// Sign the transaction
|
||||
const txAppSigned = signCallback(sender, txApp)
|
||||
|
||||
// Submit the transaction
|
||||
await this.algodClient.sendRawTransaction(txAppSigned).do()
|
||||
|
||||
return txId
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to wait until transaction txId is included in a block/round.
|
||||
* @param {String} txId transaction id to wait for
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Experimentation with pyth",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
"test": "mocha --timeout 60000"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
#pragma version 5
|
||||
// ================================================================================================
|
||||
// Pricecaster Program
|
||||
// PriceKeeper Approval Program
|
||||
// ================================================================================================
|
||||
//
|
||||
// This contract has the following invariants at
|
||||
// deployment stage:
|
||||
//
|
||||
// TMPL_VALIDATOR Unique data validator address that signs and sends the incoming message
|
||||
//
|
||||
// App-globals:
|
||||
// symbol : byte[] pair supported by this app.
|
||||
// sym : byte[] Symbol to keep price for
|
||||
// vaddr : byte[] Validator account
|
||||
// nonce : uint64 last sequence ID
|
||||
// price : byte[] current price
|
||||
// stdev : byte[] current confidence (standard deviation)
|
||||
|
@ -43,7 +39,7 @@
|
|||
int 0
|
||||
txn ApplicationID
|
||||
==
|
||||
bnz success
|
||||
bnz handle_create
|
||||
|
||||
// Handle app call: send price message
|
||||
txn OnCompletion
|
||||
|
@ -51,9 +47,37 @@ int NoOp
|
|||
==
|
||||
bnz handle_call
|
||||
|
||||
// Handle deletion.
|
||||
txn OnCompletion
|
||||
int DeleteApplication
|
||||
==
|
||||
bnz success
|
||||
|
||||
// Fail otherwise
|
||||
err
|
||||
|
||||
handle_create:
|
||||
// -----------------------------------------------------
|
||||
// Handle creation
|
||||
// Arg 0: Validator address
|
||||
// Arg 1: Symbol to keep price data
|
||||
// -----------------------------------------------------
|
||||
|
||||
byte "vaddr"
|
||||
txn ApplicationArgs 0
|
||||
app_global_put
|
||||
|
||||
byte "sym"
|
||||
txn ApplicationArgs 1
|
||||
dup
|
||||
len
|
||||
int 16
|
||||
==
|
||||
assert
|
||||
app_global_put
|
||||
|
||||
b success
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Receive price message
|
||||
// -----------------------------------------------------
|
||||
|
@ -62,7 +86,8 @@ handle_call:
|
|||
|
||||
// Verify if sender is the data validator
|
||||
txn Sender
|
||||
addr PIQHXRVFDP4KSEZUPW6TB4UCDVJ5GJ3YYJIQWKWMEW2AUHJJCHPB4GPNCU
|
||||
byte "vaddr"
|
||||
app_global_get
|
||||
==
|
||||
assert
|
||||
|
||||
|
@ -141,6 +166,8 @@ load 0
|
|||
extract 98 32
|
||||
|
||||
// Unpack pubkey X,Y components
|
||||
byte "vaddr"
|
||||
app_global_get
|
||||
ecdsa_pk_decompress Secp256k1
|
||||
|
||||
// Verify signature
|
|
@ -0,0 +1,50 @@
|
|||
const PricecasterLib = require('../lib/pricecaster')
|
||||
const tools = require('../tools/app-tools')
|
||||
const algosdk = require('algosdk')
|
||||
// Test general configuration for Betanet
|
||||
|
||||
const validatorAddr = 'OPDM7ACAW64Q4VBWAL77Z5SHSJVZZ44V3BAN7W44U43SUXEOUENZMZYOQU'
|
||||
const validatorMnemo = '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 otherAddr = 'DMTBK62XZ6KNI7L5E6TRBTPB4B3YNVB4WYGSWR42SEV4XKV4LYHGBW4O34'
|
||||
const otherMnemo = 'old agree harbor cost pink fog chunk hope vital used rural soccer model acquire clown host friend bring marriage surge dirt surge slab absent punch'
|
||||
const symbol = 'BTC/USD '
|
||||
const signatures = {}
|
||||
signatures[validatorAddr] = algosdk.mnemonicToSecretKey(validatorMnemo)
|
||||
signatures[otherAddr] = algosdk.mnemonicToSecretKey(otherMnemo)
|
||||
|
||||
function signCallback (sender, tx) {
|
||||
const txSigned = tx.signTxn(signatures[sender].sk)
|
||||
return txSigned
|
||||
}
|
||||
|
||||
describe('Price-Keeper contract tests', function () {
|
||||
let pclib
|
||||
let algodClient
|
||||
|
||||
before(async function () {
|
||||
algodClient = new algosdk.Algodv2('', 'https://api.betanet.algoexplorer.io', '')
|
||||
pclib = new PricecasterLib.PricecasterLib(algodClient)
|
||||
|
||||
console.log('Clearing accounts of all previous apps...')
|
||||
const appsTo = await tools.readCreatedApps(algodClient, validatorAddr)
|
||||
for (let i = 0; i < appsTo.length; i++) {
|
||||
console.log('Clearing ' + appsTo[i].id)
|
||||
try {
|
||||
const txId = await pclib.deleteApp(validatorAddr, signCallback, appsTo[i].id)
|
||||
await pclib.waitForConfirmation(txId)
|
||||
} catch (e) {
|
||||
console.error('Could not delete application! Reason: ' + e)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Creating new app...')
|
||||
const txId = await pclib.createApp(validatorAddr, validatorAddr, symbol, signCallback)
|
||||
const txResponse = await pclib.waitForTransactionResponse(txId)
|
||||
const appId = pclib.appIdFromCreateAppResponse(txResponse)
|
||||
pclib.setAppId(appId);
|
||||
console.log('App Id: %d', appId)
|
||||
})
|
||||
it('x', function () {
|
||||
|
||||
})
|
||||
})
|
|
@ -0,0 +1,270 @@
|
|||
/*************************************************************************
|
||||
* [2018] - [2020] Rand Labs Inc.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* NOTICE: All information contained herein is, and remains
|
||||
* the property of Rand Labs Inc.
|
||||
* The intellectual and technical concepts contained
|
||||
* herein are proprietary to Rand Labs Inc.
|
||||
*/
|
||||
const sha512 = require('js-sha512')
|
||||
const hibase32 = require('hi-base32')
|
||||
|
||||
const ALGORAND_ADDRESS_SIZE = 58
|
||||
|
||||
function timeoutPromise (ms, promise) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
reject(new Error('promise timeout'))
|
||||
}, ms)
|
||||
promise.then(
|
||||
(res) => {
|
||||
clearTimeout(timeoutId)
|
||||
resolve(res)
|
||||
},
|
||||
(err) => {
|
||||
clearTimeout(timeoutId)
|
||||
reject(err)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function getInt64Bytes (x, len) {
|
||||
if (!len) {
|
||||
len = 8
|
||||
}
|
||||
const bytes = new Uint8Array(len)
|
||||
do {
|
||||
len -= 1
|
||||
// eslint-disable-next-line no-bitwise
|
||||
bytes[len] = x & (255)
|
||||
// eslint-disable-next-line no-bitwise
|
||||
x >>= 8
|
||||
} while (len)
|
||||
return bytes
|
||||
}
|
||||
|
||||
function addressFromByteBuffer (addr) {
|
||||
const bytes = Buffer.from(addr, 'base64')
|
||||
|
||||
// compute checksum
|
||||
const checksum = sha512.sha512_256.array(bytes).slice(28, 32)
|
||||
|
||||
const c = new Uint8Array(bytes.length + checksum.length)
|
||||
c.set(bytes)
|
||||
c.set(checksum, bytes.length)
|
||||
|
||||
const v = hibase32.encode(c)
|
||||
|
||||
return v.toString().slice(0, ALGORAND_ADDRESS_SIZE)
|
||||
}
|
||||
|
||||
function printAppCallDeltaArray (deltaArray) {
|
||||
for (let i = 0; i < deltaArray.length; i++) {
|
||||
if (deltaArray[i].address) {
|
||||
console.log('Local state change address: ' + deltaArray[i].address)
|
||||
for (let j = 0; j < deltaArray[i].delta.length; j++) {
|
||||
printAppCallDelta(deltaArray[i].delta[j])
|
||||
}
|
||||
} else {
|
||||
console.log('Global state change')
|
||||
printAppCallDelta(deltaArray[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printAppStateArray (stateArray) {
|
||||
for (let n = 0; n < stateArray.length; n++) {
|
||||
printAppState(stateArray[n])
|
||||
}
|
||||
}
|
||||
|
||||
function appValueState (stateValue) {
|
||||
let text = ''
|
||||
|
||||
if (stateValue.type == 1) {
|
||||
const addr = addressFromByteBuffer(stateValue.bytes)
|
||||
if (addr.length == ALGORAND_ADDRESS_SIZE) {
|
||||
text += addr
|
||||
} else {
|
||||
text += stateValue.bytes
|
||||
}
|
||||
} else if (stateValue.type == 2) {
|
||||
text = stateValue.uint
|
||||
} else {
|
||||
text += stateValue.bytes
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
function appValueStateString (stateValue) {
|
||||
let text = ''
|
||||
|
||||
if (stateValue.type == 1) {
|
||||
const addr = addressFromByteBuffer(stateValue.bytes)
|
||||
if (addr.length == ALGORAND_ADDRESS_SIZE) {
|
||||
text += addr
|
||||
} else {
|
||||
text += stateValue.bytes
|
||||
}
|
||||
} else if (stateValue.type == 2) {
|
||||
text += stateValue.uint
|
||||
} else {
|
||||
text += stateValue.bytes
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
function printAppState (state) {
|
||||
let text = Buffer.from(state.key, 'base64').toString() + ': '
|
||||
|
||||
text += appValueStateString(state.value)
|
||||
|
||||
console.log(text)
|
||||
}
|
||||
|
||||
async function printAppLocalState (algodClient, appId, accountAddr) {
|
||||
const ret = await readAppLocalState(algodClient, appId, accountAddr)
|
||||
if (ret) {
|
||||
console.log('Application %d local state for account %s:', appId, accountAddr)
|
||||
printAppStateArray(ret)
|
||||
}
|
||||
}
|
||||
|
||||
async function printAppGlobalState (algodClient, appId, accountAddr) {
|
||||
const ret = await readAppGlobalState(algodClient, appId, accountAddr)
|
||||
if (ret) {
|
||||
console.log('Application %d global state:', appId)
|
||||
printAppStateArray(ret)
|
||||
}
|
||||
}
|
||||
|
||||
async function readCreatedApps (algodClient, accountAddr) {
|
||||
const accountInfoResponse = await algodClient.accountInformation(accountAddr).do()
|
||||
return accountInfoResponse['created-apps']
|
||||
}
|
||||
|
||||
async function readOptedInApps (algodClient, accountAddr) {
|
||||
const accountInfoResponse = await algodClient.accountInformation(accountAddr).do()
|
||||
return accountInfoResponse['apps-local-state']
|
||||
}
|
||||
|
||||
// read global state of application
|
||||
async function readAppGlobalState (algodClient, appId, accountAddr) {
|
||||
const accountInfoResponse = await algodClient.accountInformation(accountAddr).do()
|
||||
for (let i = 0; i < accountInfoResponse['created-apps'].length; i++) {
|
||||
if (accountInfoResponse['created-apps'][i].id === appId) {
|
||||
const globalState = accountInfoResponse['created-apps'][i].params['global-state']
|
||||
|
||||
return globalState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function readAppGlobalStateByKey (algodClient, appId, accountAddr, key) {
|
||||
const accountInfoResponse = await algodClient.accountInformation(accountAddr).do()
|
||||
for (let i = 0; i < accountInfoResponse['created-apps'].length; i++) {
|
||||
if (accountInfoResponse['created-apps'][i].id === appId) {
|
||||
// console.log("Application's global state:")
|
||||
const stateArray = accountInfoResponse['created-apps'][i].params['global-state']
|
||||
for (let j = 0; j < stateArray.length; j++) {
|
||||
const text = Buffer.from(stateArray[j].key, 'base64').toString()
|
||||
|
||||
if (key === text) {
|
||||
return appValueState(stateArray[j].value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read local state of application from user account
|
||||
async function readAppLocalState (algodClient, appId, accountAddr) {
|
||||
const accountInfoResponse = await algodClient.accountInformation(accountAddr).do()
|
||||
for (let i = 0; i < accountInfoResponse['apps-local-state'].length; i++) {
|
||||
if (accountInfoResponse['apps-local-state'][i].id === appId) {
|
||||
// console.log(accountAddr + " opted in, local state:")
|
||||
|
||||
if (accountInfoResponse['apps-local-state'][i]['key-value']) {
|
||||
return accountInfoResponse['apps-local-state'][i]['key-value']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function readAppLocalStateByKey (algodClient, appId, accountAddr, key) {
|
||||
const accountInfoResponse = await algodClient.accountInformation(accountAddr).do()
|
||||
for (let i = 0; i < accountInfoResponse['apps-local-state'].length; i++) {
|
||||
if (accountInfoResponse['apps-local-state'][i].id === appId) {
|
||||
const stateArray = accountInfoResponse['apps-local-state'][i]['key-value']
|
||||
|
||||
if (!stateArray) {
|
||||
return null
|
||||
}
|
||||
for (let j = 0; j < stateArray.length; j++) {
|
||||
const text = Buffer.from(stateArray[j].key, 'base64').toString()
|
||||
|
||||
if (key === text) {
|
||||
return appValueState(stateArray[j].value)
|
||||
}
|
||||
}
|
||||
// not found assume 0
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function uintArray8ToString (byteArray) {
|
||||
return Array.from(byteArray, function (byte) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return ('0' + (byte & 0xFF).toString(16)).slice(-2)
|
||||
}).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if transactionResponse has any information about a transaction local or global state change.
|
||||
* @param {Object} transactionResponse object containing the transaction response of an application call
|
||||
* @return {Boolean} returns true if there is a local or global delta meanining that
|
||||
* the transaction made a change in the local or global state
|
||||
*/
|
||||
function anyAppCallDelta (transactionResponse) {
|
||||
return (transactionResponse['global-state-delta'] || transactionResponse['local-state-delta'])
|
||||
}
|
||||
|
||||
/**
|
||||
* Print to stdout the changes introduced by the transaction that generated the transactionResponse if any.
|
||||
* @param {Object} transactionResponse object containing the transaction response of an application call
|
||||
* @return {void}
|
||||
*/
|
||||
function printAppCallDelta (transactionResponse) {
|
||||
if (transactionResponse['global-state-delta'] !== undefined) {
|
||||
console.log('Global State updated:')
|
||||
printAppCallDeltaArray(transactionResponse['global-state-delta'])
|
||||
}
|
||||
if (transactionResponse['local-state-delta'] !== undefined) {
|
||||
console.log('Local State updated:')
|
||||
printAppCallDeltaArray(transactionResponse['local-state-delta'])
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
timeoutPromise,
|
||||
getInt64Bytes,
|
||||
addressFromByteBuffer,
|
||||
printAppStateArray,
|
||||
printAppState,
|
||||
printAppLocalState,
|
||||
printAppGlobalState,
|
||||
readCreatedApps,
|
||||
readOptedInApps,
|
||||
readAppGlobalState,
|
||||
readAppGlobalStateByKey,
|
||||
readAppLocalState,
|
||||
readAppLocalStateByKey,
|
||||
uintArray8ToString,
|
||||
anyAppCallDelta,
|
||||
printAppCallDelta
|
||||
}
|
Loading…
Reference in New Issue