diff --git a/src/plugins/oSnap/components/Input/Address.vue b/src/plugins/oSnap/components/Input/Address.vue index eddd5636..02feec9f 100644 --- a/src/plugins/oSnap/components/Input/Address.vue +++ b/src/plugins/oSnap/components/Input/Address.vue @@ -62,6 +62,7 @@ const handleBlur = () => { 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 { + InputTypes, + validateArrayInput, + validateInput, + validateTupleInput +} from '../../utils'; const props = defineProps<{ parameter: ParamType; @@ -17,37 +20,95 @@ 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( - () => - props.parameter.baseType === 'array' || props.parameter.baseType === 'tuple' -); + +const placeholders = { + string: 'a string of text', + address: '0x123...abc', + int: '123456', + bytes: '0x123abc', + bytes32: '0x123abc', + bool: 'true' +} as const; + +function reduceInt(type: string) { + if (type.includes('int')) { + return 'int'; + } + return type; +} 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'; + const baseType = props.parameter.baseType; + + if (baseType === 'array') { + if (props.parameter.type.includes('tuple')) { + return { + input: 'tuple', + type: props.parameter.components.map( + item => reduceInt(item.baseType) as InputTypes + ) + // ["string","int","address"] + } as const; + } else { + return { + input: 'array', + type: reduceInt(props.parameter.arrayChildren.baseType) as InputTypes + } as const; + } + } + + return { type: reduceInt(baseType) as InputTypes, input: 'single' } as const; }); -// function name may be null or empty string -const label = `${ +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( + () => inputType.value.input === 'single' && inputType.value.type === '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'); + +// param name may be null or empty string +const paramName = `${ props.parameter.name?.length ? props.parameter.name + ' ' : '' -}(${props.parameter.type})`; -const arrayPlaceholder = `E.g. ["text", 123, 0x123]`; +}`; +const paramType = computed(() => { + if (inputType.value.input === 'single') { + return `(${inputType.value.type})`; + } + return `( ${inputType.value.type}[ ] )`; +}); + +const label = paramName + paramType.value; + +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])}]`; + } +}); + const newValue = ref(props.value); const validationState = ref(true); const isInputValid = computed(() => validationState.value); const validationErrorMessage = ref(); + const errorMessageForDisplay = computed(() => { if (!isInputValid.value) { return validationErrorMessage.value @@ -65,12 +126,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, () => { @@ -87,56 +149,22 @@ 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; + validationErrorMessage.value = 'bytes32 too long'; + return; } - return true; - } catch { - validationErrorMessage.value = undefined; - return false; - } -} - -function validateArrayInput(value: string) { - try { - const parsedValue = JSON.parse(value) as Array | 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 = ''; } -} +}); function onChange(value: string) { newValue.value = value; @@ -148,6 +176,7 @@ function formatBytes32() { newValue.value = hexZeroPad(newValue.value, 32); } } + onMounted(() => { if (props.validateOnMount) { isDirty.value = true; @@ -157,8 +186,17 @@ onMounted(() => {