From 27ae6ccd3ab9af4ed6136bcf42734e994fdca8f8 Mon Sep 17 00:00:00 2001 From: ryanwolhuter Date: Fri, 13 Oct 2023 10:12:58 +0200 Subject: [PATCH] add proposal details types Signed-off-by: ryanwolhuter --- .../oSnap/components/HandleOutcomeUma.vue | 84 ++++++++++--------- src/plugins/oSnap/components/Tooltip.vue | 31 ------- .../TransactionBuilder/TransactionBuilder.vue | 4 + src/plugins/oSnap/types.ts | 45 ++++++++++ src/plugins/oSnap/utils/getters.ts | 37 ++++---- src/plugins/oSnap/utils/proposal.ts | 6 +- 6 files changed, 117 insertions(+), 90 deletions(-) delete mode 100644 src/plugins/oSnap/components/Tooltip.vue diff --git a/src/plugins/oSnap/components/HandleOutcomeUma.vue b/src/plugins/oSnap/components/HandleOutcomeUma.vue index 950cadb32..074e16fb8 100644 --- a/src/plugins/oSnap/components/HandleOutcomeUma.vue +++ b/src/plugins/oSnap/components/HandleOutcomeUma.vue @@ -4,11 +4,15 @@ import { formatUnits } from '@ethersproject/units'; import { getInstance } from '@snapshot-labs/lock/plugins/vue3'; import networks from '@snapshot-labs/snapshot.js/src/networks.json'; import { sleep } from '@snapshot-labs/snapshot.js/src/utils'; -import type { Network, Transaction } from '../types'; +import { + ProposalExecutionDetails, + type Network, + type Transaction +} from '../types'; import { approveBond, executeProposal, - getExecutionDetails, + getProposalExecutionDetails, submitProposal } from '../utils'; @@ -82,7 +86,6 @@ async function ensureRightNetwork(chainId: Network) { const { formatDuration } = useIntl(); const { t } = useI18n(); -const { clearBatchError } = useSafe(); const { web3 } = useWeb3(); const { createPendingTransaction, @@ -108,20 +111,20 @@ type QuestionState = type Action1State = 'idle' | 'approve-bond' | 'submit-proposal'; type Action2State = 'idle' | 'execute-proposal'; -const loading = ref(true); +const isLoading = ref(true); const action1State = ref('idle'); const action2State = ref('idle'); const voteResultsConfirmed = ref(false); -const questionDetails = ref>>(); -const closeModal = ref(false); +const proposalExecutionDetails = ref(); +const isModalOpen = ref(false); -function closeEvent() { - closeModal.value = false; +function closeModal() { + isModalOpen.value = false; voteResultsConfirmed.value = false; } -function showProposeModal() { - closeModal.value = true; +function openModal() { + isModalOpen.value = true; voteResultsConfirmed.value = true; } @@ -132,9 +135,9 @@ function getFormattedTransactions() { } async function updateDetails() { - loading.value = true; + isLoading.value = true; try { - questionDetails.value = await getExecutionDetails( + proposalExecutionDetails.value = await getProposalExecutionDetails( props.network, props.moduleAddress, props.proposal.id, @@ -144,7 +147,7 @@ async function updateDetails() { } catch (e) { console.error('Error loading uma execution details', e); } finally { - loading.value = false; + isLoading.value = false; } } @@ -224,7 +227,6 @@ async function onExecuteProposal() { } try { - clearBatchError(); const executingProposal = executeProposal( getInstance().web3, props.moduleAddress, @@ -279,12 +281,12 @@ function wasProposalFinalized(proposal: Proposal) { const questionState = computed(() => { if (!web3.value.account) return 'no-wallet-connection'; - if (loading.value) return 'loading'; + if (isLoading.value) return 'loading'; - if (!questionDetails.value) return 'error'; + if (!proposalExecutionDetails.value) return 'error'; const { assertionEvent, proposalExecuted, activeProposal, noTransactions } = - questionDetails.value; + proposalExecutionDetails.value; if (noTransactions) return 'no-transactions'; @@ -344,7 +346,10 @@ onMounted(async () => { {{ $t('safeSnap.labels.connectWallet') }} -
+
@@ -357,7 +362,7 @@ onMounted(async () => { class="my-4" >
- + {{ $t('safeSnap.labels.confirmVoteResults') }}
@@ -370,11 +375,11 @@ onMounted(async () => {
-
+
{
- + @@ -420,11 +425,11 @@ onMounted(async () => { {{ formatUnits( - questionDetails.minimumBond ?? 0, - questionDetails.decimals + proposalExecutionDetails.minimumBond ?? 0, + proposalExecutionDetails.decimals ) + ' ' + - questionDetails.symbol + proposalExecutionDetails.symbol }}
@@ -433,7 +438,11 @@ onMounted(async () => { $t('safeSnap.labels.challengePeriod') }} - {{ formatDuration(Number(questionDetails.livenessPeriod)) }} + {{ + formatDuration( + Number(proposalExecutionDetails.livenessPeriod) + ) + }}
@@ -446,8 +455,8 @@ onMounted(async () => { @@ -460,8 +469,8 @@ onMounted(async () => { @click="onSubmitProposal" class="my-1 w-full" :disabled=" - Number(questionDetails.minimumBond.toString()) > - Number(questionDetails.userBalance.toString()) || + Number(proposalExecutionDetails.minimumBond.toString()) > + Number(proposalExecutionDetails.userBalance.toString()) || Number(props.proposal.scores_total) < Number(quorum) " > @@ -475,7 +484,7 @@ onMounted(async () => { -
+
-import { shorten } from '@/helpers/utils'; - -defineProps<{ - moduleAddress: string | undefined; -}>(); - -const { copyToClipboard } = useCopy(); - - - diff --git a/src/plugins/oSnap/components/TransactionBuilder/TransactionBuilder.vue b/src/plugins/oSnap/components/TransactionBuilder/TransactionBuilder.vue index f564aacd7..f22e2c905 100644 --- a/src/plugins/oSnap/components/TransactionBuilder/TransactionBuilder.vue +++ b/src/plugins/oSnap/components/TransactionBuilder/TransactionBuilder.vue @@ -40,6 +40,10 @@ const safeLink = computed(() =>

+

+ Module address{{ moduleAddress }} +

Number of transactions{{ transactions.length }} diff --git a/src/plugins/oSnap/types.ts b/src/plugins/oSnap/types.ts index 0ca810223..411cded68 100644 --- a/src/plugins/oSnap/types.ts +++ b/src/plugins/oSnap/types.ts @@ -1,3 +1,4 @@ +import { BigNumber } from '@ethersproject/bignumber'; import networks from '@snapshot-labs/snapshot.js/src/networks.json'; import { safePrefixes, transactionTypes } from './constants'; @@ -239,4 +240,48 @@ export type OsnapPluginData = { */ export type OsnapModelValue = { oSnap: OsnapPluginData; +} + +export type AssertionEvent = { + assertionId: string; + proposalHash: string; + proposalTxHash: string; + logIndex: number; + expirationTimestamp: BigNumber; + isExpired: boolean; + isSettled: boolean; +} + +/** + * Represents the data associated with a proposal. + * + * Holds one object with this shape per proposal created. This is the shape of the data that is persisted by the plugin, along with information from the chain / graph about the Optimistic Oracle assertion associated with the proposal. + * + * We also include user data such as collateral balance and allowance so that we can determine if they can afford the bond associated with submitting a proposal. + */ +export type ProposalDetails = { + gnosisSafeAddress: string; + livenessPeriod: BigNumber | string; + oracleAddress: string; + rules: string; + collateral: string; + symbol: string; + minimumBond: BigNumber; + expiration: BigNumber; + allowance: BigNumber; + decimals: BigNumber; + userBalance: BigNumber; + needsBondApproval: boolean; + noTransactions: boolean; + activeProposal: boolean; + proposalExecuted: boolean; + assertionEvent: AssertionEvent | undefined; +} + +/** + * Combines the proposal details with the proposal id and explanation. + */ +export type ProposalExecutionDetails = ProposalDetails & { + proposalId: string; + explanation: string; } \ No newline at end of file diff --git a/src/plugins/oSnap/utils/getters.ts b/src/plugins/oSnap/utils/getters.ts index a51f58c9a..1ef3809ac 100644 --- a/src/plugins/oSnap/utils/getters.ts +++ b/src/plugins/oSnap/utils/getters.ts @@ -17,7 +17,7 @@ import { contractData, safePrefixes } from '../constants'; -import { BalanceResponse, NFT, Network, OptimisticGovernorTransaction, SafeNetworkPrefix } from '../types'; +import { AssertionEvent, BalanceResponse, NFT, Network, OptimisticGovernorTransaction, ProposalDetails, ProposalExecutionDetails, SafeNetworkPrefix } from '../types'; import { pageEvents } from './events'; /** @@ -316,9 +316,9 @@ async function getBondDetails(provider: StaticJsonRpcProvider, } /** - * Legacy / fallback function to fetch the details of a given assertion from the Optimistic Oracle V3 contract. + * Legacy / fallback function to fetch the details of a proposal associated with given assertion from the Optimistic Oracle V3 contract. */ -export async function getModuleDetailsFromChain(provider: StaticJsonRpcProvider, +export async function getProposalDetailsFromChain(provider: StaticJsonRpcProvider, network: Network, moduleAddress: string, explanation: string, @@ -382,7 +382,7 @@ export async function getModuleDetailsFromChain(provider: StaticJsonRpcProvider, } else { return { gnosisSafeAddress: moduleDetails[0][0], - oracle: moduleDetails[1][0], + oracleAddress: moduleDetails[1][0], rules: moduleDetails[2][0], expiration: moduleDetails[4][0], minimumBond, @@ -468,7 +468,10 @@ export async function getModuleDetailsFromChain(provider: StaticJsonRpcProvider, // Get the full proposal events (with state). const fullAssertionEvent = await Promise.all( thisModuleAssertionEvent.map(async (event) => { - const assertion = await oracleContract.getAssertion( + const assertion: { + expirationTime: BigNumber; + settled: boolean; + } = await oracleContract.getAssertion( event.args?.assertionId ); @@ -482,7 +485,7 @@ export async function getModuleDetailsFromChain(provider: StaticJsonRpcProvider, proposalHash: proposalHash, proposalTxHash: event.transactionHash, logIndex: event.logIndex - }; + } as AssertionEvent; }) ); @@ -501,7 +504,7 @@ export async function getModuleDetailsFromChain(provider: StaticJsonRpcProvider, return { gnosisSafeAddress: moduleDetails[0][0], - oracle: moduleDetails[1][0], + oracleAddress: moduleDetails[1][0], rules: moduleDetails[2][0], minimumBond: minimumBond, expiration: moduleDetails[4][0], @@ -520,11 +523,11 @@ export async function getModuleDetailsFromChain(provider: StaticJsonRpcProvider, } /** - * This is intended to function identically to getModuleDetailsUma but use subgraphs rather than web3 events. + * This is intended to function identically to getProposalDetailsFromChain but use subgraphs rather than web3 events. * This has a lot of duplicate code on purpose. Reducing code duplication will require a risky refactor, * and we also want a fallback function in case the graph is down, so we will leave the original untouched for now. */ -export async function getModuleDetailsGql(provider: StaticJsonRpcProvider, +export async function getProposalDetailsGql(provider: StaticJsonRpcProvider, network: Network, moduleAddress: string, explanation: string, @@ -636,18 +639,18 @@ export async function getModuleDetailsGql(provider: StaticJsonRpcProvider, } /** - * Fetches the details of a given Optimistic Governor contract deployment from the graph. + * Fetches the details of a given proposal and its associated assertion from the Optimistic Oracle. */ -export async function getModuleDetails( +export async function getProposalDetails( network: Network, moduleAddress: string, explanation = '', transactions?: OptimisticGovernorTransaction[] -) { +): Promise { const provider: StaticJsonRpcProvider = getProvider(network); try { // try optimized calls, which use the graph over web3 event queries - return await getModuleDetailsGql( + return await getProposalDetailsGql( provider, network, moduleAddress, @@ -657,7 +660,7 @@ export async function getModuleDetails( } catch (err) { console.warn('Error querying module details from the graph:', err); // fall back to web3 event queries. - return getModuleDetailsFromChain( + return getProposalDetailsFromChain( provider, network, moduleAddress, @@ -670,14 +673,14 @@ export async function getModuleDetails( /** * Merges the result of getModuleDetails with the proposalId and explanation. */ -export async function getExecutionDetails( +export async function getProposalExecutionDetails( network: Network, moduleAddress: string, proposalId: string, explanation: string, transactions: OptimisticGovernorTransaction[] -) { - const moduleDetails = await getModuleDetails( +): Promise { + const moduleDetails = await getProposalDetails( network, moduleAddress, explanation, diff --git a/src/plugins/oSnap/utils/proposal.ts b/src/plugins/oSnap/utils/proposal.ts index 7b2eff67f..8aed2b3bf 100644 --- a/src/plugins/oSnap/utils/proposal.ts +++ b/src/plugins/oSnap/utils/proposal.ts @@ -1,8 +1,8 @@ import { toUtf8Bytes } from "@ethersproject/strings"; import { sendTransaction } from "@snapshot-labs/snapshot.js/src/utils"; import { ERC20_ABI, OPTIMISTIC_GOVERNOR_ABI } from "../constants"; -import { OptimisticGovernorTransaction, Network } from "../types"; -import { getModuleDetails } from "./getters"; +import { Network, OptimisticGovernorTransaction } from "../types"; +import { getProposalDetails } from "./getters"; /** * The user must approve the spend of the collateral token before they can submit a proposal. @@ -15,7 +15,7 @@ export async function *approveBond( moduleAddress: string, transactions?: OptimisticGovernorTransaction[] ) { - const moduleDetails = await getModuleDetails( + const moduleDetails = await getProposalDetails( network, moduleAddress, network,