diff --git a/configs/envs/.env.eth_sepolia b/configs/envs/.env.eth_sepolia index 8e55fec296..2ac025a5e8 100644 --- a/configs/envs/.env.eth_sepolia +++ b/configs/envs/.env.eth_sepolia @@ -72,4 +72,5 @@ NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address \ No newline at end of file +NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address +NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS=['solidity','vyper','yul','geas'] \ No newline at end of file diff --git a/docs/ENVS.md b/docs/ENVS.md index c2c2012a58..8c1ac9f272 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -253,7 +253,7 @@ Settings for meta tags, OG tags and SEO | NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS | `Array` | Address views that should not be displayed. See below the list of the possible id values. | - | - | `'["top_accounts"]'` | v1.15.0+ | | NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED | `boolean` | Set to `true` if SolidityScan reports are supported | - | - | `true` | v1.19.0+ | | NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS | `Array<'solidity-hardhat' \| 'solidity-foundry'>` | Pass an array of additional methods from which users can choose while verifying a smart contract. Both methods are available by default, pass `'none'` string to disable them all. | - | - | `['solidity-hardhat']` | v1.33.0+ | -| NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS | `Array<'solidity' \| 'vyper' \| 'yul' \| 'scilla'>` | Pass an array of contract languages that will be displayed as options in the filter on the verified contract page. | - | `['solidity','vyper','yul']` | `['solidity','vyper','yul','scilla']` | v1.37.0+ | +| NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS | `Array<'solidity' \| 'vyper' \| 'yul' \| 'scilla' \| 'geas'>` | Pass an array of contract languages that will be displayed as options in the filter on the verified contract page. | - | `['solidity','vyper','yul']` | `['solidity','vyper','yul','scilla']` | v1.37.0+ | ##### Address views list | Id | Description | diff --git a/mocks/contract/methods.ts b/mocks/contract/methods.ts index 5fa714921c..38fbc6f9f9 100644 --- a/mocks/contract/methods.ts +++ b/mocks/contract/methods.ts @@ -34,6 +34,12 @@ export const write: Array = [ payable: true, stateMutability: 'payable', type: 'fallback', + inputs: [ + { internalType: 'bytes', name: 'input', type: 'bytes' }, + ], + outputs: [ + { internalType: 'bytes', name: 'output', type: 'bytes' }, + ], }, { constant: false, diff --git a/types/api/contracts.ts b/types/api/contracts.ts index b61c11271f..8e9ebb3838 100644 --- a/types/api/contracts.ts +++ b/types/api/contracts.ts @@ -1,12 +1,14 @@ import type { AddressParam } from './addressParams'; import type { SmartContractLicenseType } from './contract'; +export type VerifiedContractsLanguage = 'solidity' | 'vyper' | 'yul' | 'scilla' | 'stylus_rust' | 'geas'; + export interface VerifiedContract { address: AddressParam; certified?: boolean; coin_balance: string; compiler_version: string | null; - language: 'vyper' | 'yul' | 'solidity' | 'stylus_rust'; + language: VerifiedContractsLanguage; has_constructor_args: boolean; optimization_enabled: boolean; transactions_count: number | null; @@ -24,7 +26,7 @@ export interface VerifiedContractsResponse { } | null; } -export type VerifiedContractsFilter = 'solidity' | 'vyper' | 'yul' | 'scilla'; +export type VerifiedContractsFilter = Exclude; export interface VerifiedContractsFilters { q: string | undefined; diff --git a/types/client/contract.ts b/types/client/contract.ts index 95aed6816a..891b70acad 100644 --- a/types/client/contract.ts +++ b/types/client/contract.ts @@ -32,4 +32,5 @@ export const SMART_CONTRACT_LANGUAGE_FILTERS: Array = [ 'vyper', 'yul', 'scilla', + 'geas', ]; diff --git a/ui/address/AddressContract.pw.tsx b/ui/address/AddressContract.pw.tsx index 965a3dadcf..6d764c9c9e 100644 --- a/ui/address/AddressContract.pw.tsx +++ b/ui/address/AddressContract.pw.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type { Abi } from 'viem'; import * as addressMock from 'mocks/address/address'; import * as contractInfoMock from 'mocks/contract/info'; @@ -15,7 +16,7 @@ test.beforeEach(async({ mockApiResponse }) => { await mockApiResponse('general:address', addressMock.contract, { pathParams: { hash } }); await mockApiResponse( 'general:contract', - { ...contractInfoMock.verified, abi: [ ...contractMethodsMock.read, ...contractMethodsMock.write ] }, + { ...contractInfoMock.verified, abi: [ ...contractMethodsMock.read, ...contractMethodsMock.write ] as Abi }, { pathParams: { hash } }, ); }); diff --git a/ui/address/contract/ContractSourceCode.tsx b/ui/address/contract/ContractSourceCode.tsx index f1b00e16ec..0191e32954 100644 --- a/ui/address/contract/ContractSourceCode.tsx +++ b/ui/address/contract/ContractSourceCode.tsx @@ -31,6 +31,8 @@ function getEditorData(contractInfo: SmartContract | undefined) { return 'scilla'; case 'stylus_rust': return 'rs'; + case 'geas': + return 'eas'; default: return 'sol'; } diff --git a/ui/address/contract/methods/ContractMethodsCustom.pw.tsx b/ui/address/contract/methods/ContractMethodsCustom.pw.tsx index 754dbc411d..87928ee2f5 100644 --- a/ui/address/contract/methods/ContractMethodsCustom.pw.tsx +++ b/ui/address/contract/methods/ContractMethodsCustom.pw.tsx @@ -1,6 +1,6 @@ import type { BrowserContext } from '@playwright/test'; import React from 'react'; -import type { AbiItem } from 'viem'; +import type { Abi } from 'viem'; import * as addressMock from 'mocks/address/address'; import * as methodsMock from 'mocks/contract/methods'; @@ -27,7 +27,7 @@ authTest('without data', async({ render }) => { }); authTest('with data', async({ render, mockApiResponse }) => { - const abi: Array = [ ...methodsMock.read, ...methodsMock.write ]; + const abi: Abi = [ ...methodsMock.read, ...methodsMock.write ] as Abi; await mockApiResponse('general:custom_abi', [ { abi, contract_address_hash: addressHash, diff --git a/ui/address/contract/methods/ContractMethodsRegular.pw.tsx b/ui/address/contract/methods/ContractMethodsRegular.pw.tsx index afdbf85206..5281856f3d 100644 --- a/ui/address/contract/methods/ContractMethodsRegular.pw.tsx +++ b/ui/address/contract/methods/ContractMethodsRegular.pw.tsx @@ -1,6 +1,5 @@ import React from 'react'; - -import type { SmartContractMethod } from './types'; +import type { Abi } from 'viem'; import * as addressMock from 'mocks/address/address'; import * as methodsMock from 'mocks/contract/methods'; @@ -41,7 +40,7 @@ test('all methods +@dark-mode', async({ render }) => { }, }; - const abi: Array = [ ...methodsMock.read, ...methodsMock.write ]; + const abi: Abi = [ ...methodsMock.read, ...methodsMock.write ] as Abi; const component = await render(, { hooksConfig }); await component.getByText(/expand all/i).click(); await expect(component.getByText('HTTP request failed')).toBeVisible(); @@ -61,7 +60,7 @@ test.describe('all methods', () => { }, }; - const abi: Array = [ ...methodsMock.read, ...methodsMock.write ]; + const abi: Abi = [ ...methodsMock.read, ...methodsMock.write ] as Abi; const component = await render(, { hooksConfig }); await component.getByText(/expand all/i).click(); // await expect(component.getByText('HTTP request failed')).toBeVisible(); diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_with-data-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_with-data-1.png index 86cab0c619..455d466a89 100644 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_with-data-1.png and b/ui/address/contract/methods/__screenshots__/ContractMethodsCustom.pw.tsx_default_with-data-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_all-methods-dark-mode-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_all-methods-dark-mode-1.png index 1c81892f3a..e1e52b7b03 100644 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_all-methods-dark-mode-1.png and b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_dark-color-mode_all-methods-dark-mode-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-dark-mode-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-dark-mode-1.png index 5a4b2f3c69..dedb2c6532 100644 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-dark-mode-1.png and b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-dark-mode-1.png differ diff --git a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-mobile-1.png b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-mobile-1.png index 02ce6b5084..ff6f71bc85 100644 Binary files a/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-mobile-1.png and b/ui/address/contract/methods/__screenshots__/ContractMethodsRegular.pw.tsx_default_all-methods-mobile-1.png differ diff --git a/ui/address/contract/methods/form/ContractMethodForm.tsx b/ui/address/contract/methods/form/ContractMethodForm.tsx index 6eed5993f0..462a5ac7e7 100644 --- a/ui/address/contract/methods/form/ContractMethodForm.tsx +++ b/ui/address/contract/methods/form/ContractMethodForm.tsx @@ -14,7 +14,7 @@ import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import { SECOND } from 'toolkit/utils/consts'; import IconSvg from 'ui/shared/IconSvg'; -import { isReadMethod } from '../utils'; +import { isReadMethod, isWriteMethod } from '../utils'; import ContractMethodFieldAccordion from './ContractMethodFieldAccordion'; import ContractMethodFieldInput from './ContractMethodFieldInput'; import ContractMethodFieldInputArray from './ContractMethodFieldInputArray'; @@ -69,16 +69,25 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props) const args = transformFormDataToMethodArgs(formData); if (callStrategyRef.current === 'copy_calldata') { - if (!('name' in data) || !data.name) { + if (!('inputs' in data)) { + return; + } + + // since we have added additional input for native coin value + // we need to slice it off + const argsToPass = args.slice(0, data.inputs.length); + + if (!('name' in data)) { + // this condition means that the fallback method acts as a read method with inputs + const data = typeof argsToPass[0] === 'string' && argsToPass[0].startsWith('0x') ? argsToPass[0] as `0x${ string }` : '0x'; + await navigator.clipboard.writeText(data); return; } const callData = encodeFunctionData({ abi: [ data ], functionName: data.name, - // since we have added additional input for native coin value - // we need to slice it off - args: args.slice(0, data.inputs.length), + args: argsToPass, }); await navigator.clipboard.writeText(callData); return; @@ -254,6 +263,7 @@ const ContractMethodForm = ({ data, attempt, onSubmit, onReset, isOpen }: Props) basePath: `${ index }`, isDisabled: isLoading, level: 0, + isOptional: data.type === 'fallback' && isWriteMethod(data), }; if ('components' in input && input.components && input.type === 'tuple') { diff --git a/ui/address/contract/methods/form/ContractMethodOutput.tsx b/ui/address/contract/methods/form/ContractMethodOutput.tsx index a96916149b..14a071a531 100644 --- a/ui/address/contract/methods/form/ContractMethodOutput.tsx +++ b/ui/address/contract/methods/form/ContractMethodOutput.tsx @@ -2,13 +2,13 @@ import { Flex } from '@chakra-ui/react'; import React from 'react'; import type { AbiFunction } from 'viem'; -import type { ResultViewMode } from '../types'; +import type { AbiFallback, ResultViewMode } from '../types'; import ResultItem from './resultPublicClient/Item'; export interface Props { data: unknown; - abiItem: AbiFunction; + abiItem: AbiFunction | AbiFallback; onSettle: () => void; mode: ResultViewMode; } diff --git a/ui/address/contract/methods/types.ts b/ui/address/contract/methods/types.ts index c3b924de2f..6db1282974 100644 --- a/ui/address/contract/methods/types.ts +++ b/ui/address/contract/methods/types.ts @@ -1,4 +1,5 @@ -import type { AbiFunction, AbiFallback, AbiReceive } from 'abitype'; +import type { AbiFunction, AbiFallback as AbiFallbackViem, AbiReceive } from 'abitype'; +import type { AbiParameter, AbiStateMutability } from 'viem'; export type ContractAbiItemInput = AbiFunction['inputs'][number] & { fieldType?: 'native_coin' }; @@ -6,6 +7,18 @@ export type MethodType = 'read' | 'write' | 'all'; export type MethodCallStrategy = 'read' | 'write' | 'simulate' | 'copy_calldata'; export type ResultViewMode = 'preview' | 'result'; +// we manually add inputs and outputs to the fallback method because viem doesn't support it +// but as we discussed with @k1rill-fedoseev, it's a good idea to have them for fallback method of any contract +// also, according to @k1rill-fedoseev, fallback method can act as a read method when it has 'view' state mutability +// but viem doesn't aware of this and thinks that fallback method state mutability can only be 'payable' or 'nonpayable' +// so we have to redefine the stateMutability as well to include "view" option +// see "addInputsToFallback" and "isReadMethod" functions in utils.ts +export interface AbiFallback extends Pick { + inputs: Array; + outputs: Array; + stateMutability: Exclude; +} + export type SmartContractMethodCustomFields = { method_id: string } | { is_invalid: boolean }; export type SmartContractMethodRead = AbiFunction & SmartContractMethodCustomFields; export type SmartContractMethodWrite = AbiFunction & SmartContractMethodCustomFields | AbiFallback | AbiReceive; diff --git a/ui/address/contract/methods/useCallMethodPublicClient.ts b/ui/address/contract/methods/useCallMethodPublicClient.ts index a6ab71ee52..503b9d82d9 100644 --- a/ui/address/contract/methods/useCallMethodPublicClient.ts +++ b/ui/address/contract/methods/useCallMethodPublicClient.ts @@ -14,7 +14,7 @@ interface Params { item: SmartContractMethod; args: Array; addressHash: string; - strategy: Exclude; + strategy: Exclude; } export default function useCallMethodPublicClient(): (params: Params) => Promise { @@ -24,8 +24,8 @@ export default function useCallMethodPublicClient(): (params: Params) => Promise const { address: account } = useAccount(); return React.useCallback(async({ args, item, addressHash, strategy }) => { - if (!('name' in item)) { - throw new Error('Unknown contract method'); + if (item.type === 'receive') { + throw new Error('Incorrect contract method'); } if (!publicClient) { @@ -33,10 +33,28 @@ export default function useCallMethodPublicClient(): (params: Params) => Promise } const address = getAddress(addressHash); - // for write payable methods we add additional input for native coin value - // so in simulate mode we need to strip it off - const _args = args.slice(0, item.inputs.length); - const value = getNativeCoinValue(args[item.inputs.length]); + + // for payable methods we add additional input for native coin value + const inputs = 'inputs' in item ? item.inputs : []; + const _args = args.slice(0, inputs.length); + const value = getNativeCoinValue(args[inputs.length]); + + if (item.type === 'fallback') { + // if the fallback method acts as a read method, it can only have one input of type bytes + // so we pass the input value as data without encoding it + const data = typeof _args[0] === 'string' && _args[0].startsWith('0x') ? _args[0] as `0x${ string }` : undefined; + const result = await publicClient.call({ + account, + to: address, + value, + ...(data ? { data } : {}), + }); + + return { + source: 'public_client' as const, + data: result.data, + }; + } const params = { abi: [ item ], diff --git a/ui/address/contract/methods/useCallMethodWalletClient.ts b/ui/address/contract/methods/useCallMethodWalletClient.ts index 146e025c59..b6bb669835 100644 --- a/ui/address/contract/methods/useCallMethodWalletClient.ts +++ b/ui/address/contract/methods/useCallMethodWalletClient.ts @@ -41,11 +41,19 @@ export default function useCallMethodWalletClient(): (params: Params) => Promise const address = getAddress(addressHash); const activityResponse = await trackTransaction(account ?? '', address); + // for payable methods we add additional input for native coin value + const inputs = 'inputs' in item ? item.inputs : []; + const _args = args.slice(0, inputs.length); + const value = getNativeCoinValue(args[inputs.length]); + if (item.type === 'receive' || item.type === 'fallback') { - const value = getNativeCoinValue(args[0]); + // if the fallback method acts as a read method, it can only have one input of type bytes + // so we pass the input value as data without encoding it + const data = typeof _args[0] === 'string' && _args[0].startsWith('0x') ? _args[0] as `0x${ string }` : undefined; const hash = await walletClient.sendTransaction({ to: address, value, + ...(data ? { data } : {}), }); if (activityResponse?.token) { @@ -61,9 +69,6 @@ export default function useCallMethodWalletClient(): (params: Params) => Promise throw new Error('Method name is not defined'); } - const _args = args.slice(0, item.inputs.length); - const value = getNativeCoinValue(args[item.inputs.length]); - const hash = await walletClient.writeContract({ args: _args, // Here we provide the ABI as an array containing only one item from the submitted form. diff --git a/ui/address/contract/methods/utils.ts b/ui/address/contract/methods/utils.ts index fe63631784..1fefb7f238 100644 --- a/ui/address/contract/methods/utils.ts +++ b/ui/address/contract/methods/utils.ts @@ -1,5 +1,4 @@ -import type { Abi, AbiFallback, AbiReceive } from 'abitype'; -import type { AbiFunction } from 'viem'; +import type { Abi } from 'abitype'; import { toFunctionSelector } from 'viem'; import type { MethodType, SmartContractMethod, SmartContractMethodRead, SmartContractMethodWrite } from './types'; @@ -12,19 +11,22 @@ export const getNativeCoinValue = (value: unknown) => { return BigInt(value); }; -export const isMethod = (method: Abi[number]): method is SmartContractMethod => +export const isMethod = (method: Abi[number]) => (method.type === 'function' || method.type === 'fallback' || method.type === 'receive'); -export const isReadMethod = (method: Abi[number]): method is SmartContractMethodRead => - method.type === 'function' && ( - method.constant || method.stateMutability === 'view' || method.stateMutability === 'pure' +export const isReadMethod = (method: SmartContractMethod): method is SmartContractMethodRead => + ( + method.type === 'function' && + (method.constant || method.stateMutability === 'view' || method.stateMutability === 'pure') + ) || ( + method.type === 'fallback' && method.stateMutability === 'view' ); -export const isWriteMethod = (method: Abi[number]): method is SmartContractMethodWrite => +export const isWriteMethod = (method: SmartContractMethod): method is SmartContractMethodWrite => (method.type === 'function' || method.type === 'fallback' || method.type === 'receive') && !isReadMethod(method); -export const enrichWithMethodId = (method: AbiFunction | AbiFallback | AbiReceive): SmartContractMethod => { +export const enrichWithMethodId = (method: SmartContractMethod): SmartContractMethod => { if (method.type !== 'function') { return method; } @@ -42,7 +44,19 @@ export const enrichWithMethodId = (method: AbiFunction | AbiFallback | AbiReceiv } }; -const getNameForSorting = (method: SmartContractMethod | AbiFallback | AbiReceive) => { +export const addInputsToFallback = (method: SmartContractMethod): SmartContractMethod => { + if (method.type === 'fallback') { + return { + ...method, + inputs: [ { internalType: 'bytes', name: 'input', type: 'bytes' } ], + outputs: [ { internalType: 'bytes', name: 'output', type: 'bytes' } ], + }; + } + + return method; +}; + +const getNameForSorting = (method: SmartContractMethod) => { if ('name' in method) { return method.name; } @@ -51,9 +65,12 @@ const getNameForSorting = (method: SmartContractMethod | AbiFallback | AbiReceiv }; export const formatAbi = (abi: Abi) => { - return abi - .filter(isMethod) + + const methods = abi.filter(isMethod) as Array; + + return methods .map(enrichWithMethodId) + .map(addInputsToFallback) .sort((a, b) => { const aName = getNameForSorting(a); const bName = getNameForSorting(b); diff --git a/ui/shared/monaco/CodeEditor.tsx b/ui/shared/monaco/CodeEditor.tsx index 1c00f1028f..b358cd6438 100644 --- a/ui/shared/monaco/CodeEditor.tsx +++ b/ui/shared/monaco/CodeEditor.tsx @@ -21,6 +21,7 @@ import CodeEditorTabs from './CodeEditorTabs'; import addExternalLibraryWarningDecoration from './utils/addExternalLibraryWarningDecoration'; import addFileImportDecorations from './utils/addFileImportDecorations'; import addMainContractCodeDecoration from './utils/addMainContractCodeDecoration'; +import { defGeas, configGeas } from './utils/defGeas'; import { defScilla, configScilla } from './utils/defScilla'; import getFullPathOfImportedFile from './utils/getFullPathOfImportedFile'; import * as themes from './utils/themes'; @@ -75,6 +76,8 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile, contractN return 'scilla'; case 'stylus_rust': return 'rust'; + case 'geas': + return 'geas'; default: return 'javascript'; } @@ -98,6 +101,12 @@ const CodeEditor = ({ data, remappings, libraries, language, mainFile, contractN monaco.languages.setLanguageConfiguration(editorLanguage, configScilla); } + if (editorLanguage === 'geas') { + monaco.languages.register({ id: editorLanguage }); + monaco.languages.setMonarchTokensProvider(editorLanguage, defGeas); + monaco.languages.setLanguageConfiguration(editorLanguage, configGeas); + } + const loadedModels = monaco.editor.getModels(); const loadedModelsPaths = loadedModels.map((model) => model.uri.path); const newModels = data.slice(1) diff --git a/ui/shared/monaco/utils/defGeas.ts b/ui/shared/monaco/utils/defGeas.ts new file mode 100644 index 0000000000..1a3680c664 --- /dev/null +++ b/ui/shared/monaco/utils/defGeas.ts @@ -0,0 +1,189 @@ +import type * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; + +export const configGeas: monaco.languages.LanguageConfiguration = { + comments: { + lineComment: ';;', + }, +}; + +export const defGeas: monaco.languages.IMonarchLanguage = { + + builtins: [ + 'abs', + 'selector', + 'keccak256', + 'address', + 'include', + 'assemble', + 'pragma', + 'define', + 'push', + ...Array.from({ length: 32 }, (_, i) => `push${ i }`), + ], + + keywords: [ + // geasEnvOpcode + 'address', + 'balance', + 'origin', + 'caller', + 'callvalue', + 'calldataload', + 'calldatasize', + 'calldatacopy', + 'codesize', + 'codecopy', + 'gasprice', + 'returndatasize', + 'returndatacopy', + 'blockhash', + 'coinbase', + 'timestamp', + 'number', + 'difficulty', + 'gaslimit', + 'chainid', + 'selfbalance', + 'basefee', + + // geasTrieOpcode + 'extcodesize', + 'extcodecopy', + 'extcodehash', + 'sload', + 'sstore', + 'selfdestruct', + + // geasCallOpcode + 'create', + 'call', + 'callcode', + 'delegatecall', + 'create2', + 'staticcall', + ], + + operators: [ + // geasRegularOpcode + 'stop', + 'add', + 'mul', + 'sub', + 'div', + 'sdiv', + 'mod', + 'smod', + 'addmod', + 'mulmod', + 'exp', + 'signextend', + 'lt', + 'gt', + 'slt', + 'sgt', + 'eq', + 'iszero', + 'and', + 'or', + 'xor', + 'not', + 'byte', + 'shl', + 'shr', + 'sar', + 'keccak256', + 'pop', + 'mload', + 'mstore', + 'mstore8', + 'jump', + 'jumpi', + 'pc', + 'msize', + 'gas', + 'jumpdest', + 'revert', + 'invalid', + 'return', + + // geasRegularOpcode (dynamic) + 'dup', + ...Array.from({ length: 16 }, (_, i) => `dup${ i }`), + 'swap', + ...Array.from({ length: 16 }, (_, i) => `swap${ i }`), + 'log', + ...Array.from({ length: 4 }, (_, i) => `log${ i }`), + ], + + calls: /@\.\w+/, + macros: /#?(include|assemble|push|abs|selector|keccak256|address|pragma|define)/, + + // we include these common regular expressions + symbols: /[=> value === 'all' || config.UI.views.address.languageFilters.includes(value)) as Array<{ value: OptionValue; label: string }>; const collection = createListCollection({