├── __init__.py ├── backend ├── __init__.py ├── errors │ └── __init__.py ├── node │ ├── __init__.py │ ├── genvm │ │ ├── origin │ │ │ ├── __init__.py │ │ │ ├── host_fns.py │ │ │ └── public_abi.py │ │ └── __init__.py │ └── create_nodes │ │ └── default_providers │ │ ├── xai_grok-3.json │ │ ├── openai_gpt-4o.json │ │ ├── openai_gpt-4.1.json │ │ ├── openai_gpt-4.1-mini.json │ │ ├── openai_gpt-4.1-nano.json │ │ ├── openai_gpt-4-1106-preview.json │ │ ├── anthropic_claude-4-opus.json │ │ ├── anthropic_claude-4-sonnet.json │ │ ├── google_gemini-2.5-flash.json │ │ ├── google_gemini-2.5-flash-lite.json │ │ ├── anthropic_claude-3-5-haiku-20241022.json │ │ ├── anthropic_claude-3-7-sonnet-20250219.json │ │ ├── heuristai_deepseekdeepseek-v3.json │ │ ├── google_googlegemini-2.0-flash-lite-001.json │ │ ├── openai_gpt-5-nano.json │ │ ├── openai_gpt-5.1-nano.json │ │ ├── heuristai_mistralaimixtral-8x7b-instruct.json │ │ ├── heuristai_meta-llamallama-3.3-70b-instruct.json │ │ ├── openai_gpt-5.json │ │ ├── openai_gpt-5.1.json │ │ ├── openai_gpt-5-mini.json │ │ ├── openai_gpt-5.1-mini.json │ │ ├── ollama_gemma.json │ │ ├── ollama_llama3.json │ │ └── ollama_mistral.json ├── rollup │ ├── __init__.py │ └── default_contracts │ │ └── __init__.py ├── consensus │ ├── __init__.py │ ├── vrf.py │ └── utils.py ├── protocol_rpc │ ├── __init__.py │ ├── message_handler │ │ ├── __init__.py │ │ ├── config │ │ │ ├── logging.dev.json │ │ │ └── logging.prod.json │ │ └── types.py │ ├── configuration.py │ ├── calls_intercept │ │ └── __init__.py │ ├── call_interceptor.py │ └── rpc_decorators.py ├── database_handler │ ├── __init__.py │ ├── migration │ │ ├── requirements.txt │ │ ├── script.py.mako │ │ ├── README.md │ │ └── versions │ │ │ ├── 1daddff774b2_drop_from_constraint_on_rollup_tx.py │ │ │ ├── 3566595124f6_add_leader_only_flag.py │ │ │ ├── 47519c0bf986_validators_private_key.py │ │ │ ├── 130836786511_config_rotation_rounds.py │ │ │ ├── a32f85df2806_add_client_session_id_to_transactions.py │ │ │ ├── 3bc34e44eb72_add_providers_unique_constraint.py │ │ │ ├── 035ef00c2779_add_sim_config_to_transactions.py │ │ │ ├── 15fde6faebaf_contract_snapshot.py │ │ │ ├── 2de9c3151194_consensus_history.py │ │ │ ├── 2a4ac5eb9455_appeal_failed.py │ │ │ ├── 0a4312659782_remove_ghost_contract.py │ │ │ ├── 579e86111b36_remove_client_session_id_from_.py │ │ │ ├── d932a5fef8b1_appeal_processing_time.py │ │ │ ├── 96840ab9133a_add_blocked_at_and_worker_id_to_.py │ │ │ ├── b5acc405bcca_add_transactions_triggers.py │ │ │ ├── 22319ddb113d_tx_receipt.py │ │ │ └── ab256b41602a_remove_invalid_models.py │ ├── errors.py │ └── session_manager.py ├── requirements.txt └── healthcheck.py ├── tests ├── e2e │ └── __init__.py ├── common │ ├── __init__.py │ ├── transactions.py │ └── response.py ├── integration │ ├── __init__.py │ ├── rpc │ │ └── __init__.py │ ├── accounts │ │ └── __init__.py │ ├── icontracts │ │ ├── __init__.py │ │ ├── schemas │ │ │ ├── __init__.py │ │ │ ├── wizard_get_contract_schema_for_code.py │ │ │ ├── storage_get_contract_schema_for_code.py │ │ │ ├── football_prediction_market_get_contract_schema_for_code.py │ │ │ ├── user_storage_get_contract_schema_for_code.py │ │ │ ├── llm_erc20_get_contract_schema_for_code.py │ │ │ └── log_indexer_get_contract_schema_for_code.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_multi_file_contract.py │ │ │ ├── test_wizard_of_coin.py │ │ │ ├── test_storage.py │ │ │ ├── test_football_prediction_market.py │ │ │ └── test_read_erc20.py │ │ ├── contracts │ │ │ ├── __init__.py │ │ │ ├── multi_file_contract │ │ │ │ ├── runner.json │ │ │ │ ├── other.py │ │ │ │ └── __init__.py │ │ │ ├── read_erc20.py │ │ │ ├── error_web_contract.py │ │ │ └── multi_read_erc20.py │ │ └── ideas.md │ ├── features │ │ ├── transaction_state_machine.feature │ │ ├── security_resilience.feature │ │ ├── load_testing.feature │ │ ├── parallel_deployment.feature │ │ ├── websocket_notifications.feature │ │ └── concurrent_transactions.feature │ ├── pytest.ini │ └── README.md ├── unit │ ├── consensus │ │ └── __init__.py │ ├── pytest.ini │ ├── __init__.py │ └── test_types.py ├── __init__.py ├── load │ ├── .last_deployed_contract │ └── reset_db.sh ├── hardhat │ ├── requirements.txt │ ├── Dockerfile │ └── docker-compose.yml ├── db-sqlalchemy │ ├── requirements.txt │ ├── Dockerfile │ └── docker-compose.yml └── test_rpc │ └── test_address_utils.py ├── frontend ├── .nvmrc ├── env.d.ts ├── .prettierignore ├── src │ ├── plugins │ │ └── index.ts │ ├── services │ │ ├── index.ts │ │ └── IJsonRpcService.ts │ ├── components │ │ ├── global │ │ │ ├── fields │ │ │ │ ├── FieldError.vue │ │ │ │ ├── AnyFieldValue.ts │ │ │ │ ├── FieldLabel.vue │ │ │ │ ├── BooleanField.vue │ │ │ │ ├── StringField.vue │ │ │ │ ├── IntegerField.vue │ │ │ │ ├── FloatField.vue │ │ │ │ └── AnyField.vue │ │ │ ├── ContentLoader.vue │ │ │ ├── MoreInfo.vue │ │ │ ├── inputs │ │ │ │ ├── CheckboxInput.vue │ │ │ │ ├── TextInput.vue │ │ │ │ ├── TextAreaInput.vue │ │ │ │ └── NumberInput.vue │ │ │ ├── CopyTextButton.vue │ │ │ ├── GhostBtn.vue │ │ │ ├── registerGlobalComponents.js │ │ │ ├── Loader.vue │ │ │ └── ConfirmationModal.vue │ │ ├── JsonViewer │ │ │ ├── icon.svg │ │ │ └── types │ │ │ │ ├── json-boolean.vue │ │ │ │ ├── json-undefined.vue │ │ │ │ ├── json-function.vue │ │ │ │ ├── json-date.vue │ │ │ │ └── json-number.vue │ │ ├── Simulator │ │ │ ├── EmptyListPlaceholder.vue │ │ │ ├── MainTitle.vue │ │ │ ├── TransactionStatusBadge.vue │ │ │ ├── ModalSection.vue │ │ │ ├── PageSection.vue │ │ │ ├── HomeFeatureItem.vue │ │ │ ├── HomeContractItem.vue │ │ │ ├── ContractParams.ts │ │ │ ├── ContractsPanel.vue │ │ │ ├── LogFilterBtn.vue │ │ │ ├── parsers │ │ │ │ └── splitTopLevelLines.ts │ │ │ └── settings │ │ │ │ └── ConsensusSection.vue │ │ ├── SimulatorMenuLink.vue │ │ ├── SimulatorMenuItem.vue │ │ ├── Notification.vue │ │ └── Tutorial │ │ │ └── constants.ts │ ├── hooks │ │ ├── useRpcClient.ts │ │ ├── useUniqueId.ts │ │ ├── useFileName.ts │ │ ├── useShortAddress.ts │ │ ├── useConfig.ts │ │ ├── index.ts │ │ ├── useEventTracking.ts │ │ ├── useInputMap.ts │ │ ├── useContractListener.ts │ │ ├── useGenlayer.ts │ │ ├── useTransactionListener.ts │ │ └── useMockContractData.ts │ ├── types │ │ ├── responses.ts │ │ ├── results.ts │ │ ├── events.ts │ │ ├── store.ts │ │ ├── index.ts │ │ └── requests.ts │ ├── stores │ │ ├── index.ts │ │ └── ui.ts │ ├── global.d.ts │ ├── constants │ │ └── links.ts │ ├── assets │ │ ├── images │ │ │ ├── mark_black.svg │ │ │ └── mark_white.svg │ │ └── icons │ │ │ ├── DiscordIcon.vue │ │ │ └── TelegramIcon.vue │ ├── views │ │ └── Simulator │ │ │ └── SettingsView.vue │ ├── index.d.ts │ ├── router │ │ └── index.ts │ ├── main.ts │ └── clients │ │ └── rpc.ts ├── .mocharc.cjs ├── postcss.config.cjs ├── .vscode │ └── settings.json ├── tsconfig.json ├── .prettierrc.json ├── tsconfig.vitest.json ├── test │ ├── pages │ │ ├── RunDebugPage.ts │ │ ├── ContractsPage.ts │ │ └── BasePage.ts │ ├── utils │ │ ├── driver.ts │ │ └── pages.ts │ ├── unit │ │ ├── hooks │ │ │ ├── useConfig.test.ts │ │ │ ├── useUniqueId.test.ts │ │ │ ├── useRpcClient.test.ts │ │ │ ├── useFileName.test.ts │ │ │ └── useInputMap.test.ts │ │ └── calldata │ │ │ └── decode.test.ts │ └── e2e │ │ └── CreateNodeValidator.spec.ts ├── shims.d.ts ├── vitest.config.ts ├── tsconfig.node.json ├── tsconfig.app.json ├── .eslintrc.cjs ├── tailwind.config.js ├── .gitignore ├── scripts │ └── contract-examples.js ├── .stylelintrc.json ├── README.md ├── vite.config.ts └── public │ └── loader.css ├── .gitattributes ├── .github ├── workflows │ ├── README.md │ ├── conventional-commit-pr.yml │ ├── frontend-unit-tests.yml │ ├── test-semgrep.yml │ ├── release-staging.yml │ ├── .pre-commit.yml │ ├── unit-tests-pr.yml │ ├── scan-codeql.yml │ └── release-from-main.yml └── pull_request_template.md ├── .last_deployed_contract ├── .last_deployment_tx ├── .vscode ├── extensions.json └── settings.json ├── requirements.txt ├── .claude └── settings.json ├── docker ├── Dockerfile.nginx ├── Dockerfile.database-migration ├── Dockerfile.frontend └── entrypoint-frontend.sh ├── requirements.test.txt ├── hardhat ├── contracts │ ├── v2_contracts │ │ ├── interfaces │ │ │ ├── IGenConsensus.sol │ │ │ ├── IGenLayerToken.sol │ │ │ ├── IGenManager.sol │ │ │ ├── IMessages.sol │ │ │ └── IGhostFactory.sol │ │ ├── ghost_contracts │ │ │ └── GhostManager.sol │ │ └── transactions │ │ │ ├── Utils.sol │ │ │ └── interfaces │ │ │ └── IIdleness.sol │ ├── GhostContract.sol │ └── BasicERC20.sol └── package.json ├── traefik └── tls.yml ├── .cursor └── mcp.json ├── decisions └── README.md ├── sonar-project.properties ├── examples └── contracts │ ├── storage.py │ └── user_storage.py ├── renovate.json ├── .dockerignore ├── asgi.py ├── .pre-commit-config.yaml ├── LICENSE ├── nginx └── ssl │ └── README.md ├── release.config.js └── uvicorn_config.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/e2e/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/errors/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/node/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/rollup/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.nvmrc: -------------------------------------------------------------------------------- 1 | 22.14.0 2 | -------------------------------------------------------------------------------- /tests/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | text=auto eol=lf -------------------------------------------------------------------------------- /backend/consensus/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/protocol_rpc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/rpc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/consensus/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/database_handler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/icontracts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/protocol_rpc/message_handler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/rollup/default_contracts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/icontracts/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/icontracts/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # Trigger CI re-run 2 | -------------------------------------------------------------------------------- /tests/integration/icontracts/contracts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | package*.json -------------------------------------------------------------------------------- /.last_deployed_contract: -------------------------------------------------------------------------------- 1 | 0x6652823eb62FC70E0F16F3ce4Dd6F19F9716Ae66 -------------------------------------------------------------------------------- /frontend/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './persistStore'; 2 | -------------------------------------------------------------------------------- /backend/node/genvm/origin/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is taken from genvm repo 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | 3 | load_dotenv() 4 | -------------------------------------------------------------------------------- /tests/load/.last_deployed_contract: -------------------------------------------------------------------------------- 1 | 0x0fA93e0C3196E6a9D6C1ad10acD785063b9e8330 -------------------------------------------------------------------------------- /.last_deployment_tx: -------------------------------------------------------------------------------- 1 | 0x5a87615b3590a4553437d0e1973c68f6e41903b5ded2bb8a6ba556ba10cdce79 -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "sumneko.lua" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /tests/unit/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | asyncio_default_fixture_loop_scope = function 3 | timeout = 240 4 | -------------------------------------------------------------------------------- /frontend/src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './IJsonRpcService'; 2 | export * from './JsonRpcService'; 3 | -------------------------------------------------------------------------------- /backend/database_handler/migration/requirements.txt: -------------------------------------------------------------------------------- 1 | SQLAlchemy==2.0.40 2 | alembic==1.15.2 3 | psycopg2-binary==2.9.10 -------------------------------------------------------------------------------- /tests/integration/icontracts/contracts/multi_file_contract/runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "Depends": "py-genlayer-multi:test" 3 | } 4 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | 3 | if not load_dotenv(): 4 | load_dotenv(".env.example") 5 | -------------------------------------------------------------------------------- /frontend/.mocharc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = 2 | { 3 | "$schema": "https://json.schemastore.org/mocharc.json", 4 | "require": "tsx" 5 | } -------------------------------------------------------------------------------- /frontend/src/components/global/fields/FieldError.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/integration/features/transaction_state_machine.feature: -------------------------------------------------------------------------------- 1 | Feature: Transaction State Machine 2 | 3 | Scenario: Transaction completes full lifecycle 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.32.3 2 | python-dotenv==1.1.0 3 | black==25.1.0 4 | pytest==8.3.5 5 | pytest-mock==3.14.0 6 | pre-commit==4.2.0 7 | aiohttp==3.11.16 -------------------------------------------------------------------------------- /tests/hardhat/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==8.2.1 2 | debugpy==1.8.5 3 | requests==2.32.3 4 | numpy==1.26.4 5 | web3==7.5.0 6 | eth-typing==5.0.1 7 | python-dotenv==1.0.1 -------------------------------------------------------------------------------- /backend/node/genvm/__init__.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | 4 | def get_code_slot() -> bytes: 5 | return hashlib.sha3_256(b"\x00" * 32 + b"\x01\x00\x00\x00").digest() 6 | -------------------------------------------------------------------------------- /frontend/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | /* global module */ 2 | module.exports = { 3 | plugins: { 4 | 'postcss-import': {}, 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/components/global/ContentLoader.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Loading... 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/components/JsonViewer/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.claude/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabledPlugins": { 3 | "code-review@claude-plugins-official": true, 4 | "feature-dev@claude-plugins-official": true, 5 | "hookify@claude-plugins-official": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docker/Dockerfile.nginx: -------------------------------------------------------------------------------- 1 | # This Dockerfile is used to build the nginx image with lua and LuaRocks installed 2 | FROM fabiocicerchia/nginx-lua:1.27.2-almalinux9.4-20240923 3 | 4 | # Install lua-cjson 5 | RUN luarocks install lua-cjson -------------------------------------------------------------------------------- /tests/db-sqlalchemy/requirements.txt: -------------------------------------------------------------------------------- 1 | SQLAlchemy==2.0.31 2 | pytest==8.2.1 3 | psycopg2-binary==2.9.9 4 | debugpy==1.8.5 5 | requests==2.32.3 6 | numpy==1.26.4 7 | eth-account==0.13.1 8 | python-dotenv==1.0.1 9 | web3==7.5.0 10 | -------------------------------------------------------------------------------- /frontend/src/hooks/useRpcClient.ts: -------------------------------------------------------------------------------- 1 | import { RpcClient } from '@/clients/rpc'; 2 | import { JsonRpcService } from '@/services/JsonRpcService'; 3 | 4 | export function useRpcClient() { 5 | return new JsonRpcService(new RpcClient()); 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/types/responses.ts: -------------------------------------------------------------------------------- 1 | export interface JsonRPCResponse { 2 | id: string; 3 | jsonrpc: string; 4 | result: T; 5 | error?: { 6 | code: number; 7 | message: string; 8 | data?: any; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/stores/index.ts: -------------------------------------------------------------------------------- 1 | export * from './accounts'; 2 | export * from './contracts'; 3 | export * from './node'; 4 | export * from './ui'; 5 | export * from './tutorial'; 6 | export * from './transactions'; 7 | export * from './consensus'; 8 | -------------------------------------------------------------------------------- /requirements.test.txt: -------------------------------------------------------------------------------- 1 | eth-account==0.13.6 2 | eth-abi==5.2.0 3 | web3==7.10.0 4 | psycopg2-binary==2.9.10 5 | pytest==8.3.5 6 | pytest-xdist==3.6.1 7 | pytest-asyncio==0.26.0 8 | pytest-timeout==2.3.1 9 | genlayer-test==0.9.0 10 | httpx==0.27.2 11 | -------------------------------------------------------------------------------- /frontend/src/components/global/fields/AnyFieldValue.ts: -------------------------------------------------------------------------------- 1 | export class AnyFieldValue { 2 | value: string; 3 | constructor(value: string) { 4 | this.value = value; 5 | } 6 | 7 | toString(): string { 8 | return this.value; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/integration/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | asyncio_default_fixture_loop_scope = function 3 | addopts = -m "not error_handling" 4 | markers = 5 | error_handling: marks tests that verify error handling of genvm and consensus (run with '-m error_handling') 6 | -------------------------------------------------------------------------------- /backend/protocol_rpc/configuration.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | 5 | class GlobalConfiguration: 6 | @staticmethod 7 | def get_disabled_info_logs_endpoints() -> list: 8 | return json.loads(os.environ.get("DISABLE_INFO_LOGS_ENDPOINTS", "[]")) 9 | -------------------------------------------------------------------------------- /frontend/src/global.d.ts: -------------------------------------------------------------------------------- 1 | // global.d.ts 2 | interface Window { 3 | ethereum?: { 4 | isMetaMask?: boolean; 5 | request: (args: { method: string; params?: unknown[] }) => Promise; 6 | on: (method: string, callback: Function) => {}; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": "explicit" 4 | }, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "[vue]": { 7 | "editor.defaultFormatter": "esbenp.prettier-vscode" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ], 11 | "compilerOptions": { 12 | "module": "NodeNext" 13 | } 14 | } -------------------------------------------------------------------------------- /frontend/src/hooks/useUniqueId.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | 3 | export function useUniqueId(prefix?: string): string { 4 | const uid = uuidv4(); 5 | 6 | if (prefix) { 7 | return `${prefix}-${uid}`; 8 | } else { 9 | return uid; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/hardhat/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | ENV TEST_PATH=tests/hardhat 6 | 7 | COPY ${TEST_PATH}/requirements.txt . 8 | 9 | RUN pip install --no-cache-dir -r requirements.txt 10 | 11 | COPY ${TEST_PATH} . 12 | 13 | ENTRYPOINT ["pytest", "-svv"] 14 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/xai_grok-3.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "xai", 3 | "plugin": "openai-compatible", 4 | "model": "grok-3", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "XAI_API_KEY", 8 | "api_url": "https://api.x.ai" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-4o.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-4o", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "OPENAIKEY", 8 | "api_url": "https://api.openai.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": true, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "printWidth": 80, 7 | "trailingComma": "all", 8 | "bracketSpacing": true, 9 | "plugins": ["prettier-plugin-tailwindcss"] 10 | } 11 | -------------------------------------------------------------------------------- /hardhat/contracts/v2_contracts/interfaces/IGenConsensus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | interface IGenConsensus { 5 | function executeMessage( 6 | address _recipient, 7 | uint _value, 8 | bytes memory _data 9 | ) external returns (bool success); 10 | } -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-4.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-4.1", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "OPENAIKEY", 8 | "api_url": "https://api.openai.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-4.1-mini.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-4.1-mini", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "OPENAIKEY", 8 | "api_url": "https://api.openai.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-4.1-nano.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-4.1-nano", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "OPENAIKEY", 8 | "api_url": "https://api.openai.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/tsconfig.vitest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "exclude": [], 4 | "compilerOptions": { 5 | "composite": true, 6 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo", 7 | 8 | "lib": [], 9 | "types": ["node", "jsdom"] 10 | } 11 | } -------------------------------------------------------------------------------- /frontend/src/components/Simulator/EmptyListPlaceholder.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | No items to show. 7 | 8 | 9 | -------------------------------------------------------------------------------- /traefik/tls.yml: -------------------------------------------------------------------------------- 1 | tls: 2 | certificates: 3 | - certFile: /etc/ssl/traefik/genlayer.com.crt 4 | keyFile: /etc/ssl/traefik/genlayer.com.key 5 | stores: 6 | default: 7 | defaultCertificate: 8 | certFile: /etc/ssl/traefik/genlayer.com.crt 9 | keyFile: /etc/ssl/traefik/genlayer.com.key 10 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-4-1106-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-4-1106-preview", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "OPENAIKEY", 8 | "api_url": "https://api.openai.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/integration/features/security_resilience.feature: -------------------------------------------------------------------------------- 1 | Feature: Security Resilience 2 | Scenario: DOS attack mitigation 3 | Given an attacker sends 10000 requests per second 4 | When rate limiting kicks in after 100 requests 5 | Then subsequent requests should be throttled 6 | And legitimate users should still be served -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/anthropic_claude-4-opus.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "anthropic", 3 | "plugin": "anthropic", 4 | "model": "claude-opus-4-20250514", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "ANTHROPIC_API_KEY", 8 | "api_url": "https://api.anthropic.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/anthropic_claude-4-sonnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "anthropic", 3 | "plugin": "anthropic", 4 | "model": "claude-sonnet-4-20250514", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "ANTHROPIC_API_KEY", 8 | "api_url": "https://api.anthropic.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/google_gemini-2.5-flash.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google", 3 | "plugin": "google", 4 | "model": "gemini-2.5-flash", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "GEMINI_API_KEY", 8 | "api_url": "https://generativelanguage.googleapis.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/db-sqlalchemy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | ENV TEST_PATH=tests/db-sqlalchemy 6 | 7 | COPY ${TEST_PATH}/requirements.txt . 8 | 9 | RUN pip install --no-cache-dir -r requirements.txt 10 | 11 | COPY ${TEST_PATH} . 12 | 13 | COPY backend /app/backend 14 | 15 | ENTRYPOINT ["pytest", "-svv"] 16 | -------------------------------------------------------------------------------- /frontend/src/hooks/useFileName.ts: -------------------------------------------------------------------------------- 1 | export const useFileName = () => { 2 | function cleanupFileName(name: string) { 3 | const tokens = name.split('.'); 4 | if (tokens.length > 0) { 5 | return `${tokens[0]}.py`; 6 | } 7 | return `${name}.py`; 8 | } 9 | 10 | return { 11 | cleanupFileName, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/google_gemini-2.5-flash-lite.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google", 3 | "plugin": "google", 4 | "model": "gemini-2.5-flash-lite", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "GEMINI_API_KEY", 8 | "api_url": "https://generativelanguage.googleapis.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/anthropic_claude-3-5-haiku-20241022.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "anthropic", 3 | "plugin": "anthropic", 4 | "model": "claude-3-5-haiku-20241022", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "ANTHROPIC_API_KEY", 8 | "api_url": "https://api.anthropic.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/anthropic_claude-3-7-sonnet-20250219.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "anthropic", 3 | "plugin": "anthropic", 4 | "model": "claude-3-7-sonnet-20250219", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "ANTHROPIC_API_KEY", 8 | "api_url": "https://api.anthropic.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/heuristai_deepseekdeepseek-v3.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "heuristai", 3 | "plugin": "openai-compatible", 4 | "model": "deepseek/deepseek-v3", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "HEURISTAIAPIKEY", 8 | "api_url": "https://llm-gateway.heurist.xyz" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.cursor/mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "postgres": { 4 | "command": "docker", 5 | "args": [ 6 | "run", 7 | "-i", 8 | "--rm", 9 | "--network=host", 10 | "mcp/postgres", 11 | "postgresql://postgres:postgres@host.docker.internal:5432/genlayer_state" 12 | ] 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/google_googlegemini-2.0-flash-lite-001.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google", 3 | "plugin": "google", 4 | "model": "gemini-2.0-flash-lite-001", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "GEMINI_API_KEY", 8 | "api_url": "https://generativelanguage.googleapis.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-5-nano.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-5-nano", 5 | "config": { "temperature": 1, "use_max_completion_tokens": true }, 6 | "plugin_config": { 7 | "api_key_env_var": "OPENAIKEY", 8 | "api_url": "https://api.openai.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-5.1-nano.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-5.1-nano", 5 | "config": { "temperature": 1, "use_max_completion_tokens": true }, 6 | "plugin_config": { 7 | "api_key_env_var": "OPENAIKEY", 8 | "api_url": "https://api.openai.com" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/integration/icontracts/contracts/multi_file_contract/other.py: -------------------------------------------------------------------------------- 1 | # { "Depends": "py-genlayer:test" } 2 | 3 | from genlayer import * 4 | 5 | 6 | class Other(gl.Contract): 7 | data: str 8 | 9 | def __init__(self, data: str): 10 | self.data = data 11 | 12 | @gl.public.view 13 | def test(self) -> str: 14 | return self.data 15 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/heuristai_mistralaimixtral-8x7b-instruct.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "heuristai", 3 | "plugin": "openai-compatible", 4 | "model": "mistralai/mixtral-8x7b-instruct", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "HEURISTAIAPIKEY", 8 | "api_url": "https://llm-gateway.heurist.xyz" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/MainTitle.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/test/pages/RunDebugPage.ts: -------------------------------------------------------------------------------- 1 | import { By, type Locator } from 'selenium-webdriver'; 2 | import { BasePage } from './BasePage'; 3 | 4 | export class RunDebugPage extends BasePage { 5 | override baseurl = 'http://localhost:8080/run-debug'; 6 | override visibleLocator: Locator = By.xpath( 7 | "//*[@data-testid='run-debug-page-title']", 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/heuristai_meta-llamallama-3.3-70b-instruct.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "heuristai", 3 | "plugin": "openai-compatible", 4 | "model": "meta-llama/llama-3.3-70b-instruct", 5 | "config": {}, 6 | "plugin_config": { 7 | "api_key_env_var": "HEURISTAIAPIKEY", 8 | "api_url": "https://llm-gateway.heurist.xyz" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-5.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-5", 5 | "config": { 6 | "temperature": 1, 7 | "use_max_completion_tokens": true 8 | }, 9 | "plugin_config": { 10 | "api_key_env_var": "OPENAIKEY", 11 | "api_url": "https://api.openai.com" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-5.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-5.1", 5 | "config": { 6 | "temperature": 1, 7 | "use_max_completion_tokens": true 8 | }, 9 | "plugin_config": { 10 | "api_key_env_var": "OPENAIKEY", 11 | "api_url": "https://api.openai.com" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-5-mini.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-5-mini", 5 | "config": { 6 | "temperature": 1, 7 | "use_max_completion_tokens": true 8 | }, 9 | "plugin_config": { 10 | "api_key_env_var": "OPENAIKEY", 11 | "api_url": "https://api.openai.com" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /decisions/README.md: -------------------------------------------------------------------------------- 1 | # Decisions documentation 2 | 3 | Greatly inspired by ADRs, this directory contains important decisions about the `Studio` code. 4 | 5 | # Index 6 | 7 | - [Handle database migrations with SQLAlchemy + Alembic](001-handle-database-migrations.md) 8 | 9 | # Useful documentation 10 | 11 | - https://github.com/joelparkerhenderson/architecture-decision-record 12 | -------------------------------------------------------------------------------- /frontend/src/constants/links.ts: -------------------------------------------------------------------------------- 1 | type Links = { 2 | discord: string; 3 | docs: string; 4 | feedbackForm: string; 5 | }; 6 | 7 | export const LINKS: Links = { 8 | discord: 'https://discord.gg/8Jm4v89VAu', 9 | docs: 'https://docs.genlayer.com/', 10 | feedbackForm: 11 | 'https://docs.google.com/forms/d/1IVNsZwm936kSNCiXmlAP8bgJnbik7Bqaoc3I6UYhr-o/viewform', 12 | }; 13 | -------------------------------------------------------------------------------- /backend/database_handler/errors.py: -------------------------------------------------------------------------------- 1 | class AccountNotFoundError(Exception): 2 | """Exception raised when a given account is not found.""" 3 | 4 | def __init__( 5 | self, 6 | address: str, 7 | message: str = "Account not found.", 8 | ): 9 | self.address = address 10 | self.message = message 11 | super().__init__(self.message) 12 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/openai_gpt-5.1-mini.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "openai", 3 | "plugin": "openai-compatible", 4 | "model": "gpt-5.1-mini", 5 | "config": { 6 | "temperature": 1, 7 | "use_max_completion_tokens": true 8 | }, 9 | "plugin_config": { 10 | "api_key_env_var": "OPENAIKEY", 11 | "api_url": "https://api.openai.com" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.organization=yeagerai 2 | sonar.projectKey=yeagerai_genlayer-simulator 3 | 4 | # relative paths to source directories. More details and properties are described 5 | # in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/ 6 | sonar.sources=. 7 | sonar.exclusions=tests/**,frontend/test/**/* 8 | sonar.python.coverage.reportPaths=coverage.xml 9 | -------------------------------------------------------------------------------- /tests/integration/features/load_testing.feature: -------------------------------------------------------------------------------- 1 | Feature: System Performance 2 | 3 | Scenario: Sustained high load 4 | Given 100 clients are connected 5 | When each client submits 10 transactions per second 6 | And this continues for 60 seconds 7 | Then 95% of transactions should complete within 5 seconds 8 | And no transactions should be lost 9 | And memory usage should remain stable -------------------------------------------------------------------------------- /frontend/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | 8 | 9 | declare module 'vue-prism-component' { 10 | import { ComponentOptions } from 'vue' 11 | const component: ComponentOptions 12 | export default component 13 | } 14 | declare module 'vue-shepherd'; 15 | -------------------------------------------------------------------------------- /hardhat/contracts/GhostContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.24; 3 | 4 | contract GhostContract { 5 | // Define an event 6 | event ReceivedData(bytes data); 7 | 8 | // Fallback function logs any raw data received 9 | fallback() external payable { 10 | emit ReceivedData(msg.data); // Logs the data in a blockchain event 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/components/global/MoreInfo.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/components/global/fields/FieldLabel.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/TransactionStatusBadge.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /frontend/src/components/SimulatorMenuLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/components/JsonViewer/types/json-boolean.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[vue]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | }, 5 | "python.testing.pytestArgs": ["tests", "backend"], 6 | "python.testing.unittestEnabled": false, 7 | "python.testing.pytestEnabled": true, 8 | "python.analysis.typeCheckingMode": "standard", 9 | "sonarlint.connectedMode.project": { 10 | "connectionId": "YeagerAI", 11 | "projectKey": "yeagerai_genlayer-simulator" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/ModalSection.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/types/results.ts: -------------------------------------------------------------------------------- 1 | export interface GetContractStateResult extends Record {} 2 | 3 | export interface GetProvidersAndModelsData 4 | extends Array<{ 5 | config: Record; 6 | id: number; 7 | model: string; 8 | plugin: string; 9 | plugin_config: Record; 10 | provider: string; 11 | is_available: boolean; 12 | is_model_available: boolean; 13 | is_default: boolean; 14 | }> {} 15 | -------------------------------------------------------------------------------- /frontend/src/hooks/useShortAddress.ts: -------------------------------------------------------------------------------- 1 | export const useShortAddress = () => { 2 | function shorten(address?: string) { 3 | if (!address) { 4 | return '0x'; 5 | } 6 | 7 | const maxChars = 4; 8 | const displayedChars = Math.min(Math.floor(address.length / 3), maxChars); 9 | 10 | return ( 11 | address.slice(0, displayedChars) + '...' + address.slice(-displayedChars) 12 | ); 13 | } 14 | 15 | return { 16 | shorten, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/assets/images/mark_black.svg: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/assets/images/mark_white.svg: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/PageSection.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/hooks/useConfig.ts: -------------------------------------------------------------------------------- 1 | import { getRuntimeConfigBoolean } from '@/utils/runtimeConfig'; 2 | 3 | export const useConfig = () => { 4 | const isHostedEnvironment = getRuntimeConfigBoolean('VITE_IS_HOSTED', false); 5 | const canUpdateValidators = !isHostedEnvironment; 6 | const canUpdateProviders = !isHostedEnvironment; 7 | const canUpdateFinalityWindow = !isHostedEnvironment; 8 | 9 | return { 10 | canUpdateValidators, 11 | canUpdateProviders, 12 | canUpdateFinalityWindow, 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /hardhat/contracts/v2_contracts/interfaces/IGenLayerToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | interface IGenLayerToken is IERC20 { 7 | function mint(address account, uint256 amount) external; 8 | function burn(uint256 amount) external; 9 | function setTimelockFactory(address _timelockFactory) external; 10 | function getInflationOverSeconds( 11 | uint256 secs 12 | ) external view returns (uint256); 13 | } -------------------------------------------------------------------------------- /frontend/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url'; 2 | import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'; 3 | import viteConfig from './vite.config'; 4 | 5 | export default defineConfig((env) => 6 | mergeConfig( 7 | viteConfig(env), 8 | defineConfig({ 9 | test: { 10 | environment: 'jsdom', 11 | exclude: [...configDefaults.exclude, 'test/e2e/**'], 12 | root: fileURLToPath(new URL('./', import.meta.url)), 13 | }, 14 | }), 15 | ), 16 | ); 17 | -------------------------------------------------------------------------------- /frontend/src/components/JsonViewer/types/json-undefined.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /tests/integration/features/parallel_deployment.feature: -------------------------------------------------------------------------------- 1 | @parallel @cleanup_transactions 2 | Feature: Parallel Contract Deployment 3 | 4 | Background: 5 | Given all required containers are running 6 | 7 | Scenario: Deploy multiple contracts simultaneously 8 | Given I have a slow contract that takes time to initialize 9 | When I start deploying the contract 5 times in parallel 10 | Then within 2 seconds all 5 transactions should have status "PROPOSING" 11 | And all 5 transactions should have different transaction IDs -------------------------------------------------------------------------------- /tests/integration/features/websocket_notifications.feature: -------------------------------------------------------------------------------- 1 | Feature: WebSocket Notifications 2 | 3 | Scenario: Client receives real-time status updates 4 | Given a client is connected via WebSocket 5 | And subscribed to transaction "tx_123" 6 | When the transaction moves through states 7 | Then the client should receive events: 8 | | event_type | status | timestamp | 9 | | update | PROPOSING | | 10 | | update | ACCEPTED | | 11 | | update | FINALIZED | | -------------------------------------------------------------------------------- /.github/workflows/conventional-commit-pr.yml: -------------------------------------------------------------------------------- 1 | name: PR Conventional Commit Validation 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, edited] 6 | branches: [main] 7 | 8 | jobs: 9 | validate-pr-title: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: PR Conventional Commit Validation 13 | uses: ytanikin/PRConventionalCommits@1.3.0 14 | with: 15 | task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]' 16 | add_label: "false" 17 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "noEmit": true, 13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 14 | 15 | "module": "ESNext", 16 | "moduleResolution": "Bundler", 17 | "types": ["node"] 18 | } 19 | } -------------------------------------------------------------------------------- /hardhat/contracts/v2_contracts/interfaces/IGenManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | interface IGenManager { 5 | function updateRandomSeedForRecipient( 6 | address _recipient, 7 | address _sender, 8 | bytes calldata _vrfProof 9 | ) external returns (bytes32 randomSeed); 10 | 11 | function addNewRandomSeedForRecipient( 12 | address _recipient, 13 | bytes32 _randomSeed 14 | ) external; 15 | 16 | function recipientRandomSeed( 17 | address _recipient 18 | ) external view returns (bytes32); 19 | } -------------------------------------------------------------------------------- /frontend/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useUniqueId'; 2 | export * from './useEventTracking'; 3 | export * from './useRpcClient'; 4 | export * from './useDb'; 5 | export * from './useWebSocketClient'; 6 | export * from './useInputMap'; 7 | export * from './useContractQueries'; 8 | export * from './useFileName'; 9 | export * from './useSetupStores'; 10 | export * from './useGenlayer'; 11 | export * from './useShortAddress'; 12 | export * from './useConfig'; 13 | export * from './useTransactionListener'; 14 | export * from './useContractListener'; 15 | -------------------------------------------------------------------------------- /frontend/test/utils/driver.ts: -------------------------------------------------------------------------------- 1 | import { Builder, Browser, WebDriver } from 'selenium-webdriver'; 2 | import chrome from 'selenium-webdriver/chrome'; 3 | 4 | export async function getDriver(): Promise { 5 | const options = new chrome.Options(); 6 | options.addArguments('--disable-search-engine-choice-screen'); 7 | 8 | const driver = await new Builder() 9 | .forBrowser(Browser.CHROME) 10 | .setChromeOptions(options) 11 | .build(); 12 | 13 | await driver.manage().setTimeouts({ implicit: 5000 }); 14 | 15 | return driver; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "test", "src/global.d.ts"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "types": ["node", "jsdom"], 7 | "lib": ["ES6", "dom", "ESNext", "ES2015"], 8 | "composite": true, 9 | "skipLibCheck": true, 10 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 11 | "allowJs": true, 12 | "baseUrl": ".", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/integration/icontracts/contracts/read_erc20.py: -------------------------------------------------------------------------------- 1 | # v0.1.0 2 | # { "Depends": "py-genlayer:test" } 3 | 4 | from genlayer import * 5 | 6 | 7 | class read_erc20(gl.Contract): 8 | token_contract: Address 9 | 10 | def __init__(self, token_contract: str): 11 | self.token_contract = Address(token_contract) 12 | 13 | @gl.public.view 14 | def get_balance_of(self, account_address: str) -> int: 15 | return ( 16 | gl.get_contract_at(self.token_contract) 17 | .view() 18 | .get_balance_of(account_address) 19 | ) 20 | -------------------------------------------------------------------------------- /frontend/src/components/JsonViewer/types/json-function.vue: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /frontend/src/components/global/inputs/CheckboxInput.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /tests/load/reset_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script to clear the transactions table in GenLayer Studio database 4 | # This is useful when you get duplicate key constraint errors 5 | 6 | # Base URL (can be overridden via environment variable) 7 | BASE_URL=${BASE_URL:-"http://localhost:4000/api"} 8 | 9 | echo "Clearing transactions table at $BASE_URL..." 10 | 11 | curl -X POST "$BASE_URL" \ 12 | -H "Content-Type: application/json" \ 13 | -d '{"jsonrpc":"2.0","method":"sim_clearDbTables","params":[["transactions"]],"id":1}' 14 | 15 | echo "" 16 | echo "Transactions table cleared." -------------------------------------------------------------------------------- /frontend/src/components/JsonViewer/types/json-date.vue: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /examples/contracts/storage.py: -------------------------------------------------------------------------------- 1 | # v0.1.0 2 | # { "Depends": "py-genlayer:latest" } 3 | 4 | from genlayer import * 5 | 6 | 7 | # contract class 8 | class Storage(gl.Contract): 9 | storage: str 10 | 11 | # constructor 12 | def __init__(self, initial_storage: str): 13 | self.storage = initial_storage 14 | 15 | # read methods must be annotated with view 16 | @gl.public.view 17 | def get_storage(self) -> str: 18 | return self.storage 19 | 20 | # write method 21 | @gl.public.write 22 | def update_storage(self, new_storage: str) -> None: 23 | self.storage = new_storage 24 | -------------------------------------------------------------------------------- /frontend/test/pages/ContractsPage.ts: -------------------------------------------------------------------------------- 1 | import { By, type Locator, until } from 'selenium-webdriver'; 2 | import { BasePage } from './BasePage'; 3 | 4 | export class ContractsPage extends BasePage { 5 | override baseurl = 'http://localhost:8080/contracts'; 6 | override visibleLocator: Locator = By.xpath( 7 | "//*[@data-testid='contracts-page-title']", 8 | ); 9 | 10 | async openContract(name: string) { 11 | const locator = By.xpath( 12 | `//*[@data-testid='contract-file' and contains(text(), '${name}')]`, 13 | ); 14 | return this.driver.wait(until.elementLocated(locator), 2000).click(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/HomeFeatureItem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | {{ title }} 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/components/global/fields/BooleanField.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | {{ label }} 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/integration/features/concurrent_transactions.feature: -------------------------------------------------------------------------------- 1 | Feature: Concurrent Transactions 2 | 3 | Scenario: Multiple transactions for same contract address 4 | Given I have a contract at address "0x123" 5 | When I submit 3 different updates simultaneously 6 | Then only 1 transaction should be accepted 7 | And the other 2 should be queued as "PENDING" 8 | 9 | Scenario: Rapid fire transactions from same user 10 | Given a user has a rate limit of 10 tx/minute 11 | When the user submits 15 transactions in 10 seconds 12 | Then the first 10 should be processed 13 | And the remaining 5 should be queued or rejected -------------------------------------------------------------------------------- /backend/protocol_rpc/message_handler/config/logging.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "formatters": { 4 | "default": { 5 | "format": "[%(asctime)s] %(levelname)s | %(module)s >>> %(message)s", 6 | "datefmt": "%B %d, %Y %H:%M:%S %Z" 7 | }, 8 | "simple": { 9 | "format": "%(levelname)s | %(message)s" 10 | } 11 | }, 12 | "handlers": { 13 | "console": { 14 | "class": "logging.StreamHandler", 15 | "formatter": "simple" 16 | } 17 | }, 18 | "root": { 19 | "level": "INFO", 20 | "handlers": ["console"] 21 | } 22 | } -------------------------------------------------------------------------------- /backend/protocol_rpc/calls_intercept/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from backend.database_handler.transactions_processor import TransactionsProcessor 3 | 4 | 5 | class CallHandler(ABC): 6 | """Abstract base class for ConsensusData contract call handlers.""" 7 | 8 | @abstractmethod 9 | def can_handle(self, data: str) -> bool: 10 | """Check if this handler can process the given call data.""" 11 | pass 12 | 13 | @abstractmethod 14 | def handle(self, transactions_processor: TransactionsProcessor, data: str) -> str: 15 | """Handle the contract call and return the result.""" 16 | pass 17 | -------------------------------------------------------------------------------- /hardhat/contracts/v2_contracts/interfaces/IMessages.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | interface IMessages { 5 | enum MessageType { 6 | External, 7 | Internal 8 | } 9 | 10 | struct SubmittedMessage { 11 | MessageType messageType; 12 | address recipient; 13 | uint256 value; 14 | bytes data; 15 | bool onAcceptance; // true = on acceptance, false = on finalization 16 | } 17 | 18 | function executeMessage(IMessages.SubmittedMessage memory message) external; 19 | 20 | function emitMessagesOnAcceptance(bytes32 _tx_id) external; 21 | 22 | function emitMessagesOnFinalization(bytes32 _tx_id) external; 23 | } -------------------------------------------------------------------------------- /frontend/src/components/JsonViewer/types/json-number.vue: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended", "group:recommended", "group:allNonMajor", "group:githubArtifactActions"], 4 | "packageRules": [ 5 | { 6 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 7 | "automerge": true 8 | } 9 | ], 10 | "vulnerabilityAlerts": { 11 | "enabled": true 12 | }, 13 | "osvVulnerabilityAlerts": true, 14 | "dependencyDashboard": true, 15 | "npm": { 16 | "minimumReleaseAge": "3 days" 17 | }, 18 | "reviewers": ["denishacquin", "mpaya5", "kstroobants", "epsjunior"], 19 | "baseBranches": ["staging"] 20 | } 21 | -------------------------------------------------------------------------------- /tests/integration/icontracts/tests/test_multi_file_contract.py: -------------------------------------------------------------------------------- 1 | from gltest import get_contract_factory 2 | from gltest.assertions import tx_execution_succeeded 3 | from gltest.types import TransactionStatus 4 | 5 | 6 | def test_deploy(setup_validators): 7 | setup_validators() 8 | factory = get_contract_factory("MultiFileContract") 9 | contract = factory.deploy( 10 | args=[], 11 | wait_transaction_status=TransactionStatus.FINALIZED, 12 | wait_triggered_transactions=True, 13 | wait_triggered_transactions_status=TransactionStatus.ACCEPTED, 14 | ) 15 | 16 | res = contract.test(args=[]).call() 17 | assert res == "123" 18 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript', 10 | '@vue/eslint-config-prettier/skip-formatting' 11 | ], 12 | rules: { 13 | 'vue/multi-word-component-names': 'off' 14 | }, 15 | overrides: [ 16 | { 17 | files: [ 18 | 'tests/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}', 19 | 'tests/support/**/*.{js,ts,jsx,tsx}' 20 | ] 21 | } 22 | ], 23 | parserOptions: { 24 | ecmaVersion: 'latest' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | import colors from 'tailwindcss/colors'; 3 | import forms from '@tailwindcss/forms'; 4 | import containerQueries from '@tailwindcss/container-queries'; 5 | 6 | export default { 7 | content: ['./src/**/*.{html,js,ts,tsx,vue}'], 8 | darkMode: ['selector', '[data-mode="dark"]'], 9 | theme: { 10 | extend: { 11 | transitionDuration: { 12 | DEFAULT: '100ms', 13 | }, 14 | colors: { 15 | primary: '#1a3851', 16 | accent: colors.sky[500], 17 | transparent: 'transparent', 18 | }, 19 | }, 20 | }, 21 | plugins: [forms, containerQueries], 22 | }; 23 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/ollama_gemma.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "ollama", 3 | "plugin": "ollama", 4 | "model": "gemma", 5 | "config": { 6 | "mirostat": 0, 7 | "mirostat_eta": 0.1, 8 | "microstat_tau": 5, 9 | "num_ctx": 2048, 10 | "num_qga": 8, 11 | "num_gpu": 0, 12 | "num_thread": 2, 13 | "repeat_last_n": 64, 14 | "repeat_penalty": 1.1, 15 | "temprature": 0.8, 16 | "seed": 0, 17 | "stop": "", 18 | "tfs_z": 1.0, 19 | "num_predict": 128, 20 | "top_k": 40, 21 | "top_p": 0.9 22 | }, 23 | "plugin_config": { 24 | "api_key_env_var": "", 25 | "api_url": "http://ollama:11434" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/ollama_llama3.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "ollama", 3 | "plugin": "ollama", 4 | "model": "llama3", 5 | "config": { 6 | "mirostat": 0, 7 | "mirostat_eta": 0.1, 8 | "microstat_tau": 5, 9 | "num_ctx": 2048, 10 | "num_qga": 8, 11 | "num_gpu": 0, 12 | "num_thread": 2, 13 | "repeat_last_n": 64, 14 | "repeat_penalty": 1.1, 15 | "temprature": 0.8, 16 | "seed": 0, 17 | "stop": "", 18 | "tfs_z": 1.0, 19 | "num_predict": 128, 20 | "top_k": 40, 21 | "top_p": 0.9 22 | }, 23 | "plugin_config": { 24 | "api_key_env_var": "", 25 | "api_url": "http://ollama:11434" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/components/global/fields/StringField.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | {{ name }} 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /backend/node/create_nodes/default_providers/ollama_mistral.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "ollama", 3 | "plugin": "ollama", 4 | "model": "mistral", 5 | "config": { 6 | "mirostat": 0, 7 | "mirostat_eta": 0.1, 8 | "microstat_tau": 5, 9 | "num_ctx": 2048, 10 | "num_qga": 8, 11 | "num_gpu": 0, 12 | "num_thread": 2, 13 | "repeat_last_n": 64, 14 | "repeat_penalty": 1.1, 15 | "temprature": 0.8, 16 | "seed": 0, 17 | "stop": "", 18 | "tfs_z": 1.0, 19 | "num_predict": 128, 20 | "top_k": 40, 21 | "top_p": 0.9 22 | }, 23 | "plugin_config": { 24 | "api_key_env_var": "", 25 | "api_url": "http://ollama:11434" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/components/global/fields/IntegerField.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | {{ name }} 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/integration/icontracts/contracts/multi_file_contract/__init__.py: -------------------------------------------------------------------------------- 1 | from genlayer import * 2 | 3 | 4 | class MultiFileContract(gl.Contract): 5 | other_addr: Address 6 | 7 | def __init__(self): 8 | with open("/contract/other.py", "rt") as f: 9 | text = f.read() 10 | self.other_addr = gl.deploy_contract( 11 | code=text.encode("utf-8"), 12 | args=["123"], 13 | salt_nonce=u256(1), 14 | value=u256(0), 15 | ) 16 | 17 | @gl.public.write 18 | def wait(self) -> None: 19 | pass 20 | 21 | @gl.public.view 22 | def test(self) -> str: 23 | return gl.get_contract_at(self.other_addr).view().test() 24 | -------------------------------------------------------------------------------- /frontend/src/types/events.ts: -------------------------------------------------------------------------------- 1 | export interface EventProperties { 2 | created_account: {}; 3 | created_validator: { 4 | validator_provider: string; 5 | validator_model: string; 6 | validator_stake: number; 7 | }; 8 | created_contract: { 9 | contract_name: string; 10 | }; 11 | deployed_contract: { 12 | contract_name: string; 13 | }; 14 | called_read_method: { 15 | contract_name: string; 16 | method_name: string; 17 | }; 18 | called_write_method: { 19 | contract_name: string; 20 | method_name: string; 21 | }; 22 | simulated_write_method: { 23 | contract_name: string; 24 | method_name: string; 25 | }; 26 | } 27 | 28 | export type EventName = keyof EventProperties; 29 | -------------------------------------------------------------------------------- /frontend/src/components/global/CopyTextButton.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /frontend/src/components/global/inputs/TextInput.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 28 | 29 | -------------------------------------------------------------------------------- /frontend/src/components/global/inputs/TextAreaInput.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/src/components/global/GhostBtn.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/integration/icontracts/schemas/wizard_get_contract_schema_for_code.py: -------------------------------------------------------------------------------- 1 | wizard_contract_schema = { 2 | "id": 1, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "ctor": {"kwparams": {}, "params": [["have_coin", "bool"]]}, 6 | "methods": { 7 | "ask_for_coin": { 8 | "kwparams": {}, 9 | "params": [["request", "string"]], 10 | "payable": False, 11 | "readonly": False, 12 | "ret": "null", 13 | }, 14 | "get_have_coin": { 15 | "kwparams": {}, 16 | "params": [], 17 | "readonly": True, 18 | "ret": "bool", 19 | }, 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /backend/node/genvm/origin/host_fns.py: -------------------------------------------------------------------------------- 1 | # This file is auto-generated. Do not edit! 2 | 3 | from enum import IntEnum, StrEnum 4 | import typing 5 | 6 | 7 | class Methods(IntEnum): 8 | GET_CALLDATA = 0 9 | STORAGE_READ = 1 10 | STORAGE_WRITE = 2 11 | CONSUME_RESULT = 3 12 | GET_LEADER_NONDET_RESULT = 4 13 | POST_NONDET_RESULT = 5 14 | POST_MESSAGE = 6 15 | POST_EVENT = 7 16 | CONSUME_FUEL = 8 17 | DEPLOY_CONTRACT = 9 18 | ETH_CALL = 10 19 | ETH_SEND = 11 20 | GET_BALANCE = 12 21 | REMAINING_FUEL_AS_GEN = 13 22 | NOTIFY_NONDET_DISAGREEMENT = 14 23 | 24 | 25 | class Errors(IntEnum): 26 | OK = 0 27 | ABSENT = 1 28 | FORBIDDEN = 2 29 | I_AM_LEADER = 3 30 | OUT_OF_STORAGE_GAS = 4 31 | -------------------------------------------------------------------------------- /tests/integration/icontracts/ideas.md: -------------------------------------------------------------------------------- 1 | # ideas for contract-to-contract interaction 2 | 3 | - read / write 4 | - run / deploy 5 | 6 | ## Chainlink like read contract 7 | 8 | main "chainlink" oracle contract which reads data from the internet (e.g. which day is it today) 9 | 10 | - this could use web requests + llms for parsing 11 | - it could also use a public API 12 | 13 | secondary consumer contracts would plug into the "chainlink" contract to consume it's data 14 | 15 | ## Uniswap 16 | 17 | ## Multiple Wizards passing the coin between them 18 | 19 | ## Multi sig 20 | 21 | ## Lottery 22 | 23 | would require allowance 24 | stardard ERC20 25 | 26 | ## Proxy for upgrades 27 | 28 | would require a sort of fallback method like python's `__getattr__` 29 | -------------------------------------------------------------------------------- /tests/integration/icontracts/schemas/storage_get_contract_schema_for_code.py: -------------------------------------------------------------------------------- 1 | storage_contract_schema = { 2 | "id": 1, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "ctor": {"kwparams": {}, "params": [["initial_storage", "string"]]}, 6 | "methods": { 7 | "get_storage": { 8 | "kwparams": {}, 9 | "params": [], 10 | "readonly": True, 11 | "ret": "string", 12 | }, 13 | "update_storage": { 14 | "kwparams": {}, 15 | "params": [["new_storage", "string"]], 16 | "payable": False, 17 | "readonly": False, 18 | "ret": "null", 19 | }, 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/components/global/fields/FloatField.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | {{ name }} 21 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/contracts/user_storage.py: -------------------------------------------------------------------------------- 1 | # v0.1.0 2 | # { "Depends": "py-genlayer:latest" } 3 | 4 | from genlayer import * 5 | 6 | 7 | class UserStorage(gl.Contract): 8 | storage: TreeMap[Address, str] 9 | 10 | # constructor 11 | def __init__(self): 12 | pass 13 | 14 | # read methods must be annotated 15 | @gl.public.view 16 | def get_complete_storage(self) -> dict[str, str]: 17 | return {k.as_hex: v for k, v in self.storage.items()} 18 | 19 | @gl.public.view 20 | def get_account_storage(self, account_address: str) -> str: 21 | return self.storage[Address(account_address)] 22 | 23 | @gl.public.write 24 | def update_storage(self, new_storage: str) -> None: 25 | self.storage[gl.message.sender_address] = new_storage 26 | -------------------------------------------------------------------------------- /backend/database_handler/migration/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from typing import Sequence, Union 9 | 10 | from alembic import op 11 | import sqlalchemy as sa 12 | ${imports if imports else ""} 13 | 14 | # revision identifiers, used by Alembic. 15 | revision: str = ${repr(up_revision)} 16 | down_revision: Union[str, None] = ${repr(down_revision)} 17 | branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} 18 | depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} 19 | 20 | 21 | def upgrade() -> None: 22 | ${upgrades if upgrades else "pass"} 23 | 24 | 25 | def downgrade() -> None: 26 | ${downgrades if downgrades else "pass"} 27 | -------------------------------------------------------------------------------- /frontend/test/unit/hooks/useConfig.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach, vi } from 'vitest'; 2 | import { useConfig } from '@/hooks'; 3 | 4 | describe('useConfig', () => { 5 | beforeEach(() => { 6 | vi.stubEnv('VITE_IS_HOSTED', 'false'); 7 | }); 8 | 9 | it('should return true for canUpdateValidators when not in hosted environment', () => { 10 | vi.stubEnv('VITE_IS_HOSTED', 'false'); 11 | 12 | const { canUpdateValidators } = useConfig(); 13 | expect(canUpdateValidators).toBe(true); 14 | }); 15 | 16 | it('should return false for canUpdateValidators when in hosted environment', () => { 17 | vi.stubEnv('VITE_IS_HOSTED', 'true'); 18 | 19 | const { canUpdateValidators } = useConfig(); 20 | expect(canUpdateValidators).toBe(false); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /frontend/test/unit/hooks/useUniqueId.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest'; 2 | import { useUniqueId } from '@/hooks'; 3 | import { v4 as uuidv4 } from 'uuid'; 4 | 5 | vi.mock('uuid', () => ({ 6 | v4: vi.fn(() => 'mocked-uuid'), 7 | })); 8 | 9 | describe('useUniqueId function', () => { 10 | it('should generate a unique ID without prefix', () => { 11 | const result = useUniqueId(); 12 | 13 | expect(uuidv4).toHaveBeenCalled(); 14 | expect(result).toBe('mocked-uuid'); 15 | }); 16 | 17 | it('should generate a unique ID with the correct prefix', () => { 18 | const prefix = 'testPrefix'; 19 | const result = useUniqueId(prefix); 20 | 21 | expect(uuidv4).toHaveBeenCalled(); 22 | expect(result).toBe(`${prefix}-mocked-uuid`); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/integration/icontracts/contracts/error_web_contract.py: -------------------------------------------------------------------------------- 1 | # v0.1.0 2 | # { "Depends": "py-genlayer:test" } 3 | 4 | from genlayer import * 5 | 6 | 7 | class ErrorWebContract(gl.Contract): 8 | def __init__(self, testcase: int, url: str): 9 | 10 | if testcase == 1: 11 | self.test_system_error(url) 12 | elif testcase == 2: 13 | self.test_connect_to_url(url) 14 | 15 | def test_system_error(self, url: str): 16 | result = gl.nondet.web.render(url, mode="text") 17 | return result 18 | 19 | def test_connect_to_url(self, url: str): 20 | def get_url_data(): 21 | web_data = gl.nondet.web.render(url, mode="text") 22 | return web_data 23 | 24 | result = gl.eq_principle.strict_eq(get_url_data) 25 | -------------------------------------------------------------------------------- /frontend/src/views/Simulator/SettingsView.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | Settings 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | /cypress/videos/ 17 | /cypress/screenshots/ 18 | 19 | # 👉 Custom Git ignores 20 | 21 | # Editor directories and files 22 | .vscode/* 23 | !.vscode/extensions.json 24 | !.vscode/settings.json 25 | !.vscode/*.code-snippets 26 | !.vscode/tours 27 | .idea 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | *.sw? 33 | .yarn 34 | 35 | # iconify dist files 36 | src/plugins/iconify/icons.css 37 | 38 | # Ignore MSW script 39 | public/mockServiceWorker.js 40 | 41 | # Env files 42 | .env* 43 | !.env.example 44 | 45 | src/assets/examples 46 | .nyc_output 47 | coverage 48 | vite.config.ts.timestamp-* -------------------------------------------------------------------------------- /frontend/scripts/contract-examples.js: -------------------------------------------------------------------------------- 1 | import { existsSync } from 'node:fs'; 2 | import pkg from 'ncp'; 3 | const { ncp } = pkg; 4 | import path from 'node:path'; 5 | import fs from 'node:fs'; 6 | 7 | ncp.limit = 16; 8 | const source = path.resolve('../examples'); 9 | const destionation = path.resolve('./src/assets/examples'); 10 | if (existsSync(destionation)) { 11 | console.log('Contract Examples already exists'); 12 | try { 13 | fs.rmSync(destionation, { recursive: true, force: true }); 14 | } catch (error) { 15 | console.log('Could not remove existing examples, copying anyway...'); 16 | } 17 | } 18 | 19 | ncp(source, destionation, 20 | function (err) { 21 | if (err) { 22 | return console.error(err); 23 | } 24 | console.log('Contract Examples copied'); 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/components/global/inputs/NumberInput.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/test/utils/pages.ts: -------------------------------------------------------------------------------- 1 | import { ContractsPage } from '../pages/ContractsPage.js'; 2 | import { RunDebugPage } from '../pages/RunDebugPage.js'; 3 | import { SettingsPage } from '../pages/SettingsPage.js'; 4 | import { TutorialPage } from '../pages/TutorialPage.js'; 5 | import { WebDriver } from 'selenium-webdriver'; 6 | 7 | export class PageFactory { 8 | private _driver: WebDriver; 9 | 10 | constructor(driver: WebDriver) { 11 | this._driver = driver; 12 | } 13 | 14 | getContractsPage() { 15 | return new ContractsPage(this._driver); 16 | } 17 | getRunDebugPage() { 18 | return new RunDebugPage(this._driver); 19 | } 20 | getSettingsPage() { 21 | return new SettingsPage(this._driver); 22 | } 23 | 24 | getTutorialPage() { 25 | return new TutorialPage(this._driver); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/stores/ui.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import type { UIMode, UIState } from '@/types'; 3 | 4 | export const useUIStore = defineStore('ui', { 5 | state: (): UIState => { 6 | return { 7 | mode: (localStorage.getItem('genLayer.ui-mode') as UIMode) || 'light', 8 | showTutorial: false, 9 | }; 10 | }, 11 | actions: { 12 | toggleMode() { 13 | if (this.mode === 'light') { 14 | this.mode = 'dark'; 15 | } else { 16 | this.mode = 'light'; 17 | } 18 | 19 | this.initialize(); 20 | }, 21 | initialize() { 22 | localStorage.setItem('genLayer.ui-mode', this.mode); 23 | document.documentElement.setAttribute('data-mode', this.mode); 24 | }, 25 | runTutorial() { 26 | this.showTutorial = true; 27 | }, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-JSONRPC==3.0.1 2 | Flask[async]==3.1.0 3 | flask-socketio==5.5.1 4 | Flask-Cors==5.0.1 5 | psycopg2-binary==2.9.10 6 | requests==2.32.3 7 | python-dotenv==1.1.0 8 | pydantic==2.11.3 9 | numpy==1.26.4 10 | pytest==8.3.5 11 | pytest-asyncio==0.26.0 12 | colorama==0.4.6 13 | debugpy==1.8.14 14 | aiohttp==3.11.16 15 | SQLAlchemy[asyncio]==2.0.40 16 | alembic==1.15.2 17 | eth-account==0.13.6 18 | eth-utils==5.3.0 19 | jsf==0.11.2 20 | jsonschema==4.23.0 21 | loguru==0.7.3 22 | web3==7.10.0 23 | PyYAML==6.0.2 24 | genvm-linter==0.1.0 25 | 26 | # FastAPI and ASGI dependencies 27 | fastapi==0.104.1 28 | uvicorn[standard]==0.30.6 29 | websockets==12.0 30 | hypercorn==0.17.3 31 | asgiref==3.8.1 32 | 33 | # Production dependencies 34 | setuptools==75.7.0 35 | redis==5.0.1 36 | prometheus-flask-exporter==0.23.0 37 | psutil==5.9.8 38 | -------------------------------------------------------------------------------- /frontend/test/unit/hooks/useRpcClient.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from 'vitest'; 2 | import { useRpcClient } from '@/hooks'; 3 | import { RpcClient } from '@/clients/rpc'; 4 | import { JsonRpcService } from '@/services/JsonRpcService'; 5 | 6 | vi.mock('@/clients/rpc', () => ({ 7 | RpcClient: vi.fn(), 8 | })); 9 | 10 | vi.mock('@/services/JsonRpcService', () => ({ 11 | JsonRpcService: vi.fn(), 12 | })); 13 | 14 | describe('useRpcClient', () => { 15 | beforeEach(() => { 16 | vi.clearAllMocks(); 17 | }); 18 | 19 | it('should create a new JsonRpcService with a new RpcClient instance', () => { 20 | useRpcClient(); 21 | 22 | expect(RpcClient).toHaveBeenCalledTimes(1); 23 | expect(JsonRpcService).toHaveBeenCalledWith(expect.any(RpcClient)); 24 | expect(JsonRpcService).toHaveBeenCalledTimes(1); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /hardhat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-node", 3 | "version": "1.0.0", 4 | "description": "Hardhat node for genlayer-simulator", 5 | "scripts": { 6 | "node": "hardhat node" 7 | }, 8 | "dependencies": { 9 | "@nomicfoundation/hardhat-ethers": "^3.0.0", 10 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 11 | "@nomicfoundation/hardhat-toolbox": "^5.0.0", 12 | "@nomicfoundation/hardhat-verify": "^2.0.0", 13 | "@openzeppelin/contracts": "^5.1.0", 14 | "@openzeppelin/contracts-upgradeable": "^5.1.0", 15 | "@typechain/hardhat": "^9.0.0", 16 | "ethers": "^6.4.0", 17 | "hardhat": "^2.22.1", 18 | "hardhat-gas-reporter": "^1.0.8", 19 | "solidity-coverage": "^0.8.0" 20 | }, 21 | "engines": { 22 | "node": ">=18.0.0" 23 | }, 24 | "devDependencies": { 25 | "hardhat-deploy": "^1.0.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hardhat/contracts/v2_contracts/interfaces/IGhostFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | /// @title IGhostFactory 5 | /// @notice Interface for creating and managing ghost contracts in the GenVM system 6 | interface IGhostFactory { 7 | /// @notice Creates a new ghost contract 8 | /// @return The address where the contract will be deployed 9 | function createGhost() external returns (address); 10 | 11 | /// @notice Checks if an address is a ghost contract 12 | /// @param contractAddress The address to check 13 | /// @return bool True if the address is a ghost contract 14 | function isGhost(address contractAddress) external view returns (bool); 15 | 16 | /// @notice Returns the latest ghost contract address 17 | /// @return The address of the latest ghost contract 18 | function latestGhost() external view returns (address); 19 | } -------------------------------------------------------------------------------- /frontend/src/components/SimulatorMenuItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/DiscordIcon.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docker/Dockerfile.database-migration: -------------------------------------------------------------------------------- 1 | # base image mostly copied from Dockerfile.backend to reuse cache 2 | FROM python:3.13.3-slim AS base 3 | 4 | ARG path=/app 5 | WORKDIR $path 6 | 7 | RUN apt-get update && \ 8 | apt-get install -y --no-install-recommends build-essential && \ 9 | apt-get clean && \ 10 | rm -rf /var/lib/apt/lists/* 11 | 12 | COPY backend/requirements.txt backend/requirements.txt 13 | RUN --mount=type=cache,target=/root/.cache/pip \ 14 | pip install --cache-dir=/root/.cache/pip -r backend/requirements.txt 15 | 16 | COPY ../.env . 17 | COPY backend $path/backend 18 | 19 | FROM base AS migration 20 | 21 | ENV PYTHONPATH="" 22 | WORKDIR /app/backend/database_handler 23 | 24 | RUN --mount=type=cache,target=/root/.cache/pip \ 25 | pip install --cache-dir=/root/.cache/pip -r migration/requirements.txt 26 | 27 | ENTRYPOINT [ "alembic" ] 28 | CMD [ "upgrade", "head" ] 29 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Items that don't need to be in a Docker image. 2 | # Anything not used by the build system should go here. 3 | .git 4 | .dockerignore 5 | .gitignore 6 | .github/ 7 | README.md 8 | 9 | docker/ 10 | !docker/entrypoint-*.sh 11 | .venv/ 12 | .vscode/ 13 | .ollama/ 14 | 15 | # Artifacts that will be built during image creation. 16 | # This should contain all files created during `yarn build`. 17 | frontend/dist 18 | frontend/node_modules 19 | frontend/npm-debug.log 20 | frontend/.DS_Store 21 | 22 | frontend/src/assets/examples 23 | frontend/.nyc_output 24 | frontend/coverage 25 | frontend/vite.config.ts.timestamp-* 26 | 27 | # Python 28 | # Byte-compiled / optimized / DLL files 29 | **/__pycache__/ 30 | **/*.py[cod] 31 | 32 | # Hardhat files 33 | hardhat/cache/ 34 | hardhat/artifacts/ 35 | hardhat/node_modules/ 36 | hardhat/coverage/ 37 | hardhat/.env 38 | hardhat/coverage.json 39 | hardhat/typechain-types/ 40 | -------------------------------------------------------------------------------- /frontend/test/pages/BasePage.ts: -------------------------------------------------------------------------------- 1 | import { By, until, WebDriver, type Locator } from 'selenium-webdriver'; 2 | 3 | export class BasePage { 4 | protected readonly driver: WebDriver; 5 | readonly baseurl?: string; 6 | readonly visibleLocator?: Locator; 7 | 8 | public constructor(driver: WebDriver) { 9 | this.driver = driver; 10 | } 11 | 12 | async close() { 13 | await this.driver.quit(); 14 | } 15 | 16 | async waitUntilVisible() { 17 | if (this.visibleLocator) { 18 | await this.driver.wait(until.elementLocated(this.visibleLocator), 2000); 19 | } 20 | } 21 | 22 | async skipTutorial() { 23 | const locator = By.xpath("//button[@data-testid='tutorial-skip-btn']"); 24 | return this.driver.wait(until.elementLocated(locator), 5000).click(); 25 | } 26 | 27 | async navigate() { 28 | if (this.baseurl) { 29 | await this.driver.navigate().to(this.baseurl); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/index.d.ts: -------------------------------------------------------------------------------- 1 | // declare module '@globalhive/vuejs-tour' 2 | declare module 'vue3-json-viewer' { 3 | import { 4 | AllowedComponentProps, 5 | App, 6 | Component, 7 | ComponentCustomProps, 8 | VNodeProps, 9 | } from 'vue'; 10 | 11 | interface JsonViewerProps { 12 | value: Record | Array | string | number | boolean; 13 | expanded: boolean; 14 | expandDepth: number; 15 | copyable: boolean | object; 16 | sort: boolean; 17 | boxed: boolean; 18 | theme: string; 19 | previewMode: boolean; 20 | timeformat: (value: any) => string; 21 | } 22 | 23 | type JsonViewerType = JsonViewerProps & 24 | VNodeProps & 25 | AllowedComponentProps & 26 | ComponentCustomProps; 27 | 28 | const JsonViewer: Component; 29 | 30 | export { JsonViewer }; 31 | 32 | const def: { install: (app: App) => void }; 33 | export default def; 34 | } 35 | -------------------------------------------------------------------------------- /tests/unit/test_types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict 2 | from backend.domain.types import LLMProvider, Validator 3 | 4 | 5 | def test_validator_to_dict(): 6 | validator = Validator( 7 | address="0x1234", 8 | stake=100, 9 | llmprovider=LLMProvider( 10 | provider="provider", 11 | model="model", 12 | config={"config": "config"}, 13 | plugin="plugin", 14 | plugin_config={"plugin_config": "plugin_config"}, 15 | ), 16 | ) 17 | 18 | result = validator.to_dict() 19 | 20 | assert result == { 21 | "address": "0x1234", 22 | "stake": 100, 23 | "provider": "provider", 24 | "model": "model", 25 | "config": {"config": "config"}, 26 | "plugin": "plugin", 27 | "plugin_config": {"plugin_config": "plugin_config"}, 28 | "private_key": None, 29 | "fallback_validator": None, 30 | } 31 | -------------------------------------------------------------------------------- /backend/database_handler/migration/README.md: -------------------------------------------------------------------------------- 1 | ## Pre-requisites 2 | 3 | ### Window One 4 | 5 | 1. Launch the Studio 6 | 7 | ### Window Two 8 | 9 | 1. Set your virtual environment 10 | 2. Pip install `pip install -r backend/database_handler/migration/requirements.txt` 11 | 3. Update the "models" at `models.py` with your preferences 12 | 4. Set your current working directory to `backend/database_handler` 13 | 5. Run `alembic revision --autogenerate -m "migration name here"` to generate the migration file 14 | 6. Modify the migration file if needed 15 | 7. Apply the migration: we have different options here 16 | - [preferred] If using `docker-compose`, run `docker compose up database-migration --build` 17 | - Run `alembic upgrade head` to "manually" apply the migration 18 | 19 | ## Revert migrations 20 | To revert the latest migration, run `alembic downgrade -1` 21 | 22 | ## Docs 23 | 24 | - [Alembic](https://alembic.sqlalchemy.org/en/latest/) 25 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/HomeContractItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 17 | 18 | {{ title }} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | Open Contract 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /frontend/src/hooks/useEventTracking.ts: -------------------------------------------------------------------------------- 1 | import { usePlausible } from 'v-plausible/vue'; 2 | import { type EventName, type EventProperties } from '@/types'; 3 | 4 | export const useEventTracking = () => { 5 | const isDevelopment = import.meta.env.MODE === 'development'; 6 | const { trackEvent: trackPlausibleEvent } = usePlausible(); 7 | 8 | const trackEvent = ( 9 | name: T, 10 | properties?: EventProperties[T], 11 | ) => { 12 | try { 13 | if (isDevelopment) { 14 | console.debug('Track Event (blocked in dev mode)', { 15 | name, 16 | properties, 17 | }); 18 | } else { 19 | console.debug('Track Event', { name, properties }); 20 | trackPlausibleEvent(name, { props: properties }); 21 | } 22 | } catch (err) { 23 | console.error('Failed to track event', err); 24 | } 25 | }; 26 | 27 | return { 28 | trackEvent, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /hardhat/contracts/v2_contracts/ghost_contracts/GhostManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 4 | import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; 7 | 8 | contract GhostManager is 9 | Initializable, 10 | Ownable2StepUpgradeable, 11 | ReentrancyGuardUpgradeable, 12 | AccessControlUpgradeable 13 | { 14 | bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); 15 | bytes32 public constant VALIDATOR_ROLE = keccak256("VALIDATOR_ROLE"); 16 | 17 | receive() external payable {} 18 | 19 | function initialize() public initializer { 20 | __Ownable2Step_init(); 21 | __ReentrancyGuard_init(); 22 | __AccessControl_init(); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/integration/icontracts/schemas/football_prediction_market_get_contract_schema_for_code.py: -------------------------------------------------------------------------------- 1 | football_prediction_market_contract_schema = { 2 | "id": 1, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "ctor": { 6 | "kwparams": {}, 7 | "params": [ 8 | ["game_date", "string"], 9 | ["team1", "string"], 10 | ["team2", "string"], 11 | ], 12 | }, 13 | "methods": { 14 | "get_resolution_data": { 15 | "kwparams": {}, 16 | "params": [], 17 | "readonly": True, 18 | "ret": {"$dict": "any"}, 19 | }, 20 | "resolve": { 21 | "kwparams": {}, 22 | "params": [], 23 | "payable": False, 24 | "readonly": False, 25 | "ret": "any", 26 | }, 27 | }, 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /frontend/test/unit/calldata/decode.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { abi } from 'genlayer-js'; 4 | import { parse as calldataParse } from '@/calldata/parser'; 5 | import { b64ToArray } from '@/calldata/jsonifier'; 6 | 7 | describe('calldata decoding tests', () => { 8 | it('smoke', () => { 9 | const bin_b64 = 10 | 'DgF4PQAQCBgBAQEBAQEBAQEBAQEBAQEBAQEBAcwB0YDRg9GB0YHQutC40LUg0LHRg9C60LLRi1FK'; 11 | const bin_text = 12 | 'eyd4JzpbbnVsbCx0cnVlLGZhbHNlLGFkZHIjMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMSwn0YDRg9GB0YHQutC40LUg0LHRg9C60LLRiycsMTAsLTEwLF0sfQ=='; 13 | 14 | const bin = b64ToArray(bin_b64); 15 | 16 | const text_decoded_to_arr = b64ToArray(bin_text); 17 | const text = new TextDecoder('utf-8').decode(text_decoded_to_arr); 18 | 19 | const parsed = calldataParse(text); 20 | const decoded = abi.calldata.decode(bin); 21 | expect(decoded).toEqual(parsed); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /asgi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | ASGI entry point for FastAPI with native WebSocket support. 4 | """ 5 | 6 | import os 7 | import sys 8 | 9 | # Set production environment 10 | os.environ.setdefault("UVICORN_WORKER", "true") 11 | 12 | # Import FastAPI app 13 | from backend.protocol_rpc.fastapi_server import app 14 | 15 | # Export the ASGI application 16 | 17 | application = app 18 | 19 | if __name__ == "__main__": 20 | import uvicorn 21 | 22 | # Enable reload in development mode 23 | is_debug = os.getenv("BACKEND_BUILD_TARGET") == "debug" 24 | 25 | uvicorn.run( 26 | "asgi:application", 27 | host="0.0.0.0", 28 | port=int(os.getenv("RPCPORT", "4000")), 29 | workers=1 if is_debug else int(os.getenv("WEB_CONCURRENCY", "1")), 30 | log_level=os.getenv("LOG_LEVEL", "info").lower(), 31 | reload=is_debug, 32 | reload_dirs=["backend"] if is_debug else None, 33 | access_log=True, 34 | ) 35 | -------------------------------------------------------------------------------- /backend/healthcheck.py: -------------------------------------------------------------------------------- 1 | """ 2 | File used to check the health of the backend API. 3 | We only check that the API is responding, and that the response is valid. 4 | """ 5 | 6 | import argparse 7 | import json 8 | 9 | import requests 10 | 11 | parser = argparse.ArgumentParser(description="Check the health of the backend API.") 12 | parser.add_argument( 13 | "--port", 14 | type=str, 15 | nargs="?", 16 | help="The port number for the API", 17 | ) 18 | 19 | args = parser.parse_args() 20 | 21 | response = requests.post( 22 | f"http://localhost:{args.port}/api", 23 | data=json.dumps( 24 | { 25 | "jsonrpc": "2.0", 26 | "method": "ping", 27 | "params": [], 28 | "id": 1, 29 | } 30 | ), 31 | headers={"Content-Type": "application/json"}, 32 | ) 33 | assert response.status_code == 200 34 | print(response.json()) 35 | assert response.json() == {"id": 1, "jsonrpc": "2.0", "result": "OK"} 36 | -------------------------------------------------------------------------------- /.github/workflows/frontend-unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: "Frontend Unit Tests" 2 | 3 | on: 4 | workflow_call: 5 | secrets: 6 | codecov_token: 7 | required: true 8 | 9 | jobs: 10 | frontend: 11 | runs-on: ubuntu-latest 12 | defaults: 13 | run: 14 | working-directory: frontend 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Use Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | - name: Copy examples to assets 22 | run: cp -r examples frontend/src/assets/examples 23 | working-directory: . 24 | 25 | - run: npm ci 26 | - run: npm run coverage 27 | 28 | - name: Upload coverage reports to Codecov 29 | 30 | uses: codecov/codecov-action@v5.4.2 31 | with: 32 | verbose: true 33 | token: ${{ secrets.codecov_token }} 34 | fail_ci_if_error: true 35 | directory: frontend/coverage 36 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/TelegramIcon.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/hardhat/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | hardhat: 3 | build: 4 | context: . 5 | dockerfile: ./docker/Dockerfile.hardhat 6 | ports: 7 | - "8545:8545" 8 | volumes: 9 | - ./hardhat/contracts:/app/contracts 10 | - ./hardhat/scripts:/app/scripts 11 | - ./hardhat/test:/app/test 12 | - ./hardhat/hardhat.config.js:/app/hardhat.config.js 13 | - ./hardhat/artifacts:/app/artifacts 14 | environment: 15 | - HARDHAT_NETWORK=hardhat 16 | healthcheck: 17 | test: ["CMD", "nc", "-z", "localhost", "8545"] 18 | interval: 20s 19 | timeout: 20s 20 | retries: 10 21 | start_period: 20s 22 | 23 | tests: 24 | build: 25 | context: ./ 26 | dockerfile: ./tests/hardhat/Dockerfile 27 | volumes: 28 | - ./hardhat/artifacts:/app/hardhat/artifacts 29 | depends_on: 30 | hardhat: 31 | condition: service_healthy 32 | environment: 33 | - HARDHAT_URL=http://hardhat:8545 34 | -------------------------------------------------------------------------------- /tests/integration/icontracts/schemas/user_storage_get_contract_schema_for_code.py: -------------------------------------------------------------------------------- 1 | user_storage_contract_schema = { 2 | "id": 1, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "ctor": {"kwparams": {}, "params": []}, 6 | "methods": { 7 | "get_account_storage": { 8 | "kwparams": {}, 9 | "params": [["account_address", "string"]], 10 | "readonly": True, 11 | "ret": "string", 12 | }, 13 | "get_complete_storage": { 14 | "kwparams": {}, 15 | "params": [], 16 | "readonly": True, 17 | "ret": {"$dict": "string"}, 18 | }, 19 | "update_storage": { 20 | "kwparams": {}, 21 | "params": [["new_storage", "string"]], 22 | "payable": False, 23 | "readonly": False, 24 | "ret": "null", 25 | }, 26 | }, 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /tests/integration/icontracts/schemas/llm_erc20_get_contract_schema_for_code.py: -------------------------------------------------------------------------------- 1 | llm_erc20_contract_schema = { 2 | "id": 1, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "ctor": {"kwparams": {}, "params": [["total_supply", "int"]]}, 6 | "methods": { 7 | "get_balance_of": { 8 | "kwparams": {}, 9 | "params": [["address", "string"]], 10 | "readonly": True, 11 | "ret": "int", 12 | }, 13 | "get_balances": { 14 | "kwparams": {}, 15 | "params": [], 16 | "readonly": True, 17 | "ret": {"$dict": "int"}, 18 | }, 19 | "transfer": { 20 | "kwparams": {}, 21 | "params": [["amount", "int"], ["to_address", "string"]], 22 | "payable": False, 23 | "readonly": False, 24 | "ret": "null", 25 | }, 26 | }, 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/1daddff774b2_drop_from_constraint_on_rollup_tx.py: -------------------------------------------------------------------------------- 1 | """drop_from_constraint_on_rollup_tx 2 | 3 | Revision ID: 1daddff774b2 4 | Revises: 579e86111b36 5 | Create Date: 2024-11-21 16:25:11.469033 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "1daddff774b2" 17 | down_revision: Union[str, None] = "579e86111b36" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | op.alter_column( 24 | "rollup_transactions", 25 | "from_", 26 | existing_type=sa.String(length=255), 27 | nullable=True, 28 | ) 29 | 30 | 31 | def downgrade() -> None: 32 | op.alter_column( 33 | "rollup_transactions", 34 | "from_", 35 | existing_type=sa.String(length=255), 36 | nullable=False, 37 | ) 38 | -------------------------------------------------------------------------------- /backend/database_handler/session_manager.py: -------------------------------------------------------------------------------- 1 | # backend/database_handler/session_manager.py 2 | 3 | from contextlib import contextmanager 4 | 5 | 6 | @contextmanager 7 | def managed_session(open_session): 8 | """ 9 | Context manager for database sessions with automatic cleanup. 10 | 11 | Ensures that database sessions are properly committed on success, 12 | rolled back on error, and always closed when done. 13 | 14 | Args: 15 | open_session: A callable that returns a new database session 16 | 17 | Yields: 18 | session: The database session for use within the context 19 | 20 | Example: 21 | with managed_session(create_session) as session: 22 | # Use session for database operations 23 | user = session.query(User).first() 24 | """ 25 | session = open_session() 26 | try: 27 | yield session 28 | session.commit() 29 | except Exception: 30 | session.rollback() 31 | raise 32 | finally: 33 | session.close() 34 | -------------------------------------------------------------------------------- /tests/integration/icontracts/contracts/multi_read_erc20.py: -------------------------------------------------------------------------------- 1 | # v0.1.0 2 | # { "Depends": "py-genlayer:test" } 3 | 4 | from genlayer import * 5 | 6 | 7 | class multi_read_erc20(gl.Contract): 8 | balances: TreeMap[Address, TreeMap[Address, u256]] 9 | 10 | def __init__(self): 11 | pass 12 | 13 | @gl.public.write 14 | def update_token_balances( 15 | self, account_address: str, token_contracts: list[str] 16 | ) -> None: 17 | for token_contract in token_contracts: 18 | contract = gl.get_contract_at(Address(token_contract)) 19 | balance = contract.view().get_balance_of(account_address) 20 | self.balances.get_or_insert_default(Address(account_address))[ 21 | Address(token_contract) 22 | ] = balance 23 | 24 | @gl.public.view 25 | def get_balances(self) -> dict[str, dict[str, int]]: 26 | return { 27 | k.as_hex: {k.as_hex: v for k, v in v.items()} 28 | for k, v in self.balances.items() 29 | } 30 | -------------------------------------------------------------------------------- /docker/Dockerfile.frontend: -------------------------------------------------------------------------------- 1 | FROM node:22.14.0-alpine3.20 AS base 2 | 3 | WORKDIR /app 4 | COPY ./frontend/package*.json . 5 | RUN --mount=type=cache,target=/root/.npm npm ci 6 | COPY ./frontend . 7 | COPY ./examples src/assets/examples 8 | 9 | COPY ../backend/node/create_nodes/providers_schema.json /app/src/assets/schemas/providers_schema.json 10 | 11 | FROM base AS dev 12 | COPY docker/entrypoint-frontend.sh /entrypoint.sh 13 | RUN chmod +x /entrypoint.sh 14 | ENTRYPOINT ["/entrypoint.sh"] 15 | 16 | FROM base AS builder 17 | RUN npm run build 18 | 19 | FROM alpine:latest AS final 20 | RUN apk add --no-cache nodejs npm && \ 21 | addgroup --system frontend-user && adduser --system --ingroup frontend-user frontend-user && \ 22 | mkdir /app && chown -R frontend-user:frontend-user /app 23 | WORKDIR /app 24 | COPY --from=builder --chown=frontend-user:frontend-user /app /app 25 | COPY docker/entrypoint-frontend.sh /entrypoint.sh 26 | RUN chmod +x /entrypoint.sh 27 | USER frontend-user 28 | EXPOSE 8080 29 | ENTRYPOINT ["/entrypoint.sh"] 30 | -------------------------------------------------------------------------------- /frontend/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard-scss", 4 | "stylelint-config-idiomatic-order" 5 | ], 6 | "plugins": [ 7 | "stylelint-use-logical-spec", 8 | "stylelint-codeguide" 9 | ], 10 | "overrides": [ 11 | { 12 | "files": [ 13 | "**/*.scss" 14 | ], 15 | "customSyntax": "postcss-scss" 16 | }, 17 | { 18 | "files": [ 19 | "**/*.vue" 20 | ], 21 | "customSyntax": "postcss-html" 22 | } 23 | ], 24 | "rules": { 25 | "codeguide/max-line-length": [ 26 | 120, 27 | { 28 | "ignore": "comments" 29 | } 30 | ], 31 | "codeguide/indentation": 2, 32 | "liberty/use-logical-spec": true, 33 | "selector-class-pattern": null, 34 | "color-function-notation": null, 35 | "annotation-no-unknown": [ 36 | true, 37 | { 38 | "ignoreAnnotations": [ 39 | "default" 40 | ] 41 | } 42 | ], 43 | "media-feature-range-notation": null 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/test_rpc/test_address_utils.py: -------------------------------------------------------------------------------- 1 | # tests/rpc/test_address_utils.py 2 | 3 | from rpc.address_utils import create_new_address, address_is_in_correct_format 4 | 5 | 6 | def test_create_new_address_length(): 7 | address = create_new_address() 8 | assert len(address) == 42 9 | 10 | 11 | def test_create_new_address_format(): 12 | address = create_new_address() 13 | assert address.startswith("0x") 14 | assert address_is_in_correct_format(address) 15 | 16 | 17 | def test_address_is_in_correct_format_valid(): 18 | valid_address = "0x" + "a" * 40 19 | assert address_is_in_correct_format(valid_address) 20 | 21 | 22 | def test_address_is_in_correct_format_invalid(): 23 | invalid_addresses = [ 24 | "0x" + "z" * 40, # Invalid character 'z' 25 | "0x" + "a" * 39, # Incorrect length 26 | "1x" + "a" * 40, # Incorrect prefix 27 | "0x" + "a" * 41, # Incorrect length 28 | ] 29 | for address in invalid_addresses: 30 | assert not address_is_in_correct_format(address) 31 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/3566595124f6_add_leader_only_flag.py: -------------------------------------------------------------------------------- 1 | """add leader_only flag 2 | 3 | Revision ID: 3566595124f6 4 | Revises: eb32e4bdb446 5 | Create Date: 2024-09-19 11:39:50.612010 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "3566595124f6" 17 | down_revision: Union[str, None] = "eb32e4bdb446" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | op.add_column("transactions", sa.Column("leader_only", sa.Boolean(), nullable=True)) 24 | op.execute("UPDATE transactions SET leader_only = false") 25 | op.alter_column("transactions", "leader_only", nullable=False) 26 | 27 | 28 | def downgrade() -> None: 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.drop_column("transactions", "leader_only") 31 | # ### end Alembic commands ### 32 | -------------------------------------------------------------------------------- /frontend/src/hooks/useInputMap.ts: -------------------------------------------------------------------------------- 1 | import AnyField from '@/components/global/fields/AnyField.vue'; 2 | import StringField from '@/components/global/fields/StringField.vue'; 3 | import IntegerField from '@/components/global/fields/IntegerField.vue'; 4 | import BooleanField from '@/components/global/fields/BooleanField.vue'; 5 | import type { ContractParamsSchema } from 'genlayer-js/types'; 6 | 7 | export const InputTypesMap: { [k: string]: any } = { 8 | string: StringField, 9 | int: IntegerField, 10 | bool: BooleanField, 11 | any: AnyField, 12 | }; 13 | 14 | export const useInputMap = () => { 15 | const getComponent = (type: ContractParamsSchema) => { 16 | if (typeof type !== 'string') { 17 | type = 'any'; 18 | } 19 | const component = InputTypesMap[type]; 20 | 21 | if (!component) { 22 | console.warn( 23 | `Component not found for input type: ${type}, defaulting to any`, 24 | ); 25 | return AnyField; 26 | } 27 | 28 | return component; 29 | }; 30 | 31 | return { getComponent }; 32 | }; 33 | -------------------------------------------------------------------------------- /tests/common/transactions.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | from eth_utils import to_hex 3 | import rlp 4 | from eth_account._utils.legacy_transactions import Transaction 5 | 6 | 7 | def serialize_one(data: bytes | str) -> bytes: 8 | return to_hex(data) 9 | 10 | 11 | def encode_transaction_data(data: list) -> str: 12 | """ 13 | Encode transaction data using RLP encoding 14 | Returns hex string with '0x' prefix 15 | """ 16 | serialized_data = rlp.encode(data) 17 | return to_hex(serialized_data) 18 | 19 | 20 | def sign_transaction( 21 | account: Account, data: list = None, to: str = None, value: int = 0, nonce: int = 0 22 | ) -> dict: 23 | transaction = { 24 | "nonce": nonce, 25 | "gasPrice": 0, 26 | "gas": 20000000, 27 | "to": to, 28 | "value": value, 29 | } 30 | if data is not None: 31 | transaction["data"] = data 32 | 33 | signed_transaction = Account.sign_transaction(transaction, account.key) 34 | return to_hex(signed_transaction.raw_transaction) 35 | -------------------------------------------------------------------------------- /backend/consensus/vrf.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import numpy as np 3 | from backend.consensus.base import DEFAULT_VALIDATORS_COUNT 4 | 5 | 6 | def get_validators_for_transaction( 7 | nodes: list[dict], 8 | num_validators: int | None = None, 9 | rng=np.random.default_rng(seed=int(datetime.now().timestamp())), 10 | ) -> list[dict]: 11 | """ 12 | Returns subset of validators for a transaction. 13 | The selelction and order is given by a random sampling based on the stake of the validators. 14 | """ 15 | if num_validators is None: 16 | num_validators = DEFAULT_VALIDATORS_COUNT 17 | 18 | num_validators = min(num_validators, len(nodes)) 19 | 20 | total_stake = sum(validator["stake"] for validator in nodes) 21 | probabilities = [validator["stake"] / total_stake for validator in nodes] 22 | 23 | selected_validators = rng.choice( 24 | nodes, 25 | p=probabilities, 26 | size=num_validators, 27 | replace=False, 28 | ) 29 | 30 | return list(selected_validators) 31 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/47519c0bf986_validators_private_key.py: -------------------------------------------------------------------------------- 1 | """validators_private_key 2 | 3 | Revision ID: 47519c0bf986 4 | Revises: 130836786511 5 | Create Date: 2025-03-11 18:18:33.157960 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "47519c0bf986" 17 | down_revision: Union[str, None] = "130836786511" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "validators", sa.Column("private_key", sa.String(length=255), nullable=True) 26 | ) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade() -> None: 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_column("validators", "private_key") 33 | # ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /tests/integration/icontracts/tests/test_wizard_of_coin.py: -------------------------------------------------------------------------------- 1 | # tests/e2e/test_wizard_of_coin.py 2 | from gltest import get_contract_factory 3 | from gltest.assertions import tx_execution_succeeded 4 | import json 5 | 6 | 7 | def test_wizard_of_coin(setup_validators): 8 | mock_response = { 9 | "response": { 10 | "wizard": json.dumps( 11 | { 12 | "reasoning": "I am a grumpy wizard and I never give away my coins!", 13 | "give_coin": False, 14 | } 15 | ), 16 | }, 17 | "eq_principle_prompt_comparative": { 18 | "The value of give_coin has to match": True 19 | }, 20 | } 21 | setup_validators(mock_response) 22 | 23 | factory = get_contract_factory("WizardOfCoin") 24 | contract = factory.deploy(args=[True]) 25 | 26 | transaction_response_call_1 = contract.ask_for_coin( 27 | args=["Can you please give me my coin?"] 28 | ).transact() 29 | assert tx_execution_succeeded(transaction_response_call_1) 30 | -------------------------------------------------------------------------------- /frontend/src/components/global/fields/AnyField.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 29 | {{ name }} 30 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/test-semgrep.yml: -------------------------------------------------------------------------------- 1 | name: Semgrep OSS scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | schedule: 8 | - cron: "00 18 * * *" # Sets Semgrep to scan every day at 18:00 UTC. 9 | workflow_dispatch: 10 | 11 | permissions: 12 | contents: read 13 | security-events: write 14 | 15 | jobs: 16 | semgrep: 17 | name: semgrep-oss/scan 18 | runs-on: ubuntu-latest 19 | container: 20 | # A Docker image with Semgrep installed. Do not change this. 21 | image: semgrep/semgrep 22 | 23 | # Skip any PR created by dependabot/renovatebot to avoid permission issues: 24 | if: (github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]') 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - id: semgrep_scan 29 | run: semgrep scan --config auto --sarif > semgrep.sarif 30 | 31 | - name: Upload SARIF file for GitHub Advanced Security Dashboard 32 | uses: github/codeql-action/upload-sarif@v3 33 | with: 34 | sarif_file: semgrep.sarif 35 | if: always() 36 | -------------------------------------------------------------------------------- /.github/workflows/release-staging.yml: -------------------------------------------------------------------------------- 1 | name: Release Staging 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | branches: 7 | - main # or your default branch name 8 | workflow_dispatch: # Add manual trigger 9 | 10 | jobs: 11 | trigger-infra-workflow: 12 | if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Get CI Bot Token 16 | uses: tibdex/github-app-token@v2 17 | id: ci_bot_token 18 | with: 19 | app_id: ${{ secrets.CI_BOT_APP_ID }} 20 | private_key: ${{ secrets.CI_BOT_SECRET }} 21 | 22 | - name: Trigger Infra Deploy Studio to Staging Environment 23 | run: | 24 | curl -L -X POST \ 25 | -H "Accept: application/vnd.github.v3+json" \ 26 | -H "Authorization: token ${{ steps.ci_bot_token.outputs.token }}" \ 27 | https://api.github.com/repos/genlayerlabs/genlayer-infra/actions/workflows/deploy-stage.yml/dispatches \ 28 | -d '{"ref":"main"}' 29 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/130836786511_config_rotation_rounds.py: -------------------------------------------------------------------------------- 1 | """config_rotation_rounds 2 | 3 | Revision ID: 130836786511 4 | Revises: 15fde6faebaf 5 | Create Date: 2025-01-16 16:01:59.319254 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "130836786511" 17 | down_revision: Union[str, None] = "15fde6faebaf" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", sa.Column("config_rotation_rounds", sa.Integer(), nullable=True) 26 | ) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade() -> None: 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_column("transactions", "config_rotation_rounds") 33 | # ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /frontend/src/components/global/registerGlobalComponents.js: -------------------------------------------------------------------------------- 1 | import Modal from '@/components/global/Modal.vue'; 2 | import Btn from '@/components/global/Btn.vue'; 3 | import GhostBtn from '@/components/global/GhostBtn.vue'; 4 | import ConfirmationModal from '@/components/global/ConfirmationModal.vue'; 5 | import CopyTextButton from '@/components/global/CopyTextButton.vue'; 6 | import Alert from '@/components/global/Alert.vue'; 7 | import Loader from '@/components/global/Loader.vue'; 8 | import ContentLoader from '@/components/global/ContentLoader.vue'; 9 | import MoreInfo from '@/components/global/MoreInfo.vue'; 10 | 11 | export default function registerGlobalComponents(app) { 12 | app.component('Modal', Modal); 13 | app.component('Btn', Btn); 14 | app.component('GhostBtn', GhostBtn); 15 | app.component('ConfirmationModal', ConfirmationModal); 16 | app.component('CopyTextButton', CopyTextButton); 17 | app.component('Alert', Alert); 18 | app.component('Loader', Loader); 19 | app.component('ContentLoader', ContentLoader); 20 | app.component('MoreInfo', MoreInfo); 21 | } 22 | -------------------------------------------------------------------------------- /tests/db-sqlalchemy/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | postgrestest: 3 | image: postgres:12.19-alpine 4 | environment: 5 | POSTGRES_USER: postgres 6 | POSTGRES_PASSWORD: postgres 7 | POSTGRES_DB: postgres 8 | # Uncomment for debugging 9 | # ports: 10 | # - 5432:5432 11 | healthcheck: 12 | test: pg_isready -U postgres -d postgres 13 | interval: 1s 14 | timeout: 1s 15 | retries: 3 16 | container_name: genlayer_simulator_postgres_db_tests 17 | 18 | tests: 19 | build: 20 | context: ./ 21 | dockerfile: ./tests/db-sqlalchemy/Dockerfile 22 | # Uncomment for debugging 23 | # ports: 24 | # - 5678:5678 25 | environment: 26 | # Uncomment for debugging 27 | # WAIT_FOR_DEBUGGER: true 28 | POSTGRES_URL: postgresql+psycopg2://postgres:postgres@postgrestest:5432/postgres 29 | depends_on: 30 | postgrestest: 31 | condition: service_healthy 32 | container_name: genlayer_simulator_db_tests 33 | # command: ["-svv", "transactions_processor_test.py"] # Uncomment to run your particular test 34 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/a32f85df2806_add_client_session_id_to_transactions.py: -------------------------------------------------------------------------------- 1 | """add client session id to transactions 2 | 3 | Revision ID: a32f85df2806 4 | Revises: 3566595124f6 5 | Create Date: 2024-10-01 12:09:19.995482 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "a32f85df2806" 17 | down_revision: Union[str, None] = "3566595124f6" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", 26 | sa.Column("client_session_id", sa.String(length=255), nullable=True), 27 | ) 28 | # ### end Alembic commands ### 29 | 30 | 31 | def downgrade() -> None: 32 | # ### commands auto generated by Alembic - please adjust! ### 33 | op.drop_column("transactions", "client_session_id") 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/ContractParams.ts: -------------------------------------------------------------------------------- 1 | import { type CalldataEncodable } from 'genlayer-js/types'; 2 | import { parse as calldataParse } from '@/calldata/parser'; 3 | import { AnyFieldValue } from '../global/fields/AnyFieldValue'; 4 | 5 | export interface SingleArgData { 6 | val: CalldataEncodable | AnyFieldValue; 7 | key: number | string; 8 | } 9 | 10 | export interface ArgData { 11 | args: SingleArgData[]; 12 | kwargs: { [k: string]: SingleArgData }; 13 | } 14 | 15 | export function unfoldArgsData(args: ArgData): { 16 | args: CalldataEncodable[]; 17 | kwargs: { [key: string]: CalldataEncodable }; 18 | } { 19 | const unfoldOne = (x: SingleArgData) => { 20 | if (x.val instanceof AnyFieldValue) { 21 | try { 22 | return calldataParse(x.val.value); 23 | } catch (e) { 24 | throw new Error(`failed to parse ${x.key}`); 25 | } 26 | } 27 | return x.val; 28 | }; 29 | return { 30 | args: args.args.map(unfoldOne), 31 | kwargs: Object.fromEntries( 32 | Object.entries(args.kwargs).map(([k, v]) => [k, unfoldOne(v)]), 33 | ), 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /backend/consensus/utils.py: -------------------------------------------------------------------------------- 1 | from backend.consensus.types import ConsensusResult 2 | from backend.node.types import Vote 3 | 4 | 5 | def determine_consensus_from_votes(votes_list: list[str]) -> ConsensusResult: 6 | """ 7 | Determine consensus from a list of votes. 8 | 9 | Args: 10 | votes_list: List of vote strings 11 | 12 | Returns: 13 | ConsensusResult: The consensus result 14 | """ 15 | agree_count = votes_list.count(Vote.AGREE.value) 16 | disagree_count = votes_list.count(Vote.DISAGREE.value) 17 | timeout_count = votes_list.count(Vote.TIMEOUT.value) 18 | 19 | if timeout_count > agree_count and timeout_count > disagree_count: 20 | consensus_result = ConsensusResult.TIMEOUT 21 | elif agree_count > disagree_count and agree_count > timeout_count: 22 | consensus_result = ConsensusResult.MAJORITY_AGREE 23 | elif disagree_count > agree_count and disagree_count > timeout_count: 24 | consensus_result = ConsensusResult.MAJORITY_DISAGREE 25 | else: 26 | consensus_result = ConsensusResult.NO_MAJORITY 27 | 28 | return consensus_result 29 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/3bc34e44eb72_add_providers_unique_constraint.py: -------------------------------------------------------------------------------- 1 | """add providers unique constraint 2 | 3 | Revision ID: 3bc34e44eb72 4 | Revises: b5acc405bcca 5 | Create Date: 2024-10-11 10:14:05.194224 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "3bc34e44eb72" 17 | down_revision: Union[str, None] = "b5acc405bcca" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.create_unique_constraint( 25 | "unique_provider_model_plugin", "llm_provider", ["provider", "model", "plugin"] 26 | ) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade() -> None: 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_constraint("unique_provider_model_plugin", "llm_provider", type_="unique") 33 | # ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.6.0 # Use the ref you want to point at 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: check-added-large-files 7 | - id: check-json 8 | - id: check-yaml 9 | - id: check-merge-conflict 10 | - id: no-commit-to-branch 11 | args: ["--branch", "main"] 12 | # Copied from https://black.readthedocs.io/en/stable/integrations/source_version_control.html 13 | - repo: https://github.com/psf/black-pre-commit-mirror 14 | rev: 24.4.2 15 | hooks: 16 | - id: black 17 | language_version: python3.12 18 | - repo: local 19 | hooks: 20 | - id: eslint 21 | name: eslint 22 | entry: sh -c "cd frontend && npm run lint" 23 | language: system 24 | files: frontend 25 | - repo: local 26 | hooks: 27 | - id: prettier 28 | name: prettier 29 | entry: sh -c "cd frontend && npm run format" 30 | language: system 31 | files: frontend 32 | 33 | default_install_hook_types: 34 | - pre-commit 35 | - commit-msg 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2024 GenLayer Labs Corp. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. -------------------------------------------------------------------------------- /frontend/src/types/store.ts: -------------------------------------------------------------------------------- 1 | import type { Address, TransactionStatus } from 'genlayer-js/types'; 2 | 3 | export interface ContractFile { 4 | id: string; 5 | name: string; 6 | content: string; 7 | example?: boolean; 8 | updatedAt?: string; 9 | } 10 | 11 | export interface OpenedFile { 12 | id: string; 13 | name: string; 14 | } 15 | 16 | export interface DeployedContract { 17 | contractId: string; 18 | address: Address; 19 | defaultState: string; 20 | } 21 | 22 | export interface NodeLog { 23 | scope: string; 24 | name: string; 25 | type: 'error' | 'warning' | 'info' | 'success'; 26 | message: string; 27 | data?: any; 28 | } 29 | 30 | export interface TransactionItem { 31 | hash: `0x${string}`; 32 | type: 'deploy' | 'method'; 33 | statusName: TransactionStatus; 34 | contractAddress: string; 35 | localContractId: string; 36 | data?: any; 37 | decodedData?: { 38 | functionName: string; 39 | args: any[]; 40 | kwargs: { [key: string]: any }; 41 | }; 42 | } 43 | 44 | export type UIMode = 'light' | 'dark'; 45 | export interface UIState { 46 | mode: UIMode; 47 | showTutorial: boolean; 48 | } 49 | -------------------------------------------------------------------------------- /nginx/ssl/README.md: -------------------------------------------------------------------------------- 1 | # Directory for TLS Certificates and Keys 2 | 3 | Place your TLS (Transport Layer Security) certificates and private keys for NGINX in this directory. These files are essential for enabling HTTPS, ensuring secure communication, and authenticating server identity. Make sure to use the correct certificate/private key pair for the domain you are running the studio on. 4 | 5 | ## Required Files 6 | Ensure the following files are correctly placed in the specified directory (`/etc/nginx/ssl`): 7 | 8 | - **Server certificate**: `genlayer.com.crt` 9 | - **Private key**: `genlayer.com.key` 10 | - **Client certificate**: `cloudflare.crt` 11 | 12 | ## File Permissions 13 | Permissions should follow these guidelines: 14 | 15 | - **Certificates** (`.crt` or `.pem`): `chmod 644` 16 | - **Private keys** (`.key`): `chmod 600` 17 | 18 | ## Configuration Alignment 19 | Verify that the NGINX configuration matches the expected file names and locations: 20 | 21 | ```nginx 22 | ssl_certificate /etc/nginx/ssl/genlayer.com.crt; 23 | ssl_certificate_key /etc/nginx/ssl/genlayer.com.key; 24 | ssl_client_certificate /etc/nginx/ssl/cloudflare.crt; 25 | ``` -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/035ef00c2779_add_sim_config_to_transactions.py: -------------------------------------------------------------------------------- 1 | """add_sim_config_to_transactions 2 | 3 | Revision ID: 035ef00c2779 4 | Revises: a221e802477c 5 | Create Date: 2025-09-02 05:19:02.382649 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | from sqlalchemy.dialects import postgresql 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "035ef00c2779" 17 | down_revision: Union[str, None] = "a221e802477c" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", 26 | sa.Column("sim_config", postgresql.JSONB(astext_type=sa.Text()), nullable=True), 27 | ) 28 | # ### end Alembic commands ### 29 | 30 | 31 | def downgrade() -> None: 32 | # ### commands auto generated by Alembic - please adjust! ### 33 | op.drop_column("transactions", "sim_config") 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /backend/protocol_rpc/message_handler/types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from dataclasses import dataclass 3 | 4 | 5 | class EventType(Enum): 6 | DEBUG = "debug" 7 | INFO = "info" 8 | SUCCESS = "success" 9 | WARNING = "warning" 10 | ERROR = "error" 11 | 12 | 13 | class EventScope(Enum): 14 | RPC = "RPC" 15 | GENVM = "GenVM" 16 | CONSENSUS = "Consensus" 17 | TRANSACTION = "Transaction" 18 | 19 | 20 | @dataclass 21 | class LogEvent: 22 | name: str 23 | type: EventType 24 | scope: EventScope 25 | message: str 26 | data: dict | None = None 27 | transaction_hash: str | None = None 28 | client_session_id: str | None = None 29 | account_address: str | None = None 30 | 31 | def to_dict(self): 32 | return { 33 | "name": self.name, 34 | "type": self.type.value, 35 | "scope": self.scope.value, 36 | "message": self.message, 37 | "data": self.data, 38 | "transaction_hash": self.transaction_hash, 39 | "client_id": self.client_session_id, 40 | "account_address": self.account_address, 41 | } 42 | -------------------------------------------------------------------------------- /frontend/test/unit/hooks/useFileName.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { useFileName } from '@/hooks'; 3 | 4 | describe('useFileName composable', () => { 5 | const { cleanupFileName } = useFileName(); 6 | 7 | it('should return the original name with ".py" when there is no period in the name', () => { 8 | const result = cleanupFileName('example'); 9 | expect(result).toBe('example.py'); 10 | }); 11 | 12 | it('should replace the extension with ".py" if there is a period in the name', () => { 13 | const result = cleanupFileName('document.txt'); 14 | expect(result).toBe('document.py'); 15 | }); 16 | 17 | it('should return ".py" if the name is just a period', () => { 18 | const result = cleanupFileName('.'); 19 | expect(result).toBe('.py'); 20 | }); 21 | 22 | it('should handle names with multiple periods correctly', () => { 23 | const result = cleanupFileName('archive.tar.gz'); 24 | expect(result).toBe('archive.py'); 25 | }); 26 | 27 | it('should return ".py" for an empty string', () => { 28 | const result = cleanupFileName(''); 29 | expect(result).toBe('.py'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/15fde6faebaf_contract_snapshot.py: -------------------------------------------------------------------------------- 1 | """contract_snapshot 2 | 3 | Revision ID: 15fde6faebaf 4 | Revises: d932a5fef8b1 5 | Create Date: 2025-01-06 10:42:19.972610 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | from sqlalchemy.dialects import postgresql 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "15fde6faebaf" 17 | down_revision: Union[str, None] = "d932a5fef8b1" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", 26 | sa.Column( 27 | "contract_snapshot", postgresql.JSONB(astext_type=sa.Text()), nullable=True 28 | ), 29 | ) 30 | # ### end Alembic commands ### 31 | 32 | 33 | def downgrade() -> None: 34 | # ### commands auto generated by Alembic - please adjust! ### 35 | op.drop_column("transactions", "contract_snapshot") 36 | # ### end Alembic commands ### 37 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/2de9c3151194_consensus_history.py: -------------------------------------------------------------------------------- 1 | """consensus_history 2 | 3 | Revision ID: 2de9c3151194 4 | Revises: 67943badcbe9 5 | Create Date: 2024-12-25 17:42:40.018638 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | from sqlalchemy.dialects import postgresql 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "2de9c3151194" 17 | down_revision: Union[str, None] = "67943badcbe9" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", 26 | sa.Column( 27 | "consensus_history", postgresql.JSONB(astext_type=sa.Text()), nullable=True 28 | ), 29 | ) 30 | # ### end Alembic commands ### 31 | 32 | 33 | def downgrade() -> None: 34 | # ### commands auto generated by Alembic - please adjust! ### 35 | op.drop_column("transactions", "consensus_history") 36 | # ### end Alembic commands ### 37 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/ContractsPanel.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /backend/protocol_rpc/call_interceptor.py: -------------------------------------------------------------------------------- 1 | from backend.database_handler.transactions_processor import TransactionsProcessor 2 | from backend.protocol_rpc.calls_intercept.get_latest_pending_tx_count import ( 3 | GetLatestPendingTxCountHandler, 4 | ) 5 | 6 | CONSENSUS_DATA_CONTRACT_ADDRESS = "0x88B0F18613Db92Bf970FfE264E02496e20a74D16" 7 | 8 | # Registry of available handler instances 9 | HANDLERS = [ 10 | GetLatestPendingTxCountHandler(), 11 | ] 12 | 13 | 14 | def is_consensus_data_contract_call(to_address: str) -> bool: 15 | """Check if the eth_call is targeting the ConsensusData contract.""" 16 | return to_address.lower() == CONSENSUS_DATA_CONTRACT_ADDRESS.lower() 17 | 18 | 19 | def handle_consensus_data_call( 20 | transactions_processor: TransactionsProcessor, to_address: str, data: str 21 | ) -> str | None: 22 | """Handle ConsensusData contract calls by intercepting and processing locally.""" 23 | if not is_consensus_data_contract_call(to_address): 24 | return None 25 | 26 | for handler in HANDLERS: 27 | if handler.can_handle(data): 28 | return handler.handle(transactions_processor, data) 29 | 30 | return None 31 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/2a4ac5eb9455_appeal_failed.py: -------------------------------------------------------------------------------- 1 | """appeal_failed 2 | 3 | Revision ID: 2a4ac5eb9455 4 | Revises: b9421e9f12aa 5 | Create Date: 2024-11-22 09:48:50.048787 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "2a4ac5eb9455" 17 | down_revision: Union[str, None] = "b9421e9f12aa" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", sa.Column("appeal_failed", sa.Integer(), nullable=True) 26 | ) 27 | # Set all existing 'appeal_failed' values to 0 28 | op.execute("UPDATE transactions SET appeal_failed = 0 WHERE appeal_failed IS NULL") 29 | # ### end Alembic commands ### 30 | 31 | 32 | def downgrade() -> None: 33 | # ### commands auto generated by Alembic - please adjust! ### 34 | op.drop_column("transactions", "appeal_failed") 35 | # ### end Alembic commands ### 36 | -------------------------------------------------------------------------------- /backend/node/genvm/origin/public_abi.py: -------------------------------------------------------------------------------- 1 | # This file is auto-generated. Do not edit! 2 | 3 | from enum import IntEnum, StrEnum 4 | import typing 5 | 6 | 7 | class ResultCode(IntEnum): 8 | RETURN = 0 9 | USER_ERROR = 1 10 | VM_ERROR = 2 11 | INTERNAL_ERROR = 3 12 | 13 | 14 | class StorageType(IntEnum): 15 | DEFAULT = 0 16 | LATEST_FINAL = 1 17 | LATEST_NON_FINAL = 2 18 | 19 | 20 | class EntryKind(IntEnum): 21 | MAIN = 0 22 | SANDBOX = 1 23 | CONSENSUS_STAGE = 2 24 | 25 | 26 | class MemoryLimiterConsts(IntEnum): 27 | TABLE_ENTRY = 64 28 | FILE_MAPPING = 256 29 | FD_ALLOCATION = 96 30 | 31 | 32 | class SpecialMethod(StrEnum): 33 | GET_SCHEMA = "#get-schema" 34 | ERRORED_MESSAGE = "#error" 35 | 36 | 37 | class VmError(StrEnum): 38 | TIMEOUT = "timeout" 39 | EXIT_CODE = "exit_code" 40 | VALIDATOR_DISAGREES = "validator_disagrees" 41 | VERSION_TOO_BIG = "version_too_big" 42 | OOM = "OOM" 43 | INVALID_CONTRACT = "invalid_contract" 44 | 45 | 46 | EVENT_MAX_TOPICS: typing.Final[int] = 4 47 | 48 | 49 | ABSENT_VERSION: typing.Final[str] = "v0.1.0" 50 | 51 | 52 | CODE_SLOT_OFFSET: typing.Final[int] = 1 53 | -------------------------------------------------------------------------------- /frontend/src/hooks/useContractListener.ts: -------------------------------------------------------------------------------- 1 | import { useContractsStore, useTransactionsStore } from '@/stores'; 2 | import { useWebSocketClient } from '@/hooks'; 3 | 4 | export function useContractListener() { 5 | const contractsStore = useContractsStore(); 6 | const transactionsStore = useTransactionsStore(); 7 | const webSocketClient = useWebSocketClient(); 8 | 9 | function init() { 10 | webSocketClient.on('deployed_contract', handleContractDeployed); 11 | } 12 | 13 | async function handleContractDeployed(eventData: any) { 14 | const localDeployTx = transactionsStore.transactions.find( 15 | (t) => t.hash === eventData.transaction_hash, 16 | ); 17 | 18 | // Check for a local transaction to: 19 | // - match the contract file ID since it is only stored client-side 20 | // - make sure to scope the websocket event to the correct client 21 | if (localDeployTx) { 22 | contractsStore.addDeployedContract({ 23 | contractId: localDeployTx.localContractId, 24 | address: eventData.data.id, 25 | defaultState: eventData.data.data.state, 26 | }); 27 | } 28 | } 29 | 30 | return { 31 | init, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # genlayer-studio 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) (and disable Vetur). 8 | 9 | ## Type Support for `.vue` Imports in TS 10 | 11 | Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. 12 | 13 | However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can run `Volar: Switch TS Plugin on/off` from VS Code command palette. 14 | 15 | ## Customize configuration 16 | 17 | See [Vite Configuration Reference](https://vitejs.dev/config/). 18 | 19 | ## Project Setup 20 | 21 | ```sh 22 | npm install 23 | ``` 24 | 25 | ### Compile and Hot-Reload for Development 26 | 27 | ```sh 28 | npm run dev 29 | ``` 30 | 31 | ### Type-Check, Compile and Minify for Production 32 | 33 | ```sh 34 | npm run build 35 | ``` 36 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/0a4312659782_remove_ghost_contract.py: -------------------------------------------------------------------------------- 1 | """remove_ghost_contract 2 | 3 | Revision ID: 0a4312659782 4 | Revises: 47519c0bf986 5 | Create Date: 2025-04-08 15:33:05.159432 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "0a4312659782" 17 | down_revision: Union[str, None] = "47519c0bf986" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.drop_column("transactions", "ghost_contract_address") 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade() -> None: 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.add_column( 31 | "transactions", 32 | sa.Column( 33 | "ghost_contract_address", 34 | sa.VARCHAR(length=255), 35 | autoincrement=False, 36 | nullable=True, 37 | ), 38 | ) 39 | # ### end Alembic commands ### 40 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/579e86111b36_remove_client_session_id_from_.py: -------------------------------------------------------------------------------- 1 | """remove client session id from transactions 2 | 3 | Revision ID: 579e86111b36 4 | Revises: 1ecaa2085aec 5 | Create Date: 2024-11-08 10:41:56.112444 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "579e86111b36" 17 | down_revision: Union[str, None] = "1ecaa2085aec" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.drop_column("transactions", "client_session_id") 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade() -> None: 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.add_column( 31 | "transactions", 32 | sa.Column( 33 | "client_session_id", 34 | sa.VARCHAR(length=255), 35 | autoincrement=False, 36 | nullable=True, 37 | ), 38 | ) 39 | # ### end Alembic commands ### 40 | -------------------------------------------------------------------------------- /frontend/src/components/global/Loader.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 20 | 25 | 30 | 31 | 32 | 33 | 44 | -------------------------------------------------------------------------------- /.github/workflows/.pre-commit.yml: -------------------------------------------------------------------------------- 1 | # Sometimes this workflow fails due to problems in the target PR branch. I (Agustín Díaz) don't know yet the reason why the code is analyzing for the merge commit instead of the PR branch. 2 | # Anyways, it's an indicator that the target PR branch is not clean. This is a good thing because it helps to keep the codebase clean. 3 | # To fix this, rebase the PR branch with the target branch and check `pre-commit run --all-files` locally. 4 | name: pre-commit 5 | 6 | on: 7 | pull_request: 8 | types: [opened, synchronize, reopened, ready_for_review] 9 | 10 | jobs: 11 | pre-commit: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | # Set up Python 16 | - uses: actions/setup-python@v5 17 | with: 18 | python-version: 3.11 19 | cache: pip 20 | - run: pip install black 21 | # Set up Node.js 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: 21 25 | cache: "npm" 26 | cache-dependency-path: frontend/package-lock.json 27 | - run: npm ci 28 | working-directory: frontend 29 | # Run pre-commit 30 | - uses: pre-commit/action@v3.0.1 31 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url'; 2 | import svgLoader from 'vite-svg-loader'; 3 | 4 | import { defineConfig, loadEnv, UserConfig } from 'vite'; 5 | import vue from '@vitejs/plugin-vue'; 6 | import vueJsx from '@vitejs/plugin-vue-jsx'; 7 | import VueDevTools from 'vite-plugin-vue-devtools'; 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig(({ mode }) => { 11 | const env = loadEnv(mode, process.cwd()); 12 | const config: UserConfig = { 13 | base: '/', 14 | plugins: [vue(), svgLoader(), vueJsx(), VueDevTools()], 15 | resolve: { 16 | alias: { 17 | '@': fileURLToPath(new URL('./src', import.meta.url)), 18 | }, 19 | }, 20 | preview: { 21 | port: 8080, 22 | strictPort: true, 23 | allowedHosts: [ 24 | '.genlayer.com', // match all genlayer.com sub-domains 25 | '.genlayerlabs.com', // match all genlayerlabs.com sub-domains 26 | '.genlayer.org', // match all genlayer.org sub-domains 27 | ], 28 | }, 29 | server: { 30 | port: 8080, 31 | strictPort: true, 32 | host: true, 33 | origin: 'http://0.0.0.0:8080', 34 | }, 35 | }; 36 | 37 | return config; 38 | }); 39 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/d932a5fef8b1_appeal_processing_time.py: -------------------------------------------------------------------------------- 1 | """appeal_processing_time 2 | 3 | Revision ID: d932a5fef8b1 4 | Revises: 2de9c3151194 5 | Create Date: 2025-01-31 15:39:44.618075 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "d932a5fef8b1" 17 | down_revision: Union[str, None] = "2de9c3151194" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", sa.Column("timestamp_appeal", sa.BigInteger(), nullable=True) 26 | ) 27 | op.add_column( 28 | "transactions", sa.Column("appeal_processing_time", sa.Integer(), nullable=True) 29 | ) 30 | # ### end Alembic commands ### 31 | 32 | 33 | def downgrade() -> None: 34 | # ### commands auto generated by Alembic - please adjust! ### 35 | op.drop_column("transactions", "appeal_processing_time") 36 | op.drop_column("transactions", "timestamp_appeal") 37 | # ### end Alembic commands ### 38 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests-pr.yml: -------------------------------------------------------------------------------- 1 | name: "Unit Tests" 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | push: 9 | branches: 10 | - main # so that test reports get uploaded to Codecov and SonarCloud 11 | workflow_dispatch: 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | frontend-unit-tests: 18 | if: (github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]') 19 | name: Unit Tests 20 | uses: ./.github/workflows/frontend-unit-tests.yml 21 | secrets: 22 | codecov_token: ${{ secrets.CODECOV_TOKEN }} 23 | 24 | backend-unit-tests: 25 | if: (github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]') 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: actions/setup-python@v5 30 | with: 31 | python-version: 3.13.3 32 | cache: pip 33 | - run: pip install -r backend/requirements.txt 34 | - run: pip install pytest-cov 35 | - run: pytest tests/unit --cov=backend --cov-report=xml --cov-branch 36 | - name: SonarCloud Scan 37 | uses: sonarsource/sonarqube-scan-action@v5.1.0 38 | env: 39 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 40 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/96840ab9133a_add_blocked_at_and_worker_id_to_.py: -------------------------------------------------------------------------------- 1 | """add blocked at and worker id to transactions 2 | 3 | Revision ID: 96840ab9133a 4 | Revises: 035ef00c2779 5 | Create Date: 2025-09-11 13:18:28.225878 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "96840ab9133a" 17 | down_revision: Union[str, None] = "035ef00c2779" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", 26 | sa.Column("blocked_at", sa.DateTime(timezone=True), nullable=True), 27 | ) 28 | op.add_column( 29 | "transactions", sa.Column("worker_id", sa.String(length=255), nullable=True) 30 | ) 31 | # ### end Alembic commands ### 32 | 33 | 34 | def downgrade() -> None: 35 | # ### commands auto generated by Alembic - please adjust! ### 36 | op.drop_column("transactions", "worker_id") 37 | op.drop_column("transactions", "blocked_at") 38 | # ### end Alembic commands ### 39 | -------------------------------------------------------------------------------- /frontend/src/components/global/ConfirmationModal.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | Are you sure? 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | Cancel 33 | {{ buttonText }} 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/integration/icontracts/tests/test_storage.py: -------------------------------------------------------------------------------- 1 | # tests/e2e/test_storage.py 2 | from gltest import get_contract_factory 3 | from gltest.assertions import tx_execution_succeeded 4 | 5 | from tests.integration.icontracts.schemas.call_contract_function import ( 6 | call_contract_function_response, 7 | ) 8 | from tests.common.response import ( 9 | assert_dict_struct, 10 | ) 11 | 12 | INITIAL_STATE = "a" 13 | UPDATED_STATE = "b" 14 | 15 | 16 | def test_storage(setup_validators): 17 | setup_validators() 18 | factory = get_contract_factory(contract_file_path="examples/contracts/storage.py") 19 | contract = factory.deploy(args=[INITIAL_STATE]) 20 | 21 | # Get initial state 22 | contract_state_1 = contract.get_storage(args=[]).call() 23 | assert contract_state_1 == INITIAL_STATE 24 | 25 | # Update State 26 | transaction_response_call_1 = contract.update_storage( 27 | args=[UPDATED_STATE] 28 | ).transact() 29 | assert tx_execution_succeeded(transaction_response_call_1) 30 | # Assert response format 31 | assert_dict_struct(transaction_response_call_1, call_contract_function_response) 32 | 33 | # Get Updated State 34 | contract_state_2 = contract.get_storage(args=[]).call() 35 | assert contract_state_2 == UPDATED_STATE 36 | -------------------------------------------------------------------------------- /.github/workflows/scan-codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL Scan" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: 9 | - opened 10 | - ready_for_review 11 | - reopened 12 | pull_request_review: 13 | types: 14 | - submitted 15 | - edited 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: [ubuntu-latest] 21 | timeout-minutes: 10 22 | if: (github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]') 23 | permissions: 24 | actions: read 25 | contents: read 26 | security-events: write 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: ["python", "javascript"] 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v4 36 | 37 | # Initializes the CodeQL tools for scanning. 38 | - name: Initialize CodeQL 39 | uses: github/codeql-action/init@v3 40 | with: 41 | languages: ${{ matrix.language }} 42 | 43 | - name: Autobuild 44 | uses: github/codeql-action/autobuild@v3 45 | 46 | - name: Perform CodeQL Analysis 47 | uses: github/codeql-action/analyze@v3 48 | with: 49 | category: "/language:${{matrix.language}}" 50 | -------------------------------------------------------------------------------- /frontend/src/components/Notification.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 17 | 18 | {{ props.item.title }} 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | {{ props.item.text }} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /tests/integration/icontracts/schemas/log_indexer_get_contract_schema_for_code.py: -------------------------------------------------------------------------------- 1 | log_indexer_contract_schema = { 2 | "id": 1, 3 | "jsonrpc": "2.0", 4 | "result": { 5 | "ctor": {"kwparams": {}, "params": []}, 6 | "methods": { 7 | "add_log": { 8 | "kwparams": {}, 9 | "params": [["log", "string"], ["log_id", "int"]], 10 | "payable": False, 11 | "readonly": False, 12 | "ret": "null", 13 | }, 14 | "get_closest_vector": { 15 | "kwparams": {}, 16 | "params": [["text", "string"]], 17 | "readonly": True, 18 | "ret": {"$or": ["dict", "null"]}, 19 | }, 20 | "remove_log": { 21 | "kwparams": {}, 22 | "params": [["id", "int"]], 23 | "payable": False, 24 | "readonly": False, 25 | "ret": "null", 26 | }, 27 | "update_log": { 28 | "kwparams": {}, 29 | "params": [["log_id", "int"], ["log", "string"]], 30 | "payable": False, 31 | "readonly": False, 32 | "ret": "null", 33 | }, 34 | }, 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /hardhat/contracts/v2_contracts/transactions/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import { ITransactions } from "./interfaces/ITransactions.sol"; 5 | import { Errors } from "../utils/Errors.sol"; 6 | 7 | contract Utils { 8 | function checkStatus( 9 | ITransactions.TransactionStatus _status, 10 | ITransactions.TransactionStatus _allowedStatus 11 | ) external pure { 12 | if (_status != _allowedStatus) { 13 | revert Errors.InvalidTransactionStatus(); 14 | } 15 | } 16 | 17 | function createDefaultUpdateInfo() 18 | external 19 | pure 20 | returns (ITransactions.UpdateTransactionInfo memory) 21 | { 22 | return 23 | ITransactions.UpdateTransactionInfo({ 24 | id: bytes32(0), 25 | activator: address(0), 26 | timestamps: ITransactions.Timestamps({ 27 | created: 0, 28 | pending: 0, 29 | activated: 0, 30 | proposed: 0, 31 | committed: 0, 32 | lastVote: 0 33 | }), 34 | consumedValidators: new address[](0), 35 | roundData: ITransactions.RoundData( 36 | 0, 37 | 0, 38 | 0, 39 | 0, 40 | 0, 41 | 0, 42 | ITransactions.ResultType(0), 43 | new address[](0), 44 | new bytes32[](0), 45 | new ITransactions.VoteType[](0) 46 | ), 47 | round: 0 48 | }); 49 | } 50 | } -------------------------------------------------------------------------------- /hardhat/contracts/BasicERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; 5 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 7 | import { ERC20Pausable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; 8 | 9 | /** 10 | * This file was generated with Openzeppelin Wizard and later modified. 11 | * GO TO: https://wizard.openzeppelin.com/#erc20 12 | */ 13 | contract BasicERC20 is ERC20, ERC20Burnable, ERC20Pausable, Ownable { 14 | constructor( 15 | string memory name, 16 | string memory symbol, 17 | address initialOwner, 18 | uint256 initialSupply 19 | ) ERC20(name, symbol) Ownable(initialOwner) { 20 | _mint(initialOwner, initialSupply); 21 | } 22 | 23 | function pause() external onlyOwner { 24 | _pause(); 25 | } 26 | 27 | function unpause() external onlyOwner { 28 | _unpause(); 29 | } 30 | 31 | function mint(address to, uint256 amount) external onlyOwner { 32 | _mint(to, amount); 33 | } 34 | 35 | function _update( 36 | address from, 37 | address to, 38 | uint256 value 39 | ) internal override(ERC20, ERC20Pausable) { 40 | super._update(from, to, value); 41 | } 42 | } -------------------------------------------------------------------------------- /frontend/src/components/Simulator/LogFilterBtn.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 41 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/b5acc405bcca_add_transactions_triggers.py: -------------------------------------------------------------------------------- 1 | """add transactions triggers 2 | 3 | Revision ID: b5acc405bcca 4 | Revises: a32f85df2806 5 | Create Date: 2024-10-03 10:41:45.957685 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "b5acc405bcca" 17 | down_revision: Union[str, None] = "a32f85df2806" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", 26 | sa.Column("triggered_by_hash", sa.String(length=66), nullable=True), 27 | ) 28 | op.create_foreign_key( 29 | "triggered_by_hash_fkey", 30 | "transactions", 31 | "transactions", 32 | ["triggered_by_hash"], 33 | ["hash"], 34 | ) 35 | # ### end Alembic commands ### 36 | 37 | 38 | def downgrade() -> None: 39 | # ### commands auto generated by Alembic - please adjust! ### 40 | op.drop_constraint("triggered_by_hash_fkey", "transactions", type_="foreignkey") 41 | op.drop_column("transactions", "triggered_by_hash") 42 | # ### end Alembic commands ### 43 | -------------------------------------------------------------------------------- /tests/common/response.py: -------------------------------------------------------------------------------- 1 | def _assert_dict_struct(data, structure): 2 | if isinstance(structure, dict): 3 | assert_is_instance(data, dict) 4 | for key, value in structure.items(): 5 | try: 6 | assert key in data 7 | assert_dict_struct(data[key], value) 8 | except BaseException as e: 9 | e.add_note(f"dict key {key!r}") 10 | raise 11 | elif isinstance(structure, list): 12 | assert_is_instance(data, list) 13 | for idx, item in enumerate(data): 14 | try: 15 | assert_dict_struct(item, structure[0]) 16 | except BaseException as e: 17 | e.add_note(f"list item [{idx}]") 18 | raise 19 | else: 20 | assert_is_instance(data, structure) 21 | 22 | 23 | def assert_dict_struct(data, structure): 24 | try: 25 | return _assert_dict_struct(data, structure) 26 | except BaseException as e: 27 | e.add_note(f"while asserting dict structure of {data!r}") 28 | 29 | 30 | def assert_is_instance(data, structure): 31 | assert isinstance(data, structure), f"Expected {structure}, but got {data}" 32 | 33 | 34 | def has_error_status(result: dict) -> bool: 35 | return "error" in result 36 | 37 | 38 | def has_success_status(result: dict) -> bool: 39 | return "error" not in result 40 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Fixes #issue-number-here 4 | 5 | # What 6 | 7 | 8 | 9 | - changed thing a for b 10 | - also did this other unrelated thing in my path 11 | 12 | # Why 13 | 14 | 15 | 16 | - to fix a bug 17 | - to add more value to the user 18 | 19 | # Testing done 20 | 21 | 22 | 23 | - tested the new feature 24 | - tested the bug fix 25 | 26 | # Decisions made 27 | 28 | 29 | 30 | # Checks 31 | 32 | - [ ] I have tested this code 33 | - [ ] I have reviewed my own PR 34 | - [ ] I have created an issue for this PR 35 | - [ ] I have set a descriptive PR title compliant with [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) 36 | 37 | # Reviewing tips 38 | 39 | 40 | 41 | # User facing release notes 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/src/hooks/useGenlayer.ts: -------------------------------------------------------------------------------- 1 | import { localnet } from 'genlayer-js/chains'; 2 | import { createClient, createAccount } from 'genlayer-js'; 3 | import type { GenLayerClient } from 'genlayer-js/types'; 4 | import { ref, watch, type Ref } from 'vue'; 5 | import { useAccountsStore } from '@/stores'; 6 | import { getRuntimeConfig } from '@/utils/runtimeConfig'; 7 | 8 | type UseGenlayerReturn = { 9 | client: Ref | null>; 10 | initClient: () => void; 11 | }; 12 | 13 | export function useGenlayer(): UseGenlayerReturn { 14 | const accountsStore = useAccountsStore(); 15 | const client = ref | null>(null); 16 | 17 | if (!client.value) { 18 | initClient(); 19 | } 20 | 21 | watch([() => accountsStore.selectedAccount?.address], () => { 22 | initClient(); 23 | }); 24 | 25 | function initClient() { 26 | const clientAccount = 27 | accountsStore.selectedAccount?.type === 'local' 28 | ? createAccount(accountsStore.selectedAccount?.privateKey) 29 | : accountsStore.selectedAccount?.address; 30 | 31 | client.value = createClient({ 32 | chain: localnet, 33 | endpoint: getRuntimeConfig( 34 | 'VITE_JSON_RPC_SERVER_URL', 35 | 'http://127.0.0.1:4000/api', 36 | ), 37 | account: clientAccount, 38 | }); 39 | } 40 | 41 | return { 42 | client, 43 | initClient, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /frontend/test/unit/hooks/useInputMap.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { useInputMap } from '@/hooks'; 3 | import StringField from '@/components/global/fields/StringField.vue'; 4 | import IntegerField from '@/components/global/fields/IntegerField.vue'; 5 | import BooleanField from '@/components/global/fields/BooleanField.vue'; 6 | import AnyField from '@/components/global/fields/AnyField.vue'; 7 | 8 | describe('useInputMap composable', () => { 9 | const { getComponent } = useInputMap(); 10 | 11 | it('should return the correct component for type "string"', () => { 12 | const component = getComponent('string'); 13 | expect(component).toBe(StringField); 14 | }); 15 | 16 | it('should return the correct component for type "int"', () => { 17 | const component = getComponent('int'); 18 | expect(component).toBe(IntegerField); 19 | }); 20 | 21 | it('should return the correct component for type "bool"', () => { 22 | const component = getComponent('bool'); 23 | expect(component).toBe(BooleanField); 24 | }); 25 | 26 | it('should default to string for an empty type', () => { 27 | const component = getComponent('' as any); 28 | expect(component).toBe(AnyField); 29 | }); 30 | 31 | it('should default to string for an unknown type', () => { 32 | const component = getComponent('unknown' as any); 33 | expect(component).toBe(AnyField); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/22319ddb113d_tx_receipt.py: -------------------------------------------------------------------------------- 1 | """tx_receipt 2 | 3 | Revision ID: 22319ddb113d 4 | Revises: c0244b1d49b0 5 | Create Date: 2025-05-28 19:06:34.835960 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "22319ddb113d" 17 | down_revision: Union[str, None] = "c0244b1d49b0" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column( 25 | "transactions", 26 | sa.Column("num_of_initial_validators", sa.Integer(), nullable=True), 27 | ) 28 | op.add_column( 29 | "transactions", sa.Column("last_vote_timestamp", sa.BigInteger(), nullable=True) 30 | ) 31 | op.add_column( 32 | "transactions", sa.Column("rotation_count", sa.Integer(), nullable=True) 33 | ) 34 | # ### end Alembic commands ### 35 | 36 | 37 | def downgrade() -> None: 38 | # ### commands auto generated by Alembic - please adjust! ### 39 | op.drop_column("transactions", "rotation_count") 40 | op.drop_column("transactions", "last_vote_timestamp") 41 | op.drop_column("transactions", "num_of_initial_validators") 42 | # ### end Alembic commands ### 43 | -------------------------------------------------------------------------------- /backend/protocol_rpc/rpc_decorators.py: -------------------------------------------------------------------------------- 1 | """Decorator-based RPC endpoint registration utilities.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Callable, Iterable, List, Optional 6 | 7 | from backend.protocol_rpc.rpc_endpoint_manager import ( 8 | LogPolicy, 9 | RPCEndpointDefinition, 10 | ) 11 | 12 | 13 | class RPCEndpointRegistry: 14 | def __init__(self) -> None: 15 | self._definitions: list[RPCEndpointDefinition] = [] 16 | 17 | def method( 18 | self, 19 | name: str, 20 | *, 21 | description: Optional[str] = None, 22 | log_policy: Optional[LogPolicy] = None, 23 | ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: 24 | def decorator(func: Callable[..., Any]) -> Callable[..., Any]: 25 | definition = RPCEndpointDefinition( 26 | name=name, 27 | handler=func, 28 | description=description, 29 | log_policy=log_policy or LogPolicy(), 30 | ) 31 | self._definitions.append(definition) 32 | return func 33 | 34 | return decorator 35 | 36 | def extend(self, definitions: Iterable[RPCEndpointDefinition]) -> None: 37 | self._definitions.extend(definitions) 38 | 39 | def to_list(self) -> List[RPCEndpointDefinition]: 40 | return list(self._definitions) 41 | 42 | 43 | rpc = RPCEndpointRegistry() 44 | -------------------------------------------------------------------------------- /tests/integration/icontracts/tests/test_football_prediction_market.py: -------------------------------------------------------------------------------- 1 | # tests/e2e/test_storage.py 2 | from gltest import get_contract_factory 3 | from gltest.assertions import tx_execution_succeeded 4 | import json 5 | 6 | 7 | def test_football_prediction_market(setup_validators): 8 | team_1 = "Georgia" 9 | team_2 = "Portugal" 10 | score = "2:0" 11 | winner = 1 12 | mock_response = { 13 | "response": { 14 | f"Team 1: {team_1}\nTeam 2: {team_2}": json.dumps( 15 | { 16 | "score": score, 17 | "winner": winner, 18 | } 19 | ), 20 | } 21 | } 22 | setup_validators(mock_response) 23 | 24 | # Deploy Contract 25 | factory = get_contract_factory("PredictionMarket") 26 | contract = factory.deploy(args=["2024-06-26", team_1, team_2]) 27 | 28 | ######################################## 29 | ############# RESOLVE match ############ 30 | ######################################## 31 | transaction_response_call_1 = contract.resolve(args=[]).transact() 32 | assert tx_execution_succeeded(transaction_response_call_1) 33 | 34 | # Get Updated State 35 | contract_state_2 = contract.get_resolution_data(args=[]).call() 36 | 37 | assert contract_state_2["winner"] == winner 38 | assert contract_state_2["score"] == score 39 | assert contract_state_2["has_resolved"] == True 40 | -------------------------------------------------------------------------------- /frontend/src/hooks/useTransactionListener.ts: -------------------------------------------------------------------------------- 1 | import { useTransactionsStore } from '@/stores'; 2 | import type { TransactionItem } from '@/types'; 3 | import { useWebSocketClient } from '@/hooks'; 4 | 5 | export function useTransactionListener() { 6 | const transactionsStore = useTransactionsStore(); 7 | const webSocketClient = useWebSocketClient(); 8 | 9 | function init() { 10 | webSocketClient.on('transaction_status_updated', handleTransactionUpdate); 11 | webSocketClient.on('transaction_appeal_updated', handleTransactionUpdate); 12 | } 13 | 14 | async function handleTransactionUpdate(eventData: any) { 15 | const newTx = await transactionsStore.getTransaction(eventData.data.hash); 16 | 17 | const currentTx = transactionsStore.transactions.find( 18 | (t: TransactionItem) => t.hash === eventData.data.hash, 19 | ); 20 | 21 | if (currentTx && !newTx) { 22 | console.log('Server tx not found for local tx:', currentTx); 23 | // We're cleaning up local txs that don't exist on the server anymore 24 | transactionsStore.removeTransaction(currentTx); 25 | return; 26 | } 27 | 28 | if (!currentTx) { 29 | // This happens regularly when local transactions get cleared (e.g. user clears all txs or deploys new contract instance) 30 | return; 31 | } 32 | 33 | transactionsStore.updateTransaction(newTx); 34 | } 35 | 36 | return { 37 | init, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /tests/integration/icontracts/tests/test_read_erc20.py: -------------------------------------------------------------------------------- 1 | from gltest import get_contract_factory 2 | 3 | 4 | def test_read_erc20(setup_validators, default_account): 5 | """ 6 | Tests that recursive contract calls work by: 7 | 1. creating an LLM ERC20 contract 8 | 2. creating a read_erc20 contract that reads the LLM ERC20 contract 9 | 3. creating a read_erc20 contract that reads the previous read_erc20 contract 10 | Repeats step 3 a few times. 11 | 12 | It's like a linked list, but with contracts. 13 | """ 14 | setup_validators() 15 | TOKEN_TOTAL_SUPPLY = 1000 16 | 17 | # LLM ERC20 18 | llm_erc20_factory = get_contract_factory("LlmErc20") 19 | 20 | # Deploy Contract 21 | llm_erc20_contract = llm_erc20_factory.deploy(args=[TOKEN_TOTAL_SUPPLY]) 22 | last_contract_address = llm_erc20_contract.address 23 | 24 | # Read ERC20 25 | read_erc20_factory = get_contract_factory("read_erc20") 26 | 27 | for i in range(5): 28 | print(f"Deploying contract, iteration {i}") 29 | 30 | # deploy contract 31 | read_erc20_contract = read_erc20_factory.deploy(args=[last_contract_address]) 32 | last_contract_address = read_erc20_contract.address 33 | 34 | # check balance 35 | contract_state = read_erc20_contract.get_balance_of( 36 | args=[default_account.address] 37 | ).call() 38 | assert contract_state == TOKEN_TOTAL_SUPPLY 39 | -------------------------------------------------------------------------------- /frontend/src/hooks/useMockContractData.ts: -------------------------------------------------------------------------------- 1 | import { type DeployedContract, type TransactionItem } from '@/types'; 2 | import { TransactionStatus } from 'genlayer-js/types'; 3 | 4 | export function useMockContractData() { 5 | const mockContractId = '1a621cad-1cfd-4dbd-892a-f6bbde7a2fab'; 6 | const mockContractAddress = 7 | '0x3F9Fb6C6aBaBD0Ae6cB27c513E7b0fE4C0B3E9C8' as const; 8 | 9 | const mockDeployedContract: DeployedContract = { 10 | address: mockContractAddress, 11 | contractId: mockContractId, 12 | defaultState: '{}', 13 | }; 14 | 15 | const mockContractSchema = { 16 | ctor: { 17 | kwparams: {}, 18 | params: [['initial_storage', 'string']], 19 | }, 20 | methods: { 21 | get_storage: { 22 | kwparams: {}, 23 | params: [], 24 | readonly: true, 25 | ret: 'string', 26 | }, 27 | update_storage: { 28 | kwparams: {}, 29 | params: [['new_storage', 'string']], 30 | readonly: false, 31 | ret: 'null', 32 | }, 33 | }, 34 | }; 35 | 36 | const mockDeploymentTx: TransactionItem = { 37 | contractAddress: mockContractAddress, 38 | localContractId: mockContractId, 39 | hash: '0x123', 40 | type: 'deploy', 41 | statusName: TransactionStatus.FINALIZED, 42 | data: {}, 43 | }; 44 | 45 | return { 46 | mockContractId, 47 | mockDeployedContract, 48 | mockContractSchema, 49 | mockDeploymentTx, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router'; 2 | import SimulatorView from '@/views/Simulator/SimulatorView.vue'; 3 | import ContractsView from '@/views/Simulator/ContractsView.vue'; 4 | import RunDebugView from '@/views/Simulator/RunDebugView.vue'; 5 | import ValidatorsView from '@/views/Simulator/ValidatorsView.vue'; 6 | import SettingsView from '@/views/Simulator/SettingsView.vue'; 7 | 8 | const router = createRouter({ 9 | history: createWebHistory(import.meta.env.BASE_URL), 10 | routes: [ 11 | { 12 | path: '/', 13 | redirect: { name: 'contracts' }, 14 | component: SimulatorView, 15 | children: [ 16 | { 17 | path: 'contracts', 18 | name: 'contracts', 19 | component: ContractsView, 20 | props: true, 21 | }, 22 | { 23 | path: 'run-debug', 24 | name: 'run-debug', 25 | component: RunDebugView, 26 | props: true, 27 | }, 28 | { 29 | path: 'validators', 30 | name: 'validators', 31 | component: ValidatorsView, 32 | props: true, 33 | }, 34 | { 35 | path: 'settings', 36 | name: 'settings', 37 | component: SettingsView, 38 | props: true, 39 | }, 40 | ], 41 | }, 42 | { 43 | path: '/:pathMatch(.*)*', 44 | redirect: '/', 45 | }, 46 | ], 47 | }); 48 | 49 | export default router; 50 | -------------------------------------------------------------------------------- /frontend/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './results'; 2 | export * from './requests'; 3 | export * from './responses'; 4 | export * from './store'; 5 | export * from './events'; 6 | 7 | export interface ValidatorModel { 8 | address: string; 9 | config: any; 10 | id: number; 11 | model: string; 12 | provider: string; 13 | stake: number; 14 | updated_at: string; 15 | plugin: string; 16 | plugin_config: Record; 17 | } 18 | 19 | export interface NewValidatorDataModel { 20 | config: string; 21 | model: string; 22 | provider: string; 23 | stake: number; 24 | plugin: string; 25 | plugin_config: Record; 26 | } 27 | 28 | export interface ProviderModel { 29 | id: number; 30 | provider: string; 31 | model: string; 32 | config: Record; 33 | plugin: string; 34 | plugin_config: Record; 35 | is_available: boolean; 36 | is_model_available: boolean; 37 | is_default: boolean; 38 | } 39 | 40 | export interface NewProviderDataModel { 41 | provider: string; 42 | model: string; 43 | config: Record; 44 | plugin: string; 45 | plugin_config: Record; 46 | } 47 | 48 | export type Address = `0x${string}`; 49 | 50 | export interface SchemaProperty { 51 | type?: string | string[]; 52 | default?: any; 53 | minimum?: number; 54 | maximum?: number; 55 | multipleOf?: number; 56 | enum?: any[]; 57 | $comment?: string; 58 | properties?: Record; 59 | } 60 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/main.css'; 2 | import { createApp } from 'vue'; 3 | import { createPinia } from 'pinia'; 4 | import Notifications from '@kyvg/vue3-notification'; 5 | import App from './App.vue'; 6 | import router from './router'; 7 | import { persistStorePlugin } from '@/plugins'; 8 | import { VueSpinnersPlugin } from 'vue3-spinners'; 9 | import registerGlobalComponents from '@/components/global/registerGlobalComponents'; 10 | import { VueQueryPlugin } from '@tanstack/vue-query'; 11 | import FloatingVue from 'floating-vue'; 12 | import 'floating-vue/dist/style.css'; 13 | import { createPlausible } from 'v-plausible/vue'; 14 | import { getRuntimeConfig } from '@/utils/runtimeConfig'; 15 | 16 | const app = createApp(App); 17 | const pinia = createPinia(); 18 | 19 | pinia.use(persistStorePlugin); 20 | app.use(pinia); 21 | app.use(VueQueryPlugin); 22 | app.use(router); 23 | app.use(FloatingVue, { 24 | themes: { 25 | tooltip: { 26 | distance: 10, 27 | delay: { 28 | show: 0, 29 | hide: 0, 30 | }, 31 | }, 32 | }, 33 | }); 34 | app.use(Notifications); 35 | app.use(VueSpinnersPlugin); 36 | 37 | const plausible = createPlausible({ 38 | init: { 39 | domain: getRuntimeConfig('VITE_PLAUSIBLE_DOMAIN', 'studio.genlayer.com'), 40 | trackLocalhost: true, 41 | }, 42 | settings: { 43 | enableAutoPageviews: true, 44 | }, 45 | }); 46 | 47 | app.use(plausible); 48 | 49 | registerGlobalComponents(app); 50 | 51 | app.mount('#app'); 52 | -------------------------------------------------------------------------------- /tests/integration/README.md: -------------------------------------------------------------------------------- 1 | # Integration Tests 2 | 3 | This directory contains integration tests for the GenLayer Studio project. These tests verify that different components of the system work together correctly. 4 | 5 | ## Prerequisites 6 | 7 | - `gltest` command-line tool installed 8 | - Access to the required contracts directory 9 | 10 | ## Setup 11 | 12 | 1. Ensure you have all dependencies installed: 13 | ```sh 14 | pip install -r requirements.txt 15 | pip install -r requirements.test.txt 16 | ``` 17 | 18 | 2. Make sure you have the necessary intelligent contracts in your workspace 19 | 20 | ## Running Tests 21 | 22 | From the root of the repository, run the following command: 23 | 24 | ```sh 25 | gltest --contracts-dir . tests/integration/ -svv 26 | ``` 27 | 28 | ### Command Options 29 | 30 | - `--contracts-dir .`: Specifies the directory containing the contract files 31 | - `-svv`: Verbose output flag for detailed test information 32 | 33 | ### Test Structure 34 | 35 | The integration tests are organized to test: 36 | - Contract interactions 37 | - RPC endpoints 38 | 39 | ## Troubleshooting 40 | 41 | If you encounter issues: 42 | 1. Verify that all dependencies are installed 43 | 2. Check that the contracts directory is properly configured 44 | 45 | 46 | ## Contributing 47 | 48 | When adding new integration tests: 49 | 1. Follow the existing test structure 50 | 2. Include clear test descriptions 51 | 3. Add appropriate assertions 52 | 4. Document any special setup requirements -------------------------------------------------------------------------------- /backend/database_handler/migration/versions/ab256b41602a_remove_invalid_models.py: -------------------------------------------------------------------------------- 1 | """remove invalid models 2 | 3 | Revision ID: ab256b41602a 4 | Revises: 3bc34e44eb72 5 | Create Date: 2024-10-16 15:34:32.798280 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | from alembic import op 12 | import sqlalchemy as sa 13 | 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "ab256b41602a" 17 | down_revision: Union[str, None] = "3bc34e44eb72" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | op.execute("DELETE FROM validators WHERE model = 'dolphin-2.9-llama3-8b'") 24 | op.execute("DELETE FROM validators WHERE model = 'mistralai/mixtral-8x7b-instruct'") 25 | op.execute("DELETE FROM validators WHERE model = 'openhermes-2-yi-34b-gptq'") 26 | op.execute("DELETE FROM validators WHERE model = 'meta-llama/llama-2-70b-chat'") 27 | 28 | op.execute("DELETE FROM llm_provider WHERE model = 'dolphin-2.9-llama3-8b'") 29 | op.execute( 30 | "DELETE FROM llm_provider WHERE model = 'mistralai/mixtral-8x7b-instruct'" 31 | ) 32 | op.execute("DELETE FROM llm_provider WHERE model = 'openhermes-2-yi-34b-gptq'") 33 | op.execute("DELETE FROM llm_provider WHERE model = 'meta-llama/llama-2-70b-chat'") 34 | 35 | 36 | def downgrade() -> None: 37 | # ### commands auto generated by Alembic - please adjust! ### 38 | pass 39 | # ### end Alembic commands ### 40 | -------------------------------------------------------------------------------- /frontend/src/clients/rpc.ts: -------------------------------------------------------------------------------- 1 | import type { JsonRPCRequest, JsonRPCResponse } from '@/types'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | import { useWebSocketClient } from '@/hooks'; 4 | import { getRuntimeConfig } from '@/utils/runtimeConfig'; 5 | 6 | const JSON_RPC_SERVER_URL = getRuntimeConfig( 7 | 'VITE_JSON_RPC_SERVER_URL', 8 | 'http://127.0.0.1:4000/api', 9 | ); 10 | 11 | export interface IRpcClient { 12 | call(request: JsonRPCRequest): Promise>; 13 | } 14 | 15 | export class RpcClient implements IRpcClient { 16 | async call({ 17 | method, 18 | params, 19 | }: JsonRPCRequest): Promise> { 20 | const webSocketClient = useWebSocketClient(); 21 | // Wait for the websocket client to connect 22 | await new Promise((resolve) => { 23 | if (webSocketClient.connected) { 24 | resolve(); 25 | } else { 26 | webSocketClient.on('connect', () => { 27 | resolve(); 28 | }); 29 | } 30 | }); 31 | 32 | const requestId = uuidv4(); 33 | const data = { 34 | jsonrpc: '2.0', 35 | method, 36 | params, 37 | id: requestId, 38 | }; 39 | const response = await fetch(JSON_RPC_SERVER_URL, { 40 | method: 'POST', 41 | headers: { 42 | 'Content-Type': 'application/json', 43 | 'x-session-id': webSocketClient.id ?? '', 44 | }, 45 | body: JSON.stringify(data), 46 | }); 47 | return response.json() as Promise>; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/parsers/splitTopLevelLines.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Split a string into lines only at top-level (not inside strings or nested 3 | * braces/brackets). Preserves chunks such as JSON objects/arrays that may 4 | * span multiple lines. 5 | */ 6 | export function splitTopLevelLines(s: string): string[] { 7 | const out: string[] = []; 8 | let buf = ''; 9 | let depthCurly = 0; 10 | let depthSquare = 0; 11 | let inString: '"' | "'" | null = null; 12 | let escaped = false; 13 | 14 | for (let i = 0; i < s.length; i++) { 15 | const ch = s[i]; 16 | buf += ch; 17 | 18 | if (escaped) { 19 | escaped = false; 20 | continue; 21 | } 22 | if (inString && ch === '\\') { 23 | escaped = true; 24 | continue; 25 | } 26 | 27 | if (inString) { 28 | if (ch === inString) inString = null; 29 | continue; 30 | } else if (ch === '"' || ch === "'") { 31 | inString = ch as '"' | "'"; 32 | continue; 33 | } 34 | 35 | if (ch === '{') depthCurly++; 36 | else if (ch === '}') depthCurly = Math.max(0, depthCurly - 1); 37 | else if (ch === '[') depthSquare++; 38 | else if (ch === ']') depthSquare = Math.max(0, depthSquare - 1); 39 | 40 | if ((ch === '\n' || ch === '\r') && depthCurly === 0 && depthSquare === 0) { 41 | const trimmed = buf.trim(); 42 | if (trimmed) out.push(trimmed); 43 | buf = ''; 44 | } 45 | } 46 | 47 | const tail = buf.trim(); 48 | if (tail) out.push(tail); 49 | return out; 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/types/requests.ts: -------------------------------------------------------------------------------- 1 | export interface JsonRPCRequest { 2 | method: string; 3 | params: any[]; 4 | } 5 | 6 | export interface GetContractStateRequest { 7 | to: string; 8 | from: string; 9 | data: string; 10 | } 11 | 12 | export interface GetContractSchemaRequest { 13 | code: string; 14 | } 15 | 16 | export interface GetDeployedContractSchemaRequest { 17 | address: string; 18 | } 19 | 20 | export interface CreateValidatorRequest { 21 | stake: number; 22 | provider: string; 23 | model: string; 24 | config?: Record; 25 | plugin?: string; 26 | plugin_config?: Record; 27 | } 28 | 29 | export interface UpdateValidatorRequest { 30 | address: string; 31 | stake: number; 32 | provider: string; 33 | model: string; 34 | config?: Record; 35 | plugin?: string; 36 | plugin_config?: Record; 37 | } 38 | 39 | export interface DeleteValidatorRequest { 40 | address: string; 41 | } 42 | 43 | export interface AddProviderRequest { 44 | provider: string; 45 | model: string; 46 | config: Record; 47 | plugin: string; 48 | plugin_config: Record; 49 | } 50 | 51 | export interface UpdateProviderRequest { 52 | id: number; 53 | provider: string; 54 | model: string; 55 | config: Record; 56 | plugin: string; 57 | plugin_config: Record; 58 | } 59 | 60 | export interface DeleteProviderRequest { 61 | id: number; 62 | } 63 | 64 | export interface GetTransactionCountRequest { 65 | address: string; 66 | } 67 | -------------------------------------------------------------------------------- /frontend/src/services/IJsonRpcService.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | GetContractStateRequest, 3 | GetContractStateResult, 4 | GetDeployedContractSchemaRequest, 5 | AddProviderRequest, 6 | UpdateProviderRequest, 7 | DeleteProviderRequest, 8 | CreateValidatorRequest, 9 | UpdateValidatorRequest, 10 | DeleteValidatorRequest, 11 | GetContractSchemaRequest, 12 | GetTransactionCountRequest, 13 | } from '@/types'; 14 | 15 | export interface IJsonRpcService { 16 | getContractState( 17 | request: GetContractStateRequest, 18 | ): Promise; 19 | sendTransaction(signedTransaction: string): Promise; 20 | getContractSchema(request: GetContractSchemaRequest): Promise; 21 | getDeployedContractSchema( 22 | request: GetDeployedContractSchemaRequest, 23 | ): Promise; 24 | getValidators(): Promise; 25 | getProvidersAndModels(): Promise; 26 | addProvider(request: AddProviderRequest): Promise; 27 | updateProvider(request: UpdateProviderRequest): Promise; 28 | deleteProvider(request: DeleteProviderRequest): Promise; 29 | createValidator(request: CreateValidatorRequest): Promise; 30 | updateValidator(request: UpdateValidatorRequest): Promise; 31 | deleteValidator(request: DeleteValidatorRequest): Promise; 32 | getTransactionByHash(hash: string): Promise; 33 | getTransactionCount(address: GetTransactionCountRequest): Promise; 34 | setFinalityWindowTime(time: number): Promise; 35 | getFinalityWindowTime(): Promise; 36 | } 37 | -------------------------------------------------------------------------------- /docker/entrypoint-frontend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # Determine if we're in dev or prod mode based on which directory exists 5 | if [ -d "/app/dist" ]; then 6 | # Production mode: built app exists in /app/dist 7 | CONFIG_DIR="/app/dist" 8 | START_CMD="npm run preview" 9 | MODE="production" 10 | else 11 | # Development mode: no build, use /app/public for Vite to serve 12 | CONFIG_DIR="/app/public" 13 | START_CMD="npm run dev" 14 | MODE="development" 15 | fi 16 | 17 | echo "Generating runtime configuration for ${MODE}..." 18 | 19 | # Create config directory if it doesn't exist 20 | mkdir -p "${CONFIG_DIR}" 21 | 22 | # Generate runtime config that overrides build-time values 23 | cat > "${CONFIG_DIR}/runtime-config.js" < { 14 | before(async () => { 15 | driver = await getDriver(); 16 | validatorsPage = new ValidatorsPage(driver); 17 | contractsPage = new ContractsPage(driver); 18 | }); 19 | 20 | it('should create a new validator', async () => { 21 | await contractsPage.navigate(); 22 | await contractsPage.waitUntilVisible(); 23 | await contractsPage.skipTutorial(); 24 | 25 | await validatorsPage.navigate(); 26 | await validatorsPage.waitUntilVisible(); 27 | 28 | const initialValidators = await validatorsPage.getValidatorsElements(); 29 | await validatorsPage.createValidator({ 30 | provider: 'heuristai', 31 | model: 'mistralai/mixtral-8x7b-instruct', 32 | stake: 7, 33 | }); 34 | const existingValidators = await validatorsPage.getValidatorsElements(); 35 | expect( 36 | existingValidators.length, 37 | 'number of validators should be greather than old validators list', 38 | ).be.greaterThan(initialValidators.length); 39 | }); 40 | 41 | after(() => driver.quit()); 42 | }); 43 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branches: ['main'], 3 | "plugins": [ 4 | [ 5 | "@semantic-release/commit-analyzer", 6 | { 7 | "preset": "conventionalcommits", 8 | "releaseRules": [ 9 | { "type": "feat", "release": "minor" }, 10 | { "type": "*", "release": "patch" }, 11 | ], 12 | } 13 | ], 14 | [ 15 | "@semantic-release/release-notes-generator", 16 | { 17 | "preset": "conventionalcommits", 18 | "presetConfig": { 19 | "types": [ 20 | { "type": "feat", "section": "Features" }, 21 | { "type": "fix", "section": "Bug Fixes" }, 22 | { "type": "chore", "section": "Miscellaneous" }, 23 | { "type": "docs", "section": "Miscellaneous" }, 24 | { "type": "style", "section": "Miscellaneous" }, 25 | { "type": "refactor", "section": "Miscellaneous" }, 26 | { "type": "perf", "section": "Miscellaneous" }, 27 | { "type": "test", "section": "Miscellaneous" } 28 | ] 29 | }, 30 | } 31 | ], 32 | [ 33 | "@semantic-release/npm", 34 | { 35 | "pkgRoot": "frontend", 36 | "npmPublish": false 37 | } 38 | ], 39 | '@semantic-release/github', 40 | ] 41 | }; 42 | -------------------------------------------------------------------------------- /frontend/src/components/Simulator/settings/ConsensusSection.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | Consensus 26 | 27 | 28 | 29 | Max Rotations 30 | 42 | 43 | 44 | Please enter a positive integer. 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /uvicorn_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Uvicorn configuration for production deployment. 3 | """ 4 | 5 | import os 6 | import multiprocessing 7 | 8 | # Server socket 9 | bind = f"0.0.0.0:{os.getenv('RPCPORT', '4000')}" 10 | host = "0.0.0.0" 11 | port = int(os.getenv("RPCPORT", "4000")) 12 | 13 | # Worker processes 14 | workers = int(os.getenv("WEB_CONCURRENCY", 1)) 15 | 16 | # Worker class - use uvloop for better async performance 17 | worker_class = "uvicorn.workers.UvicornWorker" 18 | loop = "uvloop" # High-performance event loop 19 | 20 | # Logging 21 | log_level = os.getenv("LOG_LEVEL", "info").lower() 22 | access_log = True 23 | error_log = "-" 24 | access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s' 25 | 26 | # Timeouts 27 | timeout = 60 28 | keepalive = 5 29 | 30 | # SSL (if needed) 31 | # ssl_keyfile = '/path/to/keyfile' 32 | # ssl_certfile = '/path/to/certfile' 33 | 34 | # Reload 35 | reload = False # Never use in production 36 | 37 | # Process naming 38 | proc_name = "genlayer-asgi" 39 | 40 | # Server mechanics 41 | forwarded_allow_ips = "*" # Be careful with this in production 42 | proxy_headers = True 43 | proxy_protocol = False 44 | 45 | # Limits 46 | limit_concurrency = 1000 # Maximum number of concurrent connections 47 | limit_max_requests = 1000 # Restart workers after this many requests 48 | 49 | # WebSocket support 50 | ws = "auto" # Auto-detect WebSocket support 51 | ws_max_size = 16777216 # 16MB max WebSocket message size 52 | ws_ping_interval = 20 # Ping interval for WebSocket keepalive 53 | ws_ping_timeout = 60 # Timeout for WebSocket ping responses 54 | -------------------------------------------------------------------------------- /frontend/public/loader.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | html { 6 | overflow-x: hidden; 7 | overflow-y: scroll; 8 | } 9 | 10 | #loading-bg { 11 | position: absolute; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | background: var(--initial-loader-bg, #fff); 17 | block-size: 100%; 18 | gap: 1rem 0; 19 | inline-size: 100%; 20 | } 21 | 22 | .loading { 23 | position: relative; 24 | box-sizing: border-box; 25 | border: 3px solid transparent; 26 | border-radius: 50%; 27 | block-size: 55px; 28 | inline-size: 55px; 29 | } 30 | 31 | .loading .effect-1, 32 | .loading .effect-2, 33 | .loading .effect-3 { 34 | position: absolute; 35 | box-sizing: border-box; 36 | border: 3px solid transparent; 37 | border-radius: 50%; 38 | block-size: 100%; 39 | border-inline-start: 3px solid var(--initial-loader-color, #eee); 40 | inline-size: 100%; 41 | } 42 | 43 | .loading .effect-1 { 44 | animation: rotate 1s ease infinite; 45 | } 46 | 47 | .loading .effect-2 { 48 | animation: rotate-opacity 1s ease infinite 0.1s; 49 | } 50 | 51 | .loading .effect-3 { 52 | animation: rotate-opacity 1s ease infinite 0.2s; 53 | } 54 | 55 | .loading .effects { 56 | transition: all 0.3s ease; 57 | } 58 | 59 | @keyframes rotate { 60 | 0% { 61 | transform: rotate(0deg); 62 | } 63 | 64 | 100% { 65 | transform: rotate(1turn); 66 | } 67 | } 68 | 69 | @keyframes rotate-opacity { 70 | 0% { 71 | opacity: 0.1; 72 | transform: rotate(0deg); 73 | } 74 | 75 | 100% { 76 | opacity: 1; 77 | transform: rotate(1turn); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.github/workflows/release-from-main.yml: -------------------------------------------------------------------------------- 1 | name: Create releases from main 2 | on: 3 | schedule: 4 | - cron: "00 08 * * 1-4" # 08:00 AM (UTC) from Monday to Thursday 5 | workflow_dispatch: 6 | permissions: 7 | contents: read # for checkout 8 | 9 | concurrency: 10 | group: release-please 11 | 12 | jobs: 13 | release: 14 | name: Release 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write # to be able to publish a GitHub release 18 | issues: write # to be able to comment on released issues 19 | pull-requests: write # to be able to comment on released pull requests 20 | id-token: write # to enable use of OIDC for npm provenance 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: "lts/*" 30 | - name: Install dependencies 31 | run: npm install @semantic-release/github @semantic-release/release-notes-generator @semantic-release/commit-analyzer @semantic-release/npm conventional-changelog-conventionalcommits 32 | - name: Release 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | run: npx semantic-release # Uses `release.config.js` for configuration 36 | push-docker-images: 37 | name: Build images and push to DockerHub # Since GH Actions do not trigger other GH Actions (https://github.com/orgs/community/discussions/25702) we trigger it manually 38 | uses: ./.github/workflows/docker-build-and-push-all.yml 39 | needs: release 40 | secrets: inherit 41 | -------------------------------------------------------------------------------- /hardhat/contracts/v2_contracts/transactions/interfaces/IIdleness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { ITransactions } from "./ITransactions.sol"; 5 | import { IGenStaking } from "../../interfaces/IGenStaking.sol"; 6 | import { Utils } from "../Utils.sol"; 7 | 8 | interface IIdleness { 9 | struct ExternalContracts { 10 | ITransactions transactions; 11 | IGenStaking staking; 12 | Utils utils; 13 | } 14 | 15 | struct Timeouts { 16 | uint256 activate; 17 | uint256 propose; 18 | uint256 commit; 19 | uint256 reveal; 20 | uint256 accept; 21 | } 22 | 23 | event ValidatorSlashed(bytes32 indexed txId, address indexed validator); 24 | event TransactionActivatorChanged( 25 | bytes32 indexed txId, 26 | address indexed newActivator 27 | ); 28 | event TransactionLeaderChanged( 29 | bytes32 indexed txId, 30 | address indexed newLeader 31 | ); 32 | event TransactionValidatorsChanged( 33 | bytes32 indexed txId, 34 | address[] indexed newValidators 35 | ); 36 | event TimeoutsSet( 37 | uint256 activate, 38 | uint256 propose, 39 | uint256 commit, 40 | uint256 reveal, 41 | uint256 accept 42 | ); 43 | 44 | function checkIdle( 45 | ITransactions.Transaction memory _transaction 46 | ) external returns (ITransactions.UpdateTransactionInfo memory); 47 | 48 | function getNextValidators( 49 | bytes32 _randomSeed, 50 | uint256 _slots, 51 | address[] memory _consumedValidators, 52 | bool _isWeighted 53 | ) external view returns (address[] memory); 54 | 55 | function getTimeouts() external view returns (Timeouts memory); 56 | 57 | function getPageSize() external view returns (uint256); 58 | } -------------------------------------------------------------------------------- /backend/protocol_rpc/message_handler/config/logging.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "formatters": { 4 | "default": { 5 | "format": "[%(asctime)s] %(levelname)s | %(module)s >>> %(message)s", 6 | "datefmt": "%B %d, %Y %H:%M:%S %Z" 7 | } 8 | }, 9 | "handlers": { 10 | "console": { 11 | "class": "logging.StreamHandler", 12 | "formatter": "default" 13 | }, 14 | "wsgi": { 15 | "class": "logging.StreamHandler", 16 | "formatter": "default", 17 | "stream": "ext://flask.logging.wsgi_errors_stream" 18 | }, 19 | "wsgi_simple": { 20 | "class": "logging.StreamHandler", 21 | "formatter": "simple", 22 | "stream": "ext://flask.logging.wsgi_errors_stream" 23 | }, 24 | "file": { 25 | "class": "logging.FileHandler", 26 | "formatter": "default", 27 | "filename": "system.log" 28 | }, 29 | "file_rotate": { 30 | "class": "logging.handlers.TimedRotatingFileHandler", 31 | "formatter": "default", 32 | "filename": "system.log", 33 | "when": "D", 34 | "interval": 1, 35 | "backupCount": 30 36 | }, 37 | "size_rotate": { 38 | "class": "logging.handlers.RotatingFileHandler", 39 | "filename": "flask.log", 40 | "maxBytes": 1000000, 41 | "backupCount": 5, 42 | "formatter": "default" 43 | } 44 | }, 45 | "root": { 46 | "level": "ERROR", 47 | "handlers": ["wsgi", "file_rotate"] 48 | } 49 | } -------------------------------------------------------------------------------- /frontend/src/components/Tutorial/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_OPTIONS = { 2 | highlight: true, 3 | labels: { 4 | buttonSkip: 'Skip tutorial', 5 | buttonPrevious: 'Previous', 6 | buttonNext: 'Next', 7 | buttonStop: 'Finish', 8 | }, 9 | enabledButtons: { 10 | buttonSkip: true, 11 | buttonPrevious: true, 12 | buttonNext: true, 13 | buttonStop: true, 14 | }, 15 | startTimeout: 100, 16 | stopOnTargetNotFound: false, 17 | useKeyboardNavigation: true, 18 | enabledNavigationKeys: { 19 | escape: false, 20 | arrowRight: false, 21 | arrowLeft: false, 22 | }, 23 | debug: false, 24 | }; 25 | 26 | export const HIGHLIGHT = { 27 | classes: { 28 | active: 'v-tour--active', 29 | targetHighlighted: 'v-tour__target--highlighted', 30 | targetRelative: 'v-tour__target--relative', 31 | }, 32 | transition: 'box-shadow 0s ease-in-out 0s', 33 | }; 34 | 35 | export const DEFAULT_STEP_OPTIONS = { 36 | enableScrolling: true, 37 | highlight: DEFAULT_OPTIONS.highlight, // By default use the global tour setting 38 | enabledButtons: DEFAULT_OPTIONS.enabledButtons, 39 | modifiers: [ 40 | { 41 | name: 'arrow', 42 | options: { 43 | element: '.v-step__arrow', 44 | padding: 10, 45 | }, 46 | }, 47 | { 48 | name: 'preventOverflow', 49 | options: { 50 | rootBoundary: 'window', 51 | }, 52 | }, 53 | { 54 | name: 'offset', 55 | options: { 56 | offset: [0, 10], 57 | }, 58 | }, 59 | ], 60 | placement: 'bottom', 61 | }; 62 | 63 | export const KEYS = { 64 | ARROW_RIGHT: 39, 65 | ARROW_LEFT: 37, 66 | ESCAPE: 27, 67 | }; 68 | --------------------------------------------------------------------------------
21 | 22 |
22 | 23 |