diff --git a/schema.graphql b/schema.graphql index aa25ed2..047ff9c 100755 --- a/schema.graphql +++ b/schema.graphql @@ -54,6 +54,8 @@ type Protocol @entity { pendingDeactivation: [Transcoder!]! "Total number of delegators on the network" delegatorsCount: BigInt! + "Broadcasters active within the current 90 day fee window" + activeBroadcasters: [String!]! } """ @@ -231,6 +233,22 @@ type Broadcaster @entity { deposit: BigDecimal! "Amount of funds in reserve" reserve: BigDecimal! + "Total fees paid out by this broadcaster in ETH" + totalVolumeETH: BigDecimal! + "Total fees paid out by this broadcaster in USD" + totalVolumeUSD: BigDecimal! + "Fees paid out by this broadcaster in ETH during the last 30 days" + thirtyDayVolumeETH: BigDecimal! + "Fees paid out by this broadcaster in ETH during the last 60 days" + sixtyDayVolumeETH: BigDecimal! + "Fees paid out by this broadcaster in ETH during the last 90 days" + ninetyDayVolumeETH: BigDecimal! + "The date this broadcaster first funded a deposit or reserve, beginning at 12:00am UTC" + firstActiveDay: Int! + "The date this broadcaster last paid fees, beginning at 12:00am UTC" + lastActiveDay: Int! + "Days in which this broadcaster paid out fees" + broadcasterDays: [BroadcasterDay!]! } """ @@ -342,7 +360,7 @@ type Day @entity { Transcoder data accumulated and condensed into day stats """ type TranscoderDay @entity { - "Combination of the transcoder address and the timestamp rounded to current day by dividing by 86400" + "Concatenation of the transcoder address and the day timestamp (e.g.
-)" id: ID! "The date beginning at 12:00am UTC" date: Int! @@ -354,6 +372,22 @@ type TranscoderDay @entity { transcoder: Transcoder! } +""" +Broadcaster data accumulated and condensed into day stats +""" +type BroadcasterDay @entity { + "Concatenation of the broadcaster address and the day timestamp (e.g.
-)" + id: ID! + "The date beginning at 12:00am UTC" + date: Int! + "Fees paid this day in ETH" + volumeETH: BigDecimal! + "Fees paid this day in USD" + volumeUSD: BigDecimal! + "Broadcaster associated with the day" + broadcaster: Broadcaster! +} + type TreasuryProposal @entity { "Governor proposal ID formatted as a decimal number" id: ID! diff --git a/src/mappings/roundsManager.ts b/src/mappings/roundsManager.ts index 228fbaf..6ed9919 100644 --- a/src/mappings/roundsManager.ts +++ b/src/mappings/roundsManager.ts @@ -28,6 +28,8 @@ import { } from "../types/RoundsManager/RoundsManager"; // Import entity types generated from the GraphQL schema import { + Broadcaster, + BroadcasterDay, NewRoundEvent, ParameterUpdateEvent, Pool, @@ -177,6 +179,59 @@ export function newRound(event: NewRound): void { } } + // Update rolling fee windows for tracked broadcasters (gateways) + let trackedBroadcasters = protocol.activeBroadcasters; + let activeBroadcasters: string[] = []; + if (trackedBroadcasters.length) { + for (let i = 0; i < trackedBroadcasters.length; i++) { + let broadcaster = Broadcaster.load(trackedBroadcasters[i]); + + if (broadcaster) { + // --- Get the 30, 60, 90 day sums of volume --- + let broadcasterThirtyDaySum = ZERO_BD; + let broadcasterSixtyDaySum = ZERO_BD; + let broadcasterNinetyDaySum = ZERO_BD; + + // capped at <90 - broadcaster days are ordered newest first + let broadcasterDays = broadcaster.broadcasterDays; + let broadcasterDaysLength = + broadcasterDays.length > 90 ? 90 : broadcasterDays.length; + for (let j = 0; j < broadcasterDaysLength; j++) { + let broadcasterDay = BroadcasterDay.load(broadcasterDays[j]); + + if (broadcasterDay) { + if (broadcasterDay.date >= thirtyDayTimestamp) { + broadcasterThirtyDaySum = broadcasterThirtyDaySum.plus( + broadcasterDay.volumeETH + ); + } + if (broadcasterDay.date >= sixtyDayTimestamp) { + broadcasterSixtyDaySum = broadcasterSixtyDaySum.plus( + broadcasterDay.volumeETH + ); + } + if (broadcasterDay.date >= ninetyDayTimestamp) { + broadcasterNinetyDaySum = broadcasterNinetyDaySum.plus( + broadcasterDay.volumeETH + ); + } + } + } + + broadcaster.thirtyDayVolumeETH = broadcasterThirtyDaySum; + broadcaster.sixtyDayVolumeETH = broadcasterSixtyDaySum; + broadcaster.ninetyDayVolumeETH = broadcasterNinetyDaySum; + broadcaster.save(); + + if (broadcaster.lastActiveDay >= ninetyDayTimestamp) { + activeBroadcasters.push(broadcaster.id); + } + } + } + } + + protocol.activeBroadcasters = activeBroadcasters; + let lptPriceEth = getLptPriceEth(); protocol.lptPriceEth = lptPriceEth; diff --git a/src/mappings/ticketBroker.ts b/src/mappings/ticketBroker.ts index 90d08f4..b929106 100644 --- a/src/mappings/ticketBroker.ts +++ b/src/mappings/ticketBroker.ts @@ -2,6 +2,7 @@ import { Address, BigInt, dataSource, log } from "@graphprotocol/graph-ts"; import { convertToDecimal, createOrLoadBroadcaster, + createOrLoadBroadcasterDay, createOrLoadDay, createOrLoadProtocol, createOrLoadRound, @@ -32,13 +33,15 @@ import { export function winningTicketRedeemed(event: WinningTicketRedeemed): void { let round = createOrLoadRound(getBlockNum()); - let day = createOrLoadDay(event.block.timestamp.toI32()); + let timestamp = event.block.timestamp.toI32(); + let day = createOrLoadDay(timestamp); let winningTicketRedeemedEvent = new WinningTicketRedeemedEvent( makeEventId(event.transaction.hash, event.logIndex) ); let protocol = createOrLoadProtocol(); let faceValue = convertToDecimal(event.params.faceValue); let ethPrice = getEthPriceUsd(); + let faceValueUSD = faceValue.times(ethPrice); let poolId = makePoolId(event.params.recipient.toHex(), round.id); let pool = Pool.load(poolId); @@ -50,7 +53,7 @@ export function winningTicketRedeemed(event: WinningTicketRedeemed): void { winningTicketRedeemedEvent.sender = event.params.sender.toHex(); winningTicketRedeemedEvent.recipient = event.params.recipient.toHex(); winningTicketRedeemedEvent.faceValue = faceValue; - winningTicketRedeemedEvent.faceValueUSD = faceValue.times(ethPrice); + winningTicketRedeemedEvent.faceValueUSD = faceValueUSD; winningTicketRedeemedEvent.winProb = event.params.winProb; winningTicketRedeemedEvent.senderNonce = event.params.senderNonce; winningTicketRedeemedEvent.recipientRand = event.params.recipientRand; @@ -76,6 +79,23 @@ export function winningTicketRedeemed(event: WinningTicketRedeemed): void { } else { broadcaster.deposit = broadcaster.deposit.minus(faceValue); } + + broadcaster.totalVolumeETH = broadcaster.totalVolumeETH.plus(faceValue); + broadcaster.totalVolumeUSD = broadcaster.totalVolumeUSD.plus(faceValueUSD); + + let broadcasterDay = createOrLoadBroadcasterDay( + timestamp, + event.params.sender.toHex() + ); + broadcaster.lastActiveDay = broadcasterDay.date; + broadcasterDay.volumeETH = broadcasterDay.volumeETH.plus(faceValue); + broadcasterDay.volumeUSD = broadcasterDay.volumeUSD.plus(faceValueUSD); + broadcasterDay.save(); + let broadcasterDays = broadcaster.broadcasterDays; + if (!broadcasterDays.includes(broadcasterDay.id)) { + broadcasterDays.unshift(broadcasterDay.id); + broadcaster.broadcasterDays = broadcasterDays; + } broadcaster.save(); // Update transcoder's fee volume @@ -84,15 +104,11 @@ export function winningTicketRedeemed(event: WinningTicketRedeemed): void { event.block.timestamp.toI32() ); transcoder.totalVolumeETH = transcoder.totalVolumeETH.plus(faceValue); - transcoder.totalVolumeUSD = transcoder.totalVolumeUSD.plus( - faceValue.times(ethPrice) - ); + transcoder.totalVolumeUSD = transcoder.totalVolumeUSD.plus(faceValueUSD); // Update total protocol fee volume protocol.totalVolumeETH = protocol.totalVolumeETH.plus(faceValue); - protocol.totalVolumeUSD = protocol.totalVolumeUSD.plus( - faceValue.times(ethPrice) - ); + protocol.totalVolumeUSD = protocol.totalVolumeUSD.plus(faceValueUSD); protocol.winningTicketCount = protocol.winningTicketCount + 1; protocol.save(); @@ -107,17 +123,15 @@ export function winningTicketRedeemed(event: WinningTicketRedeemed): void { day.totalActiveStake = protocol.totalActiveStake; day.participationRate = protocol.participationRate; day.volumeETH = day.volumeETH.plus(faceValue); - day.volumeUSD = day.volumeUSD.plus(faceValue.times(ethPrice)); + day.volumeUSD = day.volumeUSD.plus(faceValueUSD); day.save(); let transcoderDay = createOrLoadTranscoderDay( - event.block.timestamp.toI32(), + timestamp, event.params.recipient.toHex() ); transcoderDay.volumeETH = transcoderDay.volumeETH.plus(faceValue); - transcoderDay.volumeUSD = transcoderDay.volumeUSD.plus( - faceValue.times(ethPrice) - ); + transcoderDay.volumeUSD = transcoderDay.volumeUSD.plus(faceValueUSD); transcoderDay.save(); // Manually manage the array of transcoder days (add newest to the beginning of the list) @@ -130,13 +144,19 @@ export function winningTicketRedeemed(event: WinningTicketRedeemed): void { // Update fee volume for this round round.volumeETH = round.volumeETH.plus(faceValue); - round.volumeUSD = round.volumeUSD.plus(faceValue.times(ethPrice)); + round.volumeUSD = round.volumeUSD.plus(faceValueUSD); round.save(); } export function depositFunded(event: DepositFunded): void { let round = createOrLoadRound(getBlockNum()); let broadcaster = createOrLoadBroadcaster(event.params.sender.toHex()); + const timestamp = event.block.timestamp.toI32(); + + // One-time initialization: set to start of day for this timestamp. + if (broadcaster.firstActiveDay == 0) { + broadcaster.firstActiveDay = (timestamp / 86400) * 86400; + } broadcaster.deposit = broadcaster.deposit.plus( convertToDecimal(event.params.amount) @@ -149,7 +169,7 @@ export function depositFunded(event: DepositFunded): void { makeEventId(event.transaction.hash, event.logIndex) ); depositFundedEvent.transaction = event.transaction.hash.toHex(); - depositFundedEvent.timestamp = event.block.timestamp.toI32(); + depositFundedEvent.timestamp = timestamp; depositFundedEvent.round = round.id; depositFundedEvent.sender = event.params.sender.toHex(); depositFundedEvent.amount = convertToDecimal(event.params.amount); @@ -159,6 +179,12 @@ export function depositFunded(event: DepositFunded): void { export function reserveFunded(event: ReserveFunded): void { let round = createOrLoadRound(getBlockNum()); let broadcaster = createOrLoadBroadcaster(event.params.reserveHolder.toHex()); + const timestamp = event.block.timestamp.toI32(); + + // One-time initialization: set to start of day for this timestamp. + if (broadcaster.firstActiveDay == 0) { + broadcaster.firstActiveDay = (timestamp / 86400) * 86400; + } broadcaster.reserve = broadcaster.reserve.plus( convertToDecimal(event.params.amount) @@ -171,7 +197,7 @@ export function reserveFunded(event: ReserveFunded): void { makeEventId(event.transaction.hash, event.logIndex) ); reserveFundedEvent.transaction = event.transaction.hash.toHex(); - reserveFundedEvent.timestamp = event.block.timestamp.toI32(); + reserveFundedEvent.timestamp = timestamp; reserveFundedEvent.round = round.id; reserveFundedEvent.reserveHolder = event.params.reserveHolder.toHex(); reserveFundedEvent.amount = convertToDecimal(event.params.amount); diff --git a/utils/helpers.ts b/utils/helpers.ts index 84c30d5..d068d43 100644 --- a/utils/helpers.ts +++ b/utils/helpers.ts @@ -9,6 +9,7 @@ import { import { RoundsManager } from "../src/types/RoundsManager/RoundsManager"; import { Broadcaster, + BroadcasterDay, Day, Delegator, LivepeerAccount, @@ -158,6 +159,7 @@ export function createOrLoadProtocol(): Protocol { protocol.winningTicketCount = 0; protocol.roundCount = 0; protocol.lptPriceEth = ZERO_BD; + protocol.activeBroadcasters = []; const network = dataSource.network(); // 3520 is the count of total delegators from the mainnet subgraph (in the final round) @@ -169,6 +171,7 @@ export function createOrLoadProtocol(): Protocol { protocol.pendingDeactivation = []; protocol.save(); } + return protocol; } @@ -179,8 +182,24 @@ export function createOrLoadBroadcaster(id: string): Broadcaster { broadcaster = new Broadcaster(id); broadcaster.deposit = ZERO_BD; broadcaster.reserve = ZERO_BD; + broadcaster.totalVolumeETH = ZERO_BD; + broadcaster.totalVolumeUSD = ZERO_BD; + broadcaster.thirtyDayVolumeETH = ZERO_BD; + broadcaster.sixtyDayVolumeETH = ZERO_BD; + broadcaster.ninetyDayVolumeETH = ZERO_BD; + broadcaster.firstActiveDay = 0; + broadcaster.lastActiveDay = 0; + broadcaster.broadcasterDays = []; broadcaster.save(); + } + + let protocol = createOrLoadProtocol(); + let activeBroadcasters = protocol.activeBroadcasters; + if (!activeBroadcasters.includes(id)) { + activeBroadcasters.push(id); + protocol.activeBroadcasters = activeBroadcasters; + protocol.save(); } return broadcaster; @@ -316,6 +335,29 @@ export function createOrLoadTranscoderDay( return transcoderDay; } +export function createOrLoadBroadcasterDay( + timestamp: i32, + broadcasterAddress: string +): BroadcasterDay { + let dayID = timestamp / 86400; + let dayStartTimestamp = dayID * 86400; + let broadcasterDayID = broadcasterAddress + .concat("-") + .concat(BigInt.fromI32(dayID).toString()); + let broadcasterDay = BroadcasterDay.load(broadcasterDayID); + + if (broadcasterDay == null) { + broadcasterDay = new BroadcasterDay(broadcasterDayID); + broadcasterDay.date = dayStartTimestamp; + broadcasterDay.broadcaster = broadcasterAddress; + broadcasterDay.volumeUSD = ZERO_BD; + broadcasterDay.volumeETH = ZERO_BD; + + broadcasterDay.save(); + } + return broadcasterDay; +} + export function createOrLoadRound(blockNumber: BigInt): Round { let protocol = createOrLoadProtocol(); let roundsSinceLastUpdate = blockNumber