Store exp as 2-comp signed (Pyth emits NEGATIVE exponent!)

This commit is contained in:
Hernán Di Pietro 2021-10-08 15:45:46 -03:00
parent 7b1da2bda6
commit 4f439cf788
3 changed files with 29 additions and 32 deletions

View File

@ -116,9 +116,11 @@ class PricecasterLib {
* Compile application approval program.
* @return {String} base64 string containing the compiled program
*/
this.compileApprovalProgram = async function (validatorAddress) {
this.compileApprovalProgram = async function () {
const program = fs.readFileSync(approvalProgramFilename, 'utf8')
return this.compileProgram(program)
const compiledApprovalProgram = await this.compileProgram(program)
this.approvalProgramHash = compiledApprovalProgram.hash
return compiledApprovalProgram
}
/**
@ -143,8 +145,8 @@ class PricecasterLib {
symbol = symbol.padEnd(16, ' ')
const localInts = 0
const localBytes = 0
const globalInts = 5
const globalBytes = 2
const globalInts = 4
const globalBytes = 3
// declare onComplete as NoOp
const onComplete = algosdk.OnApplicationComplete.NoOpOC
@ -158,7 +160,6 @@ class PricecasterLib {
const compiledProgram = await this.compileApprovalProgram()
const approvalProgramCompiled = compiledProgram.compiledBytes
const clearProgramCompiled = (await this.compileClearProgram()).compiledBytes
this.approvalProgramHash = compiledProgram.hash
const enc = new TextEncoder()
const appArgs = [new Uint8Array(algosdk.decodeAddress(validatorAddr).publicKey), enc.encode(symbol)]
@ -352,7 +353,6 @@ class PricecasterLib {
/**
* Creates a message with price data for the PriceKeeper contract
* @param {BigInt} nonce Sequence number
* @param {String} symbol Symbol, must match appid support, 16-char UTF long
* @param {BigInt} price Aggregated price
* @param {BigInt} confidence Confidence
@ -372,18 +372,18 @@ class PricecasterLib {
buf.writeBigUInt64BE(appId === undefined ? BigInt(this.appId) : appId, 10)
buf.write(symbol, 18)
buf.writeBigUInt64BE(price, 34)
buf.writeBigUInt64BE(exp, 42)
// (!) Libraries like Pyth publish negative exponents. Write as signed 64bit
buf.writeBigInt64BE(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, 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, 74)
// console.log(buf.toString('base64'))
return buf
}
@ -394,6 +394,9 @@ class PricecasterLib {
* @returns Transaction identifier (txid)
*/
this.submitMessage = async function (sender, msgBuffer, signCallback) {
if (!algosdk.isValidAddress(sender)) {
throw new Error('Invalid sender address: ' + sender)
}
const appArgs = []
appArgs.push(new Uint8Array(msgBuffer))
return await this.callAppInDummyGroup(sender, appArgs, [], signCallback, 3)

View File

@ -9,7 +9,7 @@
// price : uint64 current price
// stdev : uint64 current confidence (standard deviation)
// slot : uint64 slot of this onchain publication
// exp : uint64 exponent (fixed point position)
// exp : byte[] exponent. Interpret as two-compliment, Big-Endian 64bit
// ts : uint64 last timestamp
//
// Slots:
@ -24,7 +24,7 @@
// 8 dest This appId
// 16 symbol String filled with spaces e.g ("ALGO/USD ")
// 8 price Price. 64bit integer.
// 8 priceexp Price exponent (fixed point position).
// 8 priceexp Price exponent. Interpret as two-compliment, Big-Endian 64bit
// 8 conf Confidence (stdev). 64bit integer.
// 8 slot Valid-slot of this aggregate price.
// 8 ts timestamp of this price submitted by PriceFetcher service
@ -165,14 +165,6 @@ int 0
!=
assert
// Reject out-of-range exponent
load 0
extract 42 8
btoi
int 18
<=
assert
// Check timestamp:
// * must be higher than ts recorded in global state
// * must be lower than current block stamp + 10s
@ -233,7 +225,6 @@ app_global_put
byte "exp"
load 0
extract 42 8
btoi
app_global_put
byte "conf"

View File

@ -60,9 +60,6 @@ describe('Price-Keeper contract tests', function () {
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(VALIDATOR_ADDR, VALIDATOR_ADDR, 'XXXXX', signCallback)).to.be.rejectedWith('Bad Request')
})
it('Must accept valid message and store data', async function () {
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)
@ -74,7 +71,7 @@ describe('Price-Keeper contract tests', function () {
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((Buffer.from(stExp, 'base64')).readBigInt64BE()).to.equal(VALID_EXPONENT)
expect(stSlot.toString()).to.equal(VALID_SLOT.toString())
expect(stConf.toString()).to.equal(VALID_CONF.toString())
})
@ -88,11 +85,21 @@ describe('Price-Keeper contract tests', function () {
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((Buffer.from(stExp, 'base64')).readBigInt64BE()).to.equal(VALID_EXPONENT + BigInt(3))
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 accept negative exponent, stored as 2-complement 64bit', async function () {
const msgBuffer = pclib.createMessage(VALID_SYMBOL, VALID_PRICE, BigInt(-9), 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)
const stExp = await tools.readAppGlobalStateByKey(algodClient, appId, VALIDATOR_ADDR, 'exp')
const bufExp = Buffer.from(stExp, 'base64')
const val = bufExp.readBigInt64BE()
expect(val.toString()).to.equal('-9')
})
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')
@ -106,17 +113,13 @@ describe('Price-Keeper contract tests', function () {
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))
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')