From c76aaabaa5a870a29a0fbb478e675957637d54ee Mon Sep 17 00:00:00 2001 From: Hadi Lee Date: Tue, 11 Jun 2024 16:48:59 +0800 Subject: [PATCH 1/7] feat: add transaction stats entity [SF-1135] --- schema.graphql | 16 +++++++++ src/helper/initializer.ts | 62 ++++++++++++++++++++++++++++++++++ src/mappings/lending-market.ts | 7 ++++ 3 files changed, 85 insertions(+) diff --git a/schema.graphql b/schema.graphql index a3fd3c2..b732691 100644 --- a/schema.graphql +++ b/schema.graphql @@ -156,3 +156,19 @@ type TransactionCandleStick @entity { volumeInFV: BigInt! lendingMarket: LendingMarket! } + +type TransactionStatsContainer @entity { + id: ID! + stats: [TransactionStats!]! @derivedFrom(field: "container") +} + +# TODO: add for APR +# TOOD: add for vol +type TransactionStats @entity { + id: ID! + currency: Bytes! + maturity: BigInt! + priceChange: BigInt! + percentageChange: BigDecimal! + container: TransactionStatsContainer! +} diff --git a/src/helper/initializer.ts b/src/helper/initializer.ts index 0a56a2c..6d136b6 100644 --- a/src/helper/initializer.ts +++ b/src/helper/initializer.ts @@ -14,6 +14,8 @@ import { Protocol, Transaction, TransactionCandleStick, + TransactionStats, + TransactionStatsContainer, Transfer, User, } from '../../generated/schema'; @@ -276,6 +278,66 @@ export const initTransfer = ( user.save(); }; +export function updateTransactionStats( + currency: Bytes, + maturity: BigInt, + currentTimestamp: BigInt +): void { + const currentCandleStickId = getTransactionCandleStickEntityId( + currency, + maturity, + BigInt.fromI32(86400), // 1 day interval + currentTimestamp.div(BigInt.fromI32(86400)) + ); + const currentCandleStick = + TransactionCandleStick.load(currentCandleStickId); + + if (currentCandleStick) { + const previousTimestamp = currentTimestamp.minus(BigInt.fromI32(86400)); + const previousCandleStickId = getTransactionCandleStickEntityId( + currency, + maturity, + BigInt.fromI32(86400), // 1 day interval + previousTimestamp.div(BigInt.fromI32(86400)) + ); + const previousCandleStick = TransactionCandleStick.load( + previousCandleStickId + ); + + if (previousCandleStick) { + const currentPrice = currentCandleStick.close; + const previousPrice = previousCandleStick.close; + + const priceChange = currentPrice.minus(previousPrice); + const percentageChange = priceChange + .times(BigInt.fromI32(10000)) + .div(previousPrice); + + const containerId = 'CONTAINER'; + let container = TransactionStatsContainer.load(containerId); + + if (!container) { + container = new TransactionStatsContainer(containerId); + } + + const statsId = currency.toHexString() + '-' + maturity.toString(); + let stats = TransactionStats.load(statsId); + + if (!stats) { + stats = new TransactionStats(statsId); + stats.currency = currency; + stats.maturity = maturity; + stats.container = container.id; + stats.save(); + } + + stats.priceChange = priceChange; + stats.percentageChange = percentageChange.toBigDecimal(); + stats.save(); + } + } +} + export const initOrUpdateTransactionCandleStick = ( currency: Bytes, maturity: BigInt, diff --git a/src/mappings/lending-market.ts b/src/mappings/lending-market.ts index 9f7b018..8a8648e 100644 --- a/src/mappings/lending-market.ts +++ b/src/mappings/lending-market.ts @@ -14,6 +14,7 @@ import { initOrUpdateTransactionCandleStick, initOrder, initTransaction, + updateTransactionStats, } from '../helper/initializer'; import { getOrderEntityId } from '../utils/id-generation'; @@ -213,6 +214,12 @@ export function handlePositionUnwound(event: PositionUnwound): void { BigInt.fromI32(intervals[i]) ); } + // TODO: check if this needs to be added in other handlers + updateTransactionStats( + event.params.ccy, + event.params.maturity, + event.block.timestamp + ); } } From 03845a80101928644b7ca9d6eb06f23c495523d0 Mon Sep 17 00:00:00 2001 From: Hadi Lee Date: Tue, 11 Jun 2024 17:48:44 +0800 Subject: [PATCH 2/7] feat: add transaction stats entity [SF-1135] --- schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema.graphql b/schema.graphql index b732691..9a36288 100644 --- a/schema.graphql +++ b/schema.graphql @@ -168,7 +168,7 @@ type TransactionStats @entity { id: ID! currency: Bytes! maturity: BigInt! - priceChange: BigInt! + priceChange: BigInt percentageChange: BigDecimal! container: TransactionStatsContainer! } From 3b70d8fc938c4383da899ca7e4b1115739176316 Mon Sep 17 00:00:00 2001 From: Hadi Lee Date: Tue, 11 Jun 2024 17:50:18 +0800 Subject: [PATCH 3/7] feat: add transaction stats entity [SF-1135] --- schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema.graphql b/schema.graphql index 9a36288..040837d 100644 --- a/schema.graphql +++ b/schema.graphql @@ -169,6 +169,6 @@ type TransactionStats @entity { currency: Bytes! maturity: BigInt! priceChange: BigInt - percentageChange: BigDecimal! + percentageChange: BigDecimal container: TransactionStatsContainer! } From 101ec2a858026ded9e30b00841580a570c766de5 Mon Sep 17 00:00:00 2001 From: Hadi Lee Date: Thu, 25 Jul 2024 09:38:03 +0800 Subject: [PATCH 4/7] feat: add daily transactions [SF-1135] --- schema.graphql | 17 ++++---- src/helper/initializer.ts | 75 +++++++++++++++------------------- src/mappings/lending-market.ts | 17 ++++++-- subgraph.yaml | 12 +++--- 4 files changed, 62 insertions(+), 59 deletions(-) diff --git a/schema.graphql b/schema.graphql index 040837d..04008c0 100644 --- a/schema.graphql +++ b/schema.graphql @@ -157,18 +157,19 @@ type TransactionCandleStick @entity { lendingMarket: LendingMarket! } -type TransactionStatsContainer @entity { +type DailyTransactionStatsContainer @entity { id: ID! - stats: [TransactionStats!]! @derivedFrom(field: "container") + stats: [DailyTransactionStats!]! @derivedFrom(field: "container") } -# TODO: add for APR -# TOOD: add for vol -type TransactionStats @entity { +type DailyTransactionStats @entity { id: ID! currency: Bytes! maturity: BigInt! - priceChange: BigInt - percentageChange: BigDecimal - container: TransactionStatsContainer! + timestamp: BigInt! + close: BigInt! + open: BigInt! + container: DailyTransactionStatsContainer! + change: BigInt! + percentageChange: BigInt! } diff --git a/src/helper/initializer.ts b/src/helper/initializer.ts index 6d136b6..a2c628b 100644 --- a/src/helper/initializer.ts +++ b/src/helper/initializer.ts @@ -6,6 +6,8 @@ import { log, } from '@graphprotocol/graph-ts'; import { + DailyTransactionStats, + DailyTransactionStatsContainer, DailyVolume, Deposit, LendingMarket, @@ -14,8 +16,6 @@ import { Protocol, Transaction, TransactionCandleStick, - TransactionStats, - TransactionStatsContainer, Transfer, User, } from '../../generated/schema'; @@ -278,7 +278,7 @@ export const initTransfer = ( user.save(); }; -export function updateTransactionStats( +export function initOrUpdateDailyTransactionStats( currency: Bytes, maturity: BigInt, currentTimestamp: BigInt @@ -286,56 +286,47 @@ export function updateTransactionStats( const currentCandleStickId = getTransactionCandleStickEntityId( currency, maturity, - BigInt.fromI32(86400), // 1 day interval + BigInt.fromI32(86400), // 24 hours in seconds currentTimestamp.div(BigInt.fromI32(86400)) ); const currentCandleStick = TransactionCandleStick.load(currentCandleStickId); - if (currentCandleStick) { - const previousTimestamp = currentTimestamp.minus(BigInt.fromI32(86400)); - const previousCandleStickId = getTransactionCandleStickEntityId( - currency, - maturity, - BigInt.fromI32(86400), // 1 day interval - previousTimestamp.div(BigInt.fromI32(86400)) - ); - const previousCandleStick = TransactionCandleStick.load( - previousCandleStickId - ); - - if (previousCandleStick) { - const currentPrice = currentCandleStick.close; - const previousPrice = previousCandleStick.close; - - const priceChange = currentPrice.minus(previousPrice); - const percentageChange = priceChange - .times(BigInt.fromI32(10000)) - .div(previousPrice); + const containerId = 'TRANSACTION_STATS_CONTAINER'; + let container = DailyTransactionStatsContainer.load(containerId); - const containerId = 'CONTAINER'; - let container = TransactionStatsContainer.load(containerId); + if (!container) { + container = new DailyTransactionStatsContainer(containerId); + container.save(); + } - if (!container) { - container = new TransactionStatsContainer(containerId); - } + const statsId = currency.toHexString() + '-' + maturity.toString(); + let stats = DailyTransactionStats.load(statsId); - const statsId = currency.toHexString() + '-' + maturity.toString(); - let stats = TransactionStats.load(statsId); + if (!stats) { + stats = new DailyTransactionStats(statsId); + } - if (!stats) { - stats = new TransactionStats(statsId); - stats.currency = currency; - stats.maturity = maturity; - stats.container = container.id; - stats.save(); - } + stats.currency = currency; + stats.maturity = maturity; + stats.container = container.id; + stats.timestamp = currentTimestamp; - stats.priceChange = priceChange; - stats.percentageChange = percentageChange.toBigDecimal(); - stats.save(); - } + if (currentCandleStick) { + stats.open = currentCandleStick.open; + stats.close = currentCandleStick.close; + stats.change = currentCandleStick.close.minus(currentCandleStick.open); + stats.percentageChange = stats.change + .times(BigInt.fromI32(10000)) + .div(currentCandleStick.open); + } else { + stats.open = BigInt.fromI32(0); + stats.close = BigInt.fromI32(0); + stats.change = BigInt.fromI32(0); + stats.percentageChange = BigInt.fromI32(0); } + + stats.save(); } export const initOrUpdateTransactionCandleStick = ( diff --git a/src/mappings/lending-market.ts b/src/mappings/lending-market.ts index 8a8648e..2359c76 100644 --- a/src/mappings/lending-market.ts +++ b/src/mappings/lending-market.ts @@ -11,10 +11,10 @@ import { ItayoseExecuted } from '../../generated/templates/OrderBookLogic/OrderB import { getOrInitDailyVolume, getOrInitLendingMarket, + initOrUpdateDailyTransactionStats, initOrUpdateTransactionCandleStick, initOrder, initTransaction, - updateTransactionStats, } from '../helper/initializer'; import { getOrderEntityId } from '../utils/id-generation'; @@ -113,6 +113,12 @@ export function handleOrderExecuted(event: OrderExecuted): void { BigInt.fromI32(intervals[i]) ); } + + initOrUpdateDailyTransactionStats( + event.params.ccy, + event.params.maturity, + event.block.timestamp + ); } } @@ -214,8 +220,8 @@ export function handlePositionUnwound(event: PositionUnwound): void { BigInt.fromI32(intervals[i]) ); } - // TODO: check if this needs to be added in other handlers - updateTransactionStats( + + initOrUpdateDailyTransactionStats( event.params.ccy, event.params.maturity, event.block.timestamp @@ -328,6 +334,11 @@ export function handleItayoseExecuted(event: ItayoseExecuted): void { BigInt.fromI32(intervals[i]) ); } + initOrUpdateDailyTransactionStats( + event.params.ccy, + event.params.maturity, + event.block.timestamp + ); } function addToTransactionVolume( diff --git a/subgraph.yaml b/subgraph.yaml index 5de8ca0..cbe4436 100644 --- a/subgraph.yaml +++ b/subgraph.yaml @@ -5,7 +5,7 @@ schema: dataSources: - kind: ethereum name: LendingMarketOperationLogic - network: ~ + network: mainnet source: abi: LendingMarketController mapping: @@ -27,7 +27,7 @@ dataSources: file: ./src/mappings/lending-controller.ts - kind: ethereum name: TokenVault - network: ~ + network: mainnet source: abi: TokenVault mapping: @@ -48,7 +48,7 @@ dataSources: file: ./src/mappings/token-vault.ts - kind: ethereum name: FundManagementLogic - network: ~ + network: mainnet source: abi: LendingMarketController mapping: @@ -67,7 +67,7 @@ dataSources: file: ./src/mappings/fund-management.ts - kind: ethereum name: LiquidationLogic - network: ~ + network: mainnet source: abi: LendingMarketController mapping: @@ -86,7 +86,7 @@ dataSources: templates: - kind: ethereum name: OrderActionLogic - network: ~ + network: mainnet source: abi: OrderActionLogic mapping: @@ -114,7 +114,7 @@ templates: file: ./src/mappings/lending-market.ts - kind: ethereum name: OrderBookLogic - network: ~ + network: mainnet source: abi: OrderBookLogic mapping: From 97c777459327c9afc7914bbb997c5346d96ad45c Mon Sep 17 00:00:00 2001 From: Hadi Lee Date: Thu, 25 Jul 2024 11:06:07 +0800 Subject: [PATCH 5/7] fix: fix network [SF-1135] --- subgraph.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/subgraph.yaml b/subgraph.yaml index cbe4436..5de8ca0 100644 --- a/subgraph.yaml +++ b/subgraph.yaml @@ -5,7 +5,7 @@ schema: dataSources: - kind: ethereum name: LendingMarketOperationLogic - network: mainnet + network: ~ source: abi: LendingMarketController mapping: @@ -27,7 +27,7 @@ dataSources: file: ./src/mappings/lending-controller.ts - kind: ethereum name: TokenVault - network: mainnet + network: ~ source: abi: TokenVault mapping: @@ -48,7 +48,7 @@ dataSources: file: ./src/mappings/token-vault.ts - kind: ethereum name: FundManagementLogic - network: mainnet + network: ~ source: abi: LendingMarketController mapping: @@ -67,7 +67,7 @@ dataSources: file: ./src/mappings/fund-management.ts - kind: ethereum name: LiquidationLogic - network: mainnet + network: ~ source: abi: LendingMarketController mapping: @@ -86,7 +86,7 @@ dataSources: templates: - kind: ethereum name: OrderActionLogic - network: mainnet + network: ~ source: abi: OrderActionLogic mapping: @@ -114,7 +114,7 @@ templates: file: ./src/mappings/lending-market.ts - kind: ethereum name: OrderBookLogic - network: mainnet + network: ~ source: abi: OrderBookLogic mapping: From 59d77c820d15ada698e8a13dcf842274aac7ac3d Mon Sep 17 00:00:00 2001 From: Hadi Lee Date: Fri, 26 Jul 2024 12:45:07 +0800 Subject: [PATCH 6/7] fix: handle divide by zero case [SF-1135] --- src/helper/initializer.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/helper/initializer.ts b/src/helper/initializer.ts index a2c628b..184c469 100644 --- a/src/helper/initializer.ts +++ b/src/helper/initializer.ts @@ -316,9 +316,11 @@ export function initOrUpdateDailyTransactionStats( stats.open = currentCandleStick.open; stats.close = currentCandleStick.close; stats.change = currentCandleStick.close.minus(currentCandleStick.open); - stats.percentageChange = stats.change - .times(BigInt.fromI32(10000)) - .div(currentCandleStick.open); + stats.percentageChange = currentCandleStick.open.isZero() + ? BigInt.fromI32(0) + : stats.change + .times(BigInt.fromI32(10000)) + .div(currentCandleStick.open); } else { stats.open = BigInt.fromI32(0); stats.close = BigInt.fromI32(0); From c718bc66dda2456b0b63fbac24f3ea4d1db2d7cb Mon Sep 17 00:00:00 2001 From: Hadi Lee Date: Fri, 26 Jul 2024 14:47:05 +0800 Subject: [PATCH 7/7] fix: add tests [SF-1135] --- src/helper/initializer.ts | 3 +- src/utils/id-generation.ts | 7 ++ test/lending-market.test.ts | 193 ++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 1 deletion(-) diff --git a/src/helper/initializer.ts b/src/helper/initializer.ts index 184c469..c9af426 100644 --- a/src/helper/initializer.ts +++ b/src/helper/initializer.ts @@ -20,6 +20,7 @@ import { User, } from '../../generated/schema'; import { + getDailyTransactionEntityId, getDailyVolumeEntityId, getTransactionCandleStickEntityId, } from '../utils/id-generation'; @@ -300,7 +301,7 @@ export function initOrUpdateDailyTransactionStats( container.save(); } - const statsId = currency.toHexString() + '-' + maturity.toString(); + const statsId = getDailyTransactionEntityId(currency, maturity); let stats = DailyTransactionStats.load(statsId); if (!stats) { diff --git a/src/utils/id-generation.ts b/src/utils/id-generation.ts index c57406d..ec71b26 100644 --- a/src/utils/id-generation.ts +++ b/src/utils/id-generation.ts @@ -24,3 +24,10 @@ export function getTransactionCandleStickEntityId( ): string { return `${ccy.toString()}-${maturity.toString()}-${interval.toString()}-${epochTime.toString()}`; } + +export function getDailyTransactionEntityId( + ccy: Bytes, + maturity: BigInt +): string { + return `${ccy.toHexString()}-${maturity.toString()}`; +} diff --git a/test/lending-market.test.ts b/test/lending-market.test.ts index fe1bee8..6ddf78f 100644 --- a/test/lending-market.test.ts +++ b/test/lending-market.test.ts @@ -17,6 +17,7 @@ import { handlePreOrderExecuted, } from '../src/mappings/lending-market'; import { + getDailyTransactionEntityId, getDailyVolumeEntityId, getOrderEntityId, getTransactionCandleStickEntityId, @@ -1911,6 +1912,7 @@ describe('Transaction Candle Stick', () => { 'interval', interval.toString() ); + assert.fieldEquals( 'TransactionCandleStick', id, @@ -2479,3 +2481,194 @@ describe('Transaction Candle Stick', () => { } }); }); + +describe('Daily Transaction Stats', () => { + beforeEach(() => { + clearStore(); + createLendingMarket(ccy, maturity); + }); + + test('order executed creates and updates daily stats correctly', () => { + const placedOrderId = BigInt.fromI32(0); + const filledAmount = BigInt.fromI32(81); + const filledUnitPrice = unitPrice; + const filledAmountInFV = BigInt.fromI32(90); + const totalAmount = filledAmount.plus(amount); + const feeInFV = BigInt.fromI32(1); + + const event = createOrderExecutedEvent( + ALICE, + borrow, + ccy, + maturity, + totalAmount, + BigInt.fromI32(0), + filledAmount, + filledUnitPrice, + filledAmountInFV, + feeInFV, + placedOrderId, + BigInt.fromI32(0), + BigInt.fromI32(0), + true + ); + handleOrderExecuted(event); + + const id = getDailyTransactionEntityId(ccy, maturity); + + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'currency', + ccy.toHexString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'maturity', + maturity.toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'open', + filledUnitPrice.toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'close', + filledUnitPrice.toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'change', + BigInt.fromI32(0).toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'percentageChange', + BigInt.fromI32(0).toString() + ); + }); + + test('position unwound should update the daily stats', () => { + const futureValue = BigInt.fromI32(250); + const filledAmount = BigInt.fromI32(225); + const filledUnitPrice = unitPrice; + const filledAmountInFV = BigInt.fromI32(250); + + const event = createPositionUnwoundEvent( + BOB, + lend, + ccy, + maturity, + futureValue, + filledAmount, + filledUnitPrice, + filledAmountInFV, + BigInt.fromI32(0), + false, + timestamp + ); + handlePositionUnwound(event); + + const id = getDailyTransactionEntityId(ccy, maturity); + + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'currency', + ccy.toHexString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'maturity', + maturity.toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'open', + filledUnitPrice.toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'close', + filledUnitPrice.toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'change', + BigInt.fromI32(0).toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'percentageChange', + BigInt.fromI32(0).toString() + ); + }); + + test('itayose executed event should update the daily stats', () => { + const openingUnitPrice = BigInt.fromI32(8050); + const lastLendUnitPrice = BigInt.fromI32(8100); + const lastBorrowUnitPrice = BigInt.fromI32(8000); + const offsetAmount = BigInt.fromI32(300); + const offsetAmountInFV = BigInt.fromI32(372); + const itayoseExecutedEvent = createItayoseExecutedEvent( + ccy, + maturity, + openingUnitPrice, + lastLendUnitPrice, + lastBorrowUnitPrice, + offsetAmount, + timestamp + ); + handleItayoseExecuted(itayoseExecutedEvent); + + const id = getDailyTransactionEntityId(ccy, maturity); + + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'currency', + ccy.toHexString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'maturity', + maturity.toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'open', + openingUnitPrice.toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'close', + openingUnitPrice.toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'change', + BigInt.fromI32(0).toString() + ); + assert.fieldEquals( + 'DailyTransactionStats', + id, + 'percentageChange', + BigInt.fromI32(0).toString() + ); + }); +});