From eaaa41886f1717767f3a652408b9a1956ca3806e Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 2 Feb 2024 18:12:00 +0200 Subject: [PATCH 01/29] disable publish if osnap tx invalid --- src/views/SpaceCreate.vue | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/views/SpaceCreate.vue b/src/views/SpaceCreate.vue index 6e0033519..eb9f1284b 100644 --- a/src/views/SpaceCreate.vue +++ b/src/views/SpaceCreate.vue @@ -6,6 +6,7 @@ import Plugin from '@/plugins/safeSnap'; import { getInstance } from '@snapshot-labs/lock/plugins/vue3'; import { clone } from '@snapshot-labs/snapshot.js/src/utils'; import proposalSchema from '@snapshot-labs/snapshot.js/src/schemas/proposal.json'; +import { validateTransaction } from '@/plugins/oSnap/utils'; const safeSnapPlugin = new Plugin(); @@ -18,6 +19,7 @@ enum Step { const props = defineProps<{ space: ExtendedSpace; }>(); + const spaceType = computed(() => (props.space.turbo ? 'turbo' : 'default')); const bodyCharactersLimit = computed( () => @@ -53,6 +55,7 @@ const { isGnosisAndNotSpaceNetwork } = useGnosis(props.space); const { isSnapshotLoading } = useSnapshot(); const { apolloQuery, queryLoading } = useApolloQuery(); const { containsShortUrl } = useShortUrls(); + const { isValid: isValidSpaceSettings, populateForm } = useFormSpaceSettings( 'settings', { @@ -137,7 +140,16 @@ const isFormValid = computed(() => { ? form.value.metadata.plugins.safeSnap.valid : true; + const isOsnapPluginValid = form.value.metadata.plugins?.oSnap?.safe + ?.transactions + ? form.value.metadata.plugins.oSnap.safe.transactions.every( + validateTransaction + ) + : true; + return ( + !web3.value.authLoading && + isOsnapPluginValid && !isSending.value && form.value.body.length <= bodyCharactersLimit.value && dateEnd.value && @@ -146,8 +158,7 @@ const isFormValid = computed(() => { form.value.choices.length >= 1 && !form.value.choices.some((a, i) => a.text === '' && i === 0) && isValidAuthor.value && - isSafeSnapPluginValid && - !web3.value.authLoading + isSafeSnapPluginValid ); }); @@ -379,6 +390,7 @@ function toggleShouldUseOsnap() { const legacyOsnap = ref<{ enabled: boolean; selection: boolean; + valid: boolean; }>({ selection: false, enabled: false, From 517349af41121a13602eb082ad104bfbf7249647 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 2 Feb 2024 18:14:05 +0200 Subject: [PATCH 02/29] set state upstream even if invalid, better input logic --- src/components/Ui/UiInput.vue | 4 +- src/composables/useFormSpaceProposal.ts | 2 + src/plugins/oSnap/Create.vue | 40 ++++++++++++++++--- src/plugins/oSnap/components/Input/Amount.vue | 15 +++++-- .../TransactionBuilder/TransferFunds.vue | 9 ++--- src/plugins/oSnap/types.ts | 1 + src/plugins/oSnap/utils/validators.ts | 10 +++-- 7 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/components/Ui/UiInput.vue b/src/components/Ui/UiInput.vue index cdd99e7bd..538f3ffc7 100644 --- a/src/components/Ui/UiInput.vue +++ b/src/components/Ui/UiInput.vue @@ -13,8 +13,8 @@ const props = defineProps<{ const emit = defineEmits(['update:modelValue', 'blur']); -function handleInput(e) { - const input = e.target.value; +function handleInput(e: Event) { + const input = (e.target as HTMLInputElement).value; if (props.number) { return emit('update:modelValue', !input ? undefined : parseFloat(input)); } diff --git a/src/composables/useFormSpaceProposal.ts b/src/composables/useFormSpaceProposal.ts index 997682561..a4b7732bd 100644 --- a/src/composables/useFormSpaceProposal.ts +++ b/src/composables/useFormSpaceProposal.ts @@ -2,6 +2,7 @@ import { useStorage } from '@vueuse/core'; import { clone } from '@snapshot-labs/snapshot.js/src/utils'; import schemas from '@snapshot-labs/snapshot.js/src/schemas'; import { validateForm } from '@/helpers/validation'; +import { OsnapPluginData } from '@/plugins/oSnap/types'; interface ProposalForm { name: string; @@ -15,6 +16,7 @@ interface ProposalForm { metadata: { plugins: { safeSnap?: { valid: boolean }; + oSnap?: OsnapPluginData; }; }; } diff --git a/src/plugins/oSnap/Create.vue b/src/plugins/oSnap/Create.vue index 9a9e073e7..88c2c145d 100644 --- a/src/plugins/oSnap/Create.vue +++ b/src/plugins/oSnap/Create.vue @@ -17,7 +17,8 @@ import { getGnosisSafeBalances, getGnosisSafeCollectibles, getIsOsnapEnabled, - getModuleAddressForTreasury + getModuleAddressForTreasury, + validateTransaction } from './utils'; import OsnapMarketingWidget from './components/OsnapMarketingWidget.vue'; @@ -43,22 +44,30 @@ const safes = ref([]); const tokens = ref([]); const collectables = ref([]); +const transactionsValid = ref(false); + function addTransaction(transaction: Transaction) { if (newPluginData.value.safe === null) return; - newPluginData.value.safe.transactions.push(transaction); + newPluginData.value.safe.transactions.push({ + ...transaction, + isValid: validateTransaction(transaction) + }); update(newPluginData.value); } function removeTransaction(transactionIndex: number) { if (!newPluginData.value.safe) return; - newPluginData.value.safe.transactions.splice(transactionIndex, 1); update(newPluginData.value); } function updateTransaction(transaction: Transaction, transactionIndex: number) { if (!newPluginData.value.safe) return; - newPluginData.value.safe.transactions[transactionIndex] = transaction; + newPluginData.value.safe.transactions[transactionIndex] = { + ...transaction, + isValid: validateTransaction(transaction) + }; + update(newPluginData.value); } @@ -76,7 +85,6 @@ async function fetchBalances(network: Network, safeAddress: string) { if (!safeAddress) { return []; } - try { const balances = await getGnosisSafeBalances(network, safeAddress); @@ -226,6 +234,25 @@ onMounted(async () => { update(newPluginData.value); isLoading.value = false; }); + +watch(newPluginData, () => { + // validate form here, set isValid accordingly upstream + if (!newPluginData.value.safe) { + return; + } + // can't publish without transactions + if (newPluginData.value.safe.transactions.length === 0) { + transactionsValid.value = false; + return; + } + // check ALL transactions + if (newPluginData.value.safe.transactions.every(validateTransaction)) { + transactionsValid.value = false; + return; + } + // default to invalid + transactionsValid.value = false; +}); diff --git a/src/plugins/oSnap/components/Input/Amount.vue b/src/plugins/oSnap/components/Input/Amount.vue index 0a841a5c1..386aee425 100644 --- a/src/plugins/oSnap/components/Input/Amount.vue +++ b/src/plugins/oSnap/components/Input/Amount.vue @@ -16,17 +16,19 @@ const dirty = ref(false); const format = (amount: string) => { try { - return parseUnits(amount, props.decimals).toString(); + // empty string throws + const parsed = parseUnits(amount, props.decimals).toString(); + return parsed; } catch (error) { - return undefined; + return parseUnits('0', props.decimals).toString(); } }; const handleInput = () => { dirty.value = true; const value = format(input.value); - isValid.value = !!value; - emit('update:modelValue', value ?? ''); + // empty string value will throw error being converted to BigNumber + emit('update:modelValue', value ?? '0'); }; onMounted(() => { @@ -34,6 +36,11 @@ onMounted(() => { input.value = formatUnits(props.modelValue, props.decimals); } }); + +watch(input, () => { + const value = format(input.value); + isValid.value = !!value && parseInt(value) > 0; +});