Creation successful. Fixes + test deployment Betanet

This commit is contained in:
Hernán Di Pietro 2021-10-01 15:06:33 -03:00
parent 0559f2c6b0
commit 43e68eb9b8
5 changed files with 403 additions and 34 deletions

View File

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

View File

@ -4,7 +4,7 @@
"description": "Experimentation with pyth",
"main": "index.js",
"scripts": {
"test": "mocha"
"test": "mocha --timeout 60000"
},
"author": "",
"license": "ISC",

View File

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

50
test/sc-test.js Normal file
View File

@ -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 () {
})
})

270
tools/app-tools.js Normal file
View File

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