diff --git a/schema.graphql b/schema.graphql index b6ac70d..bac8829 100755 --- a/schema.graphql +++ b/schema.graphql @@ -108,6 +108,16 @@ type Transcoder @entity { serviceURI: String "Days which the transcoder earned fees" transcoderDays: [TranscoderDay!]! + "Unclaimed orchestrator reward commission in wei. Includes both rewardCut commission and rewards earned on staked commission. Resets to zero on claim. Full pending stake = shares * crf / 10^27 + pendingRewardCommission" + pendingRewardCommission: BigInt! + "Lifetime total orchestrator reward commission earned in wei. Never resets." + lifetimeRewardCommission: BigInt! + "Snapshot of pendingRewardCommission taken when the transcoder calls reward(), matching the contract. Used to compute the transcoder's share of delegator rewards and fees earned by its own staked commission. Not cleared on claim; the next reward() replaces it." + activeCumulativeRewards: BigInt! + "Unclaimed orchestrator fee commission in wei. Includes both feeShare commission and fees earned on staked commission. Resets to zero on claim." + pendingFeeCommission: BigInt! + "Lifetime total orchestrator fee commission earned in wei. Never resets." + lifetimeFeeCommission: BigInt! } enum TranscoderStatus @entity { @@ -135,6 +145,10 @@ type Pool @entity { rewardCut: BigInt! "Transcoder's fee share during the earnings pool's round" feeShare: BigInt! + "Cumulative reward factor for computing delegator rewards without looping (27-decimal fixed-point, matches on-chain PreciseMathUtils)" + cumulativeRewardFactor: BigInt! + "Cumulative fee factor for computing delegator fees without looping (27-decimal fixed-point, matches on-chain PreciseMathUtils)" + cumulativeFeeFactor: BigInt! } """ @@ -205,10 +219,32 @@ type Delegator @entity { withdrawnFees: BigDecimal! "Amount of Livepeer Token the delegator has delegated" delegatedAmount: BigDecimal! + "Proportional claim on the orchestrator's pool (bondedAmount * 10^27 / crf[lastClaimRound]). Recomputed on bond, unbond, rebond and earnings claims; unchanged by an ordinary claim, but a self-claim adds the orchestrator's bonded commission." + shares: BigInt! "Unbonding locks associated with the delegator" unbondingLocks: [UnbondingLock!] @derivedFrom(field: "delegator") } +""" +Snapshot of delegator state at each state-changing event, enabling historical stake and reward computation via cumulative factors +""" +type DelegatorSnapshot @entity { + "Unique identifier: delegator address + round number" + id: ID! + "The delegator this snapshot belongs to" + delegator: Delegator! + "The delegate (orchestrator) at the time of this snapshot, null if fully unbonded" + delegate: Transcoder + "Bonded amount at the time of this snapshot" + bondedAmount: BigDecimal! + "Proportional claim on the orchestrator's pool. stake = shares * crf[round] / 10^27" + shares: BigInt! + "Round when this snapshot was taken" + round: Round! + "Timestamp when this snapshot was taken" + timestamp: Int! +} + """ Abstraction for accounts/delegators bonded with the protocol """ diff --git a/src/mappings/bondingManager.ts b/src/mappings/bondingManager.ts index 2a49346..ba6feb4 100755 --- a/src/mappings/bondingManager.ts +++ b/src/mappings/bondingManager.ts @@ -1,5 +1,6 @@ import { store } from "@graphprotocol/graph-ts"; import { + convertFromDecimal, convertToDecimal, createOrLoadDelegator, createOrLoadPool, @@ -9,10 +10,14 @@ import { createOrLoadTranscoder, EMPTY_ADDRESS, getBlockNum, + latestCumulativeRewardFactor, makeEventId, makeUnbondingLockId, MAXIMUM_VALUE_UINT256, ONE_BI, + percOf, + PRECISE_PERC_DIVISOR, + precisePercOf, ZERO_BI, } from "../../utils/helpers"; // Import event types from the registrar contract ABIs @@ -34,6 +39,7 @@ import { } from "../types/BondingManager/BondingManager"; import { BondEvent, + DelegatorSnapshot, EarningsClaimedEvent, ParameterUpdateEvent, RebondEvent, @@ -62,10 +68,15 @@ export function bond(event: Bond): void { event.params.newDelegate.toHex(), event.block.timestamp.toI32() ); - let delegator = createOrLoadDelegator( - event.params.delegator.toHex(), - event.block.timestamp.toI32() - ); + // When self-bonding, delegate and delegator are the same entity. Share one + // object so saving delegator doesn't overwrite delegate's delegatedAmount. + let delegator = + event.params.delegator.toHex() == event.params.newDelegate.toHex() + ? delegate + : createOrLoadDelegator( + event.params.delegator.toHex(), + event.block.timestamp.toI32() + ); let protocol = createOrLoadProtocol(); // If self delegating, set status and assign reference to self @@ -85,10 +96,15 @@ export function bond(event: Bond): void { event.params.oldDelegate.toHex(), event.block.timestamp.toI32() ); - let oldDelegate = createOrLoadDelegator( - event.params.oldDelegate.toHex(), - event.block.timestamp.toI32() - ); + // Moving stake off a self-delegation: oldDelegate is the delegator itself, + // so share the object for the same reason as above. + let oldDelegate = + event.params.oldDelegate.toHex() == event.params.delegator.toHex() + ? delegator + : createOrLoadDelegator( + event.params.oldDelegate.toHex(), + event.block.timestamp.toI32() + ); let oldDelegateData = bondingManager.getDelegator(event.params.oldDelegate); // if previous delegate was itself, set status and unassign reference to self @@ -134,12 +150,30 @@ export function bond(event: Bond): void { convertToDecimal(event.params.additionalAmount) ); + // Compute shares: bondedAmount * 10^27 / crf[lastClaimRound] + delegator.shares = event.params.bondedAmount + .times(PRECISE_PERC_DIVISOR) + .div( + latestCumulativeRewardFactor(event.params.newDelegate.toHex(), round.id) + ); + round.save(); delegate.save(); delegator.save(); transcoder.save(); protocol.save(); + // Save delegator snapshot for historical stake/reward computation + let snapshotId = event.params.delegator.toHex() + "-" + round.id; + let snapshot = new DelegatorSnapshot(snapshotId); + snapshot.delegator = event.params.delegator.toHex(); + snapshot.delegate = event.params.newDelegate.toHex(); + snapshot.bondedAmount = delegator.bondedAmount; + snapshot.shares = delegator.shares; + snapshot.round = round.id; + snapshot.timestamp = event.block.timestamp.toI32(); + snapshot.save(); + createOrLoadTransactionFromEvent(event); let bondEvent = new BondEvent( @@ -237,10 +271,15 @@ export function unbond(event: Unbond): void { event.params.delegate.toHex(), event.block.timestamp.toI32() ); - let delegate = createOrLoadDelegator( - event.params.delegate.toHex(), - event.block.timestamp.toI32() - ); + // When unbonding self-stake, delegate and delegator are the same entity. + // Share one object so saving delegator doesn't overwrite delegatedAmount. + let delegate = + event.params.delegate.toHex() == event.params.delegator.toHex() + ? delegator + : createOrLoadDelegator( + event.params.delegate.toHex(), + event.block.timestamp.toI32() + ); let unbondingLock = UnbondingLock.load(uniqueUnbondingLockId); if (unbondingLock === null) { unbondingLock = new UnbondingLock(uniqueUnbondingLockId); @@ -259,6 +298,17 @@ export function unbond(event: Unbond): void { convertToDecimal(event.params.amount) ); + // Compute shares from new bonded amount + if (delegatorData.value0.isZero()) { + delegator.shares = ZERO_BI; + } else { + delegator.shares = delegatorData.value0 + .times(PRECISE_PERC_DIVISOR) + .div( + latestCumulativeRewardFactor(event.params.delegate.toHex(), round.id) + ); + } + // Delegator no longer delegated to anyone if it does not have a bonded amount // so remove it from delegate if (delegatorData.value0.isZero()) { @@ -291,6 +341,17 @@ export function unbond(event: Unbond): void { protocol.save(); round.save(); + // Save delegator snapshot for historical stake/reward computation + let snapshotId = event.params.delegator.toHex() + "-" + round.id; + let snapshot = new DelegatorSnapshot(snapshotId); + snapshot.delegator = event.params.delegator.toHex(); + snapshot.delegate = delegator.delegate; + snapshot.bondedAmount = delegator.bondedAmount; + snapshot.shares = delegator.shares; + snapshot.round = round.id; + snapshot.timestamp = event.block.timestamp.toI32(); + snapshot.save(); + createOrLoadTransactionFromEvent(event); let unbondEvent = new UnbondEvent( @@ -319,10 +380,15 @@ export function rebond(event: Rebond): void { event.params.delegate.toHex(), event.block.timestamp.toI32() ); - let delegator = createOrLoadDelegator( - event.params.delegator.toHex(), - event.block.timestamp.toI32() - ); + // When rebonding to self, delegate and delegator are the same entity. Share + // one object so saving delegator doesn't overwrite delegatedAmount. + let delegator = + event.params.delegator.toHex() == event.params.delegate.toHex() + ? delegate + : createOrLoadDelegator( + event.params.delegator.toHex(), + event.block.timestamp.toI32() + ); let delegateData = bondingManager.getDelegator(event.params.delegate); let protocol = createOrLoadProtocol(); let uniqueUnbondingLockId = makeUnbondingLockId( @@ -351,6 +417,11 @@ export function rebond(event: Rebond): void { delegator.bondedAmount = convertToDecimal(delegatorData.value0); delegator.fees = convertToDecimal(delegatorData.value1); + // Compute shares: bondedAmount * 10^27 / crf[lastClaimRound] + delegator.shares = delegatorData.value0 + .times(PRECISE_PERC_DIVISOR) + .div(latestCumulativeRewardFactor(event.params.delegate.toHex(), round.id)); + // If the sender field for the lock is equal to the delegator's address then // we know that this is an unbonding lock the delegator created by calling // unbond() and if it is not then we know that this is an unbonding lock created @@ -371,6 +442,17 @@ export function rebond(event: Rebond): void { delegator.save(); protocol.save(); + // Save delegator snapshot for historical stake/reward computation + let snapshotId = event.params.delegator.toHex() + "-" + round.id; + let snapshot = new DelegatorSnapshot(snapshotId); + snapshot.delegator = event.params.delegator.toHex(); + snapshot.delegate = event.params.delegate.toHex(); + snapshot.bondedAmount = delegator.bondedAmount; + snapshot.shares = delegator.shares; + snapshot.round = round.id; + snapshot.timestamp = event.block.timestamp.toI32(); + snapshot.save(); + if (unbondingLock) { store.remove("UnbondingLock", uniqueUnbondingLockId); } @@ -494,10 +576,56 @@ export function reward(event: Reward): void { ); transcoder.lastRewardRound = round.id; - pool.rewardTokens = convertToDecimal(event.params.amount); + // Compute cumulative reward factor (matches on-chain PreciseMathUtils) + // The pool's CRF was propagated from the previous round during pool creation, + // so it already contains the correct previous cumulative reward factor. + let prevCRF = pool.cumulativeRewardFactor; + if (prevCRF.equals(ZERO_BI)) { + prevCRF = PRECISE_PERC_DIVISOR; // default: 10^27 = percPoints(1,1) + } + + // The contract sets the pool's commission to the transcoder's current + // rewardCut/feeShare when reward() runs (rewardWithHint's setCommission), + // so a mid-round TranscoderUpdate applies to this round's rewards pool.feeShare = transcoder.feeShare; pool.rewardCut = transcoder.rewardCut; + let totalRewardTokens = event.params.amount; // raw BigInt in wei + let transcoderCommission = percOf(totalRewardTokens, pool.rewardCut); + let delegatorsRewards = totalRewardTokens.minus(transcoderCommission); + + // Snapshot the commission that is active this round, as the contract does + // at the start of updateTranscoderWithRewards + transcoder.activeCumulativeRewards = transcoder.pendingRewardCommission; + + // Compute rewards earned by the transcoder's own staked commission + let totalStakeBI = convertFromDecimal(pool.totalStake); + let transcoderRewardStakeRewards = ZERO_BI; + if (totalStakeBI.gt(ZERO_BI)) { + transcoderRewardStakeRewards = precisePercOf( + delegatorsRewards, + transcoder.activeCumulativeRewards, + totalStakeBI + ); + } + + // Accumulate orchestrator reward commission (rewardCut + rewards on staked commission) + transcoder.pendingRewardCommission = transcoder.pendingRewardCommission + .plus(transcoderCommission) + .plus(transcoderRewardStakeRewards); + transcoder.lifetimeRewardCommission = transcoder.lifetimeRewardCommission + .plus(transcoderCommission) + .plus(transcoderRewardStakeRewards); + if (totalStakeBI.gt(ZERO_BI)) { + pool.cumulativeRewardFactor = prevCRF.plus( + precisePercOf(prevCRF, delegatorsRewards, totalStakeBI) + ); + } else { + pool.cumulativeRewardFactor = prevCRF; + } + + pool.rewardTokens = convertToDecimal(event.params.amount); + transcoder.save(); delegate.save(); pool.save(); @@ -669,8 +797,35 @@ export function earningsClaimed(event: EarningsClaimed): void { convertToDecimal(event.params.rewards) ); delegator.fees = delegator.fees.plus(convertToDecimal(event.params.fees)); + // A self-claim also bonds the orchestrator's commission, which the old + // shares don't include, so rebase them on the new lastClaimRound. An + // ordinary claim leaves shares unchanged (rewards grow with the CRF), so + // skip it there rather than let integer division drift them down. + if (event.params.delegator.toHex() == event.params.delegate.toHex()) { + delegator.shares = convertFromDecimal(delegator.bondedAmount) + .times(PRECISE_PERC_DIVISOR) + .div( + latestCumulativeRewardFactor( + event.params.delegate.toHex(), + event.params.endRound.toString() + ) + ); + } delegator.save(); + // Reset orchestrator's unclaimed commission when they claim + if (event.params.delegator.toHex() == event.params.delegate.toHex()) { + let transcoder = createOrLoadTranscoder( + event.params.delegator.toHex(), + event.block.timestamp.toI32() + ); + transcoder.pendingRewardCommission = ZERO_BI; + transcoder.pendingFeeCommission = ZERO_BI; + // activeCumulativeRewards is not cleared, matching the contract: fees + // later this round still use it, and the next reward() resets it + transcoder.save(); + } + createOrLoadTransactionFromEvent(event); let earningsClaimedEvent = new EarningsClaimedEvent( diff --git a/src/mappings/ticketBroker.ts b/src/mappings/ticketBroker.ts index 3aa9adf..05072d1 100644 --- a/src/mappings/ticketBroker.ts +++ b/src/mappings/ticketBroker.ts @@ -1,5 +1,6 @@ import { Address, BigInt, dataSource, log } from "@graphprotocol/graph-ts"; import { + convertFromDecimal, convertToDecimal, createOrLoadBroadcaster, createOrLoadBroadcasterDay, @@ -11,9 +12,16 @@ import { createOrLoadTranscoder, createOrLoadTranscoderDay, getBlockNum, + getBondingManagerAddress, getEthPriceUsd, + integerFromString, + latestCumulativeRewardFactor, makeEventId, + ONE_BI, + percOf, + precisePercOf, ZERO_BD, + ZERO_BI, } from "../../utils/helpers"; import { DepositFundedEvent, @@ -29,6 +37,7 @@ import { WinningTicketRedeemed, Withdrawal, } from "../types/TicketBroker/TicketBroker"; +import { BondingManager } from "../types/TicketBroker/BondingManager"; export function winningTicketRedeemed(event: WinningTicketRedeemed): void { let round = createOrLoadRound(getBlockNum()); @@ -118,8 +127,79 @@ export function winningTicketRedeemed(event: WinningTicketRedeemed): void { protocol.winningTicketCount = protocol.winningTicketCount + 1; protocol.save(); - // update the transcoder pool fees + // update the transcoder pool fees and cumulative fee factor let pool = createOrLoadPool(round.id, event.params.recipient.toHex()); + + // Read the round's earnings pool from the contract, which has already + // applied these fees. Its cumulativeFeeFactor is exact, including the + // contract's retroactive CRF estimate when round - 1 missed reward(). + let bondingManager = BondingManager.bind( + Address.fromString(getBondingManagerAddress()) + ); + let onChainPool = bondingManager.try_getTranscoderEarningsPoolForRound( + event.params.recipient, + integerFromString(round.id) + ); + + let totalStakeBI = convertFromDecimal(pool.totalStake); + let feeShare = pool.feeShare; + // The pool is only initialized once the contract has credited fees or + // stake for this round. A ticket that transferred nothing (sender's deposit + // and reserve exhausted) still emits WinningTicketRedeemed but skips + // updateTranscoderWithFees, leaving an all-zero pool, so ignore zeros. + if (!onChainPool.reverted && onChainPool.value.value0.gt(ZERO_BI)) { + totalStakeBI = onChainPool.value.value0; + feeShare = onChainPool.value.value2; + // Persist the contract's values so later handlers (e.g. reward) see them + pool.totalStake = convertToDecimal(totalStakeBI); + pool.rewardCut = onChainPool.value.value1; + pool.feeShare = feeShare; + } + + let delegatorsFees = percOf(event.params.faceValue, feeShare); + let transcoderFeeCommission = event.params.faceValue.minus(delegatorsFees); + + // Fees earned by the transcoder's own staked commission, mirroring the + // contract's transcoderRewardStakeFees. Until reward() is called this round + // the contract uses cumulativeRewards (pendingRewardCommission here). + let rewardCalledThisRound = + transcoder.lastRewardRound != null && + transcoder.lastRewardRound! == round.id; + let activeCumulativeRewards = rewardCalledThisRound + ? transcoder.activeCumulativeRewards + : transcoder.pendingRewardCommission; + let transcoderRewardStakeFees = ZERO_BI; + if (totalStakeBI.gt(ZERO_BI)) { + transcoderRewardStakeFees = precisePercOf( + delegatorsFees, + activeCumulativeRewards, + totalStakeBI + ); + } + + // Accumulate orchestrator fee commission (feeShare + fees on staked commission) + transcoder.pendingFeeCommission = transcoder.pendingFeeCommission + .plus(transcoderFeeCommission) + .plus(transcoderRewardStakeFees); + transcoder.lifetimeFeeCommission = transcoder.lifetimeFeeCommission + .plus(transcoderFeeCommission) + .plus(transcoderRewardStakeFees); + + if (!onChainPool.reverted && onChainPool.value.value4.gt(ZERO_BI)) { + pool.cumulativeFeeFactor = onChainPool.value.value4; + } else if (totalStakeBI.gt(ZERO_BI)) { + // Fallback: compute it as the contract does, from the previous round's + // CRF (latestCumulativeFactorsPool(_round - 1)). + let prevRoundNum = integerFromString(round.id).minus(ONE_BI); + let prevCRF = latestCumulativeRewardFactor( + event.params.recipient.toHex(), + prevRoundNum.toString() + ); + pool.cumulativeFeeFactor = pool.cumulativeFeeFactor.plus( + precisePercOf(prevCRF, delegatorsFees, totalStakeBI) + ); + } + pool.fees = pool.fees.plus(faceValue); pool.save(); diff --git a/subgraph.template.yaml b/subgraph.template.yaml index 4f9163e..bb9a78b 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -25,6 +25,7 @@ dataSources: - BondEvent - TransferBondEvent - Delegator + - DelegatorSnapshot - LivepeerAccount - EarningsClaimedEvent - ParameterUpdateEvent @@ -198,6 +199,8 @@ dataSources: abis: - name: TicketBroker file: ./abis/TicketBroker.json + - name: BondingManager + file: ./abis/BondingManager.json - name: UniswapV3Pool file: ./abis/UniswapV3Pool.json - name: RoundsManager @@ -208,7 +211,10 @@ dataSources: - Protocol - Transcoder - Broadcaster + - BroadcasterDay + - Pool - Round + - TranscoderDay - WinningTicketRedeemedEvent - ReserveFundedEvent - ReserveClaimedEvent diff --git a/utils/helpers.ts b/utils/helpers.ts index 556744d..c42dc6a 100644 --- a/utils/helpers.ts +++ b/utils/helpers.ts @@ -61,8 +61,48 @@ export function createOrLoadPool(roundId: string, transcoderAddress: string): Po pool.round = roundId; pool.delegate = transcoderAddress; pool.fees = ZERO_BD; + let transcoder = Transcoder.load(transcoderAddress); + // Propagate cumulative factors from the most recent EXISTING pool so they + // never reset to base across a round gap. Normally that is the previous + // round's pool, but when the transcoder was inactive — or newRound's + // non-deterministic enumeration skipped it (see #249) — the previous + // round's pool can be missing for several rounds. Walk back to the most + // recent pool that exists, bounded below by lastRewardRound (which is + // guaranteed to have a pool with valid factors). This mirrors the + // contract's latestCumulativeFactorsPool. + // + // Only checking round-1 (the previous behavior) left the factors at 0 + // whenever that single pool was missing, and reward() then reset the + // cumulative reward factor to base (10^27) — corrupting the stake of every + // delegator whose last claim predated the gap. + let sourcePool: Pool | null = null; + if (transcoder != null && transcoder.lastRewardRound != null) { + let cursor = integerFromString(roundId).minus(ONE_BI); + let floor = integerFromString(transcoder.lastRewardRound!); + while (cursor.ge(floor)) { + let candidate = Pool.load( + makePoolId(transcoderAddress, cursor.toString()) + ); + if (candidate != null) { + sourcePool = candidate; + break; + } + cursor = cursor.minus(ONE_BI); + } + } + if (sourcePool != null) { + pool.cumulativeRewardFactor = sourcePool.cumulativeRewardFactor; + pool.cumulativeFeeFactor = sourcePool.cumulativeFeeFactor; + } else { + // No reward history: start the reward factor at the 10^27 base the + // contract implies for an uninitialized pool, so shares * crf / 10^27 + // gives the right stake. The fee factor genuinely starts at zero. + pool.cumulativeRewardFactor = PRECISE_PERC_DIVISOR; + pool.cumulativeFeeFactor = ZERO_BI; + } + if (transcoder) { pool.totalStake = transcoder.totalStake; pool.rewardCut = transcoder.rewardCut; @@ -79,6 +119,34 @@ export function createOrLoadPool(roundId: string, transcoderAddress: string): Po return pool; } +// Cumulative reward factor for a transcoder as of a round, mirroring the +// contract's latestCumulativeFactorsPool: use the round's pool if it has a +// factor, otherwise fall back to the lastRewardRound pool when that is +// earlier, and default to the 10^27 base when neither exists. +export function latestCumulativeRewardFactor( + transcoderAddress: string, + roundId: string +): BigInt { + let pool = Pool.load(makePoolId(transcoderAddress, roundId)); + if (pool != null && !pool.cumulativeRewardFactor.equals(ZERO_BI)) { + return pool.cumulativeRewardFactor; + } + let transcoder = Transcoder.load(transcoderAddress); + if (transcoder != null && transcoder.lastRewardRound != null) { + let lastRewardRound = transcoder.lastRewardRound!; + if (integerFromString(lastRewardRound).lt(integerFromString(roundId))) { + let rewardPool = Pool.load(makePoolId(transcoderAddress, lastRewardRound)); + if ( + rewardPool != null && + !rewardPool.cumulativeRewardFactor.equals(ZERO_BI) + ) { + return rewardPool.cumulativeRewardFactor; + } + } + } + return PRECISE_PERC_DIVISOR; +} + // Make a derived share ID from a delegator address export function makeShareId(delegatorAddress: string, roundId: string): string { return leftPad(roundId, 10) + "-" + delegatorAddress; @@ -124,6 +192,36 @@ export function percPoints(_fracNum: BigInt, _fracDenom: BigInt): BigInt { return _fracNum.times(BigInt.fromI32(PERC_DIVISOR)).div(_fracDenom); } +// PreciseMathUtils equivalents (matches Solidity's 27-decimal fixed-point arithmetic) +export let PRECISE_PERC_DIVISOR = BigInt.fromString( + "1000000000000000000000000000" +); // 10^27 + +export function precisePercPoints( + _fracNum: BigInt, + _fracDenom: BigInt +): BigInt { + return _fracNum.times(PRECISE_PERC_DIVISOR).div(_fracDenom); +} + +export function precisePercOf( + _baseAmount: BigInt, + _fracNum: BigInt, + _fracDenom: BigInt +): BigInt { + return _baseAmount.times(_fracNum).div(_fracDenom); +} + +// Convert BigDecimal (in token units) back to raw BigInt (in wei) +export function convertFromDecimal(amount: BigDecimal): BigInt { + let str = amount.times(exponentToBigDecimal(BI_18)).toString(); + let dotIndex = str.indexOf("."); + if (dotIndex >= 0) { + str = str.substring(0, dotIndex); + } + return BigInt.fromString(str); +} + export function exponentToBigDecimal(decimals: BigInt): BigDecimal { let bd = BigDecimal.fromString("1"); for (let i = ZERO_BI; i.lt(decimals); i = i.plus(ONE_BI)) { @@ -266,6 +364,11 @@ export function createOrLoadTranscoder(id: string, timestamp: i32): Transcoder { transcoder.sixtyDayVolumeETH = ZERO_BD; transcoder.ninetyDayVolumeETH = ZERO_BD; transcoder.transcoderDays = []; + transcoder.pendingRewardCommission = ZERO_BI; + transcoder.lifetimeRewardCommission = ZERO_BI; + transcoder.pendingFeeCommission = ZERO_BI; + transcoder.lifetimeFeeCommission = ZERO_BI; + transcoder.activeCumulativeRewards = ZERO_BI; transcoder.save(); } @@ -287,6 +390,7 @@ export function createOrLoadDelegator(id: string, timestamp: i32): Delegator { delegator.fees = ZERO_BD; delegator.withdrawnFees = ZERO_BD; delegator.delegatedAmount = ZERO_BD; + delegator.shares = ZERO_BI; delegator.save(); }