From 42a0675bf2d3c14529556a98b0516f6cd1a00ba1 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 9 Nov 2022 13:38:37 +0100 Subject: [PATCH 1/6] Move fetching owner stakes to redux We want to fetch the owner stakes only once so the best place for fetching this data is one-shot listener effect. --- src/hooks/useFetchOwnerStakes.ts | 163 ------------------ src/store/staking/effects.ts | 38 ++++ src/store/staking/stakingSlice.ts | 13 +- .../staking/__test__/staking.test.ts | 4 +- src/threshold-ts/staking/index.ts | 5 +- 5 files changed, 53 insertions(+), 170 deletions(-) delete mode 100644 src/hooks/useFetchOwnerStakes.ts diff --git a/src/hooks/useFetchOwnerStakes.ts b/src/hooks/useFetchOwnerStakes.ts deleted file mode 100644 index ae463783c..000000000 --- a/src/hooks/useFetchOwnerStakes.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { useCallback } from "react" -import { BigNumber } from "@ethersproject/bignumber" -import { - useTStakingContract, - T_STAKING_CONTRACT_DEPLOYMENT_BLOCK, - usePREContract, - useKeepTokenStakingContract, -} from "../web3/hooks" -import { - getContractPastEvents, - getAddress, - isSameETHAddress, - isAddress, -} from "../web3/utils" -import { StakeType, Token } from "../enums" -import { StakeData } from "../types/staking" -import { setStakes } from "../store/staking" -import { useDispatch } from "react-redux" -import { useFetchPreConfigData } from "./useFetchPreConfigData" -import { useTConvertedAmount } from "./useTConvertedAmount" -import { useNuStakingEscrowContract } from "../web3/hooks/useNuStakingEscrowContract" -import { useThreshold } from "../contexts/ThresholdContext" - -export const useFetchOwnerStakes = () => { - const tStakingContract = useTStakingContract() - - const keepStakingContract = useKeepTokenStakingContract() - const nuStakingEscrowContract = useNuStakingEscrowContract() - - const simplePREApplicationContract = usePREContract() - - const fetchPreConfigData = useFetchPreConfigData() - - const { convertToT: convertKeepToT } = useTConvertedAmount(Token.Keep, "0") - const { convertToT: convertNuToT } = useTConvertedAmount(Token.Nu, "0") - - const dispatch = useDispatch() - const threshold = useThreshold() - - return useCallback( - async (address?: string): Promise => { - if ( - !tStakingContract || - !nuStakingEscrowContract || - !keepStakingContract || - !simplePREApplicationContract || - !address - ) { - return [] - } - - const stakedEvents = ( - await getContractPastEvents(tStakingContract, { - eventName: "Staked", - fromBlock: T_STAKING_CONTRACT_DEPLOYMENT_BLOCK, - filterParams: [undefined, address], - }) - ).reverse() - - const stakingProviderToBeneficiary = stakedEvents.reduce( - (reducer, event): { [stakingProvider: string]: string } => { - reducer[event.args?.stakingProvider as string] = event.args - ?.beneficiary as string - return reducer - }, - {} as { [stakingProvider: string]: string } - ) - - const stakingProviders = Object.keys(stakingProviderToBeneficiary) - - const preConfigData = await fetchPreConfigData(stakingProviders) - - const eligibleKeepStakes = await threshold.multicall.aggregate( - stakingProviders.map((stakingProvider) => ({ - interface: keepStakingContract.interface, - address: keepStakingContract.address, - method: "eligibleStake", - args: [stakingProvider, tStakingContract.address], - })) - ) - - // The NU staker can have only one stake. - const { stakingProvider: nuStakingProvider, value: nuStake } = - await nuStakingEscrowContract.stakerInfo(address) - - const stakes = stakedEvents.map((_) => { - const amount = _.args?.amount.toString() - const stakeType = _.args?.stakeType as StakeType - const stakingProvider = getAddress(_.args?.stakingProvider as string) - - return { - stakeType, - owner: _.args?.owner as string, - stakingProvider, - beneficiary: _.args?.beneficiary as string, - authorizer: _.args?.authorizer as string, - blockNumber: _.blockNumber, - blockHash: _.blockHash, - transactionHash: _.transactionHash, - nuInTStake: stakeType === StakeType.NU ? amount : "0", - keepInTStake: stakeType === StakeType.KEEP ? amount : "0", - tStake: stakeType === StakeType.T ? amount : "0", - preConfig: preConfigData[stakingProvider], - } as StakeData - }) - - const data = await threshold.multicall.aggregate( - stakes.map((_) => ({ - interface: tStakingContract.interface, - address: tStakingContract.address, - method: "stakes", - args: [_.stakingProvider], - })) - ) - - data.forEach((_, index) => { - const total = BigNumber.from(_.tStake) - .add(BigNumber.from(_.keepInTStake)) - .add(BigNumber.from(_.nuInTStake)) - const keepInTStake = _.keepInTStake.toString() - const keepEligableStakeInT = convertKeepToT( - eligibleKeepStakes[index].toString() - ) - const possibleKeepTopUpInT = BigNumber.from(keepEligableStakeInT) - .sub(BigNumber.from(keepInTStake)) - .toString() - - const stakingProvider = stakes[index].stakingProvider - const nuInTStake = stakes[index].nuInTStake.toString() - - const possibleNuTopUpInT = - isAddress(nuStakingProvider) && - isSameETHAddress(stakingProvider, nuStakingProvider) - ? BigNumber.from(convertNuToT(nuStake)) - .sub(BigNumber.from(nuInTStake)) - .toString() - : "0" - - stakes[index] = { - ...stakes[index], - tStake: _.tStake.toString(), - keepInTStake, - nuInTStake, - totalInTStake: total.toString(), - possibleKeepTopUpInT, - possibleNuTopUpInT, - } - }) - - dispatch(setStakes(stakes)) - - return stakes - }, - [ - tStakingContract, - dispatch, - convertKeepToT, - convertNuToT, - fetchPreConfigData, - threshold, - ] - ) -} diff --git a/src/store/staking/effects.ts b/src/store/staking/effects.ts index 97dc954ef..0e98270b2 100644 --- a/src/store/staking/effects.ts +++ b/src/store/staking/effects.ts @@ -1,5 +1,7 @@ +import { Stake } from "../../threshold-ts/staking" import { StakeData } from "../../types" import { AddressZero, isAddress, isAddressZero } from "../../web3/utils" +import { walletConnected } from "../account" import { AppListenerEffectAPI } from "../listener" import { selectStakeByStakingProvider } from "./selectors" import { requestStakeByStakingProvider, setStakes } from "./stakingSlice" @@ -82,3 +84,39 @@ const fetchStake = async ( ]) ) } + +export const fetchOwnerStakesEffect = async ( + actionCreator: ReturnType, + listenerApi: AppListenerEffectAPI +) => { + const address = actionCreator.payload + if (!isAddress(address)) return + + listenerApi.unsubscribe() + + try { + const stakes = await listenerApi.extra.threshold.staking.getOwnerStakes( + address + ) + + listenerApi.dispatch( + setStakes( + stakes.map( + (stake) => + ({ + ...stake, + tStake: stake.tStake.toString(), + nuInTStake: stake.tStake.toString(), + keepInTStake: stake.keepInTStake.toString(), + totalInTStake: stake.totalInTStake.toString(), + possibleKeepTopUpInT: stake.possibleKeepTopUpInT.toString(), + possibleNuTopUpInT: stake.possibleNuTopUpInT.toString(), + } as Stake) + ) + ) + ) + } catch (error) { + console.log("Could not fetch owner stakes", error) + listenerApi.subscribe() + } +} diff --git a/src/store/staking/stakingSlice.ts b/src/store/staking/stakingSlice.ts index c2d7a006a..4923180e5 100644 --- a/src/store/staking/stakingSlice.ts +++ b/src/store/staking/stakingSlice.ts @@ -12,7 +12,11 @@ import { StakeType, TopUpType, UnstakeType } from "../../enums" import { AddressZero } from "../../web3/utils" import { UpdateStateActionPayload } from "../../types/state" import { startAppListening } from "../listener" -import { fetchStakeByStakingProviderEffect } from "./effects" +import { walletConnected } from "../account" +import { + fetchStakeByStakingProviderEffect, + fetchOwnerStakesEffect, +} from "./effects" interface StakingState { stakingProvider: string @@ -187,10 +191,13 @@ export const { } = stakingSlice.actions export const registerStakingListeners = () => { + startAppListening({ + actionCreator: walletConnected, + effect: fetchOwnerStakesEffect, + }) + startAppListening({ actionCreator: requestStakeByStakingProvider, effect: fetchStakeByStakingProviderEffect, }) } - -registerStakingListeners() diff --git a/src/threshold-ts/staking/__test__/staking.test.ts b/src/threshold-ts/staking/__test__/staking.test.ts index 310410b2c..b46f69dac 100644 --- a/src/threshold-ts/staking/__test__/staking.test.ts +++ b/src/threshold-ts/staking/__test__/staking.test.ts @@ -218,10 +218,10 @@ describe("Staking test", () => { ]) expect(vendingMachines.keep.convertToT).toHaveBeenCalledWith( - eligibleKeepStake + eligibleKeepStake.toString() ) expect(vendingMachines.nu.convertToT).toHaveBeenCalledWith( - nuStakerInfo.value + nuStakerInfo.value.toString() ) expect(result).toEqual({ diff --git a/src/threshold-ts/staking/index.ts b/src/threshold-ts/staking/index.ts index 5c3d5c640..709487fcd 100644 --- a/src/threshold-ts/staking/index.ts +++ b/src/threshold-ts/staking/index.ts @@ -199,12 +199,13 @@ export class Staking implements IStaking { isAddress(nuStakingProvider) && isSameETHAddress(stakingProvider, nuStakingProvider) ? BigNumber.from( - (await this._vendingMachines.nu.convertToT(nuStake)).tAmount + (await this._vendingMachines.nu.convertToT(nuStake.toString())) + .tAmount ).sub(BigNumber.from(nuInTStake)) : ZERO const keepEligableStakeInT = ( - await this._vendingMachines.keep.convertToT(eligibleKeepStake) + await this._vendingMachines.keep.convertToT(eligibleKeepStake.toString()) ).tAmount const possibleKeepTopUpInT = BigNumber.from(keepEligableStakeInT).sub( BigNumber.from(keepInTStake) From fced6c386ffb59e81b2afb53159f8bdfbbf9b8a5 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 9 Nov 2022 13:42:19 +0100 Subject: [PATCH 2/6] Refactor registering listeners We should register listener in different file than the one in which it is defined otherwise we may run into an issue with webpack import orddering. Here we create a common function that should register all listeners and we call this fn after creating a store and after resetting the store. --- src/store/account/slice.ts | 1 - src/store/index.ts | 20 ++++++++------------ src/store/listener.ts | 11 +++++++++++ src/store/staking-applications/slice.ts | 1 - src/store/tokens/tokenSlice.ts | 1 - 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/store/account/slice.ts b/src/store/account/slice.ts index 570f397f8..75a7c4252 100644 --- a/src/store/account/slice.ts +++ b/src/store/account/slice.ts @@ -103,7 +103,6 @@ export const registerAccountListeners = () => { }) } } -registerAccountListeners() export const { walletConnected, diff --git a/src/store/index.ts b/src/store/index.ts index 242ea4aec..cf2175dcd 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -5,19 +5,16 @@ import { Reducer, } from "@reduxjs/toolkit" import { modalSlice } from "./modal" -import { registerTokensListeners, tokenSlice } from "./tokens" +import { tokenSlice } from "./tokens" import { sidebarSlice } from "./sidebar" import { transactionSlice } from "./transactions" -import { registerStakingListeners, stakingSlice } from "./staking" +import { stakingSlice } from "./staking" import { ethSlice } from "./eth" import { rewardsSlice } from "./rewards" import { tbtcSlice } from "./tbtc" -import { - registerStakingAppsListeners, - stakingApplicationsSlice, -} from "./staking-applications/slice" -import { listenerMiddleware } from "./listener" -import { accountSlice, registerAccountListeners } from "./account" +import { stakingApplicationsSlice } from "./staking-applications/slice" +import { listenerMiddleware, registerListeners } from "./listener" +import { accountSlice } from "./account" const combinedReducer = combineReducers({ account: accountSlice.reducer, @@ -41,10 +38,7 @@ export const resetStoreAction = () => ({ const rootReducer: Reducer = (state: RootState, action: AnyAction) => { if (action.type === APP_RESET_STORE) { listenerMiddleware.clearListeners() - registerStakingListeners() - registerStakingAppsListeners() - registerAccountListeners() - registerTokensListeners() + registerListeners() state = { eth: { ...state.eth }, token: { @@ -93,6 +87,8 @@ const store = configureStore({ }).prepend(listenerMiddleware.middleware), }) +registerListeners() + export type RootState = ReturnType< typeof store.getState & typeof combinedReducer > diff --git a/src/store/listener.ts b/src/store/listener.ts index f4f65b845..f760f19bf 100644 --- a/src/store/listener.ts +++ b/src/store/listener.ts @@ -6,6 +6,10 @@ import { import { AppDispatch, RootState } from "." import { Threshold } from "../threshold-ts" import { threshold } from "../utils/getThresholdLib" +import { registerTokensListeners } from "./tokens" +import { registerStakingListeners } from "./staking" +import { registerStakingAppsListeners } from "./staking-applications/slice" +import { registerAccountListeners } from "./account" export const listenerMiddleware = createListenerMiddleware({ extra: { threshold }, @@ -29,3 +33,10 @@ export type AppListenerEffectAPI = ListenerEffectAPI< export const startAppListening = listenerMiddleware.startListening as AppStartListening + +export const registerListeners = () => { + registerAccountListeners() + registerTokensListeners() + registerStakingListeners() + registerStakingAppsListeners() +} diff --git a/src/store/staking-applications/slice.ts b/src/store/staking-applications/slice.ts index 4fdeb67d1..bcc030374 100644 --- a/src/store/staking-applications/slice.ts +++ b/src/store/staking-applications/slice.ts @@ -282,4 +282,3 @@ export const registerStakingAppsListeners = () => { }) } } -registerStakingAppsListeners() diff --git a/src/store/tokens/tokenSlice.ts b/src/store/tokens/tokenSlice.ts index 7f1f5a515..847d74d60 100644 --- a/src/store/tokens/tokenSlice.ts +++ b/src/store/tokens/tokenSlice.ts @@ -114,4 +114,3 @@ export const registerTokensListeners = () => { effect: fetchTokenBalances, }) } -registerTokensListeners() From 17d6e1ba752e3b5ee3ee563592662e8fc7fe120e Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 9 Nov 2022 14:41:17 +0100 Subject: [PATCH 3/6] Clean up the `StakeData` type Use the `Stake` type from the threshold ts lib- this removes the `preConfig` filed from the `StakeData` type. The PRE is a staking app so should be stored in a dedicated slice(rewards). We are going to add the PRE app to the rewards slice in a follow-up work. --- .../Modal/TopupTModal/LegacyTopUpModal.tsx | 2 +- src/hooks/useSubscribeToStakedEvent.ts | 17 ++------------ src/pages/Staking/StakeCard/index.tsx | 6 ++--- src/store/staking/stakingSlice.ts | 6 ----- src/types/staking.ts | 22 +++---------------- 5 files changed, 9 insertions(+), 44 deletions(-) diff --git a/src/components/Modal/TopupTModal/LegacyTopUpModal.tsx b/src/components/Modal/TopupTModal/LegacyTopUpModal.tsx index 44801372b..f836ceeb0 100644 --- a/src/components/Modal/TopupTModal/LegacyTopUpModal.tsx +++ b/src/components/Modal/TopupTModal/LegacyTopUpModal.tsx @@ -108,7 +108,7 @@ const LegacyTopUpModal: FC = ({