From 059ef63c0e4b9eafc326e90d6a70a5348cde2e59 Mon Sep 17 00:00:00 2001 From: Viterbo Date: Wed, 14 Aug 2024 14:50:06 -0300 Subject: [PATCH 1/6] saving wip --- .../ContractTab/FunctionInterface.vue | 29 ++- .../ContractTab/GenericContractInterface.vue | 1 + src/components/inputs/TupleStructInput.vue | 209 ++++++++++++++++++ src/i18n/de-de/index.js | 3 + src/i18n/en-us/index.js | 3 + src/i18n/es-es/index.js | 3 + src/i18n/fr-fr/index.js | 3 + src/i18n/pt-br/index.js | 3 + src/lib/function-interface-utils.js | 71 ++++++ src/types/AbiFunction.ts | 7 + src/types/index.ts | 1 + 11 files changed, 329 insertions(+), 4 deletions(-) create mode 100644 src/components/inputs/TupleStructInput.vue diff --git a/src/components/ContractTab/FunctionInterface.vue b/src/components/ContractTab/FunctionInterface.vue index 5b6d53372..e59f829ea 100644 --- a/src/components/ContractTab/FunctionInterface.vue +++ b/src/components/ContractTab/FunctionInterface.vue @@ -21,12 +21,13 @@ import { parameterIsIntegerType, parameterTypeIsBoolean, parameterTypeIsSignedIntArray, + parameterTypeIsTupleStruct, 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 } from 'src/types'; +import { OutputType, OutputValue, InputDescription } from 'src/types'; interface Opts { value?: string; @@ -124,9 +125,10 @@ export default defineComponent({ return []; } - const getExtraBindingsForType = ({ type, name }: {type: string, name: string}, index: number) => { + const getExtraBindingsForType = (input: InputDescription, index: number) => { + const { type, name, components } = input; const label = `${name ? name : `Param ${index + 1}`}`; - const extras = {} as {[key:string]: string}; + 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; @@ -134,6 +136,8 @@ export default defineComponent({ size = getExpectedArrayLengthFromParameterType(type); } else if (parameterIsIntegerType(type)) { size = getIntegerBits(type); + } else if (parameterTypeIsTupleStruct(type) && components) { + size = toRaw(components).length; } const result = type.match(/(\d+)(?=\[)/); @@ -143,17 +147,21 @@ export default defineComponent({ extras['uint-size'] = intSize; } else if (intSize && parameterTypeIsSignedIntArray(type)) { extras['int-size'] = intSize; + } else if (parameterTypeIsTupleStruct(type) && components) { + extras['componentDescription'] = toRaw(components); } const defaultModelValue = parameterTypeIsBoolean(type) ? null : ''; - return { + const bindings = { ...extras, label, size, modelValue: this.inputModels[index] ?? defaultModelValue, name: label.toLowerCase(), }; + console.log('getExtraBindingsForType()', bindings); + return bindings; }; const handleModelValueChange = (type: string, index: number, value: string) => { @@ -192,6 +200,13 @@ export default defineComponent({ }, }, methods: { + debug() { + // parameters: + console.log('this.abi', this.abi); + console.log('this.contract', this.contract); + console.log('this.runLabel', this.runLabel); + console.log('this.write', this.write); + }, showAmountDialog(param: string) { this.amountParam = param; this.amountDecimals = 18; @@ -452,6 +467,12 @@ export default defineComponent({ icon="send" @click="run" /> +

{{ errorMessage }}

diff --git a/src/components/ContractTab/GenericContractInterface.vue b/src/components/ContractTab/GenericContractInterface.vue index 8e010078a..b8b10c80d 100644 --- a/src/components/ContractTab/GenericContractInterface.vue +++ b/src/components/ContractTab/GenericContractInterface.vue @@ -385,5 +385,6 @@ const formatAbiFunctionLists = async () => { } .c-login-button { margin-bottom: 0.5rem; + margin-left: 0.5rem; } diff --git a/src/components/inputs/TupleStructInput.vue b/src/components/inputs/TupleStructInput.vue new file mode 100644 index 000000000..1dbc7a3a1 --- /dev/null +++ b/src/components/inputs/TupleStructInput.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/src/i18n/de-de/index.js b/src/i18n/de-de/index.js index e57ed29a2..fb81c0d51 100644 --- a/src/i18n/de-de/index.js +++ b/src/i18n/de-de/index.js @@ -350,6 +350,9 @@ export default { incorrect_sigint_array_length: 'Das Array muss { size } signed Integers enthalten', incorrect_strings_array_length: 'Das Array sollte nur { size } Zeichenfolgen enthalten', incorrect_unsigint_array_length: 'Das Array muss { size } unsigned Integers enthalten', + incorrect_values_array_length: 'Es müssen { size } Werte im Array vorhanden sein', + tuple_struct_input_hint: 'Geben Sie die Tupelwerte in eckigen Klammern in der im Vertrag definierten Reihenfolge ein', + invalid_boolean_value: 'Der eingegebene Wert ist kein boolescher Wert', invalid_address_array_string: 'Eingegebener Wert repräsentiert kein Array von Adressen', invalid_booleans_array_string: 'Der eingegebene Wert entspricht keinem Array von bool(s)', diff --git a/src/i18n/en-us/index.js b/src/i18n/en-us/index.js index 09bb76b88..7a039e15f 100644 --- a/src/i18n/en-us/index.js +++ b/src/i18n/en-us/index.js @@ -364,6 +364,9 @@ export default { incorrect_sigint_array_length: 'There should be { size } signed integers in the array', incorrect_strings_array_length: 'There should be { size } strings in the array', incorrect_unsigint_array_length: 'There should be { size } unsigned integers in the array', + incorrect_values_array_length: 'There must be { size } values in the array', + tuple_struct_input_hint: 'Enter the tuple values in square brackets in the order defined in the contract', + invalid_boolean_value: 'The entered value is not a boolean', invalid_address_array_string: 'Entered value does not represent an array of addresses', invalid_booleans_array_string: 'Entered value does not represent an array of bool', diff --git a/src/i18n/es-es/index.js b/src/i18n/es-es/index.js index 1b2f52ae6..7cfd3b189 100644 --- a/src/i18n/es-es/index.js +++ b/src/i18n/es-es/index.js @@ -351,6 +351,9 @@ export default { incorrect_sigint_array_length: 'Debe haber { size } enteros firmados en la matriz', incorrect_strings_array_length: 'Debe haber { size } cadenas en la matriz', incorrect_unsigint_array_length: 'Debe haber { size } enteros sin firmar en la matriz', + incorrect_values_array_length: 'Debe haber { size } valores en la matriz', + tuple_struct_input_hint: 'Ingrese los valores de la tupla entre paréntesis rectos en el orden en que se definen en el contrato', + invalid_boolean_value: 'El valor ingresado no es un booleano', invalid_address_array_string: 'El valor ingresado no representa una matriz de direcciones', invalid_booleans_array_string: 'El valor ingresado no representa una matriz de bool', diff --git a/src/i18n/fr-fr/index.js b/src/i18n/fr-fr/index.js index dfd1d2b2d..c16e4e69e 100644 --- a/src/i18n/fr-fr/index.js +++ b/src/i18n/fr-fr/index.js @@ -323,6 +323,9 @@ export default { incorrect_sigint_array_length: 'Le tableau doit contenir { size } entiers signés', incorrect_strings_array_length: 'Le tableau doit contenir { size } chaînes de caractères', incorrect_unsigint_array_length: 'Le tableau doit contenir { size } entiers non signés', + incorrect_values_array_length: 'Il doit y avoir { size } valeurs dans le tableau', + tuple_struct_input_hint: 'Entrez les valeurs de la structure de tuple entre crochets dans l\'ordre défini dans le contrat', + invalid_boolean_value: 'La valeur saisie n\'est pas un booléen', invalid_address_array_string: 'La valeur saisie ne represente pas un tableau d\'adresses', invalid_booleans_array_string: 'La valeur saisie ne represente pas un tableau de booléens', diff --git a/src/i18n/pt-br/index.js b/src/i18n/pt-br/index.js index feb67e3fd..c67c04af5 100644 --- a/src/i18n/pt-br/index.js +++ b/src/i18n/pt-br/index.js @@ -350,6 +350,9 @@ export default { incorrect_sigint_array_length: 'Deve haver { size } inteiros com sinal na matriz', incorrect_strings_array_length: 'Deve haver { size } strings (sequência de caracteres) na matriz', incorrect_unsigint_array_length: 'Deve haver { size } inteiros sem sinal na matriz', + incorrect_values_array_length: 'Devem haver { size } valores na matriz', + tuple_struct_input_hint: 'Insira os valores da tupla entre colchetes na ordem definida no contrato', + invalid_boolean_value: 'O valor inserido não é um booleano', invalid_address_array_string: 'O valor inserido não representa uma matriz de endereços', invalid_booleans_array_string: 'O valor inserido não representa uma matriz de booleanos', diff --git a/src/lib/function-interface-utils.js b/src/lib/function-interface-utils.js index 783b6d4f2..e5de5f959 100644 --- a/src/lib/function-interface-utils.js +++ b/src/lib/function-interface-utils.js @@ -13,6 +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')), }; /** @@ -114,6 +115,15 @@ function parameterTypeIsUnsignedIntArray(type) { return /^uint\d+\[\d*]$/.test(type); } +/** + * Given a function interface type, returns true iff that type represents a tuple struct + * @param {string} type + * @returns {boolean} + */ + +function parameterTypeIsTupleStruct(type) { + return type === 'tuple'; +} /** @@ -208,6 +218,8 @@ function getComponentForInputType(type) { return asyncInputComponents.UnsignedIntInput; } else if (parameterTypeIsUnsignedIntArray(type)) { return asyncInputComponents.UnsignedIntArrayInput; + } else if (parameterTypeIsTupleStruct(type)) { + return asyncInputComponents.TupleStructInput; } return undefined; @@ -570,6 +582,63 @@ function parseStringArrayString(str, expectedLength) { return parsedArrayOfStrings; } +/** + * Given a string, returns a string iff it is a valid JSON representation of a tuple struct (without checking the types) + * + * @param str - JSON string representation of a tuple struct, e.g. '["abc", 23, 0x1234..12342]' + * @returns {string|undefined} + */ +function parseTupleString(str, expectedLength) { + let parsedTuple; + + try { + parsedTuple = JSON.parse(str); + console.log('parseTupleString()', { parsedTuple }); // FIXME: remove + } catch { + // may be addresses in the tuple that are not quoted, therefore not valid JSON + // e.g. ["0x1234", 0x1234] + // we will try to identify the those cases, replace them with quoted addresses and try again + console.log('parseTupleString() failed first try'); // FIXME: remove + const notQuotedAddressRegex = /(? { + const quotedAddress = `"${_address}"`; + _str = str.replaceAll(_address, quotedAddress); + }); + + try { + parsedTuple = JSON.parse(_str); + return parsedTuple; + } catch { + return undefined; + } + } + + return undefined; + } + + const valueIsTuple = Array.isArray(parsedTuple); + // TODO: validate tuple structure + console.log('parseTupleString()', { valueIsTuple }); // FIXME: remove + + if (!valueIsTuple) { + return undefined; + } + + if (Number.isInteger(expectedLength)) { + const actualLength = parsedTuple.length; + console.log('parseTupleString()', { actualLength, expectedLength }); // FIXME: remove + if (actualLength !== expectedLength) { + return undefined; + } + } + + return parsedTuple; +} + export { parameterIsArrayType, @@ -592,6 +661,7 @@ export { parameterTypeIsStringArray, parameterTypeIsUnsignedInt, parameterTypeIsUnsignedIntArray, + parameterTypeIsTupleStruct, parseAddressArrayString, parseAddressString, @@ -602,4 +672,5 @@ export { parseStringArrayString, parseUintArrayString, parseUintString, + parseTupleString, }; diff --git a/src/types/AbiFunction.ts b/src/types/AbiFunction.ts index c01771b4a..7c0460921 100644 --- a/src/types/AbiFunction.ts +++ b/src/types/AbiFunction.ts @@ -3,3 +3,10 @@ export interface AbiFunction { stateMutability: string; name: string; } + +export interface InputDescription { + type: string; + name: string; + internalType: string; + components?: InputDescription[]; +} diff --git a/src/types/index.ts b/src/types/index.ts index 7f4673b46..7a77bbd29 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -5,3 +5,4 @@ export * from 'src/types/Pagination'; export * from 'src/types/ERCTransfer'; export * from 'src/types/NftTransfers'; export * from 'src/types/TransactionQueryData'; +export * from 'src/types/AbiFunction'; From ba76a94a896a1e6884d643591ca1a772e3f66a58 Mon Sep 17 00:00:00 2001 From: Viterbo Date: Fri, 6 Sep 2024 10:25:40 -0300 Subject: [PATCH 2/6] now we asume contracts are always in cache, delegating all to API --- src/lib/contract/ContractManager.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/contract/ContractManager.js b/src/lib/contract/ContractManager.js index 54dfadc89..79f70c8b6 100644 --- a/src/lib/contract/ContractManager.js +++ b/src/lib/contract/ContractManager.js @@ -387,10 +387,8 @@ export default class ContractManager { } else if (this.nullContractsManager.existsContract(addressLower)) { result = this.nullContractsManager.getContractInfo(addressLower); } else { - result = await this.getContract(addressLower); - if (result) { - result = this.nullContractsManager.getContractInfo(addressLower); - } + // We are going to always assume that if the address is a contract, it is already in the cache + // Because the indexer API should always return all involved contracts in a query response } return result; } From a9ec9d2801fd2f76da4d01c4eb8f3a9b2c0798a0 Mon Sep 17 00:00:00 2001 From: Viterbo Date: Sun, 22 Sep 2024 18:18:23 -0300 Subject: [PATCH 3/6] The address page now shows feedback immediately --- src/components/AddressMoreInfo.vue | 12 +++--- src/components/AddressOverview.vue | 67 ++++++++++++++--------------- src/components/ContractMoreInfo.vue | 22 +++++----- src/pages/AccountPage.vue | 14 +++++- 4 files changed, 62 insertions(+), 53 deletions(-) diff --git a/src/components/AddressMoreInfo.vue b/src/components/AddressMoreInfo.vue index eb2f4f16e..a3d42d223 100644 --- a/src/components/AddressMoreInfo.vue +++ b/src/components/AddressMoreInfo.vue @@ -38,40 +38,40 @@ onBeforeMount(async () => {