Skip to content

Commit

Permalink
feat: load delay modifiers globally
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacook committed Oct 6, 2023
1 parent 2e6abed commit a925dc5
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 24 deletions.
29 changes: 11 additions & 18 deletions src/components/settings/Recovery/index.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,38 @@
import { Paper } from '@mui/material'
import type { ReactElement } from 'react'

import { useDelayModifier } from './useDelayModifier'
import { RecoverersList } from './RecoverersList'
import { RecoverySetup } from './RecoverySetup'
import useAsync from '@/hooks/useAsync'
import { MODULE_PAGE_SIZE } from '@/services/recovery/delay-modifier'
import { SENTINEL_ADDRESS } from '@safe-global/safe-core-sdk/dist/src/utils/constants'
import { getModuleInstance, KnownContracts } from '@gnosis.pm/zodiac'
import useWallet from '@/hooks/wallets/useWallet'
import { RecoveryProposal } from './RecoveryProposal'
import { RecoveryProposals } from './RecoveryProposals'
import { useAppSelector } from '@/store'
import { selectRecovery } from '@/store/recoverySlice'
import { useWeb3 } from '@/hooks/wallets/web3'

export function Recovery(): ReactElement {
const wallet = useWallet()
const [delayModifier] = useDelayModifier()
const recovery = useAppSelector(selectRecovery)
const web3 = useWeb3()

const [recoverers] = useAsync<Array<string> | undefined>(async () => {
if (!delayModifier) {
return
}

const [modules] = await delayModifier.getModulesPaginated(SENTINEL_ADDRESS, MODULE_PAGE_SIZE)
return modules
}, [delayModifier])

const isRecoverer = !!wallet && recoverers?.includes(wallet.address)

if (!delayModifier) {
if (!recovery || !web3) {
return (
<Paper sx={{ p: 4 }}>
<RecoverySetup />
</Paper>
)
}

const isRecoverer = !!wallet && recovery.modules.includes(wallet.address)
const delayModifier = getModuleInstance(KnownContracts.DELAY, recovery.address, web3)

return (
<Paper sx={{ p: 4 }}>
{isRecoverer ? (
<RecoveryProposal delayModifier={delayModifier} />
) : (
<RecoverersList delayModifier={delayModifier} recoverers={recoverers ?? []} />
<RecoverersList delayModifier={delayModifier} recoverers={recovery.modules} />
)}
<RecoveryProposals delayModifier={delayModifier} isRecoverer={isRecoverer} />
</Paper>
Expand Down
54 changes: 54 additions & 0 deletions src/hooks/loadables/useLoadRecovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { SENTINEL_ADDRESS } from '@safe-global/safe-core-sdk/dist/src/utils/constants'
import type { Delay } from '@gnosis.pm/zodiac'

import { getDelayModifiers, MODULE_PAGE_SIZE } from '@/services/recovery/delay-modifier'
import useAsync from '../useAsync'
import useSafeInfo from '../useSafeInfo'
import { useWeb3ReadOnly } from '../wallets/web3'
import type { AsyncResult } from '../useAsync'
import type { RecoveryState } from '@/store/recoverySlice'

const getRecoveryState = async (delayModifier: Delay): Promise<RecoveryState[number]> => {
const transactionAddedFilter = delayModifier.filters.TransactionAdded()

const [[modules], txNonce, transactionsAdded] = await Promise.all([
delayModifier.getModulesPaginated(SENTINEL_ADDRESS, MODULE_PAGE_SIZE),
delayModifier.txNonce(),
delayModifier.queryFilter(transactionAddedFilter),
])

return {
address: delayModifier.address,
modules,
txNonce: txNonce.toString(),
transactionsAdded: transactionsAdded.filter(({ args }) => args.queueNonce.gte(txNonce)),
}
}

// TODO: Polling
const useLoadRecovery = (): AsyncResult<RecoveryState> => {
const { safe } = useSafeInfo()
const web3ReadOnly = useWeb3ReadOnly()

const [delayModifiers, delayModifiersError, delayModifiersLoading] = useAsync<Array<Delay>>(() => {
if (!web3ReadOnly || safe.modules?.length === 0) {
return
}

return getDelayModifiers(safe.chainId, safe.modules, web3ReadOnly)
// Need to check length of modules array to prevent new request every time Safe info polls
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [safe.chainId, safe.modules?.length, web3ReadOnly])

const [recoveryState, recoveryStateError, recoveryStateLoading] = useAsync<RecoveryState>(() => {
if (!delayModifiers || delayModifiers.length === 0) {
return
}

return Promise.all(delayModifiers.map(getRecoveryState))
}, [delayModifiers])

return [recoveryState, delayModifiersError || recoveryStateError, delayModifiersLoading || recoveryStateLoading]
}

export default useLoadRecovery
5 changes: 4 additions & 1 deletion src/hooks/useLoadableStores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import useLoadSafeInfo from './loadables/useLoadSafeInfo'
import useLoadBalances from './loadables/useLoadBalances'
import useLoadTxHistory from './loadables/useLoadTxHistory'
import useLoadTxQueue from './loadables/useLoadTxQueue'
import useLoadSpendingLimits from './loadables/useLoadSpendingLimits'
import useLoadMessages from './loadables/useLoadSafeMessages'
import useLoadRecovery from './loadables/useLoadRecovery'

// Import all the loadable slices
import { chainsSlice } from '@/store/chainsSlice'
Expand All @@ -18,8 +20,8 @@ import { balancesSlice } from '@/store/balancesSlice'
import { txHistorySlice } from '@/store/txHistorySlice'
import { txQueueSlice } from '@/store/txQueueSlice'
import { spendingLimitSlice } from '@/store/spendingLimitsSlice'
import useLoadSpendingLimits from '@/hooks/loadables/useLoadSpendingLimits'
import { safeMessagesSlice } from '@/store/safeMessagesSlice'
import { recoverySlice } from '@/store/recoverySlice'

// Dispatch into the corresponding store when the loadable is loaded
const useUpdateStore = (slice: Slice, useLoadHook: () => AsyncResult<unknown>): void => {
Expand All @@ -46,6 +48,7 @@ const useLoadableStores = () => {
useUpdateStore(txQueueSlice, useLoadTxQueue)
useUpdateStore(safeMessagesSlice, useLoadMessages)
useUpdateStore(spendingLimitSlice, useLoadSpendingLimits)
useUpdateStore(recoverySlice, useLoadRecovery)
}

export default useLoadableStores
6 changes: 3 additions & 3 deletions src/services/recovery/delay-modifier.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ContractVersions, getModuleInstance, KnownContracts } from '@gnosis.pm/zodiac'
import type { Delay, SupportedNetworks } from '@gnosis.pm/zodiac'
import type { Web3Provider } from '@ethersproject/providers'
import type { JsonRpcProvider } from '@ethersproject/providers'
import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'

import { sameAddress } from '@/utils/addresses'
Expand All @@ -11,7 +11,7 @@ export const MODULE_PAGE_SIZE = 100
async function isOfficialDelayModifier(
chainId: string,
moduleAddress: string,
provider: Web3Provider,
provider: JsonRpcProvider,
): Promise<boolean> {
const bytecode = await provider.getCode(moduleAddress)

Expand Down Expand Up @@ -39,7 +39,7 @@ async function isOfficialDelayModifier(
export async function getDelayModifiers(
chainId: string,
modules: SafeInfo['modules'],
provider: Web3Provider,
provider: JsonRpcProvider,
): Promise<Array<Delay>> {
if (!modules) {
return []
Expand Down
4 changes: 2 additions & 2 deletions src/services/recovery/proxies.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Contract } from 'ethers'
import type { Web3Provider } from '@ethersproject/providers'
import type { JsonRpcProvider } from '@ethersproject/providers'

export function isGenericProxy(bytecode: string): boolean {
if (bytecode.length !== 92) {
Expand All @@ -20,7 +20,7 @@ export function isGnosisProxy(bytecode: string): boolean {
)
}

export async function getGnosisProxyMasterCopy(address: string, provider: Web3Provider): Promise<string> {
export async function getGnosisProxyMasterCopy(address: string, provider: JsonRpcProvider): Promise<string> {
const gnosisProxyContract = new Contract(address, ['function masterCopy() external view returns (address)'], provider)

const [masterCopy] = await gnosisProxyContract.masterCopy()
Expand Down
2 changes: 2 additions & 0 deletions src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { safeAppsSlice } from './safeAppsSlice'
import { safeMessagesListener, safeMessagesSlice } from './safeMessagesSlice'
import { pendingSafeMessagesSlice } from './pendingSafeMessagesSlice'
import { batchSlice } from './batchSlice'
import { recoverySlice } from './recoverySlice'

const rootReducer = combineReducers({
[chainsSlice.name]: chainsSlice.reducer,
Expand All @@ -49,6 +50,7 @@ const rootReducer = combineReducers({
[safeMessagesSlice.name]: safeMessagesSlice.reducer,
[pendingSafeMessagesSlice.name]: pendingSafeMessagesSlice.reducer,
[batchSlice.name]: batchSlice.reducer,
[recoverySlice.name]: recoverySlice.reducer,
})

const persistedSlices: (keyof PreloadedState<RootState>)[] = [
Expand Down
21 changes: 21 additions & 0 deletions src/store/recoverySlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createSelector } from '@reduxjs/toolkit'
import type { TransactionAddedEvent } from '@gnosis.pm/zodiac/dist/cjs/types/Delay'

import { makeLoadableSlice } from './common'

export type RecoveryState = Array<{
address: string
modules: Array<string>
txNonce: string
transactionsAdded: Array<TransactionAddedEvent>
}>

const initialState: RecoveryState = []

const { slice, selector } = makeLoadableSlice('recovery', initialState)

export const recoverySlice = slice

// TODO: Multiple Delay Modifiers
export const selectRecovery = createSelector(selector, (recovery) => recovery.data[0])
export const selectRecoveryLoading = createSelector(selector, (recovery) => recovery.loading)

0 comments on commit a925dc5

Please sign in to comment.