Skip to content

Commit 56e2801

Browse files
moshenskyDVg1nt0ki
authored andcommitted
fix: FolksFinance xAlgo circulating supply calculation (DefiLlama#13365)
1 parent 43ab962 commit 56e2801

File tree

4 files changed

+153
-4
lines changed

4 files changed

+153
-4
lines changed

projects/folks-xalgo/constants.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const xAlgoAssetId = 1134696561;
2+
const algoAssetId = 0;
3+
4+
const oracleAppId = 1040271396;
5+
const oracleDecimals = 14;
6+
7+
module.exports = {
8+
xAlgoAssetId,
9+
algoAssetId,
10+
oracleAppId,
11+
oracleDecimals,
12+
};

projects/folks-xalgo/index.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
const { getAssetInfo } = require("../helper/chain/algorand");
2-
3-
const xAlgoAssetId = 1134696561;
2+
const { getCachedPrices } = require('./prices');
3+
const { xAlgoAssetId, algoAssetId } = require('./constants');
44

55
module.exports = {
66
timetravel: false,
77
algorand: {
8-
tvl: async (api) => {
8+
tvl: async () => {
9+
const prices = await getCachedPrices();
10+
const xAlgoPrice = prices[xAlgoAssetId];
11+
const algoPrice = prices[algoAssetId];
12+
913
const info = await getAssetInfo(xAlgoAssetId);
10-
api.add(xAlgoAssetId+'', info.circulatingSupply)
14+
const totalXAlgo = info.circulatingSupply / 10 ** info.decimals;
15+
const totalAlgo = totalXAlgo * xAlgoPrice / algoPrice;
16+
return { algorand: totalAlgo };
1117
},
1218
},
1319
};

projects/folks-xalgo/prices.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
const { oracleAppId, oracleDecimals } = require("./constants");
2+
const {
3+
getAppState,
4+
getParsedValueFromState,
5+
fromIntToBytes8Hex,
6+
parseOracleValue,
7+
} = require("./utils");
8+
9+
function decodeUint64(data, decodingMode = "safe") {
10+
if (
11+
decodingMode !== "safe" &&
12+
decodingMode !== "mixed" &&
13+
decodingMode !== "bigint"
14+
) {
15+
throw new Error(`Unknown decodingMode option: ${decodingMode}`);
16+
}
17+
18+
if (data.byteLength === 0 || data.byteLength > 8) {
19+
throw new Error(
20+
`Data has unacceptable length. Expected length is between 1 and 8, got ${data.byteLength}`
21+
);
22+
}
23+
24+
// insert 0s at the beginning if data is smaller than 8 bytes
25+
const padding = Buffer.allocUnsafe(8 - data.byteLength);
26+
padding.fill(0);
27+
28+
const buf = Buffer.concat([padding, Buffer.from(data)]);
29+
30+
const num = buf.readBigUInt64BE();
31+
const isBig = num > Number.MAX_SAFE_INTEGER;
32+
33+
if (decodingMode === "safe") {
34+
if (isBig) {
35+
throw new Error(
36+
`Integer exceeds maximum safe integer: ${num.toString()}. Try decoding with "mixed" or "safe" decodingMode.`
37+
);
38+
}
39+
return Number(num);
40+
}
41+
42+
if (decodingMode === "mixed" && !isBig) {
43+
return Number(num);
44+
}
45+
46+
return num;
47+
}
48+
49+
let pricesCache;
50+
51+
async function getCachedPrices() {
52+
if (!pricesCache) pricesCache = getPrices();
53+
return pricesCache;
54+
}
55+
56+
/* Get prices from oracle */
57+
async function getPrices() {
58+
const oracleState = await getAppState(oracleAppId);
59+
60+
const prices = {};
61+
62+
// get the assets for which we need to retrieve their prices
63+
const assets = oracleState
64+
.filter(({ key }) => {
65+
// remove non asset ids global state
66+
key = Buffer.from(key, "base64").toString("utf8");
67+
return (
68+
key !== "updater_addr" &&
69+
key !== "admin" &&
70+
key !== "tinyman_validator_app_id"
71+
);
72+
})
73+
.map(({ key }) => {
74+
// convert key to asset id
75+
return decodeUint64(Buffer.from(key, "base64"), "safe");
76+
});
77+
78+
// retrieve asset prices
79+
assets.forEach((assetId) => {
80+
const assetPrice = parseOracleValue(
81+
String(
82+
getParsedValueFromState(oracleState, fromIntToBytes8Hex(assetId), "hex")
83+
)
84+
);
85+
86+
prices[assetId] = Number(assetPrice) / 10 ** oracleDecimals;
87+
});
88+
89+
return prices;
90+
}
91+
92+
module.exports = { getCachedPrices };

projects/folks-xalgo/utils.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const { lookupApplications } = require("../helper/chain/algorand");
2+
3+
function fromIntToBytes8Hex(num) {
4+
return num.toString(16).padStart(16, "0");
5+
}
6+
7+
function encodeToBase64(str, encoding = "utf8") {
8+
return Buffer.from(str, encoding).toString("base64");
9+
}
10+
11+
function parseOracleValue(base64Value) {
12+
const value = Buffer.from(base64Value, "base64").toString("hex");
13+
// first 8 bytes are the price
14+
const price = BigInt("0x" + value.slice(0, 16));
15+
16+
return price;
17+
}
18+
19+
function getParsedValueFromState(state, key, encoding = "utf8") {
20+
const encodedKey = encoding ? encodeToBase64(key, encoding) : key;
21+
const keyValue = state.find((entry) => entry.key === encodedKey);
22+
if (keyValue === undefined) return;
23+
const { value } = keyValue;
24+
if (value.type === 1) return value.bytes;
25+
if (value.type === 2) return BigInt(value.uint);
26+
return;
27+
}
28+
29+
async function getAppState(appId) {
30+
const res = await lookupApplications(appId);
31+
return res.application.params["global-state"];
32+
}
33+
34+
module.exports = {
35+
fromIntToBytes8Hex,
36+
parseOracleValue,
37+
getParsedValueFromState,
38+
getAppState,
39+
};

0 commit comments

Comments
 (0)