Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Bug of not showing the correct amount in today's rewards when staking later during epoch #749

Open
wants to merge 9 commits into
base: staging
Choose a base branch
from
Open
66 changes: 60 additions & 6 deletions frontend/context/RewardProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useQuery } from '@tanstack/react-query';
import { formatUnits } from 'ethers/lib/utils';
import { isNil } from 'lodash';
import {
createContext,
@@ -13,10 +12,13 @@ import {
import { FIVE_SECONDS_INTERVAL } from '@/constants/intervals';
import { REACT_QUERY_KEYS } from '@/constants/react-query-keys';
import { useElectronApi } from '@/hooks/useElectronApi';
import { useOnlineStatusContext } from '@/hooks/useOnlineStatus';
import { useServices } from '@/hooks/useServices';
import { useStakingContractContext } from '@/hooks/useStakingContractDetails';
import { useStore } from '@/hooks/useStore';
import { StakingRewardsInfoSchema } from '@/types/Autonolas';
import { asMiddlewareChain } from '@/utils/middlewareHelpers';
import { formatEther, formatUnits } from '@/utils/numberFormatters';

import { OnlineStatusContext } from './OnlineStatusProvider';
import { StakingProgramContext } from './StakingProgramProvider';
@@ -102,7 +104,7 @@ const useStakingRewardsDetails = () => {
* hook to fetch available rewards for the current epoch
*/
const useAvailableRewardsForEpoch = () => {
const { isOnline } = useContext(OnlineStatusContext);
const { isOnline } = useOnlineStatusContext();
const { selectedStakingProgramId } = useContext(StakingProgramContext);

const {
@@ -140,6 +142,11 @@ export const RewardProvider = ({ children }: PropsWithChildren) => {
const { storeState } = useStore();
const electronApi = useElectronApi();

const {
isSelectedStakingContractDetailsLoading,
selectedStakingContractDetails,
} = useStakingContractContext();

const {
data: stakingRewardsDetails,
refetch: refetchStakingRewardsDetails,
@@ -156,11 +163,58 @@ export const RewardProvider = ({ children }: PropsWithChildren) => {
const accruedServiceStakingRewards =
stakingRewardsDetails?.accruedServiceStakingRewards;

// available rewards for the current epoch in ETH
const rewardsPerSecond = stakingRewardsDetails?.rewardsPerSecond;
const serviceStakingStartTime =
selectedStakingContractDetails?.serviceStakingStartTime;

// available rewards for the epoch
const availableRewardsForEpochEth = useMemo<number | undefined>(() => {
if (!availableRewardsForEpoch) return;
return parseFloat(formatUnits(`${availableRewardsForEpoch}`));
}, [availableRewardsForEpoch]);
if (!rewardsPerSecond) return;
if (!isEligibleForRewards) return;

// wait for the staking details to load
if (isStakingRewardsDetailsLoading) return;
if (isSelectedStakingContractDetailsLoading) return;
if (isAvailableRewardsForEpochLoading) return;

// if agent is not staked, return the available rewards for the epoch
// i.e, agent has not yet started staking
if (isNil(serviceStakingStartTime) || serviceStakingStartTime === 0) {
return parseFloat(formatUnits(`${availableRewardsForEpoch}`));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't understand why if not staked return full amount, but if staked but not yet eligible return undefined?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are we returning undefined?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here for example
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, removed.

}

// calculate the next checkpoint timestamp
// i.e, next = last + checkpoint period
const nextCheckpointTimestamp =
stakingRewardsDetails.lastCheckpointTimestamp +
stakingRewardsDetails.livenessPeriod;

// default to the last checkpoint timestamp
// ie, if agent has not staked yet, use the last checkpoint timestamp
const agentStakingStartTime = Math.max(
stakingRewardsDetails.lastCheckpointTimestamp,
serviceStakingStartTime || 0,
);

// calculate the time agent staked in the current epoch
const stakingDurationInCurrentEpoch =
nextCheckpointTimestamp - agentStakingStartTime;

const rewardsInCurrentEpoch =
parseFloat(formatEther(`${rewardsPerSecond}`)) *
stakingDurationInCurrentEpoch;

return parseFloat(`${rewardsInCurrentEpoch}`);
}, [
isEligibleForRewards,
isSelectedStakingContractDetailsLoading,
isStakingRewardsDetailsLoading,
isAvailableRewardsForEpochLoading,
stakingRewardsDetails,
rewardsPerSecond,
serviceStakingStartTime,
availableRewardsForEpoch,
]);

// optimistic rewards earned for the current epoch in ETH
const optimisticRewardsEarnedForEpoch = useMemo<number | undefined>(() => {
30 changes: 10 additions & 20 deletions frontend/service/agents/Modius.ts
Original file line number Diff line number Diff line change
@@ -59,30 +59,19 @@ export abstract class ModiusService extends StakedAgentService {

const [
serviceInfo,
livenessPeriod,
rewardsPerSecond,
livenessPeriodInBn,
rewardsPerSecondInBn,
accruedStakingReward,
minStakingDeposit,
tsCheckpoint,
livenessRatio,
tsCheckpointInBn,
livenessRatioInBn,
currentMultisigNonces,
] = multicallResponse;

/**
* struct ServiceInfo {
// Service multisig address
address multisig;
// Service owner
address owner;
// Service multisig nonces
uint256[] nonces; <-- (we use this in the rewards eligibility check)
// Staking start time
uint256 tsStart;
// Accumulated service staking reward
uint256 reward;
// Accumulated inactivity that might lead to the service eviction
uint256 inactivity;}
*/
const rewardsPerSecond = rewardsPerSecondInBn.toNumber();
const livenessPeriod = livenessPeriodInBn.toNumber();
const tsCheckpoint = tsCheckpointInBn.toNumber();
const livenessRatio = livenessRatioInBn.toNumber();

const lastMultisigNonces = serviceInfo[2];
const nowInSeconds = Math.floor(Date.now() / 1000);
@@ -122,7 +111,8 @@ export abstract class ModiusService extends StakedAgentService {
ethers.utils.formatEther(`${accruedStakingReward}`),
),
minimumStakedAmount,
} as StakingRewardsInfo;
lastCheckpointTimestamp: tsCheckpoint,
} satisfies StakingRewardsInfo;
};

static getAvailableRewardsForEpoch = async (
40 changes: 15 additions & 25 deletions frontend/service/agents/PredictTrader.ts
Original file line number Diff line number Diff line change
@@ -61,32 +61,21 @@ export abstract class PredictTraderService extends StakedAgentService {
const multicallResponse = await provider.all(contractCalls);

const [
mechRequestCount,
mechRequestCountInBn,
serviceInfo,
livenessPeriod,
livenessRatio,
rewardsPerSecond,
livenessPeriodInBn,
livenessRatioInBn,
rewardsPerSecondInBn,
accruedStakingReward,
minStakingDeposit,
tsCheckpoint,
tsCheckpointInBn,
] = multicallResponse;

/**
* struct ServiceInfo {
// Service multisig address
address multisig;
// Service owner
address owner;
// Service multisig nonces
uint256[] nonces; <-- (we use this in the rewards eligibility check)
// Staking start time
uint256 tsStart;
// Accumulated service staking reward
uint256 reward;
// Accumulated inactivity that might lead to the service eviction
uint256 inactivity;}
*/

const mechRequestCount = mechRequestCountInBn.toNumber();
const rewardsPerSecond = rewardsPerSecondInBn.toNumber();
const livenessPeriod = livenessPeriodInBn.toNumber();
const tsCheckpoint = tsCheckpointInBn.toNumber();
const livenessRatio = livenessRatioInBn.toNumber();
const nowInSeconds = Math.floor(Date.now() / 1000);

const requiredMechRequests =
@@ -119,11 +108,12 @@ export abstract class PredictTraderService extends StakedAgentService {
rewardsPerSecond,
isEligibleForRewards,
availableRewardsForEpoch,
accruedServiceStakingRewards: parseFloat(
ethers.utils.formatEther(`${accruedStakingReward}`),
),
accruedServiceStakingRewards: accruedStakingReward
? parseFloat(ethers.utils.formatEther(`${accruedStakingReward}`))
: 0,
minimumStakedAmount,
} as StakingRewardsInfo;
lastCheckpointTimestamp: tsCheckpoint,
} satisfies StakingRewardsInfo;
};

static getAvailableRewardsForEpoch = async (
16 changes: 11 additions & 5 deletions frontend/service/agents/shared-services/AgentsFun.ts
Original file line number Diff line number Diff line change
@@ -58,15 +58,20 @@ export abstract class AgentsFunService extends StakedAgentService {

const [
serviceInfo,
livenessPeriod,
rewardsPerSecond,
livenessPeriodInBn,
rewardsPerSecondInBn,
accruedStakingReward,
minStakingDeposit,
tsCheckpoint,
livenessRatio,
tsCheckpointInBn,
livenessRatioInBn,
currentMultisigNonces,
] = multicallResponse;

const rewardsPerSecond = rewardsPerSecondInBn.toNumber();
const livenessPeriod = livenessPeriodInBn.toNumber();
const tsCheckpoint = tsCheckpointInBn.toNumber();
const livenessRatio = livenessRatioInBn.toNumber();

const lastMultisigNonces = serviceInfo[2];
const nowInSeconds = Math.floor(Date.now() / 1000);

@@ -105,7 +110,8 @@ export abstract class AgentsFunService extends StakedAgentService {
ethers.utils.formatEther(`${accruedStakingReward}`),
),
minimumStakedAmount,
} as StakingRewardsInfo;
lastCheckpointTimestamp: tsCheckpoint,
} satisfies StakingRewardsInfo;
};

static getAvailableRewardsForEpoch = async (
15 changes: 7 additions & 8 deletions frontend/types/Autonolas.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { z } from 'zod';

const zodBigNumber = z.object({
_isBigNumber: z.boolean(),
_hex: z.string().startsWith('0x'),
});

export const StakingRewardsInfoSchema = z.object({
// mechRequestCount: z.number(),
serviceInfo: z.array(z.unknown()),
livenessPeriod: zodBigNumber,
livenessRatio: zodBigNumber,
rewardsPerSecond: zodBigNumber,
/* checkpoint period (in seconds). eg. 86400 */
livenessPeriod: z.number(),
livenessRatio: z.number(),
/* rewards per second */
rewardsPerSecond: z.number(),
isEligibleForRewards: z.boolean(),
availableRewardsForEpoch: z.number(),
accruedServiceStakingRewards: z.number(),
minimumStakedAmount: z.number(),
/* last timestamp of the checkpoint */
lastCheckpointTimestamp: z.number(),
});

export type StakingRewardsInfo = z.infer<typeof StakingRewardsInfoSchema>;