14 Tests working plus enhanced contract and fixed library.
This commit is contained in:
parent
25716e8f2a
commit
f83e592556
|
@ -8,7 +8,7 @@ const approvalProgramFilename = 'teal/pricekeeper.teal'
|
|||
const clearProgramFilename = 'teal/clearstate.teal'
|
||||
|
||||
class PricecasterLib {
|
||||
constructor(algodClient, ownerAddr = undefined) {
|
||||
constructor (algodClient, ownerAddr = undefined) {
|
||||
this.algodClient = algodClient
|
||||
this.ownerAddr = ownerAddr
|
||||
this.minFee = 1000
|
||||
|
@ -139,7 +139,7 @@ class PricecasterLib {
|
|||
this.createApp = async function (sender, validatorAddr, symbol, signCallback) {
|
||||
const localInts = 0
|
||||
const localBytes = 0
|
||||
const globalInts = 6
|
||||
const globalInts = 5
|
||||
const globalBytes = 2
|
||||
|
||||
// declare onComplete as NoOp
|
||||
|
@ -221,7 +221,7 @@ class PricecasterLib {
|
|||
params.fee = this.minFee
|
||||
params.flatFee = true
|
||||
|
||||
console.log(appArgs)
|
||||
// console.log(appArgs)
|
||||
|
||||
const txns = []
|
||||
const enc = new TextEncoder()
|
||||
|
@ -355,27 +355,30 @@ class PricecasterLib {
|
|||
* @param {BigInt} exp Exponent (positive)
|
||||
* @param {BigInt} slot Valid-slot of price aggregation
|
||||
* @param {Uint8Array} sk Signing key.
|
||||
* @param {string} header (optional) Message header. 'PRICEDATA' if undefined.
|
||||
* @param {BigInt} appId (optional) AppId. Default is this contract appId.
|
||||
* @param {number} version (optional) Version. Default is 1 if undefined.
|
||||
* @param {BigInt} ts (optional) Timestamp of message. Current system ts if undefined.
|
||||
* @returns A base64-encoded message.
|
||||
*/
|
||||
this.createMessage = function (nonce, symbol, price, exp, confidence, slot, sk) {
|
||||
const buf = Buffer.alloc(146)
|
||||
buf.write('PRICEDATA', 0)
|
||||
buf.writeInt8(1, 9)
|
||||
buf.writeBigUInt64BE(BigInt(this.appId), 10)
|
||||
buf.writeBigUInt64BE(nonce, 18)
|
||||
buf.write(symbol, 26)
|
||||
buf.writeBigUInt64BE(price, 42)
|
||||
buf.writeBigUInt64BE(exp, 50)
|
||||
buf.writeBigUInt64BE(confidence, 58)
|
||||
buf.writeBigUInt64BE(slot, 66)
|
||||
buf.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 1000)), 74)
|
||||
this.createMessage = function (symbol, price, exp, confidence, slot, sk, header, appId, version, ts) {
|
||||
const buf = Buffer.alloc(138)
|
||||
buf.write(header === undefined ? 'PRICEDATA' : header, 0)
|
||||
buf.writeInt8(version === undefined ? 1 : version, 9)
|
||||
buf.writeBigUInt64BE(appId === undefined ? BigInt(this.appId) : appId, 10)
|
||||
buf.write(symbol, 18)
|
||||
buf.writeBigUInt64BE(price, 34)
|
||||
buf.writeBigUInt64BE(exp, 42)
|
||||
buf.writeBigUInt64BE(confidence, 50)
|
||||
buf.writeBigUInt64BE(slot, 58)
|
||||
buf.writeBigUInt64BE(ts === undefined ? BigInt(Math.floor(Date.now() / 1000)) : ts, 66)
|
||||
|
||||
const digestu8 = Buffer.from(sha512_256(buf.slice(0, 82)), 'hex')
|
||||
const digestu8 = Buffer.from(sha512_256(buf.slice(0, 74)), 'hex')
|
||||
// console.log(digestu8.toString('base64'))
|
||||
// console.log(this.approvalProgramHash)
|
||||
const signature = Buffer.from(algosdk.tealSign(sk, digestu8, this.approvalProgramHash))
|
||||
// console.log(Buffer.from(signature).toString('base64'))
|
||||
signature.copy(buf, 82)
|
||||
signature.copy(buf, 74)
|
||||
// console.log(buf.toString('base64'))
|
||||
return buf
|
||||
}
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
// App-globals:
|
||||
// sym : byte[] Symbol to keep price for
|
||||
// vaddr : byte[] Validator account
|
||||
// nonce : uint64 last sequence ID
|
||||
// price : uint64 current price
|
||||
// stdev : uint64 current confidence (standard deviation)
|
||||
// slot : uint64 slot of this onchain publication
|
||||
// exp : uint64 exponent (fixed point position)
|
||||
// ts : uint64 timestamp
|
||||
// ts : uint64 last timestamp
|
||||
//
|
||||
// Slots:
|
||||
// 0 Input message block
|
||||
|
@ -23,7 +22,6 @@
|
|||
// 9 header Literal "PRICEDATA"
|
||||
// 1 version int8 (Must be 1)
|
||||
// 8 dest This appId
|
||||
// 8 nonce uint64 Sequence identifier
|
||||
// 16 symbol String filled with spaces e.g ("ALGO/USD ")
|
||||
// 8 price Price. 64bit integer.
|
||||
// 8 priceexp Price exponent (fixed point position).
|
||||
|
@ -33,7 +31,7 @@
|
|||
// 32 s Signature s-component
|
||||
// 32 r Signature r-component
|
||||
//
|
||||
// Size: 146 bytes.
|
||||
// Size: 138 bytes.
|
||||
//
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -79,8 +77,8 @@ int 16
|
|||
assert
|
||||
app_global_put
|
||||
|
||||
byte "nonce"
|
||||
int 0
|
||||
byte "ts"
|
||||
global LatestTimestamp
|
||||
app_global_put
|
||||
|
||||
b success
|
||||
|
@ -97,7 +95,7 @@ int 4
|
|||
==
|
||||
assert
|
||||
|
||||
// if this is one of dummy transactions(0 or 1), exit with success
|
||||
// if this is one of dummy transactions(0, 1 or 2), exit with success
|
||||
txn GroupIndex
|
||||
int 3
|
||||
!=
|
||||
|
@ -122,7 +120,7 @@ store 0
|
|||
|
||||
load 0
|
||||
len
|
||||
int 146
|
||||
int 138
|
||||
==
|
||||
assert
|
||||
|
||||
|
@ -151,19 +149,9 @@ txn ApplicationID
|
|||
==
|
||||
assert
|
||||
|
||||
// Check nonce
|
||||
|
||||
load 0
|
||||
extract 18 8
|
||||
btoi
|
||||
byte "nonce"
|
||||
app_global_get
|
||||
>
|
||||
assert
|
||||
|
||||
// Reject zero price
|
||||
load 0
|
||||
extract 42 8
|
||||
extract 34 8
|
||||
btoi
|
||||
int 0
|
||||
!=
|
||||
|
@ -171,7 +159,7 @@ assert
|
|||
|
||||
// Reject zero slot
|
||||
load 0
|
||||
extract 66 8
|
||||
extract 58 8
|
||||
btoi
|
||||
int 0
|
||||
!=
|
||||
|
@ -179,41 +167,42 @@ assert
|
|||
|
||||
// Reject out-of-range exponent
|
||||
load 0
|
||||
extract 50 8
|
||||
extract 42 8
|
||||
btoi
|
||||
int 18
|
||||
<=
|
||||
assert
|
||||
|
||||
// Check timestamp order, must be +/-10 secs from last block ts
|
||||
// Check timestamp:
|
||||
// * must be higher than ts recorded in global state
|
||||
// * must be lower than current block stamp + 10s
|
||||
// (TODO: check this again)
|
||||
load 0
|
||||
extract 74 8
|
||||
extract 66 8
|
||||
btoi
|
||||
dup
|
||||
dup
|
||||
global LatestTimestamp
|
||||
int 10
|
||||
-
|
||||
<
|
||||
//assert
|
||||
global LatestTimestamp
|
||||
int 10
|
||||
+
|
||||
swap
|
||||
<
|
||||
assert
|
||||
byte "ts"
|
||||
app_global_get
|
||||
>
|
||||
//assert
|
||||
assert
|
||||
|
||||
|
||||
// ed25519verify args in stack:
|
||||
|
||||
// data (hash of message)
|
||||
load 0
|
||||
extract 0 82
|
||||
extract 0 74
|
||||
sha512_256
|
||||
|
||||
// (B) signature
|
||||
load 0
|
||||
extract 82 64
|
||||
extract 74 64
|
||||
|
||||
// validator-address
|
||||
byte "vaddr"
|
||||
|
@ -223,45 +212,39 @@ app_global_get
|
|||
ed25519verify
|
||||
int 1
|
||||
==
|
||||
//assert
|
||||
assert
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Verified. Store data to app globals.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
byte "nonce"
|
||||
load 0
|
||||
extract 18 8
|
||||
btoi
|
||||
app_global_put
|
||||
|
||||
byte "ts"
|
||||
load 0
|
||||
extract 74 8
|
||||
extract 66 8
|
||||
btoi
|
||||
app_global_put
|
||||
|
||||
byte "price"
|
||||
load 0
|
||||
extract 42 8
|
||||
extract 34 8
|
||||
btoi
|
||||
app_global_put
|
||||
|
||||
byte "exp"
|
||||
load 0
|
||||
extract 50 8
|
||||
extract 42 8
|
||||
btoi
|
||||
app_global_put
|
||||
|
||||
byte "stdev"
|
||||
byte "conf"
|
||||
load 0
|
||||
extract 58 8
|
||||
extract 50 8
|
||||
btoi
|
||||
app_global_put
|
||||
|
||||
byte "slot"
|
||||
load 0
|
||||
extract 66 8
|
||||
extract 58 8
|
||||
btoi
|
||||
app_global_put
|
||||
|
||||
|
|
116
test/sc-test.js
116
test/sc-test.js
|
@ -6,34 +6,45 @@ const chai = require('chai')
|
|||
chai.use(require('chai-as-promised'))
|
||||
// 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)
|
||||
const VALIDATOR_ADDR = 'OPDM7ACAW64Q4VBWAL77Z5SHSJVZZ44V3BAN7W44U43SUXEOUENZMZYOQU'
|
||||
const VALIDATOR_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_MNEMO = '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 SIGNATURES = {}
|
||||
SIGNATURES[VALIDATOR_ADDR] = algosdk.mnemonicToSecretKey(VALIDATOR_MNEMO)
|
||||
SIGNATURES[OTHER_ADDR] = algosdk.mnemonicToSecretKey(OTHER_MNEMO)
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
const VALID_SYMBOL = 'BTC/USD '
|
||||
const VALID_PRICE = BigInt(485265555)
|
||||
const VALID_EXPONENT = BigInt(4)
|
||||
const VALID_CONF = BigInt(1400000)
|
||||
const VALID_SLOT = BigInt(104566700)
|
||||
|
||||
function signCallback (sender, tx) {
|
||||
const txSigned = tx.signTxn(signatures[sender].sk)
|
||||
const txSigned = tx.signTxn(SIGNATURES[sender].sk)
|
||||
return txSigned
|
||||
}
|
||||
|
||||
describe('Price-Keeper contract tests', function () {
|
||||
let pclib
|
||||
let algodClient
|
||||
let appId
|
||||
let lastTs
|
||||
|
||||
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)
|
||||
const appsTo = await tools.readCreatedApps(algodClient, VALIDATOR_ADDR)
|
||||
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)
|
||||
const txId = await pclib.deleteApp(VALIDATOR_ADDR, signCallback, appsTo[i].id)
|
||||
await pclib.waitForConfirmation(txId)
|
||||
} catch (e) {
|
||||
console.error('Could not delete application! Reason: ' + e)
|
||||
|
@ -41,18 +52,89 @@ describe('Price-Keeper contract tests', function () {
|
|||
}
|
||||
|
||||
console.log('Creating new app...')
|
||||
const txId = await pclib.createApp(validatorAddr, validatorAddr, symbol, signCallback)
|
||||
const txId = await pclib.createApp(VALIDATOR_ADDR, VALIDATOR_ADDR, VALID_SYMBOL, signCallback)
|
||||
const txResponse = await pclib.waitForTransactionResponse(txId)
|
||||
const appId = pclib.appIdFromCreateAppResponse(txResponse)
|
||||
appId = pclib.appIdFromCreateAppResponse(txResponse)
|
||||
pclib.setAppId(appId)
|
||||
console.log('App Id: %d', appId)
|
||||
})
|
||||
it('Must create app with proper initial global state', async function () {
|
||||
const stVAddr = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'vaddr')
|
||||
const stSym = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'sym')
|
||||
expect(Buffer.from(stSym, 'base64').toString()).to.equal(VALID_SYMBOL)
|
||||
expect(stVAddr.toString()).to.equal(VALIDATOR_ADDR)
|
||||
})
|
||||
it('Must fail to create app with bad symbol', async function () {
|
||||
await expect(pclib.createApp(validatorAddr, validatorAddr, 'XXXXX', signCallback)).to.be.rejectedWith('Bad Request')
|
||||
await expect(pclib.createApp(VALIDATOR_ADDR, VALIDATOR_ADDR, '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)
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, VALID_EXPONENT, VALID_CONF, VALID_SLOT, SIGNATURES[VALIDATOR_ADDR].sk)
|
||||
const txid = await pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)
|
||||
expect(txid).to.have.length(52)
|
||||
await pclib.waitForTransactionResponse(txid)
|
||||
//console.log(await tools.printAppGlobalState(algodClient, appId, VALIDATOR_ADDR))
|
||||
const stPrice = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'price')
|
||||
const stExp = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'exp')
|
||||
const stConf = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'conf')
|
||||
const stSlot = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'slot')
|
||||
expect(stPrice.toString()).to.equal(VALID_PRICE.toString())
|
||||
expect(stExp.toString()).to.equal(VALID_EXPONENT.toString())
|
||||
expect(stSlot.toString()).to.equal(VALID_SLOT.toString())
|
||||
expect(stConf.toString()).to.equal(VALID_CONF.toString())
|
||||
})
|
||||
it('Must accept second message with different price', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE + BigInt(400), VALID_EXPONENT + BigInt(3), VALID_CONF + BigInt(2), VALID_SLOT + BigInt(100), SIGNATURES[VALIDATOR_ADDR].sk)
|
||||
const txid = await pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)
|
||||
expect(txid).to.have.length(52)
|
||||
await pclib.waitForTransactionResponse(txid)
|
||||
const stPrice = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'price')
|
||||
const stExp = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'exp')
|
||||
const stConf = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'conf')
|
||||
const stSlot = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'slot')
|
||||
expect(stPrice.toString()).to.equal((VALID_PRICE + BigInt(400)).toString())
|
||||
expect(stExp.toString()).to.equal((VALID_EXPONENT + BigInt(3)).toString())
|
||||
expect(stSlot.toString()).to.equal((VALID_SLOT + BigInt(100)).toString())
|
||||
expect(stConf.toString()).to.equal((VALID_CONF + BigInt(2)).toString())
|
||||
lastTs = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'ts')
|
||||
})
|
||||
it('Must reject non-validator as signer', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, VALID_EXPONENT, VALID_CONF, VALID_SLOT, SIGNATURES[OTHER_ADDR].sk)
|
||||
await expect(pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
it('Must reject non-validator as sender', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, VALID_EXPONENT, VALID_CONF, VALID_SLOT, SIGNATURES[VALIDATOR_ADDR].sk)
|
||||
await expect(pclib.submitMessage(OTHER_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
it('Must reject future timestamp', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, VALID_EXPONENT, VALID_CONF, VALID_SLOT, SIGNATURES[VALIDATOR_ADDR].sk, undefined, undefined, undefined, BigInt(lastTs + 200))
|
||||
await expect(pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
it('Must reject old timestamp', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, VALID_EXPONENT, VALID_CONF,VALID_SLOT, SIGNATURES[VALIDATOR_ADDR].sk, undefined, undefined, undefined, BigInt(lastTs - 999999))
|
||||
await expect(pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
it('Must reject zero-priced message', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, BigInt(0), VALID_EXPONENT, VALID_CONF, VALID_SLOT, SIGNATURES[VALIDATOR_ADDR].sk)
|
||||
await expect(pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
it('Must reject out-of-range exponent', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, BigInt(1000000), VALID_CONF, VALID_SLOT, SIGNATURES[VALIDATOR_ADDR].sk)
|
||||
await expect(pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
it('Must reject zero slot', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, VALID_EXPONENT, VALID_CONF, BigInt(0), SIGNATURES[VALIDATOR_ADDR].sk)
|
||||
await expect(pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
it('Must reject bad header', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, VALID_EXPONENT, VALID_CONF, BigInt(0), SIGNATURES[VALIDATOR_ADDR].sk, 'BADHEADER')
|
||||
await expect(pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
it('Must reject bad destination appId', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, VALID_EXPONENT, VALID_CONF, BigInt(0), SIGNATURES[VALIDATOR_ADDR].sk, undefined, BigInt(100))
|
||||
await expect(pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
it('Must reject bad message version', async function () {
|
||||
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, VALID_EXPONENT, VALID_CONF, BigInt(0), SIGNATURES[VALIDATOR_ADDR].sk, undefined, undefined, 0)
|
||||
await expect(pclib.submitMessage(VALIDATOR_ADDR, msgBuffer, signCallback)).to.be.rejectedWith('Bad Request')
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue