Skip to content

Commit

Permalink
fix: prevent unsupported treasuries from crashing proposal page (#4683)
Browse files Browse the repository at this point in the history
* if safes not supported, fail silently but show error in console

* fix interface

* hide activate button if no config set up for chain

* edit copy
  • Loading branch information
gsteenkamp89 authored Apr 20, 2024
1 parent b6fa4bd commit b6d0886
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 69 deletions.
17 changes: 13 additions & 4 deletions src/components/SettingsTreasuriesBlockItemButton.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { TreasuryWallet } from '@/helpers/interfaces';
import { Network } from '@/plugins/oSnap/types';
import { getIsOsnapEnabled } from '@/plugins/oSnap/utils/getters';
import { ConfigError, getIsOsnapEnabled } from '@/plugins/oSnap/utils/getters';
const props = defineProps<{
treasury: TreasuryWallet;
Expand All @@ -17,13 +17,21 @@ const emit = defineEmits<{
}>();
const isOsnapEnabled = ref(false);
const isChainSupported = ref(true);
async function updateIsOsnapEnabled() {
if (!props.hasOsnapPlugin) return;
isOsnapEnabled.value = await getIsOsnapEnabled(
const isEnabled = await getIsOsnapEnabled(
props.treasury.network as Network,
props.treasury.address
);
).catch(e => {
if (e instanceof ConfigError) {
isChainSupported.value = false;
return false;
}
return false;
});
isOsnapEnabled.value = isEnabled;
}
onMounted(async () => {
Expand All @@ -47,12 +55,13 @@ onUnmounted(() => {
</div>
<div class="ml-auto mr-3">
<SettingsTreasuryActivateOsnapButton
v-show="hasOsnapPlugin"
v-if="hasOsnapPlugin && isChainSupported"
:is-osnap-enabled="isOsnapEnabled"
@click.stop="
!isViewOnly && emit('configureOsnap', treasuryIndex, isOsnapEnabled)
"
/>
<div v-else>oSnap unavailable</div>
</div>
<BaseButtonIcon
v-show="!isViewOnly"
Expand Down
51 changes: 30 additions & 21 deletions src/plugins/oSnap/Create.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ExtendedSpace, TreasuryWallet } from '@/helpers/interfaces';
import { ExtendedSpace } from '@/helpers/interfaces';
import { formatUnits } from '@ethersproject/units';
import { cloneDeep } from 'lodash';
import SelectSafe from './components/Input/SelectSafe.vue';
Expand All @@ -11,7 +11,8 @@ import {
Network,
OsnapPluginData,
Token,
Transaction
Transaction,
nonNullable
} from './types';
import {
getGnosisSafeBalances,
Expand Down Expand Up @@ -159,34 +160,42 @@ async function fetchCollectibles(network: Network, gnosisSafeAddress: string) {
// maps over the treasuries and creates a safe for each one
// only returns safes that have oSnap enabled
async function createOsnapEnabledSafes() {
const treasuriesWithOsnapEnabled = (
await Promise.all(
props.space.treasuries.map(async treasury => {
const isOsnapEnabled = await getIsOsnapEnabled(
treasury.network as Network,
treasury.address
);
return isOsnapEnabled ? treasury : null;
})
)
).filter(treasury => treasury !== null) as TreasuryWallet[];
const treasuryPromises = await Promise.allSettled(
props.space.treasuries.map(async treasury => {
const isOsnapEnabled = await getIsOsnapEnabled(
treasury.network as Network,
treasury.address
);
return isOsnapEnabled ? treasury : null;
})
);
const safes: GnosisSafe[] = await Promise.all(
const treasuriesWithOsnapEnabled = treasuryPromises
.map(res => (res.status === 'fulfilled' ? res.value : null))
.filter(nonNullable);
const safePromises = await Promise.allSettled(
treasuriesWithOsnapEnabled.map(async treasury => {
const moduleAddress = await getModuleAddressForTreasury(
treasury.network as Network,
treasury.address
);
return {
safeName: treasury.name,
safeAddress: toChecksumAddress(treasury.address),
network: treasury.network as Network,
transactions: [] as Transaction[],
moduleAddress
};
return moduleAddress
? {
safeName: treasury.name,
safeAddress: toChecksumAddress(treasury.address),
network: treasury.network as Network,
transactions: [] as Transaction[],
moduleAddress
}
: null;
})
);
const safes = safePromises
.map(res => (res.status === 'fulfilled' ? res.value : null))
.filter(nonNullable);
return safes;
}
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/oSnap/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,7 @@ export namespace GnosisSafe {
components?: ContractInput[];
}
}

export function nonNullable<T>(value: T): value is NonNullable<T> {
return value !== null;
}
120 changes: 79 additions & 41 deletions src/plugins/oSnap/utils/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ import {
TransactionsProposedEvent
} from '../types';
import { getPagedEvents } from './events';
import { toChecksumAddress } from '@/helpers/utils';
import app from '../../../main';
import { shortenAddress, toChecksumAddress } from '@/helpers/utils';

/**
* Calls the Gnosis Safe Transaction API
Expand All @@ -48,8 +47,8 @@ async function callGnosisSafeTransactionApi<TResult = any>(
network: Network,
url: string
) {
if(!GNOSIS_SAFE_TRANSACTION_API_URLS[network])
throw new Error(`No gnosis safe api defined for network ${network}`)
if (!GNOSIS_SAFE_TRANSACTION_API_URLS[network])
throw new Error(`No gnosis safe api defined for network ${network}`);
const apiUrl = GNOSIS_SAFE_TRANSACTION_API_URLS[network];
const response = await fetch(apiUrl + url);
return response.json() as TResult;
Expand Down Expand Up @@ -100,6 +99,17 @@ function getDeployBlock(params: { network: Network; name: string }): number {
return 0;
}

export class ConfigError extends Error {
constructor(message: string, responsibleVar: string) {
super(message);
this.name = 'CONFIG_ERROR';
}
}

export function logIfErrorMessage(e: unknown, overrideMessage: string) {
console.error(e instanceof Error ? e.message : overrideMessage);
}

/**
* Fetches the subgraph url for a given contract on a given network.
*/
Expand All @@ -109,17 +119,17 @@ function getContractSubgraph(params: { network: Network; name: string }) {
contract.network === params.network && contract.name === params.name
);
if (results.length > 1)
throw new Error(
`Too many results finding ${params.name} subgraph on network ${params.network}`
throw new ConfigError(
`Too many results finding ${params.name} subgraph on network ${params.network}`,
'subgraph'
);
if (results.length < 1)
throw new Error(
`No results finding ${params.name} subgraph on network ${params.network}`
);
if (!results[0].subgraph)
throw new Error(
`No subgraph url defined for ${params.name} on network ${params.network}`

if (results.length < 1 || !results[0].subgraph)
throw new ConfigError(
`No subgraph url defined for ${params.name} on network ${params.network}`,
'subgraph'
);

return results[0].subgraph;
}

Expand Down Expand Up @@ -180,23 +190,34 @@ export const getModuleAddressForTreasury = async (
network: Network,
treasuryAddress: string
) => {
const subgraph = getOptimisticGovernorSubgraph(network);
const query = `
query getModuleAddressForTreasury {
safe(id: "${treasuryAddress.toLowerCase()}") {
optimisticGovernor {
id
try {
const subgraph = getOptimisticGovernorSubgraph(network);
const query = `
query getModuleAddressForTreasury {
safe(id: "${treasuryAddress.toLowerCase()}") {
optimisticGovernor {
id
}
}
}
}
`;
}
`;

type Result = {
safe: { optimisticGovernor: { id: string } };
};
type Result = {
safe: { optimisticGovernor: { id: string } };
};

const result = await queryGql<Result>(subgraph, query);
return result?.safe?.optimisticGovernor?.id ?? '';
const result = await queryGql<Result>(subgraph, query);
return result?.safe?.optimisticGovernor?.id ?? '';
} catch (error) {
logIfErrorMessage(
error,
`Unable to get module address for treasury ${shortenAddress(
treasuryAddress
)} on network ${network}`
);

throw error;
}
};

/**
Expand All @@ -206,19 +227,29 @@ export const getIsOsnapEnabled = async (
network: Network,
safeAddress: string
) => {
const subgraph = getOptimisticGovernorSubgraph(network);
const query = `
query isOSnapEnabled {
safe(id:"${safeAddress.toLowerCase()}"){
isOptimisticGovernorEnabled
try {
const subgraph = getOptimisticGovernorSubgraph(network);
const query = `
query isOSnapEnabled {
safe(id:"${safeAddress.toLowerCase()}"){
isOptimisticGovernorEnabled
}
}
}
`;
type Result = {
safe: { isOptimisticGovernorEnabled: boolean };
};
const result = await queryGql<Result>(subgraph, query);
return result?.safe?.isOptimisticGovernorEnabled ?? false;
`;
type Result = {
safe: { isOptimisticGovernorEnabled: boolean };
};
const result = await queryGql<Result>(subgraph, query);
return result?.safe?.isOptimisticGovernorEnabled ?? false;
} catch (error) {
logIfErrorMessage(
error,
`Unable to check if oSnap is enable for address ${shortenAddress(
safeAddress
)} on network ${network}`
);
throw error;
}
};

/**
Expand Down Expand Up @@ -672,7 +703,11 @@ export async function getOGProposalStateGql(params: {
}

// request execution if there is no settlement yet and liveness has expired
return { status: 'can-request-tx-execution', assertionHash, assertionLogIndex };
return {
status: 'can-request-tx-execution',
assertionHash,
assertionLogIndex
};
}

/**
Expand Down Expand Up @@ -746,7 +781,10 @@ export function getSafeNetworkPrefix(network: Network): SafeNetworkPrefix {
export function getSafeAppLink(
network: Network,
safeAddress: string,
{appUrl = 'https://app.safe.global', path = '/home' } = {appUrl: 'https://app.safe.global', path: '/home'}
{ appUrl = 'https://app.safe.global', path = '/home' } = {
appUrl: 'https://app.safe.global',
path: '/home'
}
) {
const prefix = getSafeNetworkPrefix(network);
return new URL(`${path}?safe=${prefix}:${safeAddress}`, appUrl).toString();
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/safeSnap/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const EXPLORER_API_URLS = {
'56': 'https://api.bscscan.com/api',
'42161': 'https://api.arbiscan.io/api',
// '1116': Add 'https://openapi.coredao.org/api' if API key requirement is removed
'11155111': 'https://api-sepolia.etherscan.io/api',
'11155111': 'https://api-sepolia.etherscan.io/api'
};

export const GNOSIS_SAFE_TRANSACTION_API_URLS = {
Expand All @@ -46,7 +46,7 @@ export const GNOSIS_SAFE_TRANSACTION_API_URLS = {
'56': 'https://safe-transaction-bsc.safe.global/api',
'42161': 'https://safe-transaction-arbitrum.safe.global/api',
'1116': 'https://safetx.coredao.org/api',
'11155111': 'https://safe-transaction-sepolia.safe.global/api',
'11155111': 'https://safe-transaction-sepolia.safe.global/api'
};

// ABIs
Expand Down Expand Up @@ -586,5 +586,5 @@ export const contractData: ContractData[] = [
deployBlock: 5421242,
subgraph:
'https://api.thegraph.com/subgraphs/name/reinis-frp/sepolia-optimistic-governor'
},
}
];

0 comments on commit b6d0886

Please sign in to comment.