Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation fixes #161

Open
wants to merge 41 commits into
base: gerhard/uma-2547-adding-unsupported-treasury-crashes-proposal-page
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
50d82f2
handle empty function names
gsteenkamp89 Apr 10, 2024
bc27e0c
parse and validate array and tuples param inputs
gsteenkamp89 Apr 10, 2024
ece4642
fix for tuple base type
gsteenkamp89 Apr 11, 2024
53767ff
shorter tuple type label
gsteenkamp89 Apr 11, 2024
23961ba
comment
gsteenkamp89 Apr 11, 2024
c58df81
fix bytes32 validation message
gsteenkamp89 Apr 12, 2024
f52de11
implement range checks for integers
gsteenkamp89 Apr 12, 2024
618b0d4
remove duplicate import
gsteenkamp89 Apr 12, 2024
cbeee0d
handle nested array and tuple inputs
gsteenkamp89 Apr 15, 2024
66b74ef
check length
gsteenkamp89 Apr 15, 2024
29275bf
Merge branch 'master' into validation-fixes
gsteenkamp89 Apr 17, 2024
0654a34
Merge branch 'gerhard/uma-2547-adding-unsupported-treasury-crashes-pr…
gsteenkamp89 Apr 19, 2024
b6d0886
fix: prevent unsupported treasuries from crashing proposal page (#4683)
gsteenkamp89 Apr 20, 2024
bcf4331
Update Snapshot packages (#4685)
github-actions[bot] Apr 20, 2024
ac12784
fix: ChainId is wrong while switching the network (#4690)
ChaituVR Apr 22, 2024
48c4219
fix: enable boost on shielded voting proposals (#4668)
wa0x6e Apr 23, 2024
88eba30
fix: only show activation button if plugin installed (#4691)
gsteenkamp89 Apr 24, 2024
45f1e7d
fix: Use correct chainId for sepolia in walletconnect config (#4692)
usame-algan Apr 25, 2024
05f362c
Update Snapshot packages (#4694)
github-actions[bot] Apr 29, 2024
1b090e4
feature: add moonbeam safe to known hosts (#4697)
DenSmolonski Apr 29, 2024
bd50ec1
fix: Privacy label name for any option (#4702)
ChaituVR May 1, 2024
39d3601
Update Snapshot packages (#4705)
github-actions[bot] May 3, 2024
295e578
fix: Not able to claim boost reward (#4710)
ChaituVR May 7, 2024
f6f1487
[create-pull-request] automated change (#4711)
github-actions[bot] May 7, 2024
d0b34fa
fix: Hide walletlink if coinbase is injected (#4713)
ChaituVR May 11, 2024
941e6d8
fix: Coinbase wallet issue while switching accounts in different tab …
ChaituVR May 11, 2024
de39921
[create-pull-request] automated change (#4718)
github-actions[bot] May 13, 2024
5b87159
[create-pull-request] automated change (#4720)
github-actions[bot] May 14, 2024
ffa3c1b
Whitelist alkimiexchange.eth for Boost (#4722)
aurelianoB May 15, 2024
373ff24
fix: Add created_gt filter to aliases query (#4721)
ChaituVR May 16, 2024
adced96
[create-pull-request] automated change (#4723)
github-actions[bot] May 17, 2024
a84c4af
Update Snapshot packages (#4725)
github-actions[bot] May 19, 2024
3ba500d
feat: Boost reward to winning choice (#4724)
ChaituVR May 20, 2024
8c62584
feat: add `network` params to follow/unfollow sign (#4712)
wa0x6e May 20, 2024
059fd57
fix: grammar of boost winning choice (#4731)
aurelianoB May 22, 2024
60e9788
[create-pull-request] automated change (#4732)
github-actions[bot] May 22, 2024
284ca14
[create-pull-request] automated change (#4733)
github-actions[bot] May 24, 2024
ac0d5a3
Update Snapshot packages (#4734)
github-actions[bot] May 24, 2024
0bc4eba
fix: use the updated url for treasury token list (#4736)
wa0x6e May 26, 2024
12a1d24
Merge branch 'master' into validation-fixes
gsteenkamp89 May 28, 2024
5f2dd0a
fix merge error
gsteenkamp89 May 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/plugins/oSnap/components/Input/Address.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const handleBlur = () => {
<UiInput
v-model="input"
:disabled="disabled"
placeholder="0x123...abc"
:error="props.error ?? (error || '')"
@input="handleInput()"
@blur="handleBlur"
Expand Down
199 changes: 118 additions & 81 deletions src/plugins/oSnap/components/Input/MethodParameter.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<script setup lang="ts">
import { ParamType } from '@ethersproject/abi';
import { isAddress } from '@ethersproject/address';
import { isBigNumberish } from '@ethersproject/bignumber/lib/bignumber';
import AddressInput from './Address.vue';
import { hexZeroPad, isBytesLike } from '@ethersproject/bytes';
import { isBytesLikeSafe } from '../../utils';
import { hexZeroPad } from '@ethersproject/bytes';
import {
validateArrayInput,
validateInput,
validateTupleInput,
isBytesLikeSafe
} from '../../utils';
import { InputTypes } from '../../types';

const props = defineProps<{
parameter: ParamType;
Expand All @@ -17,34 +21,93 @@ const emit = defineEmits<{
}>();

const isDirty = ref(false);
const isBooleanInput = computed(() => props.parameter.baseType === 'bool');
const isAddressInput = computed(() => props.parameter.baseType === 'address');
const isNumberInput = computed(() => props.parameter.baseType.includes('int'));
const isBytesInput = computed(() => props.parameter.baseType === 'bytes');
const isBytes32Input = computed(() => props.parameter.baseType === 'bytes32');
const isArrayInput = computed(

const placeholders = {
string: 'a string of text',
address: '0x123...abc',
int: '123',
bytes: '0x123abc',
bytes32: '0x123abc',
bool: 'true'
} as const;

const inputType = computed(() => {
const baseType = props.parameter.baseType;

if (baseType === 'tuple') {
return {
input: 'tuple',
type: props.parameter.components.map(item => item.baseType as InputTypes)
// ["string","int","address"]
} as const;
}

if (baseType === 'array') {
return {
input: 'array',
type: props.parameter.arrayChildren.baseType as InputTypes
} as const;
}

return { type: baseType as InputTypes, input: 'single' } as const;
});

const isBooleanInput = computed(
() => inputType.value.input === 'single' && inputType.value.type === 'bool'
);
const isStringInput = computed(
() => inputType.value.input === 'single' && inputType.value.type === 'string'
);
const isAddressInput = computed(
() => inputType.value.input === 'single' && inputType.value.type === 'address'
);
const isNumberInput = computed(
() =>
props.parameter.baseType === 'array' || props.parameter.baseType === 'tuple'
inputType.value.input === 'single' && inputType.value.type.includes('int')
);
const isBytesInput = computed(
() => inputType.value.input === 'single' && inputType.value.type === 'bytes'
);
const isBytes32Input = computed(
() => inputType.value.input === 'single' && inputType.value.type === 'bytes32'
);
const isArrayInput = computed(() => inputType.value.input !== 'single');

const inputType = computed(() => {
if (isBooleanInput.value) return 'boolean';
if (isAddressInput.value) return 'address';
if (isNumberInput.value) return 'number';
if (isBytesInput.value) return 'bytes';
if (isBytes32Input.value) return 'bytes32';
if (isArrayInput.value) return 'array';
return 'text';
// param name may be null or empty string
const paramName = `${
props.parameter.name?.length ? props.parameter.name + ' ' : ''
}`;
const paramType = computed(() => {
if (inputType.value.input === 'single') {
return `(${inputType.value.type})`;
}
if (inputType.value.input === 'array') {
return `(${inputType.value.type}[])`;
}
// tuple type labels can be too long and take up too much space, limit to 2
return inputType.value.type.length > 2
? `( ${inputType.value.type.slice(0, 2)}...[ ] )`
: inputType.value.type;
});

const label = paramName + paramType.value;
gsteenkamp89 marked this conversation as resolved.
Show resolved Hide resolved

const arrayPlaceholder = computed(() => {
if (inputType.value.input === 'array') {
return `E.g. [${placeholders[inputType.value.type]}]`;
}
if (inputType.value.input === 'tuple') {
return `E.g. [${inputType.value.type.map(type => placeholders[type])}]`;
}
gsteenkamp89 marked this conversation as resolved.
Show resolved Hide resolved
});

const label = `${props.parameter.name} (${props.parameter.type})`;
const arrayPlaceholder = `E.g. ["text", 123, 0x123]`;
const newValue = ref(props.value);

const validationState = ref(true);
const isInputValid = computed(() => validationState.value);

const validationErrorMessage = ref<string>();

const errorMessageForDisplay = computed(() => {
if (!isInputValid.value) {
return validationErrorMessage.value
Expand All @@ -62,12 +125,13 @@ const allowQuickFixForBytes32 = computed(() => {

function validate() {
if (!isDirty.value) return true;
if (isAddressInput.value) return isAddress(newValue.value);
if (isArrayInput.value) return validateArrayInput(newValue.value);
if (isNumberInput.value) return validateNumberInput(newValue.value);
if (isBytes32Input.value) return validateBytes32Input(newValue.value);
if (isBytesInput.value) return validateBytesInput(newValue.value);
return true;
if (inputType.value.input === 'array') {
return validateArrayInput(newValue.value, inputType.value.type);
}
if (inputType.value.input === 'tuple') {
return validateTupleInput(newValue.value, inputType.value.type);
}
return validateInput(newValue.value, inputType.value.type);
}

watch(props.parameter, () => {
Expand All @@ -84,56 +148,27 @@ watch(newValue, () => {
emit('updateParameterValue', newValue.value);
});

function validateNumberInput(value: string) {
return isBigNumberish(value);
}

function validateBytesInput(value: string) {
return isBytesLike(value);
}

// provide better feedback/validation messages for bytes32 inputs
function validateBytes32Input(value: string) {
try {
watch(newValue, value => {
if (isBytes32Input.value && !isArrayInput.value) {
const data = value?.slice(2) || '';

if (data.length < 64) {
const padded = hexZeroPad(value, 32);
if (isBytesLikeSafe(padded)) {
validationErrorMessage.value = 'Value too short';
return false;
validationErrorMessage.value = 'bytes32 too short';
return;
}
}

if (data.length > 64) {
validationErrorMessage.value = 'Value too long';
return false;
}
if (!isBytesLikeSafe(value)) {
validationErrorMessage.value = undefined;
return false;
}
return true;
} catch {
validationErrorMessage.value = undefined;
return false;
}
}

function validateArrayInput(value: string) {
try {
const parsedValue = JSON.parse(value) as Array<string> | unknown;
if (!Array.isArray(parsedValue)) return false;
if (
props.parameter.arrayLength !== -1 &&
parsedValue.length !== props.parameter.arrayLength
)
return false;
return true;
} catch (e) {
return false;
validationErrorMessage.value = undefined;
}
}
});

function onChange(value: string) {
newValue.value = value;
Expand All @@ -145,6 +180,7 @@ function formatBytes32() {
newValue.value = hexZeroPad(newValue.value, 32);
}
}

onMounted(() => {
if (props.validateOnMount) {
isDirty.value = true;
Expand All @@ -154,8 +190,17 @@ onMounted(() => {
</script>

<template>
<UiInput
v-if="isArrayInput"
:placeholder="arrayPlaceholder"
:error="errorMessageForDisplay"
:model-value="value"
@update:model-value="onChange($event)"
>
<template #label>{{ label }}</template>
</UiInput>
<UiSelect
v-if="inputType === 'boolean'"
v-if="isBooleanInput"
:model-value="value"
@update:model-value="onChange($event)"
>
Expand All @@ -165,41 +210,33 @@ onMounted(() => {
</UiSelect>

<AddressInput
v-if="inputType === 'address'"
v-if="isAddressInput"
:label="label"
:model-value="value"
@update:model-value="onChange($event)"
/>

<UiInput
v-if="inputType === 'array'"
:placeholder="arrayPlaceholder"
:error="errorMessageForDisplay"
:model-value="value"
@update:model-value="onChange($event)"
>
<template #label>{{ label }}</template>
</UiInput>
<UiInput
v-if="inputType === 'number'"
placeholder="123456"
v-if="isNumberInput"
:placeholder="placeholders.int"
:error="errorMessageForDisplay"
:model-value="value"
@update:model-value="onChange($event)"
>
<template #label>{{ label }}</template>
</UiInput>
<UiInput
v-if="inputType === 'bytes'"
placeholder="0x123abc"
v-if="isBytesInput"
:placeholder="placeholders.bytes"
:error="errorMessageForDisplay"
:model-value="value"
@update:model-value="onChange($event)"
>
<template #label>{{ label }}</template>
</UiInput>
<UiInput
v-if="inputType === 'bytes32'"
placeholder="0x123abc"
v-if="isBytes32Input"
:placeholder="placeholders.bytes"
:error="errorMessageForDisplay"
:model-value="value"
:quick-fix="allowQuickFixForBytes32 ? formatBytes32 : undefined"
Expand All @@ -209,8 +246,8 @@ onMounted(() => {
<template #label>{{ label }}</template>
</UiInput>
<UiInput
v-if="inputType === 'text'"
placeholder="a string of text"
v-if="isStringInput"
:placeholder="placeholders.string"
:model-value="value"
@update:model-value="onChange($event)"
>
Expand Down
14 changes: 14 additions & 0 deletions src/plugins/oSnap/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,17 @@ export namespace GnosisSafe {
components?: ContractInput[];
}
}

export type InputTypes =
| 'bool'
| 'string'
| 'address'
| Integer
| 'bytes'
| 'bytes32';

export type Integer = `int${number}` | `uint${number}`;

export function isIntegerType(type: InputTypes): type is Integer {
return type.includes('int');
}
Loading
Loading