diff --git a/src/utils/apr.ts b/src/utils/apr.ts index 29ade71..e89a405 100644 --- a/src/utils/apr.ts +++ b/src/utils/apr.ts @@ -55,7 +55,7 @@ export class AprState { entry.collectTimestamp.toString(), lastEntry.collectTimestamp.toString(), ]) - throw new Error("AprCalc: collectTimestamp is not in order") + throw Error("AprCalc: collectTimestamp is not in order") } else { // latest entry is the last one this.collects.push(entry) @@ -67,7 +67,7 @@ export class AprCalc { public static calculateLastApr(period: BigInt, state: AprState, now: BigInt): BigDecimal { if (period.lt(ZERO_BI) || period.equals(ZERO_BI)) { log.error("AprCalc: period cannot be negative or zero, got {}", [period.toString()]) - throw new Error("AprCalc: period cannot be negative or zero") + throw Error("AprCalc: period cannot be negative or zero") } // we need at least 1 entry to compute the apr if (state.collects.length === 0) { diff --git a/src/utils/daily-avg.ts b/src/utils/daily-avg.ts index 8fef573..8a14fcd 100644 --- a/src/utils/daily-avg.ts +++ b/src/utils/daily-avg.ts @@ -75,7 +75,7 @@ export class DailyAvgCalc { public static evictOldEntries(entriesToUse: BigInt, state: DailyAvgState): DailyAvgState { if (entriesToUse.lt(ZERO_BI) || entriesToUse.equals(ZERO_BI)) { log.error("DailyAvgCalc: entriesToUse cannot be negative or zero, got {}", [entriesToUse.toString()]) - throw new Error("DailyAvgCalc: entriesToUse cannot be negative or zero") + throw Error("DailyAvgCalc: entriesToUse cannot be negative or zero") } let lastEntryIdx = state.closedValues.length - 1 diff --git a/src/utils/snapshot.ts b/src/utils/snapshot.ts index edbe211..62d87c3 100644 --- a/src/utils/snapshot.ts +++ b/src/utils/snapshot.ts @@ -1,4 +1,5 @@ import { BigInt, Bytes } from "@graphprotocol/graph-ts" +import { YEAR, getPreviousIntervalFromTimestamp } from "./time" @inline @@ -8,8 +9,12 @@ export function getSnapshotIdSuffix(period: BigInt, interval: BigInt): Bytes { @inline -export function getPreviousSnapshotIdSuffix(period: BigInt, interval: BigInt): Bytes { +export function getPreviousSnapshotIdSuffix(period: BigInt, timestamp: BigInt): Bytes { + // just a test to prevent developer mistakes + if (timestamp.lt(YEAR)) { + throw new Error("This function, unlike getSnapshotIdSuffix, expects the timestamp instead of the interval") + } return Bytes.fromByteArray(Bytes.fromBigInt(period)).concat( - Bytes.fromByteArray(Bytes.fromBigInt(interval.minus(period))), + Bytes.fromByteArray(Bytes.fromBigInt(getPreviousIntervalFromTimestamp(timestamp, period))), ) } diff --git a/src/utils/time.ts b/src/utils/time.ts index 165f60d..a3180f3 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -1,14 +1,51 @@ import { BigInt } from "@graphprotocol/graph-ts" +import { log } from "matchstick-as" export const MINUTES_15 = BigInt.fromI32(60 * 15) export const HOUR = BigInt.fromI32(60 * 60) export const DAY = BigInt.fromI32(60 * 60 * 24) export const WEEK = BigInt.fromI32(60 * 60 * 24 * 7) +export const MONTH = BigInt.fromI32(60 * 60 * 24 * 30) +export const QUARTER = BigInt.fromI32(60 * 60 * 24 * 30 * 3) export const YEAR = BigInt.fromI32(60 * 60 * 24 * 365) -export const SNAPSHOT_PERIODS = [DAY, WEEK, YEAR] +export const SNAPSHOT_PERIODS = [HOUR, DAY, WEEK, MONTH, QUARTER, YEAR] @inline export function getIntervalFromTimestamp(timestamp: BigInt, period: BigInt): BigInt { + // if the period is not stable, use date math to calculate the interval + if (period.ge(WEEK)) { + const date = new Date(timestamp.toI64() * 1000) + date.setUTCMilliseconds(0) + date.setUTCSeconds(0) + date.setUTCMinutes(0) + date.setUTCHours(0) + date.setUTCDate(date.getUTCDate() - date.getUTCDay()) + if (period.equals(WEEK)) { + return BigInt.fromI64(date.getTime() / 1000) + } + date.setUTCDate(1) + if (period.equals(MONTH)) { + return BigInt.fromI64(date.getTime() / 1000) + } + date.setUTCMonth(date.getUTCMonth() - (date.getUTCMonth() % 3)) + if (period.equals(QUARTER)) { + return BigInt.fromI64(date.getTime() / 1000) + } + date.setUTCMonth(0) + if (period.equals(YEAR)) { + return BigInt.fromI64(date.getTime() / 1000) + } + + log.error("Unsupported period: {}", [period.toString()]) + throw Error("Unsupported period: " + period.toString()) + } return timestamp.div(period).times(period) } + + +@inline +export function getPreviousIntervalFromTimestamp(timestamp: BigInt, period: BigInt): BigInt { + const truncated = getIntervalFromTimestamp(timestamp, period) + return getIntervalFromTimestamp(truncated.minus(BigInt.fromI32(10)), period) +} diff --git a/tests/utils/time.test.ts b/tests/utils/time.test.ts new file mode 100644 index 0000000..0c0cd6a --- /dev/null +++ b/tests/utils/time.test.ts @@ -0,0 +1,60 @@ +import { assert, test, describe } from "matchstick-as/assembly/index" +import { BigInt } from "@graphprotocol/graph-ts" +import { + DAY, + HOUR, + MONTH, + QUARTER, + WEEK, + YEAR, + getIntervalFromTimestamp, + getPreviousIntervalFromTimestamp, +} from "../../src/utils/time" + +describe("time.getIntervalFromTimestamp", () => { + test("Support all the different periods", () => { + const timestamp = BigInt.fromString("1712744972") + + // simple periods + let res = getIntervalFromTimestamp(timestamp, HOUR) + assert.assertTrue(res.equals(BigInt.fromString("1712743200"))) + + res = getIntervalFromTimestamp(timestamp, DAY) + assert.assertTrue(res.equals(BigInt.fromString("1712707200"))) + + res = getIntervalFromTimestamp(timestamp, WEEK) + assert.assertTrue(res.equals(BigInt.fromString("1712448000"))) + + res = getIntervalFromTimestamp(timestamp, MONTH) + assert.assertTrue(res.equals(BigInt.fromString("1711929600"))) + + res = getIntervalFromTimestamp(timestamp, QUARTER) + assert.assertTrue(res.equals(BigInt.fromString("1711929600"))) + + res = getIntervalFromTimestamp(timestamp, YEAR) + assert.assertTrue(res.equals(BigInt.fromString("1704067200"))) + }) + + test("can query the previous interval as well", () => { + const timestamp = BigInt.fromString("1712744972") + + // simple periods + let res = getPreviousIntervalFromTimestamp(timestamp, HOUR) + assert.assertTrue(res.equals(BigInt.fromString("1712739600"))) + + res = getPreviousIntervalFromTimestamp(timestamp, DAY) + assert.assertTrue(res.equals(BigInt.fromString("1712620800"))) + + res = getPreviousIntervalFromTimestamp(timestamp, WEEK) + assert.assertTrue(res.equals(BigInt.fromString("1711843200"))) + + res = getPreviousIntervalFromTimestamp(timestamp, MONTH) + assert.assertTrue(res.equals(BigInt.fromString("1709251200"))) + + res = getPreviousIntervalFromTimestamp(timestamp, QUARTER) + assert.assertTrue(res.equals(BigInt.fromString("1704067200"))) + + res = getPreviousIntervalFromTimestamp(timestamp, YEAR) + assert.assertTrue(res.equals(BigInt.fromString("1672531200"))) + }) +})