-
Notifications
You must be signed in to change notification settings - Fork 461
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
275 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { SENTINEL_ADDRESS } from '@safe-global/safe-core-sdk/dist/src/utils/constants' | ||
import type { Delay } from '@gnosis.pm/zodiac' | ||
|
||
import { getDelayModifiers } from '@/services/recovery/delay-modifier' | ||
import useAsync from '../useAsync' | ||
import useSafeInfo from '../useSafeInfo' | ||
import { useWeb3ReadOnly } from '../wallets/web3' | ||
import { getSpendingLimitModuleAddress } from '@/services/contracts/spendingLimitContracts' | ||
import useIntervalCounter from '../useIntervalCounter' | ||
import type { AsyncResult } from '../useAsync' | ||
import type { RecoveryState } from '@/store/recoverySlice' | ||
|
||
const MAX_PAGE_SIZE = 100 | ||
const REFRESH_DELAY = 5 * 60 * 1_000 // 5 minutes | ||
|
||
const getRecoveryState = async (delayModifier: Delay): Promise<RecoveryState[number]> => { | ||
const transactionAddedFilter = delayModifier.filters.TransactionAdded() | ||
|
||
const [[modules], txCooldown, txNonce, queueNonce, transactionsAdded] = await Promise.all([ | ||
delayModifier.getModulesPaginated(SENTINEL_ADDRESS, MAX_PAGE_SIZE), | ||
delayModifier.txCooldown(), | ||
delayModifier.txNonce(), | ||
delayModifier.queueNonce(), | ||
delayModifier.queryFilter(transactionAddedFilter), | ||
]) | ||
|
||
return { | ||
address: delayModifier.address, | ||
modules, | ||
txCooldown: txCooldown.toString(), | ||
txNonce: txNonce.toString(), | ||
queueNonce: queueNonce.toString(), | ||
transactionsAdded: transactionsAdded.filter(({ args }) => args.queueNonce.gte(txNonce)), | ||
} | ||
} | ||
|
||
const useLoadRecovery = (): AsyncResult<RecoveryState> => { | ||
const { safe } = useSafeInfo() | ||
const web3ReadOnly = useWeb3ReadOnly() | ||
const [counter] = useIntervalCounter(REFRESH_DELAY) | ||
|
||
const [delayModifiers, delayModifiersError, delayModifiersLoading] = useAsync<Array<Delay>>(() => { | ||
if (!web3ReadOnly || !safe.modules || safe.modules.length === 0) { | ||
return | ||
} | ||
|
||
const isOnlySpendingLimit = | ||
safe.modules.length === 1 && safe.modules[0].value === getSpendingLimitModuleAddress(safe.chainId) | ||
|
||
if (isOnlySpendingLimit) { | ||
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)) | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [delayModifiers, counter]) | ||
|
||
return [recoveryState, delayModifiersError || recoveryStateError, delayModifiersLoading || recoveryStateLoading] | ||
} | ||
|
||
export default useLoadRecovery |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { ContractVersions, getModuleInstance, KnownContracts } from '@gnosis.pm/zodiac' | ||
import type { Delay, SupportedNetworks } from '@gnosis.pm/zodiac' | ||
import type { JsonRpcProvider } from '@ethersproject/providers' | ||
import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk' | ||
|
||
import { sameAddress } from '@/utils/addresses' | ||
import { getGenericProxyMasterCopy, getGnosisProxyMasterCopy, isGenericProxy, isGnosisProxy } from './proxies' | ||
|
||
export async function isOfficialDelayModifier( | ||
chainId: string, | ||
moduleAddress: string, | ||
provider: JsonRpcProvider, | ||
): Promise<boolean> { | ||
const bytecode = await provider.getCode(moduleAddress) | ||
|
||
if (isGenericProxy(bytecode)) { | ||
const masterCopy = getGenericProxyMasterCopy(bytecode) | ||
return await isOfficialDelayModifier(chainId, masterCopy, provider) | ||
} | ||
|
||
if (isGnosisProxy(bytecode)) { | ||
const masterCopy = await getGnosisProxyMasterCopy(moduleAddress, provider) | ||
return await isOfficialDelayModifier(chainId, masterCopy, provider) | ||
} | ||
|
||
const zodiacChainContracts = ContractVersions[Number(chainId) as SupportedNetworks] | ||
const zodiacContract = Object.entries(zodiacChainContracts).find(([, addresses]) => { | ||
return Object.values(addresses).some((address) => { | ||
return sameAddress(address, moduleAddress) | ||
}) | ||
}) | ||
|
||
return zodiacContract?.[0] === KnownContracts.DELAY | ||
} | ||
|
||
export async function getDelayModifiers( | ||
chainId: string, | ||
modules: SafeInfo['modules'], | ||
provider: JsonRpcProvider, | ||
): Promise<Array<Delay>> { | ||
if (!modules) { | ||
return [] | ||
} | ||
|
||
const instances = await Promise.all( | ||
modules.map(async ({ value }) => { | ||
const isDelayModifier = await isOfficialDelayModifier(chainId, value, provider) | ||
|
||
if (!isDelayModifier) { | ||
return null | ||
} | ||
|
||
return getModuleInstance(KnownContracts.DELAY, value, provider) | ||
}), | ||
) | ||
|
||
return instances.filter(Boolean) as Array<Delay> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Contract } from 'ethers' | ||
import type { JsonRpcProvider } from '@ethersproject/providers' | ||
|
||
export function isGenericProxy(bytecode: string): boolean { | ||
if (bytecode.length !== 92) { | ||
return false | ||
} | ||
|
||
return bytecode.startsWith('0x363d3d373d3d3d363d73') && bytecode.endsWith('5af43d82803e903d91602b57fd5bf3') | ||
} | ||
|
||
export function getGenericProxyMasterCopy(bytecode: string): string { | ||
return '0x' + bytecode.slice(22, 62) | ||
} | ||
|
||
export function isGnosisProxy(bytecode: string): boolean { | ||
return ( | ||
bytecode.toLowerCase() === | ||
'0x608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea265627a7a72315820d8a00dc4fe6bf675a9d7416fc2d00bb3433362aa8186b750f76c4027269667ff64736f6c634300050e0032' | ||
) | ||
} | ||
|
||
export async function getGnosisProxyMasterCopy(address: string, provider: JsonRpcProvider): Promise<string> { | ||
const gnosisProxyAbi = ['function masterCopy() external view returns (address)'] | ||
const gnosisProxyContract = new Contract(address, gnosisProxyAbi, provider) | ||
|
||
const [masterCopy] = await gnosisProxyContract.masterCopy() | ||
|
||
return masterCopy | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
txCooldown: string | ||
txNonce: string | ||
queueNonce: string | ||
transactionsAdded: Array<TransactionAddedEvent> | ||
}> | ||
|
||
const initialState: RecoveryState = [] | ||
|
||
const { slice, selector } = makeLoadableSlice('recovery', initialState) | ||
|
||
export const recoverySlice = slice | ||
|
||
export const selectRecovery = createSelector(selector, (recovery) => recovery.data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { faker } from '@faker-js/faker' | ||
import type { AddressEx } from '@safe-global/safe-gateway-typescript-sdk' | ||
|
||
import { Builder } from '../Builder' | ||
import type { IBuilder } from '../Builder' | ||
|
||
export const addressExBuilder = (): IBuilder<AddressEx> => { | ||
return Builder.new<AddressEx>().with({ | ||
name: faker.word.words(), | ||
value: faker.finance.ethereumAddress(), | ||
logoUri: faker.image.url(), | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { faker } from '@faker-js/faker' | ||
import { ImplementationVersionState } from '@safe-global/safe-gateway-typescript-sdk' | ||
import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk' | ||
|
||
import { generateRandomArray } from './utils' | ||
import { addressExBuilder } from './address-ex' | ||
import { LATEST_SAFE_VERSION } from '@/config/constants' | ||
import { Builder } from '../Builder' | ||
import type { IBuilder } from '../Builder' | ||
import type useSafeInfo from '@/hooks/useSafeInfo' | ||
|
||
const OWNERS_LENGTH = 5 | ||
|
||
export const safeInfo = (): IBuilder<SafeInfo> => { | ||
return Builder.new<SafeInfo>().with({ | ||
address: addressExBuilder().build(), | ||
chainId: faker.string.numeric({ exclude: '0' }), | ||
nonce: faker.number.int(), | ||
threshold: faker.number.int(), | ||
owners: generateRandomArray(addressExBuilder().build, { min: 1, max: OWNERS_LENGTH }), | ||
implementation: addressExBuilder().build(), | ||
implementationVersionState: faker.helpers.enumValue(ImplementationVersionState), | ||
modules: faker.helpers.arrayElement([generateRandomArray(addressExBuilder().build), null]), | ||
guard: faker.helpers.arrayElement([addressExBuilder().build(), null]), | ||
fallbackHandler: addressExBuilder().build(), | ||
version: faker.helpers.arrayElement(['1.0.0', '1.1.1', '1.2.0', '1.3.0', LATEST_SAFE_VERSION]), | ||
collectiblesTag: faker.string.numeric(), | ||
txQueuedTag: faker.string.numeric(), | ||
txHistoryTag: faker.string.numeric(), | ||
messagesTag: faker.string.numeric(), | ||
}) | ||
} | ||
|
||
export const useSafeInfoBuilder = (): IBuilder<ReturnType<typeof useSafeInfo>> => { | ||
const safe = safeInfo().build() | ||
|
||
return Builder.new<ReturnType<typeof useSafeInfo>>().with({ | ||
safe, | ||
safeAddress: safe.address.value, | ||
safeLoaded: true, | ||
safeError: undefined, | ||
safeLoading: false, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2706,6 +2706,27 @@ | |
dependencies: | ||
tslib "^2.1.0" | ||
|
||
"@gnosis.pm/mock-contract@^4.0.0": | ||
version "4.0.0" | ||
resolved "https://registry.yarnpkg.com/@gnosis.pm/mock-contract/-/mock-contract-4.0.0.tgz#eaf500fddcab81b5f6c22280a7b22ff891dd6f87" | ||
integrity sha512-SkRq2KwPx6vo0LAjSc8JhgQstrQFXRyn2yqquIfub7r2WHi5nUbF8beeSSXsd36hvBcQxQfmOIYNYRpj9JOhrQ== | ||
|
||
"@gnosis.pm/[email protected]": | ||
version "1.3.0" | ||
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-contracts/-/safe-contracts-1.3.0.tgz#316741a7690d8751a1f701538cfc9ec80866eedc" | ||
integrity sha512-1p+1HwGvxGUVzVkFjNzglwHrLNA67U/axP0Ct85FzzH8yhGJb4t9jDjPYocVMzLorDoWAfKicGy1akPY9jXRVw== | ||
|
||
"@gnosis.pm/zodiac@^3.4.2": | ||
version "3.4.2" | ||
resolved "https://registry.yarnpkg.com/@gnosis.pm/zodiac/-/zodiac-3.4.2.tgz#ce3e7498e39ccc3324eabc6f163bd173bf4d9aad" | ||
integrity sha512-u7BPXsoo1ZdbmsElMbuejKNTWA3NPvFdzs3vjUSIcFfHTb9B/UE+gDQ3vMYL6bt+YLVw0F/IT5ytbiruKYQpEQ== | ||
dependencies: | ||
"@gnosis.pm/mock-contract" "^4.0.0" | ||
"@gnosis.pm/safe-contracts" "1.3.0" | ||
"@openzeppelin/contracts" "^5.0.0" | ||
"@openzeppelin/contracts-upgradeable" "^5.0.0" | ||
ethers "^5.7.1" | ||
|
||
"@grpc/grpc-js@~1.9.0": | ||
version "1.9.5" | ||
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.5.tgz#22e283754b7b10d1ad26c3fb21849028dcaabc53" | ||
|
@@ -3604,11 +3625,21 @@ | |
"@nodelib/fs.scandir" "2.1.5" | ||
fastq "^1.6.0" | ||
|
||
"@openzeppelin/contracts-upgradeable@^5.0.0": | ||
version "5.0.0" | ||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.0.tgz#859c00c55f04b6dda85b3c88bce507d65019888f" | ||
integrity sha512-D54RHzkOKHQ8xUssPgQe2d/U92mwaiBDY7qCCVGq6VqwQjsT3KekEQ3bonev+BLP30oZ0R1U6YC8/oLpizgC5Q== | ||
|
||
"@openzeppelin/contracts@^4.9.2": | ||
version "4.9.3" | ||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364" | ||
integrity sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg== | ||
|
||
"@openzeppelin/contracts@^5.0.0": | ||
version "5.0.0" | ||
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.0.tgz#ee0e4b4564f101a5c4ee398cd4d73c0bd92b289c" | ||
integrity sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw== | ||
|
||
"@polka/url@^1.0.0-next.20": | ||
version "1.0.0-next.23" | ||
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.23.tgz#498e41218ab3b6a1419c735e5c6ae2c5ed609b6c" | ||
|
@@ -8798,7 +8829,7 @@ [email protected]: | |
"@ethersproject/web" "5.5.1" | ||
"@ethersproject/wordlists" "5.5.0" | ||
|
||
[email protected]: | ||
[email protected], ethers@^5.7.1: | ||
version "5.7.2" | ||
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" | ||
integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== | ||
|