diff --git a/src/components/ContractTab/FunctionInterface.vue b/src/components/ContractTab/FunctionInterface.vue index ff210301..90b8d000 100644 --- a/src/components/ContractTab/FunctionInterface.vue +++ b/src/components/ContractTab/FunctionInterface.vue @@ -13,22 +13,17 @@ import { EvmABI, EvmFunctionParam } from 'src/antelope/types'; import { WEI_PRECISION } from 'src/antelope/wallets/utils'; import { asyncInputComponents, - getComponentForInputType, - getExpectedArrayLengthFromParameterType, - getIntegerBits, - inputIsComplex, - parameterIsArrayType, - parameterIsIntegerType, - parameterTypeIsBoolean, - parameterTypeIsSignedIntArray, - parameterTypeIsTupleStruct, - parameterTypeIsTupleStructArray, - parameterTypeIsUnsignedIntArray, } from 'src/lib/function-interface-utils'; import TransactionField from 'src/components/TransactionField.vue'; import LoginModal from 'components/LoginModal.vue'; import FunctionOutputViewer from 'components/ContractTab/FunctionOutputViewer.vue'; -import { OutputType, OutputValue, InputDescription } from 'src/types'; +import { + InputComponent, + OutputType, + OutputValue, + inputComponents, +} from 'src/types'; +import { getComponentsForAbiInputs } from 'src/lib/function-interface-utils-ts'; interface Opts { value?: string; @@ -97,8 +92,12 @@ export default defineComponent({ selectDecimals: decimalOptions[0], customDecimals: 0, // number value: '0', // string - inputModels: [] as string[], // raw input values - params: [] as EvmFunctionParam[], // parsed input values + missingInputs: true, // boolean + models: { + inputs: [] as string[], // raw input values + values: [] as EvmFunctionParam[], // parsed input values + }, + valueParam: { 'name': 'value', 'type': 'amount', @@ -121,87 +120,39 @@ export default defineComponent({ functionABI(){ return `${this.abi.name}(${this.abi.inputs.map((i: { type: never; }) => i.type).join(',')})`; }, - inputComponents() { - if (!Array.isArray(this.abi?.inputs)) { - return []; + inputComponents(): inputComponents { + const isRoot = true; + const components = getComponentsForAbiInputs(this.abi?.inputs, this.models, isRoot) as unknown as inputComponents; + return components; + }, + }, + methods: { + valueParsed(inputType: string, index: number, value: EvmFunctionParam, component: InputComponent) { + component.handleValueParsed(inputType, index, value); + this.updateMissingInputs(); + }, + updateMissingInputs() { + const inputs_length = this.abi.inputs.length; + const values_length = this.models.values.length; + if (inputs_length !== values_length) { + this.missingInputs = true; + return true; } - const getExtraBindingsForType = (input: InputDescription, index: number) => { - const { type, name, components } = input; - const label = `${name ? name : `Param ${index + 1}`}`; - const extras = {} as {[key:string]: string | InputDescription[]}; - - // represents integer bits (e.g. uint256) for int types, or array length for array types - let size = undefined; - if (parameterIsArrayType(type)) { - size = getExpectedArrayLengthFromParameterType(type); - } else if (parameterIsIntegerType(type)) { - size = getIntegerBits(type); - } else if (parameterTypeIsTupleStruct(type) && components) { - size = toRaw(components).length; - } - - const result = type.match(/(\d+)(?=\[)/); - const intSize = result ? result[0] : undefined; - - if (intSize && parameterTypeIsUnsignedIntArray(type)) { - extras['uint-size'] = intSize; - } else if (intSize && parameterTypeIsSignedIntArray(type)) { - extras['int-size'] = intSize; - } else if (parameterTypeIsTupleStruct(type) && components) { - extras['componentDescription'] = toRaw(components); - } else if (parameterTypeIsTupleStructArray(type) && components) { - extras['componentDescription'] = toRaw(components); - } - - const defaultModelValue = parameterTypeIsBoolean(type) ? null : ''; - - const bindings = { - ...extras, - label, - size, - modelValue: this.inputModels[index] ?? defaultModelValue, - name: label.toLowerCase(), - }; - return bindings; - }; - - const handleModelValueChange = (type: string, index: number, value: string) => { - this.inputModels[index] = value; - - if (!inputIsComplex(type)) { - this.params[index] = value; - } - }; - const handleValueParsed = (type: string, index: number, value: EvmFunctionParam) => { - if (inputIsComplex(type)) { - this.params[index] = value; - } - }; - - return this.abi.inputs.map((input, index) => ({ - bindings: getExtraBindingsForType(input, index), - is: getComponentForInputType(input.type), - inputType: input.type, - handleModelValueChange: (type: string, index: number, value: string) => handleModelValueChange(type, index, value), - handleValueParsed: (type: string, index: number, value: EvmFunctionParam) => handleValueParsed(type, index, value), - })); - }, - missingInputs() { - if (this.abi.inputs.length !== this.params.length) { + if (inputs_length !== values_length) { + this.missingInputs = true; return true; } for (let i = 0; i < this.abi.inputs.length; i++) { - if (['', null, undefined].includes(this.params[i] as never)) { + if (['', null, undefined].includes(this.models.values[i] as never)) { + this.missingInputs = true; return true; } } - + this.missingInputs = false; return false; }, - }, - methods: { showAmountDialog(param: string) { this.amountParam = param; this.amountDecimals = 18; @@ -217,7 +168,7 @@ export default defineComponent({ if (typeof this.amountParam === 'string') { this.value = integerAmount; } else { - this.params[this.amountParam] = integerAmount as never; + this.models.values[this.amountParam] = integerAmount as never; } this.clearAmount(); @@ -229,7 +180,6 @@ export default defineComponent({ this.showLoginModal = true; }, async run() { - console.log('run'); if (!this.isLoggedIn && this.write){ this.login(); return; @@ -272,7 +222,7 @@ export default defineComponent({ }, runRead() { return this.getEthersFunction() - .then(func => func(...this.params) + .then(func => func(...this.models.values) .then((response: OutputValue | OutputValue[]) => { this.result = response as unknown as string; this.response = Array.isArray(response) ? response : [response]; @@ -289,8 +239,8 @@ export default defineComponent({ const contractInstance = toRaw(await this.contract.getContractInstance()); const func = contractInstance.populateTransaction[this.functionABI]; const gasEstimater = contractInstance.estimateGas[this.functionABI]; - const gasLimit = await gasEstimater(...this.params, Object.assign({ from: this.address }, opts)); - const unsignedTrx = await func(...this.params, opts); + const gasLimit = await gasEstimater(...this.models.values, Object.assign({ from: this.address }, opts)); + const unsignedTrx = await func(...this.models.values, opts); const nonce = parseInt(await this.$evm.telos.getNonce(this.address), 16); const gasPrice = BigNumber.from(`0x${await this.$evm.telos.getGasPrice()}`); unsignedTrx.nonce = nonce; @@ -366,7 +316,7 @@ export default defineComponent({ error, this.contract.address, [this.abi] as EvmABI, - this.params, + this.models.values, value, ).then((result) => { this.hash = result.hash; @@ -449,8 +399,8 @@ export default defineComponent({ :key="index" v-bind="component.bindings" required="true" - class="input-component q-pb-lg" - @valueParsed="component.handleValueParsed(component.inputType, index, $event)" + class="input-component q-pb-md" + @valueParsed="valueParsed(component.inputType, index, $event, component)" @update:modelValue="component.handleModelValueChange(component.inputType, index, $event)" /> @@ -482,7 +432,7 @@ export default defineComponent({ - diff --git a/src/lib/function-interface-utils-ts.ts b/src/lib/function-interface-utils-ts.ts new file mode 100644 index 00000000..3c8af8b5 --- /dev/null +++ b/src/lib/function-interface-utils-ts.ts @@ -0,0 +1,83 @@ +import { InputComponent, inputComponents, InputDescription } from 'src/types'; +import { getComponentForInputType, getExpectedArrayLengthFromParameterType, getIntegerBits, inputIsComplex, parameterIsArrayType, parameterIsIntegerType, parameterTypeIsBoolean, parameterTypeIsSignedIntArray, parameterTypeIsTupleStruct, parameterTypeIsTupleStructArray, parameterTypeIsUnsignedIntArray } from 'src/lib/function-interface-utils'; +import { toRaw } from 'vue'; +import { EvmFunctionParam } from 'src/antelope/types'; + +export function getComponentsForAbiInputs(inputs: InputDescription[], models: {inputs: string[], values: EvmFunctionParam[]}, isRoot: boolean): inputComponents { + // inputs must be an array + if (!Array.isArray(inputs)) { + return []; + } + // models must be an object with a an array of values for each input (user input text, always strings) + if ( + typeof models !== 'object' || + !Array.isArray(models.values) + ) { + return []; + } + + const getExtraBindingsForType = (input: InputDescription, index: number) => { + const { type, name, components } = input; + const label = `${name ? name : `Param ${index + 1}`}`; + const extras = {} as {[key:string]: string | InputDescription[]}; + + // represents integer bits (e.g. uint256) for int types, or array length for array types + let size = undefined; + if (parameterIsArrayType(type)) { + size = getExpectedArrayLengthFromParameterType(type); + } else if (parameterIsIntegerType(type)) { + size = getIntegerBits(type); + } else if (parameterTypeIsTupleStruct(type) && components) { + size = toRaw(components).length; + } + + const result = type.match(/(\d+)(?=\[)/); + const intSize = result ? result[0] : undefined; + + if (intSize && parameterTypeIsUnsignedIntArray(type)) { + extras['uint-size'] = intSize; + } else if (intSize && parameterTypeIsSignedIntArray(type)) { + extras['int-size'] = intSize; + } else if (parameterTypeIsTupleStruct(type) && components) { + extras['componentDescription'] = toRaw(components); + } else if (parameterTypeIsTupleStructArray(type) && components) { + extras['componentDescription'] = toRaw(components); + } + + const defaultModelValue = parameterTypeIsBoolean(type) ? null : ''; + + const bindings = { + ...extras, + isRoot, + label, + size, + modelValue: models.inputs[index] ?? defaultModelValue, + name: label.toLowerCase(), + }; + return bindings; + }; + + const handleModelValueChange = (type: string, index: number, value: string) => { + models.inputs[index] = value; + + if (!inputIsComplex(type)) { + models.values[index] = value; + } + }; + const handleValueParsed = (type: string, index: number, value: EvmFunctionParam) => { + if (inputIsComplex(type)) { + models.values[index] = value; + } + }; + + return inputs.map((input, index) => ({ + bindings: getExtraBindingsForType(input, index), + is: getComponentForInputType(input.type), + inputType: input.type, + handleModelValueChange: (type: string, index: number, value: string) => handleModelValueChange(type, index, value), + handleValueParsed: (type: string, index: number, value: EvmFunctionParam) => handleValueParsed(type, index, value), + }) as unknown as InputComponent); + +} + + diff --git a/src/lib/function-interface-utils.js b/src/lib/function-interface-utils.js index c14e8e71..11613c9e 100644 --- a/src/lib/function-interface-utils.js +++ b/src/lib/function-interface-utils.js @@ -13,7 +13,7 @@ const asyncInputComponents = { UnsignedIntArrayInput: defineAsyncComponent(() => import('components/inputs/UnsignedIntArrayInput')), UnsignedIntInput: defineAsyncComponent(() => import('components/inputs/UnsignedIntInput')), SignedIntArrayInput: defineAsyncComponent(() => import('components/inputs/SignedIntArrayInput')), - TupleStructInput: defineAsyncComponent(() => import('components/inputs/TupleStructInput')), + TupleStructInput: defineAsyncComponent(() => import('components/inputs/TupleStruct')), TupleStructArrayInput: defineAsyncComponent(() => import('components/inputs/TupleStructArrayInput')), }; diff --git a/src/types/FunctionInterfaces.ts b/src/types/FunctionInterfaces.ts new file mode 100644 index 00000000..91bd6ca5 --- /dev/null +++ b/src/types/FunctionInterfaces.ts @@ -0,0 +1,14 @@ +import { EvmFunctionParam } from 'src/antelope/types'; +import { InputDescription } from 'src/types/AbiFunction'; + +export interface InputComponent { + bindings: { + [key: string]: string | InputDescription[]; + }; + is: string; + inputType: string; + handleModelValueChange: (type: string, index: number, value: string) => void; + handleValueParsed: (type: string, index: number, value: EvmFunctionParam) => void; +} + +export type inputComponents = InputComponent[]; diff --git a/src/types/index.ts b/src/types/index.ts index d9d2151b..30d85ff4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -8,3 +8,5 @@ export * from 'src/types/SearchTypes'; export * from 'src/types/TransactionQueryData'; export * from 'src/types/AbiFunction'; export * from 'src/types/Token'; +export * from 'src/types/FunctionInterfaces'; +