Skip to content

Commit

Permalink
Add validation scripts (#194)
Browse files Browse the repository at this point in the history
* Add script to validate spot prices

* Validate asset units in pool

* Add script to find bump in on-chain token balance
  • Loading branch information
saboonikhil committed Oct 6, 2022
1 parent cacb02e commit 8cf7c02
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 0 deletions.
63 changes: 63 additions & 0 deletions test/findChangeInTokenBalance.ts
@@ -0,0 +1,63 @@
/**
* Script to find change in on-chain token balance of an account between two block numbers
* Run using `ts-node test/findChangeInTokenBalance.ts`
*/
import SDK, { util } from '@zeitgeistpm/sdk';
import { Tools } from '../src/mappings/util';

// Modify values as per requirement
const WS_NODE_URL = `wss://bsr.zeitgeist.pm`;
const ACCOUNT_ID = `dE1VdxVn8xy7HFNWfF1a8utYcbVN7BHNvTzRndwpRiNfEk9Co`;
const FROM_BLOCK_NUM = 1388585;
const TO_BLOCK_NUM = 1402375;
const MARKET_ID = 44;
const ASSET_INDEX = 0;

const findChangeInTokenBalance = async () => {
const sdk = await Tools.getSDK(WS_NODE_URL);
let fromBlockNum = FROM_BLOCK_NUM;
let toBlockNum = TO_BLOCK_NUM + 1; // Includes `TO_BLOCK_NUM` for querying
let fromBalance = await getBalanceAt(sdk, fromBlockNum);
const startBalance = BigInt(fromBalance);

// Inspired by binary search algorithm
while (fromBlockNum <= toBlockNum) {
let midBlockNum = Math.floor((fromBlockNum + toBlockNum) / 2);
const midBalance = await getBalanceAt(sdk, midBlockNum);

if (fromBalance.toString() !== midBalance.toString()) {
toBlockNum = midBlockNum;
} else {
fromBlockNum = midBlockNum;
fromBalance = midBalance;
}

// When all block numbers are covered
if (toBlockNum - fromBlockNum == 1) {
const endBalance = BigInt(midBalance);
if (endBalance - startBalance !== BigInt(0)) {
console.log(`\nAddition of ${endBalance - startBalance} units
detected on-chain at #${toBlockNum} for ${ACCOUNT_ID}`);
} else {
console.log(`\nNo change detected in on-chain balance
between #${FROM_BLOCK_NUM} and #${TO_BLOCK_NUM} for ${ACCOUNT_ID}`);
}
sdk.api.disconnect();
process.exit(1);
}
}
};

// To query chain for account balance as on block number
const getBalanceAt = async (sdk: SDK, blockNumber: number) => {
const blockHash = await sdk.api.rpc.chain.getBlockHash(blockNumber);
const data = (await sdk.api.query.tokens.accounts.at(
blockHash,
ACCOUNT_ID,
util.AssetIdFromString(`[${MARKET_ID},${ASSET_INDEX}]`)
)) as any;
console.log(`Balance fetched from #${blockNumber}`);
return data.free.toString();
};

findChangeInTokenBalance();
126 changes: 126 additions & 0 deletions test/validateHistoricalAssets.ts
@@ -0,0 +1,126 @@
/**
* Script to validate units of an asset in pool against on-chain data
* Run using `ts-node test/validateHistoricalAssets.ts`
*/
import { util } from '@zeitgeistpm/sdk';
import https from 'https';
import { Tools } from '../src/mappings/util';

// Modify values as per requirement
const NODE_URL = `wss://bsr.zeitgeist.pm`;
const QUERY_NODE_HOSTNAME = `processor.bsr.zeitgeist.pm`;
const MARKET_ID = 44;
const ASSET_INDEX = 0;
const POOL_ID = 30;

// GraphQL query for retrieving balance history of an asset in pool
const query = JSON.stringify({
query: `{
historicalAssets(where: {assetId_contains: "[${MARKET_ID},${ASSET_INDEX}]"}, orderBy: id_DESC) {
newAmountInPool
blockNumber
}
accounts(where: {poolId_eq: ${POOL_ID}}) {
accountId
}
}`,
});

const options = {
hostname: QUERY_NODE_HOSTNAME,
path: '/graphql',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': query.length,
'User-Agent': 'Node',
},
};

const req = https.request(options, (res) => {
let data = '';
res.on('data', (d) => {
data += d;
});
res.on('end', async () => {
if (res.statusCode == 200) {
console.log(`Validating response received from ${options.hostname}`);
} else {
console.log(JSON.parse(data).errors[0].message);
return;
}
let balanceDiff = BigInt(0),
diffs = ``,
falseBlockNums = ``,
trueBlockNums = ``;
const assetHistory = JSON.parse(data).data.historicalAssets;
const accountId = JSON.parse(data).data.accounts[0].accountId;
console.log(
`Number of records found in balance history of [${MARKET_ID},${ASSET_INDEX}] : ${assetHistory.length}`
);
const sdk = await Tools.getSDK(NODE_URL);

for (let i = 0; i < assetHistory.length; i++) {
const blockHash = await sdk.api.rpc.chain.getBlockHash(
assetHistory[i].blockNumber
);
const balance = (await sdk.api.query.tokens.accounts.at(
blockHash,
accountId,
util.AssetIdFromString(`[${MARKET_ID},${ASSET_INDEX}]`)
)) as any;

if (!trueBlockNums.includes(assetHistory[i].blockNumber)) {
if (
balance.free.toString() !== assetHistory[i].newAmountInPool.toString()
) {
console.log(
`\n${
assetHistory.length - i
}. Balances don't match at ${blockHash} [#${
assetHistory[i].blockNumber
}]`
);
console.log(`On Chain: ` + balance.free.toBigInt());
console.log(`On Subsquid: ` + assetHistory[i].newAmountInPool);
balanceDiff =
balance.free.toBigInt() - BigInt(assetHistory[i].newAmountInPool);
diffs += balanceDiff + `,`;
falseBlockNums += assetHistory[i].blockNumber + `,`;
} else {
console.log(
`${assetHistory.length - i}. Balances match at ${blockHash} [#${
assetHistory[i].blockNumber
}]`
);
trueBlockNums += assetHistory[i].blockNumber + `,`;
}
}
}
sdk.api.disconnect();

if (balanceDiff == BigInt(0)) {
console.log(
`\nAsset balance history on ${accountId} is in alliance with on-chain data`
);
} else {
console.log(
`\nAsset balance history on ${accountId} is not in alliance with on-chain data`
);
const diffsList = diffs.split(',');
const blockNumsList = falseBlockNums.split(',');
console.log(`The differences found are:`);
for (let i = diffsList.length; i > 1; i--) {
console.log(`${diffsList[i - 2]} units at #${blockNumsList[i - 2]}`);
}
}
process.exit(1);
});
});

req.on('error', (error) => {
console.error(error);
});

req.write(query);
req.end();
112 changes: 112 additions & 0 deletions test/validateSpotPrices.ts
@@ -0,0 +1,112 @@
/**
* Script to validate spot price of an asset against on-chain price
* Run using `ts-node test/validateSpotPrices.ts`
*/
import https from 'https';
import { Tools } from '../src/mappings/util';

// Modify values as per requirement
const NODE_URL = `wss://bsr.zeitgeist.pm`;
const QUERY_NODE_HOSTNAME = `processor.bsr.zeitgeist.pm`;
const ASSETS_LIMIT = 100; // Number of assets that need to be validated

// GraphQL query for retrieving price of assets
const query = JSON.stringify({
query: `{
assets(limit: ${ASSETS_LIMIT}, orderBy: id_DESC) {
assetId
price
poolId
}
squidStatus {
height
}
}`,
});

const options = {
hostname: QUERY_NODE_HOSTNAME,
path: '/graphql',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': query.length,
'User-Agent': 'Node',
},
};

const req = https.request(options, (res) => {
let data = '';
res.on('data', (d) => {
data += d;
});
res.on('end', async () => {
if (res.statusCode == 200) {
console.log(`Validating response received from ${options.hostname}`);
} else {
console.log(JSON.parse(data).errors[0].message);
return;
}
let falseCounter = 0,
trueCounter = 0,
falseAssets = ``;
const assets = JSON.parse(data).data.assets;
const sdk = await Tools.getSDK(NODE_URL);

const blockHash = await sdk.api.rpc.chain.getBlockHash(
JSON.parse(data).data.squidStatus.height
);
console.log();

for (let i = 0; i < assets.length; i++) {
//Remove exceptions
if (assets[i].assetId.includes('scalar')) continue;
if (assets[i].price == 0 || assets[i].price == 1) continue;

const pool = await sdk.models.fetchPoolData(Number(assets[i].poolId));
//@ts-ignore
let price = await pool.getSpotPrice({ ztg: null }, assets[i].assetId, blockHash);
price = Number(price) / Math.pow(10, 10);

const chainPrice = Math.round(price * 10); //Round to one decimal place
const squidPrice = Math.round(assets[i].price * 10) ?? 0;
if (chainPrice !== squidPrice) {
falseCounter++;
falseAssets +=
`${assets[i].assetId} - pool id ${assets[i].poolId}` + `|`;
console.log(
`\n${trueCounter + falseCounter}. Prices don't match for ${
assets[i].assetId
} of pool id ${assets[i].poolId}`
);
console.log(`On Chain: ` + price);
console.log(`On Subsquid: ` + assets[i].price);
console.log();
} else {
trueCounter++;
console.log(
`${trueCounter + falseCounter}. Prices match for ` + assets[i].assetId
);
}
}
sdk.api.disconnect();

console.log(`\nTotal assets checked: ${trueCounter + falseCounter}`);
console.log(`Prices match for ${trueCounter} assets`);
if (falseCounter > 0) {
const falseAssetsList = falseAssets.split('|');
console.log(`Prices don't match for the below ${falseCounter} asset(s):`);
for (let a in falseAssetsList) {
console.log(falseAssetsList[a]);
}
}
process.exit(1);
});
});

req.on('error', (error) => {
console.error(error);
});

req.write(query);
req.end();

0 comments on commit 8cf7c02

Please sign in to comment.